Coverage for python/lsst/obs/lsst/translators/comCam.py: 88%
66 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 09:20 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 09:20 +0000
1# This file is currently part of obs_lsst but is written to allow it
2# to be migrated to the astro_metadata_translator package at a later date.
3#
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the LICENSE file in this directory for details of code ownership.
7#
8# Use of this source code is governed by a 3-clause BSD-style
9# license that can be found in the LICENSE file.
11"""Metadata translation code for the LSST Commissioning Camera"""
13__all__ = ("LsstComCamTranslator", )
15import logging
16from numbers import Number
18from astropy.time import Time
19from .lsstCam import LsstCamTranslator
20from .lsst import SIMONYI_TELESCOPE
22log = logging.getLogger(__name__)
24DETECTOR_SERIALS = {
25 "S00": "ITL-3800C-229",
26 "S01": "ITL-3800C-251",
27 "S02": "ITL-3800C-215",
28 "S10": "ITL-3800C-326",
29 "S11": "ITL-3800C-283",
30 "S12": "ITL-3800C-243",
31 "S20": "ITL-3800C-319",
32 "S21": "ITL-3800C-209",
33 "S22": "ITL-3800C-206",
34}
36# Date ComCam left Tucson bound for Chile
37COMCAM_TO_CHILE_DATE = Time("2020-03-13T00:00", format="isot", scale="utc")
40class LsstComCamTranslator(LsstCamTranslator):
41 """Metadata translation for the LSST Commissioning Camera."""
43 name = "LSSTComCam"
44 """Name of this translation class"""
46 _const_map = {
47 "instrument": "LSSTComCam",
48 }
50 # Use the comCam raft definition
51 cameraPolicyFile = "policy/comCam.yaml"
53 # Date (YYYYMM) the camera changes from using lab day_offset (Pacific time)
54 # to summit day_offset (12 hours).
55 _CAMERA_SHIP_DATE = 202003
57 # Date we know camera is in Chile and potentially taking on-sky data.
58 # https://www.lsst.org/news/rubin-commissioning-camera-installed-telescope-mount
59 _CAMERA_ON_TELESCOPE_DATE = Time("2024-08-24T00:00", format="isot", scale="utc")
61 @classmethod
62 def can_translate(cls, header, filename=None):
63 """Indicate whether this translation class can translate the
64 supplied header.
66 Looks for "COMCAM" instrument in case-insensitive manner but
67 must be on LSST telescope. This avoids confusion with other
68 telescopes using commissioning cameras.
70 Parameters
71 ----------
72 header : `dict`-like
73 Header to convert to standardized form.
74 filename : `str`, optional
75 Name of file being translated.
77 Returns
78 -------
79 can : `bool`
80 `True` if the header is recognized by this class. `False`
81 otherwise.
82 """
83 if "INSTRUME" in header and "TELESCOP" in header: 83 ↛ 93line 83 didn't jump to line 93 because the condition on line 83 was always true
84 telescope = header["TELESCOP"]
85 instrument = header["INSTRUME"].lower()
86 if instrument == "comcam" and telescope in (SIMONYI_TELESCOPE, "LSST"):
87 return True
88 telcode = header.get("TELCODE", None)
89 # Some lab data from 2019 reports that it is LSST_CAMERA.
90 if telcode == "CC" and instrument == "lsst_camera":
91 return True
93 return False
95 @classmethod
96 def fix_header(cls, header, instrument, obsid, filename=None):
97 """Fix ComCam headers.
99 Notes
100 -----
101 Fixes the following issues:
103 * If ComCam was in Chile, the FILTER is always empty (or unknown).
104 * If LSST_NUM is missing it is filled in by looking at the CCDSLOT
105 value and assuming that the ComCam detectors are fixed.
106 * If ROTPA is missing or non-numeric, it is set to 0.0.
108 Corrections are reported as debug level log messages.
110 See `~astro_metadata_translator.fix_header` for details of the general
111 process.
112 """
113 modified = False
115 # Calculate the standard label to use for log messages
116 log_label = cls._construct_log_prefix(obsid, filename)
118 # Standardize the DAYOBS for use in time-based corrections
119 day_obs = header.get("DAYOBS")
120 i_day_obs = int(day_obs) if day_obs else None
122 physical_filter = header.get("FILTER")
123 if physical_filter in (None, "r", ""):
124 # Create a translator since we need the date
125 translator = cls(header)
126 if physical_filter is None: 126 ↛ 130line 126 didn't jump to line 130 because the condition on line 126 was always true
127 header["FILTER"] = "unknown"
128 physical_filter_str = "None"
129 else:
130 date = translator.to_datetime_begin()
131 if date > COMCAM_TO_CHILE_DATE:
132 header["FILTER"] = "empty"
133 else:
134 header["FILTER"] = "r_03" # it's currently 'r', which is a band not a physical_filter
136 physical_filter_str = f'"{physical_filter}"'
138 log.warning("%s: replaced FILTER %s with \"%s\"",
139 log_label, physical_filter_str, header["FILTER"])
140 modified = True
142 if header.get("INSTRUME") == "LSST_CAMERA":
143 header["INSTRUME"] = "ComCam" # Must match the can_translate check above
144 modified = True
145 log.debug("%s: Correct instrument header for ComCam", log_label)
147 if "LSST_NUM" not in header:
148 slot = header.get("CCDSLOT", None)
149 if slot in DETECTOR_SERIALS: 149 ↛ 154line 149 didn't jump to line 154 because the condition on line 149 was always true
150 header["LSST_NUM"] = DETECTOR_SERIALS[slot]
151 modified = True
152 log.debug("%s: Set LSST_NUM to %s", log_label, header["LSST_NUM"])
154 if "ROTPA" not in header or not isinstance(header["ROTPA"], Number):
155 header["ROTPA"] = 0.0
156 log.warning("Missing ROTPA in header - replacing with 0.0")
157 modified = True
159 # DM-45661: Correct a mislabeling of the g-band filter for data
160 # from 20240729 through the first flat taken on 20240806.
161 if 20240729 <= i_day_obs <= 20240806:
162 if header["FILTER"] == "g_07": 162 ↛ 168line 162 didn't jump to line 168 because the condition on line 162 was always true
163 header["FILTER"] = "g_01"
164 modified = True
165 log.debug("%s: Corrected FILTER g_07 to %s", log_label, header["FILTER"])
168 return modified