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 obs_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

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

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

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <http://www.gnu.org/licenses/>. 

21 

22import warnings 

23import astropy.units 

24import astropy.utils.exceptions 

25from astropy.utils import iers 

26 

27# Prefer the standard pyerfa over the Astropy version 

28try: 

29 import erfa 

30 ErfaWarning = erfa.ErfaWarning 

31except ImportError: 

32 import astropy._erfa as erfa 

33 ErfaWarning = None 

34 

35from astro_metadata_translator import ObservationInfo 

36 

37from lsst.log import Log 

38from lsst.daf.base import DateTime 

39from lsst.geom import degrees, radians 

40from lsst.afw.image import VisitInfo, RotType 

41from lsst.afw.coord import Observatory, Weather 

42from lsst.geom import SpherePoint 

43 

44__all__ = ["MakeRawVisitInfoViaObsInfo"] 

45 

46 

47class MakeRawVisitInfoViaObsInfo: 

48 """Base class functor to make a VisitInfo from the FITS header of a 

49 raw image using `~astro_metadata_translator.ObservationInfo` translators. 

50 

51 Subclasses can be used if a specific 

52 `~astro_metadata_translator.MetadataTranslator` translator should be used. 

53 

54 The design philosophy is to make a best effort and log warnings of 

55 problems, rather than raising exceptions, in order to extract as much 

56 VisitInfo information as possible from a messy FITS header without the 

57 user needing to add a lot of error handling. 

58 

59 Parameters 

60 ---------- 

61 log : `lsst.log.Log` or None 

62 Logger to use for messages. 

63 (None to use ``Log.getLogger("MakeRawVisitInfoViaObsInfo")``). 

64 doStripHeader : `bool`, optional 

65 Strip header keywords from the metadata as they are used? 

66 """ 

67 

68 metadataTranslator = None 

69 """Header translator to use to construct VisitInfo, defaulting to 

70 automatic determination.""" 

71 

72 def __init__(self, log=None, doStripHeader=False): 

73 if log is None: 

74 log = Log.getLogger("MakeRawVisitInfoViaObsInfo") 

75 self.log = log 

76 self.doStripHeader = doStripHeader 

77 

78 def __call__(self, md, exposureId=None): 

79 """Construct a VisitInfo and strip associated data from the metadata. 

80 

81 Parameters 

82 ---------- 

83 md : `lsst.daf.base.PropertyList` or `lsst.daf.base.PropertySet` 

84 Metadata to pull from. 

85 May be modified if ``stripHeader`` is ``True``. 

86 exposureId : `int`, optional 

87 Ignored. Here for compatibility with `MakeRawVisitInfo`. 

88 

89 Returns 

90 ------- 

91 visitInfo : `lsst.afw.image.VisitInfo` 

92 `~lsst.afw.image.VisitInfo` derived from the header using 

93 a `~astro_metadata_translator.MetadataTranslator`. 

94 """ 

95 

96 obsInfo = ObservationInfo(md, translator_class=self.metadataTranslator) 

97 

98 if self.doStripHeader: 

99 # Strip all the cards out that were used 

100 for c in obsInfo.cards_used: 

101 del md[c] 

102 

103 return self.observationInfo2visitInfo(obsInfo, log=self.log) 

104 

105 @staticmethod 

106 def observationInfo2visitInfo(obsInfo, log=None): 

107 """Construct a `~lsst.afw.image.VisitInfo` from an 

108 `~astro_metadata_translator.ObservationInfo` 

109 

110 Parameters 

111 ---------- 

112 obsInfo : `astro_metadata_translator.ObservationInfo` 

113 Information gathered from the observation metadata. 

114 log : `logging.Logger` or `lsst.log.Log`, optional 

115 Logger to use for logging informational messages. 

116 If `None` logging will be disabled. 

117 

118 Returns 

119 ------- 

120 visitInfo : `lsst.afw.image.VisitInfo` 

121 `~lsst.afw.image.VisitInfo` derived from the supplied 

122 `~astro_metadata_translator.ObservationInfo`. 

123 """ 

124 argDict = dict() 

125 

126 # Map the translated information into a form suitable for VisitInfo 

127 if obsInfo.exposure_time is not None: 

128 argDict["exposureTime"] = obsInfo.exposure_time.to_value("s") 

