lsst.obs.base  21.0.0-17-gf9a0284+9e5b25d042
makeRawVisitInfoViaObsInfo.py
Go to the documentation of this file.
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 import warnings
23 import astropy.units
24 import astropy.utils.exceptions
25 from astropy.utils import iers
26 
27 # Prefer the standard pyerfa over the Astropy version
28 try:
29  import erfa
30  ErfaWarning = erfa.ErfaWarning
31 except ImportError:
32  import astropy._erfa as erfa
33  ErfaWarning = None
34 
35 from astro_metadata_translator import ObservationInfo
36 
37 from lsst.log import Log
38 from lsst.daf.base import DateTime
39 from lsst.geom import degrees, radians
40 from lsst.afw.image import VisitInfo, RotType
41 from lsst.afw.coord import Observatory, Weather
42 from lsst.geom import SpherePoint
43 
44 __all__ = ["MakeRawVisitInfoViaObsInfo"]
45 
46 
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 
133  # VisitInfo uses the middle of the observation for the date
134  if obsInfo.datetime_begin is not None and obsInfo.datetime_end is not None:
135  tdelta = obsInfo.datetime_end - obsInfo.datetime_begin
136  middle = obsInfo.datetime_begin + 0.5*tdelta
137 
138  # DateTime uses nanosecond resolution, regardless of the resolution
139  # of the original date
140  middle.precision = 9
141  # isot is ISO8601 format with "T" separating date and time and no
142  # time zone
143  argDict["date"] = DateTime(middle.tai.isot, DateTime.TAI)
144 
145  # Derive earth rotation angle from UT1 (being out by a second is
146  # not a big deal given the uncertainty over exactly what part of
147  # the observation we are needing it for).
148  # ERFA needs a UT1 time split into two floats
149  # We ignore any problems with DUT1 not being defined for now.
150  try:
151  # Catch any warnings about the time being in the future
152  # since there is nothing we can do about that for simulated
153  # data and it tells us nothing for data from the past.
154  with warnings.catch_warnings():
155  # If we are using the real erfa it is not an AstropyWarning
156  # During transition period filter both
157  warnings.simplefilter("ignore", category=astropy.utils.exceptions.AstropyWarning)
158  if ErfaWarning is not None:
159  warnings.simplefilter("ignore", category=ErfaWarning)
160  ut1time = middle.ut1
161  except iers.IERSRangeError:
162  ut1time = middle
163 
164  era = erfa.era00(ut1time.jd1, ut1time.jd2)
165  argDict["era"] = era * radians
166  else:
167  argDict["date"] = DateTime()
168 
169  # Coordinates
170  if obsInfo.tracking_radec is not None:
171  icrs = obsInfo.tracking_radec.transform_to("icrs")
172  argDict["boresightRaDec"] = SpherePoint(icrs.ra.degree,
173  icrs.dec.degree, units=degrees)
174 
175  altaz = obsInfo.altaz_begin
176  if altaz is not None:
177  argDict["boresightAzAlt"] = SpherePoint(altaz.az.degree,
178  altaz.alt.degree, units=degrees)
179 
180  argDict["boresightAirmass"] = obsInfo.boresight_airmass
181 
182  if obsInfo.boresight_rotation_angle is not None:
183  argDict["boresightRotAngle"] = obsInfo.boresight_rotation_angle.degree*degrees
184 
185  if obsInfo.boresight_rotation_coord is not None:
186  rotType = RotType.UNKNOWN
187  if obsInfo.boresight_rotation_coord == "sky":
188  rotType = RotType.SKY
189  argDict["rotType"] = rotType
190 
191  # Weather and Observatory Location
192  temperature = float("nan")
193  if obsInfo.temperature is not None:
194  temperature = obsInfo.temperature.to_value("deg_C", astropy.units.temperature())
195  pressure = float("nan")
196  if obsInfo.pressure is not None:
197  pressure = obsInfo.pressure.to_value("Pa")
198  relative_humidity = float("nan")
199  if obsInfo.relative_humidity is not None:
200  relative_humidity = obsInfo.relative_humidity
201  argDict["weather"] = Weather(temperature, pressure, relative_humidity)
202 
203  if obsInfo.location is not None:
204  geolocation = obsInfo.location.to_geodetic()
205  argDict["observatory"] = Observatory(geolocation.lon.degree*degrees,
206  geolocation.lat.degree*degrees,
207  geolocation.height.to_value("m"))
208 
209  for key in list(argDict.keys()): # use a copy because we may delete items
210  if argDict[key] is None:
211  if log is not None:
212  log.warn("argDict[%s] is None; stripping", key)
213  del argDict[key]
214 
215  return VisitInfo(**argDict)
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.log
log
Definition: makeRawVisitInfoViaObsInfo.py:75
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.__init__
def __init__(self, log=None, doStripHeader=False)
Definition: makeRawVisitInfoViaObsInfo.py:72
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.__call__
def __call__(self, md, exposureId=None)
Definition: makeRawVisitInfoViaObsInfo.py:78
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.observationInfo2visitInfo
def observationInfo2visitInfo(obsInfo, log=None)
Definition: makeRawVisitInfoViaObsInfo.py:106
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo
Definition: makeRawVisitInfoViaObsInfo.py:47
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.doStripHeader
doStripHeader
Definition: makeRawVisitInfoViaObsInfo.py:76
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.metadataTranslator
metadataTranslator
Definition: makeRawVisitInfoViaObsInfo.py:68