Coverage for python/astro_metadata_translator/translators/suprimecam.py: 36%

Shortcuts on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

101 statements  

1# This file is part of astro_metadata_translator. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://www.lsst.org). 

6# See the LICENSE file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12"""Metadata translation code for SuprimeCam FITS headers""" 

13 

14__all__ = ("SuprimeCamTranslator", ) 

15 

16import re 

17import logging 

18import posixpath 

19 

20import astropy.units as u 

21from astropy.coordinates import SkyCoord, Angle 

22 

23from ..translator import cache_translation, CORRECTIONS_RESOURCE_ROOT 

24from .subaru import SubaruTranslator 

25from .helpers import altaz_from_degree_headers 

26 

27log = logging.getLogger(__name__) 

28 

29 

30class SuprimeCamTranslator(SubaruTranslator): 

31 """Metadata translator for HSC standard headers. 

32 """ 

33 

34 name = "SuprimeCam" 

35 """Name of this translation class""" 

36 

37 supported_instrument = "SuprimeCam" 

38 """Supports the SuprimeCam instrument.""" 

39 

40 default_resource_root = posixpath.join(CORRECTIONS_RESOURCE_ROOT, "SuprimeCam") 

41 """Default resource path root to use to locate header correction files.""" 

42 

43 _const_map = {"boresight_rotation_coord": "unknown", 

44 "detector_group": None} 

45 """Constant mappings""" 

46 

47 _trivial_map = {"observation_id": "EXP-ID", 

48 "object": "OBJECT", 

49 "science_program": "PROP-ID", 

50 "detector_num": "DET-ID", 

51 "detector_serial": "DETECTOR", # DETECTOR is the "call name" 

52 "boresight_airmass": "AIRMASS", 

53 "relative_humidity": "OUT-HUM", 

54 "temperature": ("OUT-TMP", dict(unit=u.K)), 

55 "pressure": ("OUT-PRS", dict(unit=u.hPa)), 

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

57 "dark_time": ("EXPTIME", dict(unit=u.s)), # Assume same as exposure time 

58 } 

59 """One-to-one mappings""" 

60 

61 # Zero point for SuprimeCam dates: 2004-01-01 

62 _DAY0 = 53005 

63 

64 @classmethod 

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

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

67 supplied header. 

68 

69 Parameters 

70 ---------- 

71 header : `dict`-like 

72 Header to convert to standardized form. 

73 filename : `str`, optional 

74 Name of file being translated. 

75 

76 Returns 

77 ------- 

78 can : `bool` 

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

80 otherwise. 

81 """ 

82 if "INSTRUME" in header: 

83 return header["INSTRUME"] == "SuprimeCam" 

84 

85 for k in ("EXP-ID", "FRAMEID"): 

86 if cls.is_keyword_defined(header, k): 

87 if header[k].startswith("SUP"): 

88 return True 

89 return False 

90 

91 def _get_adjusted_mjd(self): 

92 """Calculate the modified julian date offset from reference day 

93 

94 Returns 

95 ------- 

96 offset : `int` 

97 Offset day count from reference day. 

98 """ 

99 mjd = self._header["MJD"] 

100 self._used_these_cards("MJD") 

101 return int(mjd) - self._DAY0 

102 

103 @cache_translation 

104 def to_physical_filter(self): 

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

106 value = self._header["FILTER01"].strip().upper() 

107 self._used_these_cards("FILTER01") 

108 # Map potential "unknown" values to standard form 

109 if value in {"UNRECOGNIZED", "UNRECOGNISED", "NOTSET", "UNKNOWN"}: 

110 value = "unknown" 

111 elif value == "NONE": 

112 value = "empty" 

113 return value 

114 

115 @cache_translation 

116 def to_datetime_begin(self): 

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

118 # We know it is UTC 

119 value = self._from_fits_date_string(self._header["DATE-OBS"], 

120 time_str=self._header["UT-STR"], scale="utc") 

121 self._used_these_cards("DATE-OBS", "UT-STR") 

122 return value 

123 

124 @cache_translation 

125 def to_datetime_end(self): 

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

127 # We know it is UTC 

128 value = self._from_fits_date_string(self._header["DATE-OBS"], 

129 time_str=self._header["UT-END"], scale="utc") 

130 self._used_these_cards("DATE-OBS", "UT-END") 

131 

132 # Sometimes the end time is less than the begin time plus the 

133 # exposure time so we have to check for that. 

134 exposure_time = self.to_exposure_time() 

135 datetime_begin = self.to_datetime_begin() 

136 exposure_end = datetime_begin + exposure_time 

137 if value < exposure_end: 

138 value = exposure_end 

139 

140 return value 

141 

142 @cache_translation 

143 def to_exposure_id(self): 

144 """Calculate unique exposure integer for this observation 

