Generating synthetic signals

Adding a basic signal

The main method that generates signals is add_signal() (or generate() in legacy). We need to pass in an array of times, frequencies, and functions that describe the shape of the signal over time, over frequency within individual time samples, and over a bandpass of frequencies. setigen comes prepackaged with common functions (setigen.funcs), but you can write your own!

The most basic signal that you can generate is a constant intensity, constant drift-rate signal.

from astropy import units as u
import numpy as np

import setigen as stg

# Define time and frequency arrays, essentially labels for the 2D data array
fchans = 1024
tchans = 16
df = 2.7939677238464355*u.Hz
dt = 18.25361108*u.s
fch1 = 6095.214842353016*u.MHz

# Generate the signal
frame = stg.Frame(fchans, tchans, df, dt, fch1)
signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                            drift_rate=2*u.Hz/u.s),
                          stg.constant_t_profile(level=1),
                          stg.box_f_profile(width=20*u.Hz),
                          stg.constant_bp_profile(level=1))

signal is a 2D NumPy array with the resulting time-frequency data. To visualize this, we use matplotlib.pyplot.imshow():

import matplotlib.pyplot as plt
fig = plt.figure(figsize=(10,6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("basic_signal.png", bbox_inches='tight')
../_images/basic_signal.png

Using prepackaged signal functions

With setigen’s pre-written signal functions, you can generate a variety of signals right off the bat. The main signal parameters that customize the synthetic signal are path, t_profile, f_profile, and bp_profile.

path describes the path of the signal in time-frequency space. The path function takes in a time and outputs ‘central’ frequency corresponding to that time.

t_profile (time profile) describes the intensity of the signal over time. The t_profile function takes in a time and outputs an intensity.

f_profile (frequency profile) describes the intensity of the signal within a time sample as a function of relative frequency. The f_profile function takes in a frequency and a central frequency and computes an intensity. This function is used to control the spectral shape of the signal (with respect to a central frequency), which may be a square wave, a Gaussian, or any custom shape!

bp_profile describes the intensity of the signal over the bandpass of frequencies. Whereas f_profile computes intensity with respect to a relative frequency, bp_profile computes intensity with respect to the absolute frequency value. The bp_profile function takes in a frequency and outputs an intensity as well.

All these functions combine to form the final synthetic signal, which means you can create a host of signals by mixing and matching these parameters!

Here are some examples of pre-written signal functions. To avoid needless repetition, each example script will assume the same basic setup:

from astropy import units as u
import numpy as np

import setigen as stg

# Define time and frequency arrays, essentially labels for the 2D data array
fchans = 1024
tchans = 16
df = 2.7939677238464355*u.Hz
dt = 18.25361108*u.s
fch1 = 6095.214842353016*u.MHz

# Generate the signal
frame = stg.Frame(fchans, tchans, df, dt, fch1)

paths

Constant path

A constant path is a linear Doppler-drifted signal. To generate this path, use constant_path() and specify the starting frequency of the signal and the drift rate (in units of frequency over time, consistent with the units of your time and frequency arrays):

signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                            drift_rate=2*u.Hz/u.s),
                          stg.constant_t_profile(level=1),
                          stg.box_f_profile(width=20*u.Hz),
                          stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("basic_signal.png", bbox_inches='tight')
../_images/basic_signal.png

Sine path

This path is a sine wave, controlled by a starting frequency, drift rate, period, and amplitude, using sine_path().

signal = frame.add_signal(stg.sine_path(f_start=frame.fs[200],
                                        drift_rate=2*u.Hz/u.s,
                                        period=100*u.s,
                                        amplitude=100*u.Hz),
                          stg.constant_t_profile(level=1),
                          stg.box_f_profile(width=20*u.Hz),
                          stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("sine_signal.png", bbox_inches='tight')
../_images/sine_signal.png

Squared path

This path is a very simple quadratic with respect to time, using squared_path().

signal = frame.add_signal(stg.squared_path(f_start=frame.fs[200],
                                           drift_rate=0.01*u.Hz/u.s),
                          stg.constant_t_profile(level=1),
                          stg.box_f_profile(width=20*u.Hz),
                          stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("squared_signal.png", bbox_inches='tight')
../_images/squared_signal.png

t_profiles

Constant intensity

To generate a signal with the same intensity over time, use constant_t_profile(), specifying only the intensity level:

signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                        drift_rate=2*u.Hz/u.s),
                      stg.constant_t_profile(level=1),
                      stg.box_f_profile(width=20*u.Hz),
                      stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("basic_signal.png", bbox_inches='tight')
../_images/basic_signal.png

Sine intensity

To generate a signal with sinuisoidal intensity over time, use sine_t_profile(), specifying the period, amplitude, and average intensity level. The intensity level is essentially an offset added to a sine function, so it should be equal or greater than the amplitude so that the signal doesn’t have any negative values.

Here’s an example with equal level and amplitude:

signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                            drift_rate=2*u.Hz/u.s),
                          stg.sine_t_profile(period=100*u.s,
                                             amplitude=1,
                                             level=1),
                          stg.box_f_profile(width=20*u.Hz),
                          stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("sine_intensity_1_1.png", bbox_inches='tight')
../_images/sine_intensity_1_1.png

And here’s an example with the level a bit higher than the amplitude:

signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                            drift_rate=2*u.Hz/u.s),
                          stg.sine_t_profile(period=100*u.s,
                                             amplitude=1,
                                             level=3),
                          stg.box_f_profile(width=20*u.Hz),
                          stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("sine_intensity_1_3.png", bbox_inches='tight')