129 if obsInfo.dark_time is not None: 

130 argDict["darkTime"] = obsInfo.dark_time.to_value("s") 

131 argDict["exposureId"] = obsInfo.detector_exposure_id 

132 argDict["id"] = obsInfo.exposure_id 

133 argDict["instrumentLabel"] = obsInfo.instrument 

134 

135 # VisitInfo uses the middle of the observation for the date 

136 if obsInfo.datetime_begin is not None and obsInfo.datetime_end is not None: 

137 tdelta = obsInfo.datetime_end - obsInfo.datetime_begin 

138 middle = obsInfo.datetime_begin + 0.5*tdelta 

139 

140 # DateTime uses nanosecond resolution, regardless of the resolution 

141 # of the original date 

142 middle.precision = 9 

143 # isot is ISO8601 format with "T" separating date and time and no 

144 # time zone 

145 argDict["date"] = DateTime(middle.tai.isot, DateTime.TAI) 

146 

147 # Derive earth rotation angle from UT1 (being out by a second is 

148 # not a big deal given the uncertainty over exactly what part of 

149 # the observation we are needing it for). 

150 # ERFA needs a UT1 time split into two floats 

151 # We ignore any problems with DUT1 not being defined for now. 

152 try: 

153 # Catch any warnings about the time being in the future 

154 # since there is nothing we can do about that for simulated 

155 # data and it tells us nothing for data from the past. 

156 with warnings.catch_warnings(): 

157 # If we are using the real erfa it is not an AstropyWarning 

158 # During transition period filter both 

159 warnings.simplefilter("ignore", category=astropy.utils.exceptions.AstropyWarning) 

160 if ErfaWarning is not None: 

161 warnings.simplefilter("ignore", category=ErfaWarning) 

162 ut1time = middle.ut1 

163 except iers.IERSRangeError: 

164 ut1time = middle 

165 

166 era = erfa.era00(ut1time.jd1, ut1time.jd2) 

167 argDict["era"] = era * radians 

168 else: 

169 argDict["date"] = DateTime() 

170 

171 # Coordinates 

172 if obsInfo.tracking_radec is not None: 

173 icrs = obsInfo.tracking_radec.transform_to("icrs") 

174 argDict["boresightRaDec"] = SpherePoint(icrs.ra.degree, 

175 icrs.dec.degree, units=degrees) 

176 

177 altaz = obsInfo.altaz_begin 

178 if altaz is not None: 

179 argDict["boresightAzAlt"] = SpherePoint(altaz.az.degree, 

180 altaz.alt.degree, units=degrees) 

181 

182 argDict["boresightAirmass"] = obsInfo.boresight_airmass 

183 

184 if obsInfo.boresight_rotation_angle is not None: 

185 argDict["boresightRotAngle"] = obsInfo.boresight_rotation_angle.degree*degrees 

186 

187 if obsInfo.boresight_rotation_coord is not None: 

188 rotType = RotType.UNKNOWN 

189 if obsInfo.boresight_rotation_coord == "sky": 

190 rotType = RotType.SKY 

191 argDict["rotType"] = rotType 

192 

193 # Weather and Observatory Location 

194 temperature = float("nan") 

195 if obsInfo.temperature is not None: 

196 temperature = obsInfo.temperature.to_value("deg_C", astropy.units.temperature()) 

197 pressure = float("nan") 

198 if obsInfo.pressure is not None: 

199 pressure = obsInfo.pressure.to_value("Pa") 

200 relative_humidity = float("nan") 

201 if obsInfo.relative_humidity is not None: 

202 relative_humidity = obsInfo.relative_humidity 

203 argDict["weather"] = Weather(temperature, pressure, relative_humidity) 

204 

205 if obsInfo.location is not None: 

206 geolocation = obsInfo.location.to_geodetic() 

207 argDict["observatory"] = Observatory(geolocation.lon.degree*degrees, 

208 geolocation.lat.degree*degrees, 

209 geolocation.height.to_value("m")) 

210 

211 for key in list(argDict.keys()): # use a copy because we may delete items 

212 if argDict[key] is None: 

213 if log is not None: 

214 log.warn("argDict[%s] is None; stripping", key) 

215 del argDict[key] 

216 

217 return VisitInfo(**argDict)