Coverage for python/lsst/obs/base/makeRawVisitInfoViaObsInfo.py: 18%
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
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
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/>.
22__all__ = ["MakeRawVisitInfoViaObsInfo"]
24import logging
25import warnings
26import astropy.units
27import astropy.utils.exceptions
28from astropy.utils import iers
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
38from astro_metadata_translator import ObservationInfo
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
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.
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 : `logging.Logger` 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 = logging.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["id"] = obsInfo.exposure_id
133 argDict["instrumentLabel"] = obsInfo.instrument
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
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)
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
166 era = erfa.era00(ut1time.jd1, ut1time.jd2)
167 argDict["era"] = era * radians
168 else:
169 argDict["date"] = DateTime()
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)
177 altaz = obsInfo.altaz_begin
178 if altaz is not None:
179 argDict["boresightAzAlt"] = SpherePoint(altaz.az.degree,
180 altaz.alt.degree, units=degrees)
182 argDict["boresightAirmass"] = obsInfo.boresight_airmass
184 if obsInfo.boresight_rotation_angle is not None:
185 argDict["boresightRotAngle"] = obsInfo.boresight_rotation_angle.degree*degrees
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
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)
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"))
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.warning("argDict[%s] is None; stripping", key)
215 del argDict[key]
217 return VisitInfo(**argDict)