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

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/>.
22import warnings
23import astropy.units
24import astropy.utils.exceptions
25from astropy.utils import iers
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
35from astro_metadata_translator import ObservationInfo
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
44__all__ = ["MakeRawVisitInfoViaObsInfo"]
47class MakeRawVisitInfoViaObsInfo(object):
48 """Base class functor to make a VisitInfo from the FITS header of a
49 raw image using `~astro_metadata_translator.ObservationInfo` translators.
51 Subclasses can be used if a specific
52 `~astro_metadata_translator.MetadataTranslator` translator should be used.
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.
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 """
68 metadataTranslator = None
69 """Header translator to use to construct VisitInfo, defaulting to
70 automatic determination."""
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
78 def __call__(self, md, exposureId=None):
79 """Construct a VisitInfo and strip associated data from the metadata.
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`.
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 """
96 obsInfo = ObservationInfo(md, translator_class=self.metadataTranslator)
98 if self.doStripHeader:
99 # Strip all the cards out that were used
100 for c in obsInfo.cards_used:
101 del md[c]
103 return self.observationInfo2visitInfo(obsInfo, log=self.log)
105 @staticmethod
106 def observationInfo2visitInfo(obsInfo, log=None):
107 """Construct a `~lsst.afw.image.VisitInfo` from an
108 `~astro_metadata_translator.ObservationInfo`
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.
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()
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["instrumentLabel"] = obsInfo.instrument
134 # VisitInfo uses the middle of the observation for the date
135 if obsInfo.datetime_begin is not None and obsInfo.datetime_end is not None:
136 tdelta = obsInfo.datetime_end - obsInfo.datetime_begin
137 middle = obsInfo.datetime_begin + 0.5*tdelta
139 # DateTime uses nanosecond resolution, regardless of the resolution
140 # of the original date
141 middle.precision = 9
142 # isot is ISO8601 format with "T" separating date and time and no
143 # time zone
144 argDict["date"] = DateTime(middle.tai.isot, DateTime.TAI)
146 # Derive earth rotation angle from UT1 (being out by a second is
147 # not a big deal given the uncertainty over exactly what part of
148 # the observation we are needing it for).
149 # ERFA needs a UT1 time split into two floats
150 # We ignore any problems with DUT1 not being defined for now.
151 try:
152 # Catch any warnings about the time being in the future
153 # since there is nothing we can do about that for simulated
154 # data and it tells us nothing for data from the past.
155 with warnings.catch_warnings():
156 # If we are using the real erfa it is not an AstropyWarning
157 # During transition period filter both
158 warnings.simplefilter("ignore", category=astropy.utils.exceptions.AstropyWarning)
159 if ErfaWarning is not None:
160 warnings.simplefilter("ignore", category=ErfaWarning)
161 ut1time = middle.ut1
162 except iers.IERSRangeError:
163 ut1time = middle
165 era = erfa.era00(ut1time.jd1, ut1time.jd2)
166 argDict["era"] = era * radians
167 else:
168 argDict["date"] = DateTime()
170 # Coordinates
171 if obsInfo.tracking_radec is not None:
172 icrs = obsInfo.tracking_radec.transform_to("icrs")
173 argDict["boresightRaDec"] = SpherePoint(icrs.ra.degree,
174 icrs.dec.degree, units=degrees)
176 altaz = obsInfo.altaz_begin
177 if altaz is not None:
178 argDict["boresightAzAlt"] = SpherePoint(altaz.az.degree,
179 altaz.alt.degree, units=degrees)
181 argDict["boresightAirmass"] = obsInfo.boresight_airmass
183 if obsInfo.boresight_rotation_angle is not None:
184 argDict["boresightRotAngle"] = obsInfo.boresight_rotation_angle.degree*degrees
186 if obsInfo.boresight_rotation_coord is not None:
187 rotType = RotType.UNKNOWN
188 if obsInfo.boresight_rotation_coord == "sky":
189 rotType = RotType.SKY
190 argDict["rotType"] = rotType
192 # Weather and Observatory Location
193 temperature = float("nan")
194 if obsInfo.temperature is not None:
195 temperature = obsInfo.temperature.to_value("deg_C", astropy.units.temperature())
196 pressure = float("nan")
197 if obsInfo.pressure is not None:
198 pressure = obsInfo.pressure.to_value("Pa")
199 relative_humidity = float("nan")
200 if obsInfo.relative_humidity is not None:
201 relative_humidity = obsInfo.relative_humidity
202 argDict["weather"] = Weather(temperature, pressure, relative_humidity)
204 if obsInfo.location is not None:
205 geolocation = obsInfo.location.to_geodetic()
206 argDict["observatory"] = Observatory(geolocation.lon.degree*degrees,
207 geolocation.lat.degree*degrees,
208 geolocation.height.to_value("m"))
210 for key in list(argDict.keys()): # use a copy because we may delete items
211 if argDict[key] is None:
212 if log is not None:
213 log.warn("argDict[%s] is None; stripping", key)
214 del argDict[key]
216 return VisitInfo(**argDict)