Coverage for python / lsst / obs / lsst / translators / comCam.py: 25%
59 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-22 08:58 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-22 08:58 +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:
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 physical_filter = header.get("FILTER")
119 if physical_filter in (None, "r", ""):
120 # Create a translator since we need the date
121 translator = cls(header)
122 if physical_filter is None:
123 header["FILTER"] = "unknown"
124 physical_filter_str = "None"
125 else:
126 date = translator.to_datetime_begin()
127 if date > COMCAM_TO_CHILE_DATE:
128 header["FILTER"] = "empty"
129 else:
130 header["FILTER"] = "r_03" # it's currently 'r', which is a band not a physical_filter
132 physical_filter_str = f'"{physical_filter}"'
134 log.warning("%s: replaced FILTER %s with \"%s\"",
135 log_label, physical_filter_str, header["FILTER"])
136 modified = True
138 if header.get("INSTRUME") == "LSST_CAMERA":
139 header["INSTRUME"] = "ComCam" # Must match the can_translate check above
140 modified = True
141 log.debug("%s: Correct instrument header for ComCam", log_label)
143 if "LSST_NUM" not in header:
144 slot = header.get("CCDSLOT", None)
145 if slot in DETECTOR_SERIALS:
146 header["LSST_NUM"] = DETECTOR_SERIALS[slot]
147 modified = True
148 log.debug("%s: Set LSST_NUM to %s", log_label, header["LSST_NUM"])
150 if "ROTPA" not in header or not isinstance(header["ROTPA"], Number):
151 header["ROTPA"] = 0.0
152 log.warning("Missing ROTPA in header - replacing with 0.0")
153 modified = True
155 return modified