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

41 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-22 10:14 +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 

21 

22from astro_metadata_translator import cache_translation, merge_headers 

23from astro_metadata_translator.translators.helpers import ( 

24 tracking_from_degree_headers, 

25 altaz_from_degree_headers, 

26) 

27 

28from .lsstsim import LsstSimTranslator 

29 

30log = logging.getLogger(__name__) 

31 

32 

33class LsstCamPhoSimTranslator(LsstSimTranslator): 

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

35 

36 name = "LSSTCam-PhoSim" 

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

38 

39 _const_map = { 

40 "instrument": "LSSTCam-PhoSim", 

41 "boresight_rotation_coord": "sky", 

42 "observation_type": "science", 

43 "object": "UNKNOWN", 

44 "relative_humidity": 40.0, 

45 } 

46 

47 _trivial_map = { 

48 "detector_group": "RAFTNAME", 

49 "observation_id": "OBSID", 

50 "science_program": "RUNNUM", 

51 "exposure_id": "OBSID", 

52 "visit_id": "OBSID", 

53 "physical_filter": "FILTER", 

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

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

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

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

58 "boresight_airmass": "AIRMASS", 

59 "detector_name": "SENSNAME", 

60 "detector_serial": "LSST_NUM", 

61 } 

62 

63 cameraPolicyFile = "policy/phosim.yaml" 

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 PhoSim data. Instead we use 

71 the ``CREATOR`` 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 # Generic PhoSim data does not have an INSTRUME header. 

87 # If an INSTRUME header is present this translator class 

88 # is not suitable. 

89 if "INSTRUME" in header: 

90 return False 

91 else: 

92 return cls.can_translate_with_options( 

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

94 ) 

95 

96 @cache_translation 

97 def to_tracking_radec(self): 

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

99 radecsys = ("RADESYS",) 

100 radecpairs = ( 

101 ("RATEL", "DECTEL"), 

102 ("RA_DEG", "DEC_DEG"), 

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

104 ) 

105 return tracking_from_degree_headers(self, radecsys, radecpairs) 

106 

107 @cache_translation 

108 def to_altaz_begin(self): 

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

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

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

112 return altaz_from_degree_headers( 

113 self, 

114 (("ZENITH", "AZIMUTH"),), 

115 self.to_datetime_begin(), 

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

117 ) 

118 else: 

119 return super().to_altaz_begin() 

120 

121 @cache_translation 

122 def to_boresight_rotation_angle(self): 

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

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

125 ) 

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

127 return angle 

128 

129 @classmethod 

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

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

132 

133 Phosim splits useful metadata between the primary header and the 

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

135 first two. 

136 

137 Parameters 

138 ---------- 

139 filename : `str` 

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

141 primary : `dict`-like, optional 

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

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

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

145 ignored. 

146 

147 Yields 

148 ------ 

149 headers : iterator of `dict`-like 

150 The primary header merged with the secondary header. 

151 

152 Notes 

153 ----- 

154 This translator class is specifically tailored to raw PhoSim data 

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

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

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

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

159 translation. 

160 """ 

161 with fits.open(filename) as fits_file: 

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

163 mode="overwrite")