Skip to content

Commit

Permalink
Merge pull request #1851 from pypeit/autosec
Browse files Browse the repository at this point in the history
Enable Spectrograph base class to account for image sections that include on-chip binning
  • Loading branch information
kbwestfall authored Sep 5, 2024
2 parents 2746487 + 52b7f21 commit 2d57634
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 98 deletions.
78 changes: 8 additions & 70 deletions pypeit/spectrographs/ldt_deveny.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,79 +566,17 @@ def config_specific_par(self, scifile, inp_par=None):

def get_rawimage(self, raw_file, det):
"""
Read raw images and generate a few other bits and pieces
that are key for image processing.
Read raw spectrograph image files and return data and relevant metadata
needed for image processing.
For LDT/DeVeny, the LOIS control system automatically adjusts the
``DATASEC`` and ``OSCANSEC`` regions if the CCD is used in a binning other
than 1x1. The :meth:`~pypeit.spectrographs.spectrograph.Spectrograph.get_rawimage`
method in the base class assumes these sections are fixed and adjusts
them based on the binning -- an incorrect assumption for this instrument.
This method is a stripped-down version of the base class method and
additionally does *NOT* send the binning to :func:`~pypeit.core.parse.sec2slice`.
Parameters
----------
raw_file : :obj:`str`
File to read
det : :obj:`int`
1-indexed detector to read
Returns
-------
detector_par : :class:`~pypeit.images.detector_container.DetectorContainer`
Detector metadata parameters.
raw_img : `numpy.ndarray`_
Raw image for this detector.
hdu : `astropy.io.fits.HDUList`_
Opened fits file
exptime : :obj:`float`
Exposure time *in seconds*.
rawdatasec_img : `numpy.ndarray`_
Data (Science) section of the detector as provided by setting the
(1-indexed) number of the amplifier used to read each detector
pixel. Pixels unassociated with any amplifier are set to 0.
oscansec_img : `numpy.ndarray`_
Overscan section of the detector as provided by setting the
(1-indexed) number of the amplifier used to read each detector
pixel. Pixels unassociated with any amplifier are set to 0.
``DATASEC`` and ``OSCANSEC`` regions if the CCD is used in a binning
other than 1x1. This is a simple wrapper for
:func:`pypeit.spectrographs.spectrograph.Spectrograph.get_rawimage` that
sets ``sec_includes_binning`` to True. See the base-class function for
the detailed descriptions of the input parameters and returned objects.
"""
# Open
hdu = io.fits_open(raw_file)

# Grab the DetectorContainer and extract the raw image
detector = self.get_detector_par(det, hdu=hdu)
raw_img = hdu[detector['dataext']].data.astype(float)

# Exposure time (used by RawImage) from the header
headarr = self.get_headarr(hdu)
exptime = self.get_meta_value(headarr, 'exptime')

for section in ['datasec', 'oscansec']:
# Get the data section from Detector
image_sections = detector[section]

# Initialize the image (0 means no amplifier)
pix_img = np.zeros(raw_img.shape, dtype=int)
for i in range(detector['numamplifiers']):

if image_sections is not None:
# Convert the (FITS) data section from a string to a slice
# DO NOT send the binning (default: None)
datasec = parse.sec2slice(image_sections[i], one_indexed=True,
include_end=True, require_dim=2)
# Assign the amplifier
pix_img[datasec] = i+1

# Finish
if section == 'datasec':
rawdatasec_img = pix_img.copy()
else:
oscansec_img = pix_img.copy()

# Return
return detector, raw_img, hdu, exptime, rawdatasec_img, oscansec_img
return super().get_rawimage(raw_file, det, sec_includes_binning=True)

