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 

25filters = {'u.MP9301': 'u', 

26 'u.MP9302': 'u2', 

27 'g.MP9401': 'g', 

28 'g.MP9402': 'g2', 

29 'r.MP9601': 'r', 

30 'r.MP9602': 'r2', 

31 'i.MP9701': 'i', 

32 'i.MP9702': 'i2', 

33 'i.MP9703': 'i3', 

34 'z.MP9801': 'z', 

35 'z.MP9901': 'z2', 

36 } 

37 

38 

39class MegaPrimeTranslator(FitsTranslator): 

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

41 """ 

42 

43 name = "MegaPrime" 

44 """Name of this translation class""" 

45 

46 supported_instrument = "MegaPrime" 

47 """Supports the MegaPrime instrument.""" 

48 

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

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

51 

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

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

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

55 "boresight_rotation_coord": "sky", 

56 "detector_group": None} 

57 

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

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

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

61 "observation_id": "OBSID", 

62 "object": "OBJECT", 

63 "science_program": "RUNID", 

64 "exposure_id": "EXPNUM", 

65 "visit_id": "EXPNUM", 

66 "detector_serial": "CCDNAME", 

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

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

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

70 

71 @cache_translation 

72 def to_datetime_begin(self): 

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

74 # We know it is UTC 

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

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

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

78 return value 

79 

80 @cache_translation 

81 def to_datetime_end(self): 

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

83 # Older files are missing UTCEND 

84 if self.is_key_ok("UTCEND"): 

85 # We know it is UTC 

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

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

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

89 else: 

90 # Take a guess by adding on the exposure time 

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

92 return value 

93 

94 @cache_translation 

95 def to_location(self): 

96 """Calculate the observatory location. 

97 

98 Returns 

99 ------- 

100 location : `astropy.coordinates.EarthLocation` 

101 An object representing the location of the telescope. 

102 """ 

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

104 # EarthLocation.of_site("CFHT") 

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

106 # LATITUDE 

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

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

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

110 self._used_these_cards(long_key, lat_key) 

111 break 

112 else: 

113 value = EarthLocation.of_site("CFHT") 

114 return value 

115 

116 @cache_translation 

117 def to_detector_name(self): 

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

119 if self.is_key_ok("EXTNAME"): 

120 name = self._header["EXTNAME"] 

121 # Only valid name has form "ccdNN" 

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

123 self._used_these_cards("EXTNAME") 

124 return name 

125 

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

127 return "ccd99" 

128 

129 @cache_translation 

130 def to_detector_num(self): 

131 name = self.to_detector_name() 

132 return int(name[3:]) 

133 

134 @cache_translation 

135 def to_observation_type(self): 

136 """Calculate the observation type. 

137 

138 Returns 

139 ------- 

140 typ : `str` 

141 Observation type. Normalized to standard set. 

142 """ 

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

144 self._used_these_cards("OBSTYPE") 

145 if obstype == "object": 

146 return "science" 

147 return obstype 

148 

149 @cache_translation 

150 def to_tracking_radec(self): 

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

152 

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

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

155 

156 The method supports multiple versions of header defining tracking 

157 coordinates. 

158 

159 Returns 

160 ------- 

161 coords : `astropy.coordinates.SkyCoord` 

162 The tracking coordinates. 

163 """ 

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

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

166 return tracking_from_degree_headers(self, radecsys, radecpairs) 

167 

168 @cache_translation 

169 def to_altaz_begin(self): 

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

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

172 self.to_datetime_begin()) 

173 

174 @cache_translation 

175 def to_detector_exposure_id(self): 

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

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

178 

179 @cache_translation 

180 def to_pressure(self): 

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

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

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

184 if self.is_key_ok(key): 

185 return self.quantity_from_card(key, unit) 

186 else: 

187 raise KeyError("Could not find pressure keywords in header")