Coverage for python/lsst/obs/base/makeRawVisitInfoViaObsInfo.py: 16%

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

90 statements  

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 

22__all__ = ["MakeRawVisitInfoViaObsInfo"] 

23 

24import logging 

25import warnings 

26import astropy.units 

27import astropy.utils.exceptions 

28from astropy.utils import iers 

29 

30# Prefer the standard pyerfa over the Astropy version 

31try: 

32 import erfa 

33 ErfaWarning = erfa.ErfaWarning 

34except ImportError: 

35 import astropy._erfa as erfa 

36 ErfaWarning = None 

37 

38from astro_metadata_translator import ObservationInfo 

39 

40from lsst.daf.base import DateTime 

41from lsst.geom import degrees, radians 

42from lsst.afw.image import VisitInfo, RotType 

43from lsst.afw.coord import Observatory, Weather 

44from lsst.geom import SpherePoint 

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 : `logging.Logger` or None 

62 Logger to use for messages. 

63 (None to use 

64 ``Log.getLogger("lsst.obs.base.makeRawVisitInfoViaObsInfo")``). 

65 doStripHeader : `bool`, optional 

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

67 """ 

68 

69 metadataTranslator = None 

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

71 automatic determination.""" 

72 

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

74 if log is None: 

75 log = logging.getLogger(__name__) 

76 self.log = log 

77 self.doStripHeader = doStripHeader 

78 

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

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

81 

82 Parameters 

83 ---------- 

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

85 Metadata to pull from. 

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

87 exposureId : `int`, optional 

88 Ignored. Here for compatibility with `MakeRawVisitInfo`. 

89 

90 Returns 

91 ------- 

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

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

94 a `~astro_metadata_translator.MetadataTranslator`. 

95 """ 

96 

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

98 

99 if self.doStripHeader: 

100 # Strip all the cards out that were used 

101 for c in obsInfo.cards_used: 

102 del md[c] 

103 

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

105 

106 @staticmethod 

107 def observationInfo2visitInfo(obsInfo, log=None): 

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

109 `~astro_metadata_translator.ObservationInfo` 

110 

111 Parameters 

112 ---------- 

113 obsInfo : `astro_metadata_translator.ObservationInfo` 

114 Information gathered from the observation metadata. 

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

116 Logger to use for logging informational messages. 

117 If `None` logging will be disabled. 

118 

119 Returns 

120 ------- 

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

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

123 `~astro_metadata_translator.ObservationInfo`. 

124 """ 

125 argDict = dict() 

126 

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

128 if obsInfo.exposure_time is not None: 

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

130 if obsInfo.dark_time is not None: 

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

132 argDict["exposureId"] = obsInfo.detector_exposure_id 

133 argDict["id"] = obsInfo.exposure_id 

134 argDict["instrumentLabel"] = obsInfo.instrument 

135 

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

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

138 tdelta = obsInfo.datetime_end - obsInfo.datetime_begin 

139 middle = obsInfo.datetime_begin + 0.5*tdelta 

140 

141 # DateTime uses nanosecond resolution, regardless of the resolution 

142 # of the original date 

143 middle.precision = 9 

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

145 # time zone 

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

147 

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

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

150 # the observation we are needing it for). 

151 # ERFA needs a UT1 time split into two floats 

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

153 try: 

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

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

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

157 with warnings.catch_warnings(): 

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

159 # During transition period filter both 

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

161 if ErfaWarning is not None: 

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

163 ut1time = middle.ut1 

164 except iers.IERSRangeError: 

165 ut1time = middle 

166 

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

168 argDict["era"] = era * radians 

169 else: 

170 argDict["date"] = DateTime() 

171 

172 # Coordinates 

173 if obsInfo.tracking_radec is not None: 

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

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

176 icrs.dec.degree, units=degrees) 

177 

178 altaz = obsInfo.altaz_begin 

179 if altaz is not None: 

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

181 altaz.alt.degree, units=degrees) 

182 

183 argDict["boresightAirmass"] = obsInfo.boresight_airmass 

184 

185 if obsInfo.boresight_rotation_angle is not None: 

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

187 

188 if obsInfo.boresight_rotation_coord is not None: 

189 rotType = RotType.UNKNOWN 

190 if obsInfo.boresight_rotation_coord == "sky": 

191 rotType = RotType.SKY 

192 argDict["rotType"] = rotType 

193 

194 # Weather and Observatory Location 

195 temperature = float("nan") 

196 if obsInfo.temperature is not None: 

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

198 pressure = float("nan") 

199 if obsInfo.pressure is not None: 

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

201 relative_humidity = float("nan") 

202 if obsInfo.relative_humidity is not None: 

203 relative_humidity = obsInfo.relative_humidity 

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

205 

206 if obsInfo.location is not None: 

207 geolocation = obsInfo.location.to_geodetic() 

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

209 geolocation.lat.degree*degrees, 

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

211 

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

213 if argDict[key] is None: 

214 if log is not None: 

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

216 del argDict[key] 

217 

218 return VisitInfo(**argDict)