def calc_pattern_freq(self, frame, rawdatasec_img, oscansec_img, hdu):
"""
Expand Down
16 changes: 13 additions & 3 deletions pypeit/spectrographs/p200_dbsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from pypeit.spectrographs import spectrograph
from pypeit.core import parse
from pypeit.images import detector_container
from pypeit.spectrographs.ldt_deveny import LDTDeVenySpectrograph


def flip_fits_slice(s: str) -> str:
Expand Down Expand Up @@ -169,9 +168,20 @@ def check_frame_type(self, ftype, fitstbl, exprng=None):
return good_exp & (fitstbl['lampstat01'] != '0000000') & (fitstbl['idname'] == 'cal')
msgs.warn('Cannot determine if frames are of type {0}.'.format(ftype))
return np.zeros(len(fitstbl), dtype=bool)

def get_rawimage(self, raw_file, det):
return LDTDeVenySpectrograph.get_rawimage(self, raw_file, det)
"""
Read raw spectrograph image files and return data and relevant metadata
needed for image processing.
For P200/DBSP, the ``DATASEC`` and ``OSCANSEC`` regions are read
directly from the file header and are automatically adjusted to account
for the on-chip binning. This is a simple wrapper for
:func:`pypeit.spectrographs.spectrograph.Spectrograph.get_rawimage` that
sets ``sec_includes_binning`` to True. See the base-class function for
the detailed descriptions of the input parameters and returned objects.
"""
return super().get_rawimage(raw_file, det, sec_includes_binning=True)


class P200DBSPBlueSpectrograph(P200DBSPSpectrograph):
Expand Down
60 changes: 35 additions & 25 deletions pypeit/spectrographs/spectrograph.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,10 +1163,10 @@ def validate_det(self, det):
msgs.error(f'Provided det must have type tuple or integer, not {type(det)}.')
return 1, (det,)

def get_rawimage(self, raw_file, det):
def get_rawimage(self, raw_file, det, sec_includes_binning=False):
"""
Read raw images and generate a few other bits and pieces that are key
for image processing.
Read raw spectrograph image files and return data and relevant metadata
needed for image processing.
.. warning::
Expand All @@ -1181,6 +1181,14 @@ def get_rawimage(self, raw_file, det):
1-indexed detector(s) to read. An image mosaic is selected using a
:obj:`tuple` with the detectors in the mosaic, which must be one of
the allowed mosaics returned by :func:`allowed_mosaics`.
sec_includes_binning : :obj:`bool`, optional
Some instruments use hard-coded image-section strings to define the
data and overscan regions, which are then automatically adjusted by
the on-chip binning read from the header. Others read the data and
overscan sections directly from the header. If these sections
*include* the on-chip binning automatically when the image is
written, this flag should be set to true so that this reader returns
the correct image sections.
Returns
-------
Expand All @@ -1207,7 +1215,8 @@ def get_rawimage(self, raw_file, det):
"""
# Check extension and then open
self._check_extensions(raw_file)
hdu = io.fits_open(raw_file, ignore_missing_end=True, output_verify = 'ignore', ignore_blank=True)
hdu = io.fits_open(raw_file, ignore_missing_end=True, output_verify='ignore',
ignore_blank=True)

# Validate the entered (list of) detector(s)
nimg, _det = self.validate_det(det)
Expand All @@ -1225,15 +1234,26 @@ def get_rawimage(self, raw_file, det):
# NOTE: This *must* be (converted to) seconds.
exptime = self.get_meta_value(headarr, 'exptime')

# Rawdatasec, oscansec images
binning = self.get_meta_value(headarr, 'binning')
# NOTE: This means that `specaxis` must be the same for all detectors in
# a mosaic
if detectors[0]['specaxis'] == 1:
binning_raw = (',').join(binning.split(',')[::-1])
# Binning
if sec_includes_binning:
# The section in the header includes the binning, so set it to None
# here.
binning_raw = None
else:
binning_raw = binning
binning = self.get_meta_value(headarr, 'binning')
# NOTE: This means that `specaxis` must be the same for all detectors in
# a mosaic
if detectors[0]['specaxis'] == 1:
binning_raw = (',').join(binning.split(',')[::-1])
else:
binning_raw = binning

# Always assume normal FITS header formatting
one_indexed = True
include_last = True
require_dim = 2

# Read the image(s)
raw_img = [None]*nimg
rawdatasec_img = [None]*nimg
oscansec_img = [None]*nimg
Expand All @@ -1254,28 +1274,18 @@ def get_rawimage(self, raw_file, det):

for section in ['datasec', 'oscansec']:

# Get the data section
# Try using the image sections as header keywords
# TODO -- Deal with user windowing of the CCD (e.g. Kast red)
# Code like the following maybe useful
#hdr = hdu[detector[det - 1]['dataext']].header
#image_sections = [hdr[key] for key in detector[det - 1][section]]
# Grab from Detector
# Get the data sections from the detector object (see get_detector_par above)
# TODO: Add ability to incude user windowing (e.g., Kast Red)
image_sections = detectors[i][section]
#if not isinstance(image_sections, list):
# image_sections = [image_sections]
# Always assume normal FITS header formatting
one_indexed = True
include_last = True

# Initialize the image (0 means no amplifier)
pix_img = np.zeros(raw_img[i].shape, dtype=int)
for j in range(detectors[i]['numamplifiers']):

if image_sections is not None: # and image_sections[i] is not None:
# Convert the data section from a string to a slice
# Convert the (FITS) data section from a string to a slice
datasec = parse.sec2slice(image_sections[j], one_indexed=one_indexed,
include_end=include_last, require_dim=2,
include_end=include_last, require_dim=require_dim,
binning=binning_raw)
# Assign the amplifier
pix_img[datasec] = j+1
Expand Down

0 comments on commit 2d57634

Please sign in to comment.