import collections
import numpy as np
from . import frame as _frame
from . import plots
[docs]
class Cadence(collections.abc.MutableSequence):
"""
A class for organizing cadences of Frame objects.
Parameters
----------
frame_list : list of Frames, optional
List of frames to be included in the cadence
t_slew : float, optional
Slew time between frames
t_overwrite : bool, optional
Option to overwrite time data in frames to enforce slew time spacing
"""
[docs]
def __init__(self,
frame_list=None,
t_slew=0,
t_overwrite=False):
self.frames = list()
# Insert all initialized items, performing type _checks
if not frame_list is None:
self.extend(frame_list)
self.t_slew = t_slew
self.t_overwrite = t_overwrite
if t_overwrite:
self.overwrite_times()
@property
def t_start(self):
if len(self.frames) == 0:
return None
return self.frames[0].t_start
@property
def fch1(self):
if len(self.frames) == 0:
return None
return self.frames[0].fch1
@property
def ascending(self):
if len(self.frames) == 0:
return None
return self.frames[0].ascending
@property
def fmin(self):
if len(self.frames) == 0:
return None
return self.frames[0].fmin
@property
def fmax(self):
if len(self.frames) == 0:
return None
return self.frames[0].fmax
@property
def fmid(self):
if len(self.frames) == 0:
return None
return self.frames[0].fmid
@property
def df(self):
if len(self.frames) == 0:
return None
return self.frames[0].df
@property
def dt(self):
if len(self.frames) == 0:
return None
return self.frames[0].dt
@property
def fchans(self):
if len(self.frames) == 0:
return None
return self.frames[0].fchans
@property
def tchans(self):
if len(self.frames) == 0:
return None
return sum([frame.tchans
for frame in self.frames])
@property
def obs_range(self):
if len(self.frames) == 0:
return None
return self.frames[-1].t_stop - self.frames[0].t_start
def _check(self, v):
"""
Ensure that object is a Frame and has consistent parameters with
existing frames in the cadence.
"""
if not isinstance(v, _frame.Frame):
raise TypeError(f"{v} is not a Frame object.")
if len(self.frames) > 0:
for attr in ['df', 'dt', 'fchans', 'fmin']:
if getattr(v, attr) != getattr(self.frames[0], attr):
raise AttributeError(f"{attr}={getattr(v, attr)} does not match cadence ({getattr(self.frames[0], attr)})")
def __len__(self):
return len(self.frames)
def __getitem__(self, i):
if isinstance(i, slice):
return self.__class__(self.frames[i])
elif isinstance(i, (list, np.ndarray, tuple)):
return self.__class__(np.array(self.frames)[i])
else:
return self.frames[i]
def __delitem__(self, i):
del self.frames[i]
def __setitem__(self, i, v):
self._check(v)
self.frames[i] = v
[docs]
def insert(self, i, v):
self._check(v)
self.frames.insert(i, v)
def __str__(self):
return str(self.frames)
[docs]
def overwrite_times(self):
"""
Overwrite the starting time and time arrays used to compute signals
in each frame of the cadence, using ``slew_time`` (s) to space between
frames.
"""
for i, frame in enumerate(self.frames[1:]):
frame.t_start = self.frames[i].t_stop + self.t_slew
@property
def slew_times(self):
"""
Compute slew times in between frames.
"""
return np.array([self.frames[i].t_start - self.frames[i - 1].t_stop
for i in range(1, len(self.frames))])
[docs]
def add_signal(self, *args, **kwargs):
"""
Add signal to each frame in the cadence. Arguments are passed through
to :func:`~setigen.frame.Frame.add_signal`.
"""
for frame in self.frames:
frame.ts += frame.t_start - self.t_start
frame.add_signal(*args, **kwargs)
frame.ts -= frame.t_start - self.t_start
[docs]
def apply(self, func):
"""
Apply function to each frame in the cadence.
"""
return [func(frame) for frame in self.frames]
[docs]
def plot(self, *args, **kwargs):
"""
Plot cadence as a multi-panel figure.
Parameters
----------
cadence : Cadence
Cadence to plot
xtype : {"fmid", "fmin", "f", "px"}, default: "fmid"
Types of axis labels, particularly the x-axis. "px" puts axes in units
of pixels. The others are all in frequency: "fmid" shows frequencies
relative to the central frequency, "fmin" is relative to the minimum
frequency, and "f" is absolute frequency.
db : bool, default: True
Option to convert intensities to dB
slew_times : bool, default: False
Option to space subplots vertically proportional to slew times
colorbar : bool, default: True
Whether to display colorbar
labels : bool, default: True
Option to place target name as a label in each subplot
title : bool, default: False
Option to place first source name as the figure title
minor_ticks : bool, default: False
Option to include minor ticks on both axes
grid : bool, default: False
Option to overplot grid from major ticks
Return
------
axs : matplotlib.axes.Axes
Axes subplots
cax : matplotlib.axes.Axes
Colorbar axes, if created
"""
plots.plot_cadence(self, *args, **kwargs)
[docs]
def consolidate(self):
"""
Convert full cadence into a single concatenated Frame.
"""
if len(self.frames) == 0:
return None
c_frame = _frame.Frame(fchans=self.fchans,
tchans=self.tchans,
df=self.df,
dt=self.dt,
fch1=self.fch1,
ascending=self.ascending,
t_start=self.t_start)
c_frame.data = np.concatenate([frame.data
for frame in self.frames],
axis=0)
c_frame.ts = np.concatenate([frame.ts + frame.t_start
for frame in self.frames],
axis=0)
return c_frame
[docs]
class OrderedCadence(Cadence):
"""
A class that extends Cadence for imposing a cadence order, such as
"ABACAD" or "ABABAB". Order labels are added to each frame's metadata with
the ``order_label`` keyword.
Parameters
----------
frame_list : list of Frames, optional
List of frames to be included in the cadence
order : str, optional
Cadence order, expressed as a string of letters (e.g. "ABACAD")
t_slew : float, optional
Slew time between frames
t_overwrite : bool, optional
Option to overwrite time data in frames to enforce slew time spacing
"""
[docs]
def __init__(self,
frame_list=None,
order="ABACAD",
t_slew=0,
t_overwrite=False):
self.order = order
Cadence.__init__(self,
frame_list=frame_list,
t_slew=t_slew,
t_overwrite=t_overwrite)
def __setitem__(self, i, v):
self._check(v)
if i < 0:
i = len(self) + i
if "order_label" not in v.metadata:
v.add_metadata({"order_label": self.order[i]})
self.frames[i] = v
[docs]
def insert(self, i, v):
self._check(v)
if i < 0:
i = len(self) + i
if "order_label" not in v.metadata:
v.add_metadata({"order_label": self.order[i]})
self.frames.insert(i, v)
[docs]
def set_order(self, order):
"""
Reassign cadence order.
"""
self.order = order
for i, fr in enumerate(self.frames):
fr.add_metadata({"order_label": self.order[i]})
[docs]
def by_label(self, order_label="A"):
"""
Filter frames in cadence by their label in the cadence order, specified
as a letter. Returns matching frames as a new Cadence.
"""
return Cadence(frame_list=[frame for frame in self
if frame.metadata["order_label"] == order_label])