Source code for msfc_ccd._cameras

import abc
import dataclasses
import numpy as np
import astropy.units as u
import named_arrays as na
from ._sensors import AbstractSensor
import optika
import msfc_ccd

__all__ = [
    "AbstractCamera",
    "Camera",
]


[docs] @dataclasses.dataclass(repr=False) class AbstractCamera( optika.mixins.Printable, ): """An interface describing a generalized camera.""" @property @abc.abstractmethod def sensor(self) -> AbstractSensor: """A model of the sensor used by this camera to capture light.""" @property @abc.abstractmethod def gain(self) -> u.Quantity | na.AbstractScalar: """The conversion factor between electrons and ADC counts.""" @property @abc.abstractmethod def axis_tap_x(self) -> str: """The name of the logical axis corresponding to changing horizontal tap.""" @property @abc.abstractmethod def axis_tap_y(self) -> str: """The name of the logical axis corresponding to changing vertical tap.""" @property @abc.abstractmethod def timedelta_exposure(self) -> u.Quantity | na.AbstractScalar: """The current exposure length."""
[docs] @classmethod def calibrate_timedelta_exposure(cls, value: int) -> u.Quantity: """ Convert the exposure time from counts to physical units. Parameters ---------- value The exposure time in counts. """ return value * 0.000000025 * u.s
[docs] @classmethod def calibrate_voltage_fpga(cls, value: int) -> u.Quantity: """ Convert the FPGA voltage from counts to physical units. Parameters ---------- value The FPGA voltage in counts. """ return value * 3 / 4096 * u.V
[docs] @classmethod def calibrate_temperature_fpga(cls, value: int) -> u.Quantity: """ Convert the FPGA temperature from counts to physical units. Parameters ---------- value The FPGA temperature in counts. """ result = value * 503.975 / 4096 * u.K result = result.to(u.deg_C, equivalencies=u.temperature()) return result
[docs] @classmethod def calibrate_temperature_adc_1(cls, value: int) -> u.Quantity: """ Convert the ADC 1 temperature from counts to physical units. Parameters ---------- value The ADC 1 temperature in counts. """ r = (9.814453125 * value) / (1 - (value / 4096.0)) result = 3455.0 / np.log(r / 0.0927557) * u.K result = result.to(u.deg_C, equivalencies=u.temperature()) return result
[docs] @classmethod def calibrate_temperature_adc_234(cls, value: int) -> u.Quantity: """ Convert the ADC 2, 3, or 4 temperature from counts to physical units. Parameters ---------- value The ADC 2, 3, or 4 temperature in counts. """ r = (9.814453125 * value) / (1 - (value / 4096.0)) a = 0.0011275 b = 0.00023441 c = 0.000000086482 result = 1 / (a + (b * np.log(r)) + (c * np.log(r)) ** 3) * u.K result = result.to(u.deg_C, equivalencies=u.temperature()) return result
[docs] def dn_to_electrons( self, a: u.Quantity | na.AbstractArray, ) -> na.AbstractArray: """Convert an array from DN to electrons by multiplying by :attr:`gain`.""" return self.gain * a
[docs] @dataclasses.dataclass(repr=False) class Camera( AbstractCamera, ): """ A model of the cameras developed by the MSFC sounding rocket team. This is a composition of a :class:`msfc_ccd.abc.AbstractSensor` object and various parameters such as the exposure time etc. Also provided are some conversion equations between counts and physical units for various parameters such as the FPGA temperature, etc. """ sensor: None | AbstractSensor = None """ A model of the sensor used by this camera to capture light. If :obj:`None` (the default), :class:`TeledyneCCD230` will be used. """ gain: None | u.Quantity | na.AbstractScalar = None """ The conversion factor between electrons and DN. This is usually tap-dependent and contains :attr:`axis_tap_x` and :attr:`axis_tap_y` dimensions. """ bits_adc: int = 16 """The number of bits supported by the analog-to-digital converter""" timedelta_exposure: u.Quantity = 10 * u.s """The current exposure length.""" timedelta_exposure_min: u.Quantity = 2 * u.s """The minimum exposure length supported by this camera.""" timedelta_exposure_max: u.Quantity = 600 * u.s """The maximum exposure length supported by this camera""" timedelta_exposure_step: u.Quantity = 100 * u.ms """The smallest possible change in exposure length supported by this camera.""" timedelta_transfer: u.Quantity = 50 * u.ms """The time required to transfer the exposed pixels into the storage region.""" timedelta_readout: u.Quantity = 1.1 * u.s """The time required to perform a readout operation.""" axis_tap_x: str = dataclasses.field(default="tap_x", kw_only=True) """The name of the logical axis corresponding to changing horizontal tap.""" axis_tap_y: str = dataclasses.field(default="tap_y", kw_only=True) """The name of the logical axis corresponding to changing vertical tap.""" def __post_init__(self): if self.sensor is None: self.sensor = msfc_ccd.TeledyneCCD230()