Skip to content

Commit

Permalink
Merge pull request #423 from AstarVienna/fh/detectorcleanup
Browse files Browse the repository at this point in the history
Rename `DetectorArray` ➡️ `DetectorManager` plus Docstrings and Refactoring
  • Loading branch information
teutoburg committed Jun 28, 2024
2 parents f798375 + b519aed commit 91957f3
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 172 deletions.
2 changes: 1 addition & 1 deletion scopesim/detector/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .detector import Detector
from .detector_array import DetectorArray
from .detector_manager import DetectorManager
28 changes: 17 additions & 11 deletions scopesim/detector/detector.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,44 @@
import numpy as np

from astropy.io.fits import ImageHDU
from astropy.wcs import WCS

from ..base_classes import ImagePlaneBase, DetectorBase
from ..optics import image_plane_utils as imp_utils
from ..optics.image_plane_utils import (add_imagehdu_to_imagehdu,
sky_wcs_from_det_wcs)
from ..utils import get_logger, from_currsys, stringify_dict

from astropy.io import fits
from astropy.wcs import WCS


logger = get_logger(__name__)


class Detector(DetectorBase):
def __init__(self, header, cmds=None, **kwargs):
image = np.zeros((header["NAXIS2"], header["NAXIS1"]))
self._hdu = fits.ImageHDU(header=header, data=image)
self._hdu = ImageHDU(header=header, data=image)
self.meta = {}
self.meta.update(header)
self.meta.update(kwargs)
self.cmds = cmds

def extract_from(self, image_plane, spline_order=1, reset=True):
"""Extract HDU from ImagePlane object and add to internal HDU."""
if reset:
self.reset()
if not isinstance(image_plane, ImagePlaneBase):
raise ValueError("image_plane must be an ImagePlane object, but is: "
f"{type(image_plane)}")
raise ValueError("image_plane must be an ImagePlane object, "
f"but is: {type(image_plane)}")

self._hdu = imp_utils.add_imagehdu_to_imagehdu(image_plane.hdu,
self.hdu, spline_order,
wcs_suffix="D")
self._hdu = add_imagehdu_to_imagehdu(
image_plane.hdu, self.hdu, spline_order, wcs_suffix="D")

def reset(self):
"""Reset internal HDU data to all-zeros-array."""
self._hdu.data = np.zeros_like(self._hdu.data)

@property
def hdu(self):
"""Return internal HDU."""
new_meta = stringify_dict(self.meta)
self._hdu.header.update(new_meta)

