Source code for sofia_redux.scan.chopper.chopper
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from abc import ABC
import numpy as np
from astropy import units, log
from sofia_redux.scan.chopper import chopper_numba_functions as cnf
__all__ = ['Chopper']
[docs]
class Chopper(ABC):
def __init__(self, x=None, y=None, time=None, threshold=None):
"""
Initialize the chopper object.
Parameters
----------
x : units.Quantity (numpy.ndarray)
The chopper x-positions.
y : units.Quantity (numpy.ndarray)
The chopper y-positions.
time : units.Quantity (numpy.ndarray)
The time of each chopper (x, y) measurement.
threshold : units.Quantity
The distance threshold over which to define a "chop".
"""
self.positions = 0 # 0 for indeterminate, -1 for sweeping mode
self._frequency = np.nan * units.Unit('Hz')
self._amplitude = 0.0 * units.Unit('arcsec')
self.efficiency = np.nan
self._angle = np.nan * units.Unit('deg')
self.offset = None
self.phases = None
self.is_chopping = False
if None not in [x, y, time, threshold]:
self.analyze_xy(x, y, time, threshold)
@property
def frequency(self):
"""
Return the chop frequency.
Returns
-------
units.Quantity
"""
return self._frequency
@frequency.setter
def frequency(self, value):
"""
Set the chop frequency.
Parameters
----------
value : units.Quantity
Returns
-------
None
"""
if isinstance(value, units.Quantity):
self._frequency = value.to(units.Unit('Hz'))
else:
self._frequency = value * units.Unit('Hz')
@property
def amplitude(self):
"""
Return the chop amplitude.
Returns
-------
units.Quantity
"""
return self._amplitude
@amplitude.setter
def amplitude(self, value):
"""
Set the chop amplitude.
Parameters
----------
value : units.Quantity
Returns
-------
None
"""
if isinstance(value, units.Quantity):
self._amplitude = value.to(units.Unit('arcsec'))
else:
self._amplitude = value * units.Unit('arcsec')
@property
def angle(self):
"""
Return the chop angle.
Returns
-------
units.Quantity
"""
return self._angle
@angle.setter
def angle(self, value):
"""
Set the chop angle.
Parameters
----------
value : units.Quantity
Returns
-------
None
"""
if isinstance(value, units.Quantity):
self._angle = value.to(units.Unit('deg'))
else:
self._angle = value * units.Unit('deg')
@property
def stare_duration(self):
"""
Return the stare duration of the chopper.
Returns
-------
time : units.Quantity
"""
stare = self.efficiency / (self.positions * self.frequency)
return stare.decompose().to('second')
def __str__(self):
"""
Return a string representation of the chopper.
Returns
-------
str
"""
return (f'chop +/- {self.amplitude:.3f} at {self.angle:.3f},'
f' {self.frequency:.6f}, '
f'{100 * self.efficiency:.1f}% efficiency')
[docs]
def analyze_xy(self, x, y, time, threshold):
"""
Analyse the chopper signal to determine internal parameters.
Parameters
----------
x : units.Quantity
The x-position of the chopper.
y : units.Quantity
The y-position of the chopper.
time : units.Quantity
The time at each (x, y) measurement.
threshold : units.Quantity
The distance threshold over which to consider a chop transition.
Returns
-------
None
"""
start, end, transitions, angle, distance = cnf.find_transitions(
x.to(threshold.unit).value,
y.to(threshold.unit).value,
threshold.value)
distance = distance * threshold.unit
angle = angle * units.Unit('radian')
if transitions > 2:
dt = time[end] - time[start]
else:
log.debug("Chopper not used.")
self.is_chopping = False
return
if distance.size > 0:
amplitude = np.nanmedian(distance)
else:
amplitude = 0.0 * threshold.unit
if amplitude < threshold:
log.debug("Small chopper fluctuations "
"(assuming chopper not used).")
self.is_chopping = False
return
self.amplitude = amplitude
self.positions = 2
self.frequency = (transitions - 1) / (2 * dt)
self.angle = angle
steady = np.sum(np.abs(distance - self.amplitude) < threshold)
self.efficiency = steady / distance.size
log.debug(f"Chopper detected: {self}")
self.is_chopping = True
[docs]
def get_chop_table_entry(self, name):
"""
Return a parameter value for a given name.
Parameters
----------
name : str
Returns
-------
value
"""
if name == 'chopfreq':
return self.frequency.to('Hz')
elif name == 'chopthrow':
return self.amplitude.to('arcsec')
elif name == 'chopeff':
return self.efficiency
else:
return None