Coverage for python/lsst/obs/lsst/translators/phosim.py: 67%

44 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-03 11:30 +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 PhoSim FITS headers""" 

12 

13__all__ = ("LsstCamPhoSimTranslator",) 

14 

15import logging 

16 

17import astropy.io.fits as fits 

18import astropy.units as u 

19import astropy.units.cds as cds 

20from astropy.coordinates import Angle 

21from astropy.time import TimeDelta 

22 

23from astro_metadata_translator import cache_translation, merge_headers 

24from astro_metadata_translator.translators.helpers import ( 

25 tracking_from_degree_headers, 

26 altaz_from_degree_headers, 

27) 

28 

29from .lsstsim import LsstSimTranslator 

30 

31log = logging.getLogger(__name__) 

32 

33 

34class LsstCamPhoSimTranslator(LsstSimTranslator): 

35 """Metadata translator for LSSTCam PhoSim data.""" 

36 

37 name = "LSSTCam-PhoSim" 

38 """Name of this translation class""" 

39 

40 _const_map = { 

41 "instrument": "LSSTCam-PhoSim", 

42 "boresight_rotation_coord": "sky", 

43 "observation_type": "science", 

44 "object": "UNKNOWN", 

45 "relative_humidity": 40.0, 

46 } 

47 

48 _trivial_map = { 

49 "detector_group": "RAFTNAME", 

50 "observation_id": "OBSID", 

51 "science_program": "RUNNUM", 

52 "exposure_id": "OBSID", 

53 "visit_id": "OBSID", 

54 "physical_filter": "FILTER", 

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

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

57 "temperature": ("TEMPERA", dict(unit=u.deg_C)), 

58 "pressure": ("PRESS", dict(unit=cds.mmHg)), 

59 "boresight_airmass": "AIRMASS", 

60 "detector_name": "SENSNAME", 

61 "detector_serial": "LSST_NUM", 

62 } 

63 

64 cameraPolicyFile = "policy/phosim.yaml" 

65 

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

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

68 

69 @classmethod 

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

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

72 supplied header. 

73 

74 There is no ``INSTRUME`` header in PhoSim data. Instead we use 

75 the ``CREATOR`` header. 

76 

77 Parameters 

78 ---------- 

79 header : `dict`-like 

80 Header to convert to standardized form. 

81 filename : `str`, optional 

82 Name of file being translated. 

83 

84 Returns 

85 ------- 

86 can : `bool` 

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

88 otherwise. 

89 """ 

90 # Generic PhoSim data does not have an INSTRUME header. 

91 # If an INSTRUME header is present this translator class 

92 # is not suitable. 

93 if "INSTRUME" in header: 

94 return False 

95 else: 

96 return cls.can_translate_with_options( 

97 header, {"CREATOR": "PHOSIM", "TESTTYPE": "PHOSIM"}, filename=filename 

98 ) 

99 

100 @cache_translation 

101 def to_tracking_radec(self): 

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

103 radecsys = ("RADESYS",) 

104 radecpairs = ( 

105 ("RATEL", "DECTEL"), 

106 ("RA_DEG", "DEC_DEG"), 

107 ("BORE-RA", "BORE-DEC"), 

108 ) 

109 return tracking_from_degree_headers(self, radecsys, radecpairs) 

110 

111 @cache_translation 

112 def to_altaz_begin(self): 

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

114 # Fallback to the "derive from ra/dec" if keys are missing 

115 if self.are_keys_ok(["ZENITH", "AZIMUTH"]): 

116 return altaz_from_degree_headers( 

117 self, 

118 (("ZENITH", "AZIMUTH"),), 

119 self.to_datetime_begin(), 

120 is_zd=set(["ZENITH"]), 

121 ) 

122 else: 

123 return super().to_altaz_begin() 

124 

125 @cache_translation 

126 def to_boresight_rotation_angle(self): 

127 angle = Angle(90.0 * u.deg) - Angle( 

128 self.quantity_from_card(["ROTANGZ", "ROTANGLE"], u.deg) 

129 ) 

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

131 return angle 

132 

133 @classmethod 

134 def determine_translatable_headers(cls, filename, primary=None): 

135 """Given a file return all the headers usable for metadata translation. 

136 

137 Phosim splits useful metadata between the primary header and the 

138 amplifier headers. A single header is returned as a merge of the 

139 first two. 

140 

141 Parameters 

142 ---------- 

143 filename : `str` 

144 Path to a file in a format understood by this translator. 

145 primary : `dict`-like, optional 

146 The primary header obtained by the caller. This is sometimes 

147 already known, for example if a system is trying to bootstrap 

148 without already knowing what data is in the file. Will be 

149 ignored. 

150 

151 Yields 

152 ------ 

153 headers : iterator of `dict`-like 

154 The primary header merged with the secondary header. 

155 

156 Notes 

157 ----- 

158 This translator class is specifically tailored to raw PhoSim data 

159 and is not designed to work with general FITS files. The normal 

160 paradigm is for the caller to have read the first header and then 

161 called `determine_translator()` on the result to work out which 

162 translator class to then call to obtain the real headers to be used for 

163 translation. 

164 """ 

165 with fits.open(filename) as fits_file: 

166 yield merge_headers([fits_file[0].header, fits_file[1].header], 

167 mode="overwrite")