Coverage for python / lsst / obs / lsst / translators / comCam.py: 25%

59 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-18 09:09 +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. 

10 

11"""Metadata translation code for the LSST Commissioning Camera""" 

12 

13__all__ = ("LsstComCamTranslator", ) 

14 

15import logging 

16from numbers import Number 

17 

18from astropy.time import Time 

19from .lsstCam import LsstCamTranslator 

20from .lsst import SIMONYI_TELESCOPE 

21 

22log = logging.getLogger(__name__) 

23 

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} 

35 

36# Date ComCam left Tucson bound for Chile 

37COMCAM_TO_CHILE_DATE = Time("2020-03-13T00:00", format="isot", scale="utc") 

38 

39 

40class LsstComCamTranslator(LsstCamTranslator): 

41 """Metadata translation for the LSST Commissioning Camera.""" 

42 

43 name = "LSSTComCam" 

44 """Name of this translation class""" 

45 

46 _const_map = { 

47 "instrument": "LSSTComCam", 

48 } 

49 

50 # Use the comCam raft definition 

51 cameraPolicyFile = "policy/comCam.yaml" 

52 

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 

56 

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") 

60 

61 @classmethod 

62 def can_translate(cls, header, filename=None): 

63 """Indicate whether this translation class can translate the 

64 supplied header. 

65 

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. 

69 

70 Parameters 

71 ---------- 

72 header : `dict`-like 

73 Header to convert to standardized form. 

74 filename : `str`, optional 

75 Name of file being translated. 

76 

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 

92 

93 return False 

94 

95 @classmethod 

96 def fix_header(cls, header, instrument, obsid, filename=None): 

97 """Fix ComCam headers. 

98 

99 Notes 

100 ----- 

101 Fixes the following issues: 

102 

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. 

107 

108 Corrections are reported as debug level log messages. 

109 

110 See `~astro_metadata_translator.fix_header` for details of the general 

111 process. 

112 """ 

113 modified = False 

114 

115 # Calculate the standard label to use for log messages 

116 log_label = cls._construct_log_prefix(obsid, filename) 

117 

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 

131 

132 physical_filter_str = f'"{physical_filter}"' 

133 

134 log.warning("%s: replaced FILTER %s with \"%s\"", 

135 log_label, physical_filter_str, header["FILTER"]) 

136 modified = True 

137 

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) 

142 

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"]) 

149 

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 

154 

155 return modified