25 import astropy.coordinates
34 __all__ = [
"MakeRawVisitInfo"]
37 PascalPerMillibar = 100.0
38 PascalPerMmHg = 133.322387415
39 PascalPerTorr = 101325.0/760.0
40 KelvinMinusCentigrade = 273.15
48 """Base class functor to make a VisitInfo from the FITS header of a raw
51 A subclass will be wanted for each camera. Subclasses should override:
53 - `setArgDict`, The override can call the base implementation,
54 which simply sets exposure time and date of observation
57 The design philosophy is to make a best effort and log warnings of
58 problems, rather than raising exceptions, in order to extract as much
59 VisitInfo information as possible from a messy FITS header without the
60 user needing to add a lot of error handling.
62 However, the methods that transform units are less forgiving; they assume
63 the user provides proper data types, since type errors in arguments to
64 those are almost certainly due to coding mistakes.
68 log : `lsst.log.Log` or None
69 Logger to use for messages.
70 (None to use ``Log.getLogger("MakeRawVisitInfo")``).
71 doStripHeader : `bool`, optional
72 Strip header keywords from the metadata as they are used?
75 def __init__(self, log=None, doStripHeader=False):
77 log = Log.getLogger(
"MakeRawVisitInfo")
82 """Construct a VisitInfo and strip associated data from the metadata.
86 md : `lsst.daf.base.PropertyList` or `lsst.daf.base.PropertySet`
87 Metadata to pull from.
88 Items that are used are stripped from the metadata (except TIMESYS,
89 because it may apply to other keywords) if ``doStripHeader``.
95 The basic implementation sets `date` and `exposureTime` using typical
96 values found in FITS files and logs a warning if neither can be set.
98 argDict = dict(exposureId=exposureId)
100 for key
in list(argDict.keys()):
101 if argDict[key]
is None:
102 self.
loglog.
warn(
"argDict[%s] is None; stripping", key)
107 """Fill an argument dict with arguments for VisitInfo and pop
110 Subclasses are expected to override this method, though the override
111 may wish to call this default implementation, which:
113 - sets exposureTime from "EXPTIME"
114 - sets date by calling getDateAvg
118 md : `lsst.daf.base.PropertyList` or `PropertySet`
119 Metadata to pull from.
120 Items that are used are stripped from the metadata (except TIMESYS,
121 because it may apply to other keywords).
127 Subclasses should expand this or replace it.
129 argDict[
"exposureTime"] = self.
popFloatpopFloat(md,
"EXPTIME")
130 argDict[
"date"] = self.
getDateAvggetDateAvg(md=md, exposureTime=argDict[
"exposureTime"])
133 """Return date at the middle of the exposure.
137 md : `lsst.daf.base.PropertyList` or `PropertySet`
138 Metadata to pull from.
139 Items that are used are stripped from the metadata (except TIMESYS,
140 because it may apply to other keywords).
141 exposureTime : `float`
146 Subclasses must override. Here is a typical implementation::
148 dateObs = self.popIsoDate(md, "DATE-OBS")
149 return self.offsetDate(dateObs, 0.5*exposureTime)
151 raise NotImplementedError()
154 """Get the darkTime from the DARKTIME keyword, else expTime, else NaN,
156 If dark time is available then subclasses should call this method by
157 putting the following in their `__init__` method::
159 argDict['darkTime'] = self.getDarkTime(argDict)
169 Dark time, as inferred from the metadata.
171 darkTime = argDict.get(
"darkTime", NaN)
172 if np.isfinite(darkTime):
175 self.
loglog.
info(
"darkTime is NaN/Inf; using exposureTime")
176 exposureTime = argDict.get(
"exposureTime", NaN)
177 if not np.isfinite(exposureTime):
178 raise RuntimeError(
"Tried to substitute exposureTime for darkTime but it is not available")
183 """Return a date offset by a specified number of seconds.
185 date : `lsst.daf.base.DateTime`
186 Date baseline to offset from.
192 `lsst.daf.base.DateTime`
195 if not date.isValid():
196 self.
loglog.
warn(
"date is invalid; cannot offset it")
198 if math.isnan(offsetSec):
199 self.
loglog.
warn(
"offsetSec is invalid; cannot offset date")
201 dateNSec = date.nsecs(DateTime.TAI)
202 return DateTime(dateNSec + int(offsetSec*1.0e9), DateTime.TAI)
205 """Return an item of metadata.
207 The item is removed if ``doStripHeader`` is ``True``.
209 Log a warning if the key is not found.
213 md : `lsst.daf.base.PropertyList` or `PropertySet`
214 Metadata to pull `key` from and (optionally) remove.
216 Metadata key to extract.
218 Value to return if key not found.
223 The value of the specified key, using whatever type
224 md.getScalar(key) returns.
227 if not md.exists(key):
230 val = md.getScalar(key)
234 except Exception
as e:
237 self.
loglog.
warn(
'Could not read key="{}" in metadata: {}'.
format(key, e))
241 """Pop a float with a default of NaN.
245 md : `lsst.daf.base.PropertyList` or `PropertySet`
246 Metadata to pull `key` from.
253 Value of the requested key as a float; float("nan") if the key is
256 val = self.
popItempopItem(md, key, default=NaN)
259 except Exception
as e:
260 self.
loglog.
warn(
"Could not interpret {} value {} as a float: {}".
format(key, repr(val), e))
263 def popAngle(self, md, key, units=astropy.units.deg):
264 """Pop an lsst.afw.geom.Angle, whose metadata is in the specified
265 units, with a default of Nan
267 The angle may be specified as a float or sexagesimal string with 1-3
272 md : `lsst.daf.base.PropertyList` or `PropertySet`
273 Metadata to pull `key` from.
279 `lsst.afw.geom.Angle`
280 Value of the requested key as an angle; Angle(NaN) if the key is
283 angleStr = self.
popItempopItem(md, key, default=
None)
284 if angleStr
is not None:
286 return (astropy.coordinates.Angle(angleStr, unit=units).deg)*degrees
287 except Exception
as e:
288 self.
loglog.
warn(
"Could not intepret {} value {} as an angle: {}".
format(key, repr(angleStr), e))
292 """Pop a FITS ISO date as an lsst.daf.base.DateTime
296 md : `lsst.daf.base.PropertyList` or `PropertySet`
297 Metadata to pull `key` from.
299 Date key to read from md.
301 Time system as a string (not case sensitive), e.g. "UTC" or None;
302 if None then look for TIMESYS (but do NOT pop it, since it may be
303 used for more than one date) and if not found, use UTC.
307 `lsst.daf.base.DateTime`
308 Value of the requested date; `DateTime()` if the key is not found.
310 isoDateStr = self.
popItempopItem(md=md, key=key)
311 if isoDateStr
is not None:
314 timesys = md.getScalar(
"TIMESYS")
if md.exists(
"TIMESYS")
else "UTC"
315 if isoDateStr.endswith(
"Z"):
316 isoDateStr = isoDateStr[0:-1]
317 astropyTime = astropy.time.Time(isoDateStr, scale=timesys.lower(), format=
"fits")
320 astropyTime.precision = 9
323 return DateTime(astropyTime.tai.isot, DateTime.TAI)
324 except Exception
as e:
325 self.
loglog.
warn(
"Could not parse {} = {} as an ISO date: {}".
format(key, isoDateStr, e))
329 """Get a FITS MJD date as an ``lsst.daf.base.DateTime``.
333 md : `lsst.daf.base.PropertyList` or `PropertySet`
334 Metadata to pull `key` from.
336 Date key to read from md.
338 Time system as a string (not case sensitive), e.g. "UTC" or None;
339 if None then look for TIMESYS (but do NOT pop it, since it may be
340 used for more than one date) and if not found, use UTC.
344 `lsst.daf.base.DateTime`
345 Value of the requested date; `DateTime()` if the key is not found.
347 mjdDate = self.
popFloatpopFloat(md, key)
350 timesys = md.getScalar(
"TIMESYS")
if md.exists(
"TIMESYS")
else "UTC"
351 astropyTime = astropy.time.Time(mjdDate, format=
"mjd", scale=timesys.lower())
354 astropyTime.precision = 9
357 return DateTime(astropyTime.tai.isot, DateTime.TAI)
358 except Exception
as e:
359 self.
loglog.
warn(
"Could not parse {} = {} as an MJD date: {}".
format(key, mjdDate, e))
365 Return an approximate Earth Rotation Angle (afw:Angle) computed from
366 local sidereal time and longitude (both as afw:Angle; Longitude shares
367 the afw:Observatory covention: positive values are E of Greenwich).
369 NOTE: if we properly compute ERA via UT1 a la DM-8053, we should remove
372 return lst - longitude
376 """Convert zenith distance to altitude (lsst.afw.geom.Angle)"""
377 return 90*degrees - zd
381 """Convert temperature from Kelvin to Centigrade"""
382 return tempK - KelvinMinusCentigrade
386 """Convert pressure from millibars to Pascals
388 return mbar*PascalPerMillibar
392 """Convert pressure from mm Hg to Pascals
396 Could use the following, but astropy.units.cds is not fully compatible
397 with Python 2 as of astropy 1.2.1 (see
398 https://github.com/astropy/astropy/issues/5350#issuecomment-248612824):
399 astropy.units.cds.mmHg.to(astropy.units.pascal, mmHg)
401 return mmHg*PascalPerMmHg
405 """Convert pressure from torr to Pascals
407 return torr*PascalPerTorr
411 """Return the value if it is not NaN and within min/max, otherwise
417 metadata value returned by popItem, popFloat, or popAngle
418 defaultValue : `float``
419 default value to use if the metadata value is invalid
421 Minimum possible valid value, optional
423 Maximum possible valid value, optional
428 The "validated" value.
431 retVal = defaultValue
433 if minimum
is not None and value < minimum:
434 retVal = defaultValue
435 elif maximum
is not None and value > maximum:
436 retVal = defaultValue
Information about a single exposure of an imaging camera.
Class for handling dates/times, including MJD, UTC, and TAI.
def popAngle(self, md, key, units=astropy.units.deg)
def setArgDict(self, md, argDict)
def defaultMetadata(value, defaultValue, minimum=None, maximum=None)
def eraFromLstAndLongitude(lst, longitude)
def getDarkTime(self, argDict)
def offsetDate(self, date, offsetSec)
def altitudeFromZenithDistance(zd)
def popFloat(self, md, key)
def __init__(self, log=None, doStripHeader=False)
def popIsoDate(self, md, key, timesys=None)
def popMjdDate(self, md, key, timesys=None)
def __call__(self, md, exposureId)
def centigradeFromKelvin(tempK)
def getDateAvg(self, md, exposureTime)
def popItem(self, md, key, default=None)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
daf::base::PropertyList * list