LSST Applications  21.0.0+75b29a8a7f,21.0.0+e70536a077,21.0.0-1-ga51b5d4+62c747d40b,21.0.0-10-gbfb87ad6+3307648ee3,21.0.0-15-gedb9d5423+47cba9fc36,21.0.0-2-g103fe59+fdf0863a2a,21.0.0-2-g1367e85+d38a93257c,21.0.0-2-g45278ab+e70536a077,21.0.0-2-g5242d73+d38a93257c,21.0.0-2-g7f82c8f+e682ffb718,21.0.0-2-g8dde007+d179fbfa6a,21.0.0-2-g8f08a60+9402881886,21.0.0-2-ga326454+e682ffb718,21.0.0-2-ga63a54e+08647d4b1b,21.0.0-2-gde069b7+26c92b3210,21.0.0-2-gecfae73+0445ed2f95,21.0.0-2-gfc62afb+d38a93257c,21.0.0-27-gbbd0d29+ae871e0f33,21.0.0-28-g5fc5e037+feb0e9397b,21.0.0-3-g21c7a62+f4b9c0ff5c,21.0.0-3-g357aad2+57b0bddf0b,21.0.0-3-g4be5c26+d38a93257c,21.0.0-3-g65f322c+3f454acf5d,21.0.0-3-g7d9da8d+75b29a8a7f,21.0.0-3-gaa929c8+9e4ef6332c,21.0.0-3-ge02ed75+4b120a55c4,21.0.0-4-g3300ddd+e70536a077,21.0.0-4-g591bb35+4b120a55c4,21.0.0-4-gc004bbf+4911b9cd27,21.0.0-4-gccdca77+f94adcd104,21.0.0-4-ge8fba5a+2b3a696ff9,21.0.0-5-gb155db7+2c5429117a,21.0.0-5-gdf36809+637e4641ee,21.0.0-6-g00874e7+c9fd7f7160,21.0.0-6-g4e60332+4b120a55c4,21.0.0-7-gc8ca178+40eb9cf840,21.0.0-8-gfbe0b4b+9e4ef6332c,21.0.0-9-g2fd488a+d83b7cd606,w.2021.05
LSST Data Management Base Package
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.loglog = log
76  self.doStripHeaderdoStripHeader = 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.metadataTranslatormetadataTranslator)
97 
98  if self.doStripHeaderdoStripHeader:
99  # Strip all the cards out that were used
100  for c in obsInfo.cards_used:
101  del md[c]
102 
103  return self.observationInfo2visitInfoobservationInfo2visitInfo(obsInfo, log=self.loglog)
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  argDict["instrumentLabel"] = obsInfo.instrument
133 
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
138 
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)
145 
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
164 
165  era = erfa.era00(ut1time.jd1, ut1time.jd2)
166  argDict["era"] = era * radians
167  else:
168  argDict["date"] = DateTime()
169 
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)
175 
176  altaz = obsInfo.altaz_begin
177  if altaz is not None:
178  argDict["boresightAzAlt"] = SpherePoint(altaz.az.degree,
179  altaz.alt.degree, units=degrees)
180 
181  argDict["boresightAirmass"] = obsInfo.boresight_airmass
182 
183  if obsInfo.boresight_rotation_angle is not None:
184  argDict["boresightRotAngle"] = obsInfo.boresight_rotation_angle.degree*degrees
185 
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
191 
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)
203 
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"))
209 
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]
215 
216  return VisitInfo(**argDict)
Hold the location of an observatory.
Definition: Observatory.h:43
Basic weather information sufficient for a simple model for air mass or refraction.
Definition: Weather.h:38
Information about a single exposure of an imaging camera.
Definition: VisitInfo.h:68
Class for handling dates/times, including MJD, UTC, and TAI.
Definition: DateTime.h:64
Point in an unspecified spherical coordinate system.
Definition: SpherePoint.h:57
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Definition: Log.h:706
daf::base::PropertyList * list
Definition: fits.cc:913