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 CFHT MegaPrime FITS headers""" 

13 

14__all__ = ("MegaPrimeTranslator", ) 

15 

16import re 

17import posixpath 

18from astropy.coordinates import EarthLocation, Angle 

19import astropy.units as u 

20 

21from ..translator import cache_translation, CORRECTIONS_RESOURCE_ROOT 

22from .fits import FitsTranslator 

23from .helpers import tracking_from_degree_headers, altaz_from_degree_headers 

24 

25 

26class MegaPrimeTranslator(FitsTranslator): 

27 """Metadata translator for CFHT MegaPrime standard headers. 

28 """ 

29 

30 name = "MegaPrime" 

31 """Name of this translation class""" 

32 

33 supported_instrument = "MegaPrime" 

34 """Supports the MegaPrime instrument.""" 

35 

36 default_resource_root = posixpath.join(CORRECTIONS_RESOURCE_ROOT, "CFHT") 

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

38 

39 # CFHT Megacam has no rotator, and the instrument angle on sky is set to 

40 # +Y=N, +X=W which we define as a 0 degree rotation. 

41 _const_map = {"boresight_rotation_angle": Angle(0*u.deg), 

42 "boresight_rotation_coord": "sky", 

43 "detector_group": None} 

44 

45 _trivial_map = {"physical_filter": "FILTER", 

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

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

48 "observation_id": "OBSID", 

49 "object": "OBJECT", 

50 "science_program": "RUNID", 

51 "exposure_id": "EXPNUM", 

52 "visit_id": "EXPNUM", 

53 "detector_serial": "CCDNAME", 

54 "relative_humidity": ["RELHUMID", "HUMIDITY"], 

55 "temperature": (["TEMPERAT", "AIRTEMP"], dict(unit=u.deg_C)), 

56 "boresight_airmass": ["AIRMASS", "BORE-AIRMASS"]} 

57 

58 @cache_translation 

59 def to_datetime_begin(self): 

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

61 # We know it is UTC 

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

63 time_str=self._header["UTC-OBS"], scale="utc") 

64 self._used_these_cards("DATE-OBS", "UTC-OBS") 

65 return value 

66 

67 @cache_translation 

68 def to_datetime_end(self): 

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

70 # Older files are missing UTCEND 

71 if self.is_key_ok("UTCEND"): 

72 # We know it is UTC 

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

74 time_str=self._header["UTCEND"], scale="utc") 

75 self._used_these_cards("DATE-OBS", "UTCEND") 

76 else: 

77 # Take a guess by adding on the exposure time 

78 value = self.to_datetime_begin() + self.to_exposure_time() 

79 return value 

80 

81 @cache_translation 

82 def to_location(self): 

83 """Calculate the observatory location. 

84 

85 Returns 

86 ------- 

87 location : `astropy.coordinates.EarthLocation` 

88 An object representing the location of the telescope. 

89 """ 

90 # Height is not in some MegaPrime files. Use the value from 

91 # EarthLocation.of_site("CFHT") 

92 # Some data uses OBS-LONG, OBS-LAT, other data uses LONGITUD and 

93 # LATITUDE 

94 for long_key, lat_key in (("LONGITUD", "LATITUDE"), ("OBS-LONG", "OBS-LAT")): 

95 if self.are_keys_ok([long_key, lat_key]): 

96 value = EarthLocation.from_geodetic(self._header[long_key], self._header[lat_key], 4215.0) 

97 self._used_these_cards(long_key, lat_key) 

98 break 

99 else: 

100 value = EarthLocation.of_site("CFHT") 

101 return value 

102 

103 @cache_translation 

104 def to_detector_name(self): 

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

106 if self.is_key_ok("EXTNAME"): 

107 name = self._header["EXTNAME"] 

108 # Only valid name has form "ccdNN" 

109 if re.match(r"ccd\d+$", name): 

110 self._used_these_cards("EXTNAME") 

111 return name 

112 

113 # Dummy value, intended for PHU (need something to get filename) 

114 return "ccd99" 

115 

116 @cache_translation 

117 def to_detector_num(self): 

118 name = self.to_detector_name() 

119 return int(name[3:]) 

120 

121 @cache_translation 

122 def to_observation_type(self): 

123 """Calculate the observation type. 

124 

125 Returns 

126 ------- 

127 typ : `str` 

128 Observation type. Normalized to standard set. 

129 """ 

130 obstype = self._header["OBSTYPE"].strip().lower() 

131 self._used_these_cards("OBSTYPE") 

132 if obstype == "object": 

133 return "science" 

134 return obstype 

135 

136 @cache_translation 

137 def to_tracking_radec(self): 

138 """Calculate the tracking RA/Dec for this observation. 

139 

140 Currently will be `None` for geocentric apparent coordinates. 

141 Additionally, can be `None` for non-science observations. 

142 

143 The method supports multiple versions of header defining tracking 

144 coordinates. 

145 

146 Returns 

147 ------- 

148 coords : `astropy.coordinates.SkyCoord` 

149 The tracking coordinates. 

150 """ 

151 radecsys = ("RADECSYS", "OBJRADEC", "RADESYS") 

152 radecpairs = (("RA_DEG", "DEC_DEG"), ("BORE-RA", "BORE-DEC")) 

153 return tracking_from_degree_headers(self, radecsys, radecpairs) 

154 

155 @cache_translation 

156 def to_altaz_begin(self): 

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

158 return altaz_from_degree_headers(self, (("TELALT", "TELAZ"), ("BORE-ALT", "BORE-AZ")), 

159 self.to_datetime_begin()) 

160 

161 @cache_translation 

162 def to_detector_exposure_id(self): 

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

164 return self.to_exposure_id() * 36 + self.to_detector_num() 

165 

166 @cache_translation 

167 def to_pressure(self): 

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

169 # Can be either AIRPRESS in Pa or PRESSURE in mbar 

170 for key, unit in (("PRESSURE", u.hPa), ("AIRPRESS", u.Pa)): 

171 if self.is_key_ok(key): 

172 return self.quantity_from_card(key, unit) 

173 else: 

174 raise KeyError(f"{self._log_prefix}: Could not find pressure keywords in header") 

175 

176 @cache_translation 

177 def to_observation_counter(self): 

178 """Return the lifetime exposure number. 

179 

180 Returns 

181 ------- 

182 sequence : `int` 

183 The observation counter. 

184 """ 

185 return self.to_exposure_id()