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')
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')
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')
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')
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')
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')
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')
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')
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')
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')
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 frequencyt_profile
- Takes in time
t
and outputs an intensityf_profile
- Takes in frequency
f
and a reference central frequencyf_center
, and outputs an intensitybp_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
).