145 

146 Returns 

147 ------- 

148 visit : `int` 

149 Integer uniquely identifying this exposure. 

150 """ 

151 exp_id = self._header["EXP-ID"].strip() 

152 m = re.search(r"^SUP[A-Z](\d{7})0$", exp_id) 

153 if not m: 

154 raise RuntimeError(f"{self._log_prefix}: Unable to interpret EXP-ID: {exp_id}") 

155 exposure = int(m.group(1)) 

156 if int(exposure) == 0: 

157 # Don't believe it 

158 frame_id = self._header["FRAMEID"].strip() 

159 m = re.search(r"^SUP[A-Z](\d{7})\d{1}$", frame_id) 

160 if not m: 

161 raise RuntimeError(f"{self._log_prefix}: Unable to interpret FRAMEID: {frame_id}") 

162 exposure = int(m.group(1)) 

163 self._used_these_cards("EXP-ID", "FRAMEID") 

164 return exposure 

165 

166 @cache_translation 

167 def to_visit_id(self): 

168 """Calculate the unique integer ID for this visit. 

169 

170 Assumed to be identical to the exposure ID in this implementation. 

171 

172 Returns 

173 ------- 

174 exp : `int` 

175 Unique visit identifier. 

176 """ 

177 return self.to_exposure_id() 

178 

179 @cache_translation 

180 def to_observation_type(self): 

181 """Calculate the observation type. 

182 

183 Returns 

184 ------- 

185 typ : `str` 

186 Observation type. Normalized to standard set. 

187 """ 

188 obstype = self._header["DATA-TYP"].strip().lower() 

189 self._used_these_cards("DATA-TYP") 

190 if obstype == "object": 

191 return "science" 

192 return obstype 

193 

194 @cache_translation 

195 def to_tracking_radec(self): 

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

197 radec = SkyCoord(self._header["RA2000"], self._header["DEC2000"], 

198 frame="icrs", unit=(u.hourangle, u.deg), 

199 obstime=self.to_datetime_begin(), location=self.to_location()) 

200 self._used_these_cards("RA2000", "DEC2000") 

201 return radec 

202 

203 @cache_translation 

204 def to_altaz_begin(self): 

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

206 return altaz_from_degree_headers(self, (("ALTITUDE", "AZIMUTH"),), 

207 self.to_datetime_begin()) 

208 

209 @cache_translation 

210 def to_boresight_rotation_angle(self): 

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

212 angle = Angle(self.quantity_from_card("INR-STR", u.deg)) 

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

214 return angle 

215 

216 @cache_translation 

217 def to_detector_exposure_id(self): 

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

219 return self.to_exposure_id() * 10 + self.to_detector_num() 

220 

221 @cache_translation 

222 def to_detector_name(self): 

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

224 # See https://subarutelescope.org/Observing/Instruments/SCam/ccd.html 

225 num = self.to_detector_num() 

226 

227 names = ( 

228 "nausicaa", 

229 "kiki", 

230 "fio", 

231 "sophie", 

232 "sheeta", 

233 "satsuki", 

234 "chihiro", 

235 "clarisse", 

236 "ponyo", 

237 "san", 

238 ) 

239 

240 return names[num]