Coverage for python/astro_metadata_translator/translators/fits.py: 29%

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

54 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 standard FITS headers""" 

13 

14__all__ = ("FitsTranslator", ) 

15 

16from astropy.time import Time 

17from astropy.coordinates import EarthLocation 

18import astropy.units as u 

19 

20from ..translator import MetadataTranslator, cache_translation 

21 

22 

23class FitsTranslator(MetadataTranslator): 

24 """Metadata translator for FITS standard headers. 

25 

26 Understands: 

27 

28 - DATE-OBS 

29 - INSTRUME 

30 - TELESCOP 

31 - OBSGEO-[X,Y,Z] 

32 

33 """ 

34 

35 # Direct translation from header key to standard form 

36 _trivial_map = dict(instrument="INSTRUME", 

37 telescope="TELESCOP") 

38 

39 @classmethod 

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

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

42 supplied header. 

43 

44 Checks the instrument value and compares with the supported 

45 instruments in the class 

46 

47 Parameters 

48 ---------- 

49 header : `dict`-like 

50 Header to convert to standardized form. 

51 filename : `str`, optional 

52 Name of file being translated. 

53 

54 Returns 

55 ------- 

56 can : `bool` 

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

58 otherwise. 

59 """ 

60 if cls.supported_instrument is None: 

61 return False 

62 

63 # Protect against being able to always find a standard 

64 # header for instrument 

65 try: 

66 translator = cls(header, filename=filename) 

67 instrument = translator.to_instrument() 

68 except KeyError: 

69 return False 

70 

71 return instrument == cls.supported_instrument 

72 

73 @classmethod 

74 def _from_fits_date_string(cls, date_str, scale='utc', time_str=None): 

75 """Parse standard FITS ISO-style date string and return time object 

76 

77 Parameters 

78 ---------- 

79 date_str : `str` 

80 FITS format date string to convert to standard form. Bypasses 

81 lookup in the header. 

82 scale : `str`, optional 

83 Override the time scale from the TIMESYS header. Defaults to 

84 UTC. 

85 time_str : `str`, optional 

86 If provided, overrides any time component in the ``dateStr``, 

87 retaining the YYYY-MM-DD component and appending this time 

88 string, assumed to be of format HH:MM::SS.ss. 

89 

90 Returns 

91 ------- 

92 date : `astropy.time.Time` 

93 `~astropy.time.Time` representation of the date. 

94 """ 

95 if time_str is not None: 

96 date_str = "{}T{}".format(date_str[:10], time_str) 

97 

98 return Time(date_str, format="isot", scale=scale) 

99 

100 def _from_fits_date(self, date_key, mjd_key=None, scale=None): 

101 """Calculate a date object from the named FITS header 

102 

103 Uses the TIMESYS header if present to determine the time scale, 

104 defaulting to UTC. Can be overridden since sometimes headers 

105 use TIMESYS for DATE- style headers but also have headers using 

106 different time scales. 

107 

108 Parameters 

109 ---------- 

110 date_key : `str` 

111 The key in the header representing a standard FITS 

112 ISO-style date. Can be `None` to go straight to MJD key. 

113 mjd_key : `str`, optional 

114 The key in the header representing a standard FITS MJD 

115 style date. This key will be tried if ``date_key`` is not 

116 found, is `None`, or can not be parsed. 

117 scale : `str`, optional 

118 Override value to use for the time scale in preference to 

119 TIMESYS or the default. Should be a form understood by 

120 `~astropy.time.Time`. 

121 

122 Returns 

123 ------- 

124 date : `astropy.time.Time` 

125 `~astropy.time.Time` representation of the date. 

126 """ 

127 used = [] 

128 if scale is not None: 

129 pass 

130 elif self.is_key_ok("TIMESYS"): 

131 scale = self._header["TIMESYS"].lower() 

132 used.append("TIMESYS") 

133 else: 

134 scale = "utc" 

135 if date_key is not None and self.is_key_ok(date_key): 

136 date_str = self._header[date_key] 

137 value = self._from_fits_date_string(date_str, scale=scale) 

138 used.append(date_key) 

139 elif self.is_key_ok(mjd_key): 

140 value = Time(self._header[mjd_key], scale=scale, format="mjd") 

141 used.append(mjd_key) 

142 else: 

143 value = None 

144 self._used_these_cards(*used) 

145 return value 

146 

147 @cache_translation 

148 def to_datetime_begin(self): 

149 """Calculate start time of observation. 

150 

151 Uses FITS standard ``MJD-OBS`` or ``DATE-OBS``, in conjunction 

152 with the ``TIMESYS`` header. 

153 

154 Returns 

155 ------- 

156 start_time : `astropy.time.Time` 

157 Time corresponding to the start of the observation. 

158 """ 

159 return self._from_fits_date("DATE-OBS", mjd_key="MJD-OBS") 

160 

161 @cache_translation 

162 def to_datetime_end(self): 

163 """Calculate end time of observation. 

164 

165 Uses FITS standard ``MJD-END`` or ``DATE-END``, in conjunction 

166 with the ``TIMESYS`` header. 

167 

168 Returns 

169 ------- 

170 start_time : `astropy.time.Time` 

171 Time corresponding to the end of the observation. 

172 """ 

173 return self._from_fits_date("DATE-END", mjd_key="MJD-END") 

174 

175 @cache_translation 

176 def to_location(self): 

177 """Calculate the observatory location. 

178 

179 Uses FITS standard ``OBSGEO-`` headers. 

180 

181 Returns 

182 ------- 

183 location : `astropy.coordinates.EarthLocation` 

184 An object representing the location of the telescope. 

185 """ 

186 cards = [f"OBSGEO-{c}" for c in ("X", "Y", "Z")] 

187 coords = [self._header[c] for c in cards] 

188 value = EarthLocation.from_geocentric(*coords, unit=u.m) 

189 self._used_these_cards(*cards) 

190 return value