Hide keyboard shortcuts

Hot-keys 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

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 return value 

109 

110 @cache_translation 

111 def to_datetime_begin(self): 

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

113 # We know it is UTC 

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

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

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

117 return value 

118 

119 @cache_translation 

120 def to_datetime_end(self): 

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

122 # We know it is UTC 

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

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

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

126 

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

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

129 exposure_time = self.to_exposure_time() 

130 datetime_begin = self.to_datetime_begin() 

131 exposure_end = datetime_begin + exposure_time 

132 if value < exposure_end: 

133 value = exposure_end 

134 

135 return value 

136 

137 @cache_translation 

138 def to_exposure_id(self): 

139 """Calculate unique exposure integer for this observation 

140 

141 Returns 

142 ------- 

143 visit : `int` 

144 Integer uniquely identifying this exposure. 

145 """ 

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

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

148 if not m: 

149 raise RuntimeError("Unable to interpret EXP-ID: %s" % exp_id) 

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

151 if int(exposure) == 0: 

152 # Don't believe it 

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

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

155 if not m: 

156 raise RuntimeError("Unable to interpret FRAMEID: %s" % frame_id) 

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

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

159 return exposure 

160 

161 @cache_translation 

162 def to_visit_id(self): 

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

164 

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

166 

167 Returns 

168 ------- 

169 exp : `int` 

170 Unique visit identifier. 

171 """ 

172 return self.to_exposure_id() 

173 

174 @cache_translation 

175 def to_observation_type(self): 

176 """Calculate the observation type. 

177 

178 Returns 

179 ------- 

180 typ : `str` 

181 Observation type. Normalized to standard set. 

182 """ 

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

184 self._used_these_cards("DATA-TYP") 

185 if obstype == "object": 

186 return "science" 

187 return obstype 

188 

189 @cache_translation 

190 def to_tracking_radec(self): 

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

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

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

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

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

196 return radec 

197 

198 @cache_translation 

199 def to_altaz_begin(self): 

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

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

202 self.to_datetime_begin()) 

203 

204 @cache_translation 

205 def to_boresight_rotation_angle(self): 

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

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

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

209 return angle 

210 

211 @cache_translation 

212 def to_detector_exposure_id(self): 

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

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

215 

216 @cache_translation 

217 def to_detector_name(self): 

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

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

220 num = self.to_detector_num() 

221 

222 names = ( 

223 "nausicaa", 

224 "kiki", 

225 "fio", 

226 "sophie", 

227 "sheeta", 

228 "satsuki", 

229 "chihiro", 

230 "clarisse", 

231 "ponyo", 

232 "san", 

233 ) 

234 

235 return names[num]