Coverage for python/lsst/obs/lsst/translators/comCamSim.py: 31%

70 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-04 03:31 -0700

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 Simulated LSST Commissioning Camera""" 

12 

13__all__ = ("LsstComCamSimTranslator", ) 

14 

15import logging 

16import warnings 

17 

18import astropy 

19import astropy.utils.exceptions 

20from astropy.coordinates import AltAz 

21from astro_metadata_translator import cache_translation 

22 

23from .lsstCam import LsstCamTranslator 

24from .lsst import SIMONYI_TELESCOPE 

25 

26log = logging.getLogger(__name__) 

27 

28 

29class LsstComCamSimTranslator(LsstCamTranslator): 

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

31 

32 name = "LSSTComCamSim" 

33 """Name of this translation class""" 

34 

35 _const_map = { 

36 "instrument": "LSSTComCamSim", 

37 } 

38 

39 cameraPolicyFile = 'policy/comCamSim.yaml' 

40 

41 @classmethod 

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

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

44 supplied header. 

45 

46 Looks for "COMCAMSIM" instrument in case-insensitive manner but 

47 must be on LSST telescope. This avoids confusion with other 

48 telescopes using commissioning cameras. 

49 

50 Parameters 

51 ---------- 

52 header : `dict`-like 

53 Header to convert to standardized form. 

54 filename : `str`, optional 

55 Name of file being translated. 

56 

57 Returns 

58 ------- 

59 can : `bool` 

60 `True` if the header is recognized by this class. `False` 

61 otherwise. 

62 """ 

63 if "INSTRUME" in header and "TELESCOP" in header: 

64 telescope = header["TELESCOP"] 

65 instrument = header["INSTRUME"].lower() 

66 if instrument == "comcamsim" and telescope in (SIMONYI_TELESCOPE, "LSST"): 

67 return True 

68 

69 return False 

70 

71 @classmethod 

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

73 """Fix Simulated ComCam headers. 

74 

75 Notes 

76 ----- 

77 Content will be added as needed. 

78 

79 Corrections are reported as debug level log messages. 

80 

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

82 process. 

83 """ 

84 modified = False 

85 

86 # Calculate the standard label to use for log messages 

87 log_label = cls._construct_log_prefix(obsid, filename) 

88 

89 # Some simulated files lack RASTART/DECSTART etc headers. Since these 

90 # are simulated they can be populated directly from the RA/DEC headers. 

91 synced_radec = False 

92 for key in ("RA", "DEC"): 

93 for time in ("START", "END"): 

94 time_key = f"{key}{time}" 

95 if not header.get(time_key): 

96 if (value := header.get(key)): 

97 header[time_key] = value 

98 synced_radec = True 

99 if synced_radec: 

100 modified = True 

101 log.debug("%s: Synced RASTART/RAEND/DECSTART/DECEND headers with RA/DEC headers", log_label) 

102 

103 if not header.get("RADESYS") and header.get("RA") and header.get("DEC"): 

104 header["RADESYS"] = "ICRS" 

105 log.debug("%s: Forcing undefined RADESYS to '%s'", log_label, header["RADESYS"]) 

106 modified = True 

107 

108 if not header.get("TELCODE"): 

109 if camcode := header.get("CAMCODE"): 

110 header["TELCODE"] = camcode 

111 modified = True 

112 log.debug("%s: Setting TELCODE header from CAMCODE header", log_label) 

113 else: 

114 # Get the code from the OBSID. 

115 code, _ = obsid.split("_", 1) 

116 header["TELCODE"] = code 

117 modified = True 

118 log.debug("%s: Determining telescope code of %s from OBSID", log_label, code) 

119 

120 return modified 

121 

122 def _is_on_mountain(self): 

123 """Indicate whether these data are coming from the instrument 

124 installed on the mountain. 

125 Returns 

126 ------- 

127 is : `bool` 

128 `True` if instrument is on the mountain. 

129 

130 Notes 

131 ----- 

132 TODO: DM-33387 This is currently a terrible hack and MUST be removed 

133 once CAP-807 and CAP-808 are done. 

134 Until then, ALL non-calib ComCam data will look like it is on sky. 

135 """ 

136 return True 

137 

138 @cache_translation 

139 def to_altaz_begin(self): 

140 # Tries to calculate the value. Simulated files for ops-rehearsal 3 

141 # did not have the AZ/EL headers defined. 

142 if self.are_keys_ok(["ELSTART", "AZSTART"]): 

143 return super().to_altaz_begin() 

144 

145 # Calculate it from the RA/Dec and time. 

146 # The time is not consistent with the HASTART/AMSTART values. 

147 # This means that the elevation may well come out negative. 

148 if self.are_keys_ok(["RA", "DEC"]): 

149 # Derive from RADec in absence of any other information 

150 radec = self.to_tracking_radec() 

151 if radec is not None: 

152 # This can trigger warnings because of the future dates 

153 with warnings.catch_warnings(): 

154 warnings.simplefilter("ignore", category=astropy.utils.exceptions.AstropyWarning) 

155 altaz = radec.transform_to(AltAz()) 

156 return altaz 

157 

158 return None 

159 

160 @classmethod 

161 def observing_date_to_offset(cls, observing_date: astropy.time.Time) -> astropy.time.TimeDelta | None: 

162 # Always use the 12 hour offset. 

163 return cls._ROLLOVER_TIME