../_images/sine_intensity_1_3.png

f_profiles

Box / square intensity profile

To generate a signal with the same intensity over frequency, use box_f_profile(), specifying the width of the signal:

signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                            drift_rate=2*u.Hz/u.s),
                          stg.constant_t_profile(level=1),
                          stg.box_f_profile(width=40*u.Hz),
                          stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("basic_signal.png", bbox_inches='tight')
../_images/box_profile.png

Gaussian intensity profile

To generate a signal with a Gaussian intensity profile in the frequency direction, use gaussian_f_profile(), specifying the width of the signal:

signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                            drift_rate=2*u.Hz/u.s),
                          stg.constant_t_profile(level=1),
                          stg.gaussian_f_profile(width=40*u.Hz),
                          stg.constant_bp_profile(level=1))


fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("gaussian_profile.png", bbox_inches='tight')
../_images/gaussian_profile.png

Multiple Gaussian intensity profile

The profile multiple_gaussian_f_profile(), generates a symmetric signal with three Gaussians; one main signal and two smaller signals on either side.

signal = frame.add_signal(stg.constant_path(f_start=frame.fs[200],
                                            drift_rate=2*u.Hz/u.s),
                          stg.constant_t_profile(level=1),
                          stg.multiple_gaussian_f_profile(width=40*u.Hz),
                          stg.constant_bp_profile(level=1))

fig = plt.figure(figsize=(10, 6))
plt.imshow(frame.get_data(), aspect='auto')
plt.colorbar()
fig.savefig("multiple_gaussian_profile.png", bbox_inches='tight')
../_images/multiple_gaussian_profile.png

Writing custom signal functions

You can easily go beyond setigen’s pre-written signal functions by writing your own. For each generate() parameter (path, t_profile, f_profile, and bp_profile), you can pass in your own custom functions.

For example, here’s the code behind the sine path shape:

def sine_path(f_start, drift_rate, period, amplitude):
    def path(t):
        return f_start + amplitude * np.sin(2 * np.pi * t / period) + drift_rate * t
    return path

Alternately, you can use the lambda operator:

def sine_path(f_start, drift_rate, period, amplitude):
    return lambda t: return f_start + amplitude * np.sin(2 * np.pi * t / period) + drift_rate * t

It’s important that the function you pass into each parameter has the correct input and output. Specifically:

path
Takes in time t and outputs a frequency
t_profile
Takes in time t and outputs an intensity
f_profile
Takes in frequency f and a reference central frequency f_center, and outputs an intensity
bp_profile
Takes in frequency f and outputs an intensity

To generate synthetic signals, generate() uses these functions to compute intensity for each time, frequency pair in the data.

To see more examples on how to write your own parameter functions, check out the source code behind the pre-written functions (setigen.funcs).