Coverage for python/lsst/obs/lsst/translators/imsim.py: 46%

67 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-13 11:40 +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 LSSTCam imSim headers""" 

12 

13__all__ = ("LsstCamImSimTranslator", ) 

14 

15import logging 

16import astropy.units as u 

17from astropy.coordinates import Angle, AltAz 

18from astropy.time import TimeDelta 

19 

20try: 

21 import erfa 

22except ImportError: 

23 import astropy._erfa as erfa 

24 

25from astro_metadata_translator import cache_translation 

26from astro_metadata_translator.translators.helpers import tracking_from_degree_headers 

27 

28from .lsstsim import LsstSimTranslator 

29 

30log = logging.getLogger(__name__) 

31 

32 

33class LsstCamImSimTranslator(LsstSimTranslator): 

34 """Metadata translation class for LSSTCam imSim headers""" 

35 

36 name = "LSSTCam-imSim" 

37 """Name of this translation class""" 

38 

39 _const_map = { 

40 "instrument": "LSSTCam-imSim", 

41 "boresight_rotation_coord": "sky", 

42 "object": "UNKNOWN", 

43 "pressure": None, 

44 "temperature": None, 

45 "relative_humidity": 40.0, 

46 } 

47 

48 _trivial_map = { 

49 "detector_group": "RAFTNAME", 

50 "detector_name": "SENSNAME", 

51 "observation_id": "OBSID", 

52 "science_program": "RUNNUM", 

53 "exposure_id": "OBSID", 

54 "visit_id": "OBSID", 

55 "dark_time": ("DARKTIME", dict(unit=u.s)), 

56 "exposure_time": ("EXPTIME", dict(unit=u.s)), 

57 "detector_serial": "LSST_NUM", 

58 } 

59 

60 cameraPolicyFile = "policy/imsim.yaml" 

61 

62 _ROLLOVER_TIME = TimeDelta(0, scale="tai", format="sec") 

63 """This instrument did not offset the observing day.""" 

64 

65 @classmethod 

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

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

68 supplied header. 

69 

70 There is no ``INSTRUME`` header in ImSim data. Instead we use 

71 the ``TESTTYPE`` header. 

72 

73 Parameters 

74 ---------- 

75 header : `dict`-like 

76 Header to convert to standardized form. 

77 filename : `str`, optional 

78 Name of file being translated. 

79 

80 Returns 

81 ------- 

82 can : `bool` 

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

84 otherwise. 

85 """ 

86 return cls.can_translate_with_options(header, {"TESTTYPE": "IMSIM"}, 

87 filename=filename) 

88 

89 @cache_translation 

90 def to_tracking_radec(self): 

91 # Docstring will be inherited. Property defined in properties.py 

92 radecsys = ("RADESYS",) 

93 radecpairs = (("RATEL", "DECTEL"),) 

94 return tracking_from_degree_headers(self, radecsys, radecpairs) 

95 

96 @cache_translation 

97 def to_boresight_airmass(self): 

98 # Docstring will be inherited. Property defined in properties.py 

99 for key in ("AIRMASS", "AMSTART"): 

100 if self.is_key_ok(key): 

101 return self._header[key] 

102 altaz = self.to_altaz_begin() 

103 if altaz is not None: 

104 return altaz.secz.to_value() 

105 return None 

106 

107 @cache_translation 

108 def to_boresight_rotation_angle(self): 

109 angle = Angle(90.*u.deg) - Angle(self.quantity_from_card("ROTANGLE", u.deg)) 

110 angle = angle.wrap_at("360d") 

111 return angle 

112 

113 @cache_translation 

114 def to_physical_filter(self): 

115 # Find throughputs version from imSim header data. For DC2 

116 # data, we used throughputs version 1.4. 

117 throughputs_version = None 

118 for key, value in self._header.items(): 

119 if key.startswith("PKG") and value == "throughputs": 

120 version_key = "VER" + key[len("PKG"):] 

121 throughputs_version = self._header[version_key].strip() 

122 break 

123 if throughputs_version is None: 

124 log.warning("%s: throughputs version not found. Using FILTER keyword value '%s'.", 

125 self._log_prefix, self._header["FILTER"]) 

126 return self._header["FILTER"] 

127 return "_".join((self._header["FILTER"], "sim", throughputs_version)) 

128 

129 @cache_translation 

130 def to_altaz_begin(self): 

131 # Calculate from the hour angle if available 

132 if self.to_observation_type() != "science": 

133 return None 

134 

135 if not self.are_keys_ok(["HASTART", "DECTEL"]): 

136 # Fallback to slow method 

137 return super().to_altaz_begin() 

138 

139 location = self.to_location() 

140 ha = Angle(self._header["HASTART"], unit=u.deg) 

141 

142 # For speed over accuracy, assume this is apparent Dec not ICRS 

143 dec = Angle(self._header["DECTEL"], unit=u.deg) 

144 

145 # Use erfa directly 

146 az, el = erfa.hd2ae(ha.radian, dec.radian, location.lat.radian) 

147 

148 return AltAz(az*u.radian, el*u.radian, 

149 obstime=self.to_datetime_begin(), location=location)