lsst.obs.base  19.0.0-71-g41c0270
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 # This is an unofficial ERFA interface provided by Astropy.
28 # We need to use this to calculate the Earth rotation angle.
29 # If Astropy change their ERFA support we will need to either bring the
30 # calculation into this package or use another python ERFA binding.
31 import astropy._erfa as erfa
32 
33 from astro_metadata_translator import ObservationInfo
34 
35 from lsst.log import Log
36 from lsst.daf.base import DateTime
37 from lsst.geom import degrees, radians
38 from lsst.afw.image import VisitInfo, RotType
39 from lsst.afw.coord import Observatory, Weather
40 from lsst.geom import SpherePoint
41 
42 __all__ = ["MakeRawVisitInfoViaObsInfo"]
43 
44 
46  """Base class functor to make a VisitInfo from the FITS header of a
47  raw image using `~astro_metadata_translator.ObservationInfo` translators.
48 
49  Subclasses can be used if a specific
50  `~astro_metadata_translator.MetadataTranslator` translator should be used.
51 
52  The design philosophy is to make a best effort and log warnings of
53  problems, rather than raising exceptions, in order to extract as much
54  VisitInfo information as possible from a messy FITS header without the
55  user needing to add a lot of error handling.
56 
57  Parameters
58  ----------
59  log : `lsst.log.Log` or None
60  Logger to use for messages.
61  (None to use ``Log.getLogger("MakeRawVisitInfoViaObsInfo")``).
62  doStripHeader : `bool`, optional
63  Strip header keywords from the metadata as they are used?
64  """
65 
66  metadataTranslator = None
67  """Header translator to use to construct VisitInfo, defaulting to
68  automatic determination."""
69 
70  def __init__(self, log=None, doStripHeader=False):
71  if log is None:
72  log = Log.getLogger("MakeRawVisitInfoViaObsInfo")
73  self.log = log
74  self.doStripHeader = doStripHeader
75 
76  def __call__(self, md, exposureId=None):
77  """Construct a VisitInfo and strip associated data from the metadata.
78 
79  Parameters
80  ----------
81  md : `lsst.daf.base.PropertyList` or `lsst.daf.base.PropertySet`
82  Metadata to pull from.
83  May be modified if ``stripHeader`` is ``True``.
84  exposureId : `int`, optional
85  Ignored. Here for compatibility with `MakeRawVisitInfo`.
86 
87  Returns
88  -------
89  visitInfo : `lsst.afw.image.VisitInfo`
90  `~lsst.afw.image.VisitInfo` derived from the header using
91  a `~astro_metadata_translator.MetadataTranslator`.
92  """
93 
94  obsInfo = ObservationInfo(md, translator_class=self.metadataTranslator)
95 
96  if self.doStripHeader:
97  # Strip all the cards out that were used
98  for c in obsInfo.cards_used:
99  del md[c]
100 
101  return self.observationInfo2visitInfo(obsInfo, log=self.log)
102 
103  @staticmethod
104  def observationInfo2visitInfo(obsInfo, log=None):
105  """Construct a `~lsst.afw.image.VisitInfo` from an
106  `~astro_metadata_translator.ObservationInfo`
107 
108  Parameters
109  ----------
110  obsInfo : `astro_metadata_translator.ObservationInfo`
111  Information gathered from the observation metadata.
112  log : `logging.Logger` or `lsst.log.Log`, optional
113  Logger to use for logging informational messages.
114  If `None` logging will be disabled.
115 
116  Returns
117  -------
118  visitInfo : `lsst.afw.image.VisitInfo`
119  `~lsst.afw.image.VisitInfo` derived from the supplied
120  `~astro_metadata_translator.ObservationInfo`.
121  """
122  argDict = dict()
123 
124  # Map the translated information into a form suitable for VisitInfo
125  if obsInfo.exposure_time is not None:
126  argDict["exposureTime"] = obsInfo.exposure_time.to_value("s")
127  if obsInfo.dark_time is not None:
128  argDict["darkTime"] = obsInfo.dark_time.to_value("s")
129  argDict["exposureId"] = obsInfo.detector_exposure_id
130 
131  # VisitInfo uses the middle of the observation for the date
132  if obsInfo.datetime_begin is not None and obsInfo.datetime_end is not None:
133  tdelta = obsInfo.datetime_end - obsInfo.datetime_begin
134  middle = obsInfo.datetime_begin + 0.5*tdelta
135 
136  # DateTime uses nanosecond resolution, regardless of the resolution
137  # of the original date
138  middle.precision = 9
139  # isot is ISO8601 format with "T" separating date and time and no
140  # time zone
141  argDict["date"] = DateTime(middle.tai.isot, DateTime.TAI)
142 
143  # Derive earth rotation angle from UT1 (being out by a second is not
144  # a big deal given the uncertainty over exactly what part of the
145  # observation we are needing it for).
146  # ERFA needs a UT1 time split into two floats
147  # We ignore any problems with DUT1 not being defined for now.
148  try:
149  # Catch any warnings about the time being in the future
150  # since there is nothing we can do about that for simulated
151  # data and it tells us nothing for data from the past.
152  with warnings.catch_warnings():
153  warnings.simplefilter("ignore", category=astropy.utils.exceptions.AstropyWarning)
154  ut1time = middle.ut1
155  except iers.IERSRangeError:
156  ut1time = middle
157 
158  era = erfa.era00(ut1time.jd1, ut1time.jd2)
159  argDict["era"] = era * radians
160  else:
161  argDict["date"] = DateTime()
162 
163  # Coordinates
164  if obsInfo.tracking_radec is not None:
165  icrs = obsInfo.tracking_radec.transform_to("icrs")
166  argDict["boresightRaDec"] = SpherePoint(icrs.ra.degree,
167  icrs.dec.degree, units=degrees)
168 
169  altaz = obsInfo.altaz_begin
170  if altaz is not None:
171  argDict["boresightAzAlt"] = SpherePoint(altaz.az.degree,
172  altaz.alt.degree, units=degrees)
173 
174  argDict["boresightAirmass"] = obsInfo.boresight_airmass
175 
176  if obsInfo.boresight_rotation_angle is not None:
177  argDict["boresightRotAngle"] = obsInfo.boresight_rotation_angle.degree*degrees
178 
179  if obsInfo.boresight_rotation_coord is not None:
180  rotType = RotType.UNKNOWN
181  if obsInfo.boresight_rotation_coord == "sky":
182  rotType = RotType.SKY
183  argDict["rotType"] = rotType
184 
185  # Weather and Observatory Location
186  temperature = float("nan")
187  if obsInfo.temperature is not None:
188  temperature = obsInfo.temperature.to_value("deg_C", astropy.units.temperature())
189  pressure = float("nan")
190  if obsInfo.pressure is not None:
191  pressure = obsInfo.pressure.to_value("Pa")
192  relative_humidity = float("nan")
193  if obsInfo.relative_humidity is not None:
194  relative_humidity = obsInfo.relative_humidity
195  argDict["weather"] = Weather(temperature, pressure, relative_humidity)
196 
197  if obsInfo.location is not None:
198  geolocation = obsInfo.location.to_geodetic()
199  argDict["observatory"] = Observatory(geolocation.lon.degree*degrees,
200  geolocation.lat.degree*degrees,
201  geolocation.height.to_value("m"))
202 
203  for key in list(argDict.keys()): # use a copy because we may delete items
204  if argDict[key] is None:
205  if log is not None:
206  log.warn("argDict[%s] is None; stripping", key)
207  del argDict[key]
208 
209  return VisitInfo(**argDict)
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.log
log
Definition: makeRawVisitInfoViaObsInfo.py:73
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.__init__
def __init__(self, log=None, doStripHeader=False)
Definition: makeRawVisitInfoViaObsInfo.py:70
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.__call__
def __call__(self, md, exposureId=None)
Definition: makeRawVisitInfoViaObsInfo.py:76
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.observationInfo2visitInfo
def observationInfo2visitInfo(obsInfo, log=None)
Definition: makeRawVisitInfoViaObsInfo.py:104
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo
Definition: makeRawVisitInfoViaObsInfo.py:45
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.doStripHeader
doStripHeader
Definition: makeRawVisitInfoViaObsInfo.py:74
lsst.obs.base.makeRawVisitInfoViaObsInfo.MakeRawVisitInfoViaObsInfo.metadataTranslator
metadataTranslator
Definition: makeRawVisitInfoViaObsInfo.py:66