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 image. 50 A subclass will be wanted for each camera. Subclasses should override: 52 - `setArgDict`, The override can call the base implementation, 53 which simply sets exposure time and date of observation 56 The design philosophy is to make a best effort and log warnings of problems, 57 rather than raising exceptions, in order to extract as much VisitInfo information as possible 58 from a messy FITS header without the user needing to add a lot of error handling. 60 However, the methods that transform units are less forgiving; they assume 61 the user provides proper data types, since type errors in arguments to those 62 are almost certainly due to coding mistakes. 66 log : `lsst.log.Log` or None 67 Logger to use for messages. 68 (None to use ``Log.getLogger("MakeRawVisitInfo")``). 73 log = Log.getLogger(
"MakeRawVisitInfo")
77 """Construct a VisitInfo and strip associated data from the metadata. 81 md : `lsst.daf.base.PropertyList` or `lsst.daf.base.PropertySet` 82 Metadata to pull from. 83 Items that are used are stripped from the metadata (except TIMESYS, 84 because it may apply to other keywords). 90 The basic implementation sets `date` and `exposureTime` using typical values 91 found in FITS files and logs a warning if neither can be set. 93 argDict = dict(exposureId=exposureId)
95 for key
in list(argDict.keys()):
96 if argDict[key]
is None:
97 self.
log.
warn(
"argDict[{}] is None; stripping".
format(key, argDict[key]))
102 """Fill an argument dict with arguments for VisitInfo and pop associated metadata 104 Subclasses are expected to override this method, though the override 105 may wish to call this default implementation, which: 107 - sets exposureTime from "EXPTIME" 108 - sets date by calling getDateAvg 112 md : `lsst.daf.base.PropertyList` or `PropertySet` 113 Metadata to pull from. 114 Items that are used are stripped from the metadata (except TIMESYS, 115 because it may apply to other keywords). 121 Subclasses should expand this or replace it. 123 argDict[
"exposureTime"] = self.
popFloat(md,
"EXPTIME")
124 argDict[
"date"] = self.
getDateAvg(md=md, exposureTime=argDict[
"exposureTime"])
127 """Return date at the middle of the exposure. 131 md : `lsst.daf.base.PropertyList` or `PropertySet` 132 Metadata to pull from. 133 Items that are used are stripped from the metadata (except TIMESYS, 134 because it may apply to other keywords). 135 exposureTime : `float` 140 Subclasses must override. Here is a typical implementation:: 142 dateObs = self.popIsoDate(md, "DATE-OBS") 143 return self.offsetDate(dateObs, 0.5*exposureTime) 145 raise NotImplementedError()
148 """Get the darkTime from the DARKTIME keyword, else expTime, else NaN, 150 If dark time is available then subclasses should call this method by 151 putting the following in their `__init__` method:: 153 argDict['darkTime'] = self.getDarkTime(argDict) 163 Dark time, as inferred from the metadata. 165 darkTime = argDict.get(
"darkTime", NaN)
166 if np.isfinite(darkTime):
169 self.
log.
info(
"darkTime is NaN/Inf; using exposureTime")
170 exposureTime = argDict.get(
"exposureTime", NaN)
171 if not np.isfinite(exposureTime):
172 raise RuntimeError(
"Tried to substitute exposureTime for darkTime but it is not available")
177 """Return a date offset by a specified number of seconds. 179 date : `lsst.daf.base.DateTime` 180 Date baseline to offset from. 186 `lsst.daf.base.DateTime` 189 if not date.isValid():
190 self.
log.
warn(
"date is invalid; cannot offset it")
192 if math.isnan(offsetSec):
193 self.
log.
warn(
"offsetSec is invalid; cannot offset date")
195 dateNSec = date.nsecs(DateTime.TAI)
196 return DateTime(dateNSec +
int(offsetSec*1.0e9), DateTime.TAI)
199 """Remove an item of metadata and return the value. 201 Log a warning if the key is not found. 205 md : `lsst.daf.base.PropertyList` or `PropertySet` 206 Metadata to pull `key` from and remove. 208 Metadata key to extract. 210 Value to return if key not found. 215 The value of the specified key, using whatever type md.getScalar(key) 219 if not md.exists(key):
222 val = md.getScalar(key)
225 except Exception
as e:
227 self.
log.
warn(
'Could not read key="{}" in metadata: {}'.
format(key, e))
231 """Pop a float with a default of NaN. 235 md : `lsst.daf.base.PropertyList` or `PropertySet` 236 Metadata to pull `key` from and remove. 238 Key to read and remove from md. 243 Value of the requested key as a float; float("nan") if the key is 246 val = self.
popItem(md, key, default=NaN)
249 except Exception
as e:
250 self.
log.
warn(
"Could not interpret {} value {} as a float: {}".
format(key, repr(val), e))
253 def popAngle(self, md, key, units=astropy.units.deg):
254 """Pop an lsst.afw.geom.Angle, whose metadata is in the specified units, with a default of Nan 256 The angle may be specified as a float or sexagesimal string with 1-3 fields. 260 md : `lsst.daf.base.PropertyList` or `PropertySet` 261 Metadata to pull `key` from and remove. 263 Key to read and remove from md. 267 `lsst.afw.geom.Angle` 268 Value of the requested key as an angle; Angle(NaN) if the key is 271 angleStr = self.
popItem(md, key, default=
None)
272 if angleStr
is not None:
274 return (astropy.coordinates.Angle(angleStr, unit=units).deg)*degrees
275 except Exception
as e:
276 self.
log.
warn(
"Could not intepret {} value {} as an angle: {}".
format(key, repr(angleStr), e))
280 """Pop a FITS ISO date as an lsst.daf.base.DateTime 284 md : `lsst.daf.base.PropertyList` or `PropertySet` 285 Metadata to pull `key` from and remove. 287 Date key to read and remove from md. 289 Time system as a string (not case sensitive), e.g. "UTC" or None; 290 if None then look for TIMESYS (but do NOT pop it, since it may be 291 used for more than one date) and if not found, use UTC. 295 `lsst.daf.base.DateTime` 296 Value of the requested date; `DateTime()` if the key is not found. 298 isoDateStr = self.
popItem(md=md, key=key)
299 if isoDateStr
is not None:
302 timesys = md.getScalar(
"TIMESYS")
if md.exists(
"TIMESYS")
else "UTC" 303 if isoDateStr.endswith(
"Z"):
304 isoDateStr = isoDateStr[0:-1]
305 astropyTime = astropy.time.Time(isoDateStr, scale=timesys.lower(), format=
"fits")
307 astropyTime.precision = 9
309 return DateTime(astropyTime.tai.isot, DateTime.TAI)
310 except Exception
as e:
311 self.
log.
warn(
"Could not parse {} = {} as an ISO date: {}".
format(key, isoDateStr, e))
315 """Get a FITS MJD date as an ``lsst.daf.base.DateTime``. 319 md : `lsst.daf.base.PropertyList` or `PropertySet` 320 Metadata to pull `key` from and remove. 322 Date key to read and remove from md. 324 Time system as a string (not case sensitive), e.g. "UTC" or None; 325 if None then look for TIMESYS (but do NOT pop it, since it may be 326 used for more than one date) and if not found, use UTC. 330 `lsst.daf.base.DateTime` 331 Value of the requested date; `DateTime()` if the key is not found. 336 timesys = md.getScalar(
"TIMESYS")
if md.exists(
"TIMESYS")
else "UTC" 337 astropyTime = astropy.time.Time(mjdDate, format=
"mjd", scale=timesys.lower())
339 astropyTime.precision = 9
341 return DateTime(astropyTime.tai.isot, DateTime.TAI)
342 except Exception
as e:
343 self.
log.
warn(
"Could not parse {} = {} as an MJD date: {}".
format(key, mjdDate, e))
349 Return an approximate Earth Rotation Angle (afw:Angle) computed from 350 local sidereal time and longitude (both as afw:Angle; Longitude shares 351 the afw:Observatory covention: positive values are E of Greenwich). 353 NOTE: if we properly compute ERA via UT1 a la DM-8053, we should remove 356 return lst - longitude
360 """Convert zenith distance to altitude (lsst.afw.geom.Angle)""" 361 return 90*degrees - zd
365 """Convert temperature from Kelvin to Centigrade""" 366 return tempK - KelvinMinusCentigrade
370 """Convert pressure from millibars to Pascals 372 return mbar*PascalPerMillibar
376 """Convert pressure from mm Hg to Pascals 380 Could use the following, but astropy.units.cds is not fully compatible with Python 2 381 as of astropy 1.2.1 (see https://github.com/astropy/astropy/issues/5350#issuecomment-248612824): 382 astropy.units.cds.mmHg.to(astropy.units.pascal, mmHg) 384 return mmHg*PascalPerMmHg
388 """Convert pressure from torr to Pascals 390 return torr*PascalPerTorr
394 """Return the value if it is not NaN and within min/max, otherwise 400 metadata value returned by popItem, popFloat, or popAngle 401 defaultValue : `float`` 402 default value to use if the metadata value is invalid 404 Minimum possible valid value, optional 406 Maximum possible valid value, optional 411 The "validated" value. 414 retVal = defaultValue
416 if minimum
is not None and value < minimum:
417 retVal = defaultValue
418 elif maximum
is not None and value > maximum:
419 retVal = defaultValue
def popIsoDate(self, md, key, timesys=None)
def setArgDict(self, md, argDict)
Class for handling dates/times, including MJD, UTC, and TAI.
def eraFromLstAndLongitude(lst, longitude)
def popFloat(self, md, key)
Information about a single exposure of an imaging camera.
def popAngle(self, md, key, units=astropy.units.deg)
def defaultMetadata(value, defaultValue, minimum=None, maximum=None)
def popMjdDate(self, md, key, timesys=None)
def getDateAvg(self, md, exposureTime)
def altitudeFromZenithDistance(zd)
def popItem(self, md, key, default=None)
def __init__(self, log=None)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def getDarkTime(self, argDict)
def __call__(self, md, exposureId)
def centigradeFromKelvin(tempK)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
daf::base::PropertyList * list
def offsetDate(self, date, offsetSec)