Source code for msfc_ccd._images._tap_images

from typing_extensions import Self
import dataclasses
import named_arrays as na
from .._cameras import AbstractCamera
from ._vectors import ImageHeader
from ._images import AbstractCameraData

__all__ = [
    "TapData",
]


[docs] @dataclasses.dataclass(eq=False, repr=False) class AbstractTapData( AbstractCameraData, ): """An interface for representing data gathered by a single tap.""" @property def axis_tap_x(self) -> str: """The name of the horizontal tap axis.""" return self.camera.axis_tap_x @property def axis_tap_y(self) -> str: """The name of the vertical tap axis.""" return self.camera.axis_tap_y @property def tap(self) -> dict[str, na.AbstractScalarArray]: """The 2-dimensional index of the tap corresponding to each image.""" axis_tap_x = self.axis_tap_x axis_tap_y = self.axis_tap_y shape = self.outputs.shape shape_img = { axis_tap_x: shape[axis_tap_x], axis_tap_y: shape[axis_tap_y], } return na.indices(shape_img) @property def label(self) -> na.ScalarArray: """Human-readable name of the tap used often for plotting.""" axis_tap_x = self.axis_tap_x axis_tap_y = self.axis_tap_y tap_x = self.tap[axis_tap_x].astype(str).astype(object) tap_y = self.tap[axis_tap_y].astype(str) return "tap (" + tap_x + ", " + tap_y + ")"
[docs] def where_blank( self, num: None | int = None, ) -> na.ScalarArray: """ Create a boolean array which is :obj:`True` for all the blank columns. Parameters ---------- num The number of blank columns to use starting from those closest to the active pixels. If :obj:`None` (the default), all the blank pixels are used. """ if num is None: num = self.camera.sensor.num_blank i = self.outputs.indices[self.axis_x] lower = (self.camera.sensor.num_blank - num) <= i upper = i < self.camera.sensor.num_blank return lower & upper
[docs] def where_overscan( self, num: None | int = None, ) -> na.ScalarArray: """ Create a boolean array which is :obj:`True` for all the overscan columns. Parameters ---------- num The number of overscan columns to use starting from those closest to the active pixels. If :obj:`None` (the default), all the overscan pixels are used. """ if num is None: num = self.camera.sensor.num_overscan i = self.outputs.indices[self.axis_x] overscan_start = self.num_x - self.camera.sensor.num_overscan lower = overscan_start <= i upper = i < (overscan_start + num) return lower & upper
[docs] def bias( self, num_blank: None | int = 0, num_overscan: None | int = None, ) -> Self: """ Compute the bias (or pedastal) for each tap. Select a number of blank pixels and a number of overscan pixels and take the mean to compute the bias. Parameters ---------- num_blank The number of blank columns to use starting from those closest to the active pixels. If :obj:`None`, all the blank pixels are used. num_overscan The number of overscan columns to use starting from those closest to the active pixels. If :obj:`None` (the default), all the overscan pixels are used. .. nblinkgallery:: :caption: Relevant Reports :name: rst-link-gallery ../reports/bias """ where_blank = self.where_blank(num_blank) where_overscan = self.where_overscan(num_overscan) where = where_blank | where_overscan result = dataclasses.replace( self, outputs=self.outputs.mean( axis=(self.axis_x, self.axis_y), where=where, ), ) return result
@property def unbiased(self) -> Self: return self - self.bias() @property def active(self) -> Self: sensor = self.camera.sensor slice_active = slice(sensor.num_blank, -sensor.num_overscan) slice_active = {self.axis_x: slice_active} return dataclasses.replace( self, inputs=dataclasses.replace( self.inputs, pixel=dataclasses.replace( self.inputs.pixel, x=self.inputs.pixel.x[slice_active], ), ), outputs=self.outputs[slice_active], ) @property def electrons(self) -> Self: return self.camera.dn_to_electrons(self)
[docs] @dataclasses.dataclass(eq=False, repr=False) class TapData( AbstractTapData, ): """ An image or a sequence of images captured from each tap of the sensor. Examples -------- Load a sample image and split it into the four tap images. .. jupyter-execute:: import named_arrays as na import msfc_ccd # Load the sample image image = msfc_ccd.fits.open(msfc_ccd.samples.path_fe55_esis1) # Split the sample image into four separate images for each tap taps = image.taps # Display the four images fig, axs = na.plt.subplots( axis_rows=taps.axis_tap_y, nrows=taps.outputs.shape[taps.axis_tap_y], axis_cols=taps.axis_tap_x, ncols=taps.outputs.shape[taps.axis_tap_x], sharex=True, sharey=True, constrained_layout=True, ); na.plt.pcolormesh( taps.inputs.pixel, C=taps.outputs.value, ax=axs, ); """ inputs: ImageHeader = dataclasses.MISSING """A vector which contains the FITS header for each image.""" outputs: na.ScalarArray = dataclasses.MISSING """The underlying array storing the image data.""" camera: AbstractCamera = dataclasses.MISSING """A model of the camera used to capture these images.""" axis_x: str = dataclasses.field(default="detector_x", kw_only=True) """The name of the horizontal axis.""" axis_y: str = dataclasses.field(default="detector_y", kw_only=True) """The name of the vertical axis."""