Expand All @@ -44,20 +47,23 @@ def hdu(self):
if pixel_scale == 0 or plate_scale == 0:
logger.warning("Could not create sky WCS.")
else:
sky_wcs, _ = imp_utils.sky_wcs_from_det_wcs(
sky_wcs, _ = sky_wcs_from_det_wcs(
WCS(self._hdu.header, key="D"), pixel_scale, plate_scale)
self._hdu.header.update(sky_wcs.to_header())

return self._hdu

@property
def header(self):
"""Return header from internal HDU."""
return self._hdu.header

@property
def data(self):
"""Return data from internal HDU."""
return self._hdu.data

@property
def image(self):
"""Return data from internal HDU."""
return self.data
143 changes: 0 additions & 143 deletions scopesim/detector/detector_array.py

This file was deleted.

163 changes: 163 additions & 0 deletions scopesim/detector/detector_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
"""Contains DetectorManager and aux functions."""

from collections.abc import Sequence

from astropy.io.fits import HDUList, PrimaryHDU, TableHDU

from .detector import Detector
from ..effects import Effect
from ..utils import stringify_dict, get_logger


logger = get_logger(__name__)


class DetectorManager(Sequence):
"""Manages the individual Detectors, mostly used for readout."""

def __init__(self, detector_list=None, cmds=None, **kwargs):
self.meta = {}
self.meta.update(kwargs)
self.cmds = cmds

# The effect from which the instance is constructed
self._detector_list = detector_list

# TODO: Typing could get more specific once z_order is replaced with
# Effect subclasses, which might also be used to create the two
# lists more efficiently...
self._array_effects: list[Effect] = []
self._dtcr_effects: list[Effect] = []
self._detectors: list[Detector] = []
if self._detector_list is not None:
self._detectors = [
Detector(hdr, cmds=self.cmds, **self.meta)
for hdr in self._detector_list.detector_headers()]
else:
logger.warning("No detector effect was passed, cannot fully "
"initialize detector manager.")

self._latest_exposure: HDUList | None = None

def readout(self, image_planes, array_effects=None, dtcr_effects=None,
**kwargs) -> HDUList:
"""
Read out the detector array into a FITS HDU List.
1. Select the relevant image plane to extract images from.
2. Apply detector array effects (apply to the entire image plane)
3. Make a series of Detectors for each row in a DetectorList object.
4. Iterate through all Detectors, extract image from image_plane.
5. Apply all effects (to all Detectors).
6. Add necessary header keywords (not implemented).
7. Generate a HDUList with the ImageHDUs and any extras:
- add ``PrimaryHDU`` with meta data regarding observation in header
- add ``ImageHDU`` objects
- add ``ASCIITableHDU`` with Effects meta data in final table
extension (not implemented)
Parameters
----------
image_planes : list of ImagePlane objects
The correct image plane is automatically chosen from the list
array_effects : list of Effect objects
A list of effects related to the detector array
dtcr_effects : list of Effect objects
A list of effects related to the detectors
Returns
-------
latest_exposure : fits.HDUList
Output FITS HDU List.
"""
# .. note:: Detector is what used to be called Chip
# DetectorManager is the old Detector

self._array_effects = array_effects or []
self._dtcr_effects = dtcr_effects or []
self.meta.update(kwargs)

# 1. Get the image plane that corresponds to this detector array
# TODO: This silently only returns the first match, is that intended??
image_plane = next(implane for implane in image_planes if
implane.id == self._detector_list.image_plane_id)

# 2. Apply detector array effects (apply to the entire image plane)
for effect in self._array_effects:
image_plane = effect.apply_to(image_plane, **self.meta)

# 3. iterate through all Detectors, extract image from image_plane
logger.info("Extracting from %d detectors...", len(self))
for detector in self:
detector.extract_from(image_plane)

# 5. apply all effects (to all Detectors)
for effect in self._dtcr_effects:
detector = effect.apply_to(detector)

# 6. add necessary header keywords
# .. todo: add keywords

# FIXME: Why is this applied twice ???
for effect in self._array_effects:
image_plane = effect.apply_to(image_plane, **self.meta)

self._latest_exposure = self._make_hdulist()

return self._latest_exposure

def latest_exposure(self) -> HDUList:
"""Return the (initially empty) HDUList produced by the readout."""
if self._latest_exposure is None:
logger.warning("Run readout before accessing .latest_exposure.")
return self._latest_exposure

def __getitem__(self, index) -> Detector:
"""x.__getitem__(y) <==> x[y]."""
return self._detectors[index]

def __len__(self) -> int:
"""Return len(self)."""
return len(self._detectors)

def __repr__(self):
"""Return repr(self)."""
msg = (f"{self.__class__.__name__}"
f"({self._detector_list!r}, **{self.meta!r})")
return msg

def __str__(self):
"""Return str(self)."""
return f"{self.__class__.__name__} with {self._detector_list!s}"

def _repr_pretty_(self, printer, cycle):
"""For ipython."""
if cycle:
printer.text(f"{self.__class__.__name__}(...)")
else:
printer.text(str(self))

def _make_primary_hdu(self):
"""Create the primary header from meta data."""
prihdu = PrimaryHDU()
prihdu.header.update(stringify_dict(self.meta))
return prihdu

def _make_effects_hdu(self):
# .. todo:: decide what goes into the effects table of meta data
# effects = self._array_effects + self._dtcr_effects
return TableHDU()

def _make_hdulist(self):
"""Generate a HDUList with the ImageHDUs and any extras."""
# TODO: effects_hdu unnecessary as long as make_effects_hdu does not do anything
hdu_list = HDUList([
self._make_primary_hdu(),
*[dtcr.hdu for dtcr in self],
# *self._make_effects_hdu(),
])
return hdu_list
Loading

0 comments on commit 91957f3

Please sign in to comment.