From 91e1c39d28044236c45f22e02022221ce59c1035 Mon Sep 17 00:00:00 2001 From: "T. E. Pickering" Date: Mon, 1 Jun 2026 18:16:55 -0600 Subject: [PATCH] mmt_binospec: split setups by mask, require INCAN=on for flats Fixes the two pypeit_setup issues reported in #2137: - Add `decker` (FITS MASK) to `configuration_keys()` so frames taken with different slit masks get separate setups and calibration groups instead of all sharing calibrations by grating alone. Also propagate `MASK` via `raw_header_cards()`. - Map the incandescent flat lamp as `lampstat03` (FITS INCAN) and require `INCAN=on` in the pixelflat/trace/illumflat branch of `check_frame_type()`, so dark/noisy `INCAN=off` exposures are no longer auto-typed as flats. Adds unit tests for setup grouping and INCAN-based frame typing, plus documentation and release-note updates. Co-Authored-By: Claude Opus 4.8 (1M context) --- doc/releases/2.1.0dev.rst | 8 +++++ doc/spectrographs/mmt_binospec.rst | 16 +++++++++ pypeit/spectrographs/mmt_binospec.py | 11 +++++-- pypeit/tests/test_binospec_setup.py | 49 ++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 pypeit/tests/test_binospec_setup.py diff --git a/doc/releases/2.1.0dev.rst b/doc/releases/2.1.0dev.rst index 4fbce55be1..427f037d9e 100644 --- a/doc/releases/2.1.0dev.rst +++ b/doc/releases/2.1.0dev.rst @@ -45,6 +45,9 @@ Instrument-specific Updates :func:`~pypeit.core.wavecal.templates.build_template` with continuum subtraction on the new segment. - Implemented support for VLT/UVES data reduction. +- MMT/Binospec: ``pypeit_setup`` now splits setups by slit mask in addition to + grating (``decker`` added to ``configuration_keys``), so frames taken with + different masks no longer share calibrations. Script Changes -------------- @@ -56,6 +59,8 @@ Testing - Add new tests for :mod:`~pypeit.core.pydl` - Added new tests for :class:`~pypeit.inputfiles.InputFile` objects. +- Added tests for MMT/Binospec setup grouping and INCAN-based flat/trace frame + typing. Under-the-hood Improvements --------------------------- @@ -93,3 +98,6 @@ Bug Fixes - Fix a bug in 2D coaddition where manually identified objects were not being used to compute offsets between input frames. Add description of this failure mode to the :ref:`known_issues_coadd2d` documentation for 2D coaddition. +- MMT/Binospec: flat/trace/illumflat frame typing now requires the + incandescent lamp to be on (``INCAN`` = ``on``), preventing dark/noisy + ``INCAN`` = ``off`` exposures from being combined into pixel and trace flats. diff --git a/doc/spectrographs/mmt_binospec.rst b/doc/spectrographs/mmt_binospec.rst index 032aed5c80..ef4154b80c 100644 --- a/doc/spectrographs/mmt_binospec.rst +++ b/doc/spectrographs/mmt_binospec.rst @@ -8,6 +8,22 @@ Overview This file summarizes several instrument specific items for the MMTO's Binospec spectrograph. +Setups and Frame Typing ++++++++++++++++++++++++ + +Unique configurations (setups) are defined by both the grating +(``dispname``, FITS ``DISPERS1``) and the slit mask (``decker``, FITS +``MASK``). Frames taken with different masks therefore end up in separate +setups (separate ``.pypeit`` files and calibration groups), so that slit +traces, pixel flats, and wavelength solutions are never mixed across +distinct slit-mask geometries. + +Pixel flats, trace flats, and illumination flats require the incandescent +flat lamp to be on (FITS ``INCAN`` = ``on``), in addition to the arc lamp +being off (``HENEAR`` = ``off``) and the screen deployed (``SCRN`` = +``deployed``). Exposures taken with ``INCAN`` = ``off`` are dark/noisy and +are not auto-typed as flats. + Wavelength Calibration ++++++++++++++++++++++ diff --git a/pypeit/spectrographs/mmt_binospec.py b/pypeit/spectrographs/mmt_binospec.py index d41874cbf4..43a8fd095d 100644 --- a/pypeit/spectrographs/mmt_binospec.py +++ b/pypeit/spectrographs/mmt_binospec.py @@ -138,6 +138,8 @@ def init_meta(self): self.meta['lampstat01'] = dict(ext=1, card='HENEAR') # used for flatlamp, SCRN is actually telescope status self.meta['lampstat02'] = dict(ext=1, card='SCRN') + # incandescent (flat-field) lamp; required to be 'on' for flat/trace frames + self.meta['lampstat03'] = dict(ext=1, card='INCAN') self.meta['instrument'] = dict(ext=1, card='INSTRUME') def compound_meta(self, headarr, meta_key): @@ -173,7 +175,7 @@ def configuration_keys(self): and used to constuct the :class:`~pypeit.metadata.PypeItMetaData` object. """ - return ['dispname'] + return ['dispname', 'decker'] def raw_header_cards(self): """ @@ -193,7 +195,7 @@ def raw_header_cards(self): :obj:`list`: List of keywords from the raw data files that should be propagated in output files. """ - return ['DISPERS1'] + return ['DISPERS1', 'MASK'] @classmethod def default_pypeit_par(cls): @@ -406,7 +408,10 @@ def check_frame_type(self, ftype, fitstbl, exprng=None): if ftype in ['arc', 'tilt']: return good_exp & (fitstbl['lampstat01'] == 'on') if ftype in ['pixelflat', 'trace', 'illumflat']: - return good_exp & (fitstbl['lampstat01'] == 'off') & (fitstbl['lampstat02'] == 'deployed') + # Require the incandescent (flat) lamp to be on; INCAN=off frames + # are dark/noisy exposures and must not be combined into flats. + return good_exp & (fitstbl['lampstat01'] == 'off') \ + & (fitstbl['lampstat02'] == 'deployed') & (fitstbl['lampstat03'] == 'on') log.debug('Cannot determine if frames are of type {0}.'.format(ftype)) return np.zeros(len(fitstbl), dtype=bool) diff --git a/pypeit/tests/test_binospec_setup.py b/pypeit/tests/test_binospec_setup.py new file mode 100644 index 0000000000..a61730549b --- /dev/null +++ b/pypeit/tests/test_binospec_setup.py @@ -0,0 +1,49 @@ +""" +Tests for Binospec setup grouping (configuration_keys) and frame typing +(check_frame_type), addressing GitHub issue #2137. +""" +import numpy as np +from astropy.table import Table + +from pypeit.spectrographs.mmt_binospec import MMTBINOSPECSpectrograph + + +def test_configuration_keys_includes_decker(): + """Setups must be split by slit mask (decker), not just grating.""" + spec = MMTBINOSPECSpectrograph() + keys = spec.configuration_keys() + assert 'dispname' in keys + assert 'decker' in keys, \ + "decker (MASK) must be a configuration key so different slit masks " \ + "get separate setups" + + +def _frame_table(**columns): + """Build a minimal metadata table for frame-typing tests. + + Every column is broadcast to the same length. + """ + n = max(len(np.atleast_1d(v)) for v in columns.values()) + data = {k: np.broadcast_to(np.atleast_1d(v), n).copy() + for k, v in columns.items()} + return Table(data) + + +def test_incan_on_is_flat(): + """INCAN=on with screen deployed and arc lamp off is a flat/trace.""" + spec = MMTBINOSPECSpectrograph() + fitstbl = _frame_table(exptime=10.0, lampstat01='off', + lampstat02='deployed', lampstat03='on') + for ftype in ['pixelflat', 'trace', 'illumflat']: + assert spec.check_frame_type(ftype, fitstbl)[0], \ + f"INCAN=on frame should be typed as {ftype}" + + +def test_incan_off_is_not_flat(): + """INCAN=off must NOT be typed as a flat/trace even if screen deployed.""" + spec = MMTBINOSPECSpectrograph() + fitstbl = _frame_table(exptime=10.0, lampstat01='off', + lampstat02='deployed', lampstat03='off') + for ftype in ['pixelflat', 'trace', 'illumflat']: + assert not spec.check_frame_type(ftype, fitstbl)[0], \ + f"INCAN=off frame must not be typed as {ftype}"