27 from astropy.table
import Table
28 from astropy.io
import fits
34 __all__ = [
"IsrCalib",
"IsrProvenance"]
38 """Generic calibration type.
40 Subclasses must implement the toDict, fromDict, toTable, fromTable
41 methods that allow the calibration information to be converted
42 from dictionaries and afw tables. This will allow the calibration
43 to be persisted using the base class read/write methods.
45 The validate method is intended to provide a common way to check
46 that the calibration is valid (internally consistent) and
47 appropriate (usable with the intended data). The apply method is
48 intended to allow the calibration to be applied in a consistent
53 camera : `lsst.afw.cameraGeom.Camera`, optional
54 Camera to extract metadata from.
55 detector : `lsst.afw.cameraGeom.Detector`, optional
56 Detector to extract metadata from.
57 log : `lsst.log.Log`, optional
64 def __init__(self, camera=None, detector=None, detectorName=None, detectorId=None, log=None, **kwargs):
79 '_detectorName',
'_detectorSerial',
'_detectorId',
80 '_filter',
'_calibId',
'_metadata'])
82 self.
log = log
if log
else Log.getLogger(__name__.partition(
".")[2])
86 self.
updateMetadata(camera=camera, detector=detector, setDate=
False)
89 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, detector={self._detectorName}, )"
92 """Calibration equivalence.
94 Subclasses will need to check specific sub-properties. The
95 default is only to check common entries.
97 if not isinstance(other, self.__class__):
101 if getattr(self, attr) != getattr(other, attr):
110 @requiredAttributes.setter
115 """Retrieve metadata associated with this calibration.
119 meta : `lsst.daf.base.PropertyList`
120 Metadata. The returned `~lsst.daf.base.PropertyList` can be
121 modified by the caller and the changes will be written to
127 """Store a copy of the supplied metadata with this calibration.
131 metadata : `lsst.daf.base.PropertyList`
132 Metadata to associate with the calibration. Will be copied and
133 overwrite existing metadata.
135 if metadata
is not None:
144 setCalibId=False, setDate=False,
146 """Update metadata keywords with new values.
150 camera : `lsst.afw.cameraGeom.Camera`, optional
151 Reference camera to use to set _instrument field.
152 detector : `lsst.afw.cameraGeom.Detector`, optional
153 Reference detector to use to set _detector* fields.
154 filterName : `str`, optional
155 Filter name to assign to this calibration.
156 setCalibId : `bool` optional
157 Construct the _calibId field from other fields.
158 setDate : `bool`, optional
159 Ensure the metadata CALIBDATE fields are set to the current datetime.
160 kwargs : `dict` or `collections.abc.Mapping`, optional
161 Set of key=value pairs to assign to the metadata.
164 mdSupplemental = dict()
184 values.append(f
"instrument={self._instrument}")
if self.
_instrument else None
185 values.append(f
"raftName={self._raftName}")
if self.
_raftName else None
186 values.append(f
"detectorName={self._detectorName}")
if self.
_detectorName else None
187 values.append(f
"detector={self.detector}")
if self._detector
else None
188 values.append(f
"filter={self._filter}")
if self.
_filter else None
192 date = datetime.datetime.now()
193 mdSupplemental[
'CALIBDATE'] = date.isoformat()
194 mdSupplemental[
'CALIB_CREATION_DATE'] = date.date().isoformat()
195 mdSupplemental[
'CALIB_CREATION_TIME'] = date.time().isoformat()
206 mdSupplemental.update(kwargs)
207 mdOriginal.update(mdSupplemental)
211 """Read calibration representation from a yaml/ecsv file.
216 Name of the file containing the calibration definition.
220 calib : `~lsst.ip.isr.IsrCalibType`
226 Raised if the filename does not end in ".ecsv" or ".yaml".
228 if filename.endswith((
".ecsv",
".ECSV")):
229 data = Table.read(filename, format=
'ascii.ecsv')
232 elif filename.endswith((
".yaml",
".YAML")):
233 with open(filename,
'r')
as f:
234 data = yaml.load(f, Loader=yaml.CLoader)
237 raise RuntimeError(f
"Unknown filename extension: {filename}")
240 """Write the calibration data to a text file.
245 Name of the file to write.
247 Format to write the file as. Supported values are:
248 ``"auto"`` : Determine filetype from filename.
249 ``"yaml"`` : Write as yaml.
250 ``"ecsv"`` : Write as ecsv.
254 The name of the file used to write the data. This may
255 differ from the input if the format is explicitly chosen.
260 Raised if filename does not end in a known extension, or
261 if all information cannot be written.
265 The file is written to YAML/ECSV format and will include any
269 if format ==
'yaml' or (format ==
'auto' and filename.lower().endswith((
".yaml",
".YAML"))):
271 path, ext = os.path.splitext(filename)
272 filename = path +
".yaml"
273 with open(filename,
'w')
as f:
274 yaml.dump(outDict, f)
275 elif format ==
'ecsv' or (format ==
'auto' and filename.lower().endswith((
".ecsv",
".ECSV"))):
277 if len(tableList) > 1:
280 raise RuntimeError(f
"Unable to persist {len(tableList)}tables in ECSV format.")
283 path, ext = os.path.splitext(filename)
284 filename = path +
".ecsv"
285 table.write(filename, format=
"ascii.ecsv")
287 raise RuntimeError(f
"Attempt to write to a file {filename} "
288 "that does not end in '.yaml' or '.ecsv'")
294 """Read calibration data from a FITS file.
299 Filename to read data from.
303 calib : `lsst.ip.isr.IsrCalib`
304 Calibration contained within the file.
307 tableList.append(Table.read(filename, hdu=1))
310 with warnings.catch_warnings(
"error"):
311 newTable = Table.read(filename, hdu=extNum)
312 tableList.append(newTable)
320 """Write calibration data to a FITS file.
325 Filename to write data to.
330 The name of the file used to write the data.
334 with warnings.catch_warnings():
335 warnings.filterwarnings(
"ignore", category=Warning, module=
"astropy.io")
336 astropyList = [fits.table_to_hdu(table)
for table
in tableList]
337 astropyList.insert(0, fits.PrimaryHDU())
339 writer = fits.HDUList(astropyList)
340 writer.writeto(filename, overwrite=
True)
344 """Modify the calibration parameters to match the supplied detector.
348 detector : `lsst.afw.cameraGeom.Detector`
349 Detector to use to set parameters from.
354 This needs to be implemented by subclasses for each
357 raise NotImplementedError(
"Must be implemented by subclass.")
361 """Construct a calibration from a dictionary of properties.
363 Must be implemented by the specific calibration subclasses.
368 Dictionary of properties.
372 calib : `lsst.ip.isr.CalibType`
373 Constructed calibration.
377 NotImplementedError :
378 Raised if not implemented.
380 raise NotImplementedError(
"Must be implemented by subclass.")
383 """Return a dictionary containing the calibration properties.
385 The dictionary should be able to be round-tripped through
391 Dictionary of properties.
395 NotImplementedError :
396 Raised if not implemented.
398 raise NotImplementedError(
"Must be implemented by subclass.")
402 """Construct a calibration from a dictionary of properties.
404 Must be implemented by the specific calibration subclasses.
408 tableList : `list` [`lsst.afw.table.Table`]
409 List of tables of properties.
413 calib : `lsst.ip.isr.CalibType`
414 Constructed calibration.
418 NotImplementedError :
419 Raised if not implemented.
421 raise NotImplementedError(
"Must be implemented by subclass.")
424 """Return a list of tables containing the calibration properties.
426 The table list should be able to be round-tripped through
431 tableList : `list` [`lsst.afw.table.Table`]
432 List of tables of properties.
436 NotImplementedError :
437 Raised if not implemented.
439 raise NotImplementedError(
"Must be implemented by subclass.")
442 """Validate that this calibration is defined and can be used.
446 other : `object`, optional
447 Thing to validate against.
452 Returns true if the calibration is valid and appropriate.
457 """Method to apply the calibration to the target object.
462 Thing to validate against.
467 Returns true if the calibration was applied correctly.
471 NotImplementedError :
472 Raised if not implemented.
474 raise NotImplementedError(
"Must be implemented by subclass.")
478 """Class for the provenance of data used to construct calibration.
480 Provenance is not really a calibration, but we would like to
481 record this when constructing the calibration, and it provides an
482 example of the base calibration class.
486 instrument : `str`, optional
487 Name of the instrument the data was taken with.
488 calibType : `str`, optional
489 Type of calibration this provenance was generated for.
490 detectorName : `str`, optional
491 Name of the detector this calibration is for.
492 detectorSerial : `str`, optional
493 Identifier for the detector.
496 _OBSTYPE =
'IsrProvenance'
509 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, calibType={self.calibType}, )"
512 return super().
__eq__(other)
515 """Update calibration metadata.
519 setDate : `bool, optional
520 Update the CALIBDATE fields in the metadata to the current
521 time. Defaults to False.
522 kwargs : `dict` or `collections.abc.Mapping`, optional
523 Other keyword parameters to set in the metadata.
529 """Update provenance from dataId List.
533 dataIdList : `list` [`lsst.daf.butler.DataId`]
534 List of dataIds used in generating this calibration.
536 for dataId
in dataIdList:
544 """Construct provenance from table list.
548 tableList : `list` [`lsst.afw.table.Table`]
549 List of tables to construct the provenance from.
553 provenance : `lsst.ip.isr.IsrProvenance`
554 The provenance defined in the tables.
557 metadata = table.meta
559 inDict[
'metadata'] = metadata
560 inDict[
'detectorName'] = metadata.get(
'DET_NAME',
None)
561 inDict[
'detectorSerial'] = metadata.get(
'DET_SER',
None)
562 inDict[
'instrument'] = metadata.get(
'INSTRUME',
None)
563 inDict[
'calibType'] = metadata[
'calibType']
564 inDict[
'dimensions'] = set()
565 inDict[
'dataIdList'] = list()
568 for colName
in table.columns:
569 schema[colName.lower()] = colName
570 inDict[
'dimensions'].add(colName.lower())
571 inDict[
'dimensions'] = sorted(inDict[
'dimensions'])
575 for dim
in sorted(inDict[
'dimensions']):
576 entry[dim] = row[schema[dim]]
577 inDict[
'dataIdList'].append(entry)
583 """Construct provenance from a dictionary.
588 Dictionary of provenance parameters.
592 provenance : `lsst.ip.isr.IsrProvenance`
593 The provenance defined in the tables.
596 calib.updateMetadata(setDate=
False, **dictionary[
'metadata'])
601 calib._detectorName = dictionary.get(
'detectorName',
602 dictionary[
'metadata'].get(
'DET_NAME',
None))
603 calib._detectorSerial = dictionary.get(
'detectorSerial',
604 dictionary[
'metadata'].get(
'DET_SER',
None))
605 calib._instrument = dictionary.get(
'instrument',
606 dictionary[
'metadata'].get(
'INSTRUME',
None))
607 calib.calibType = dictionary[
'calibType']
608 calib.dimensions = set(dictionary[
'dimensions'])
609 calib.dataIdList = dictionary[
'dataIdList']
611 calib.updateMetadata()
615 """Return a dictionary containing the provenance information.
620 Dictionary of provenance.
627 outDict[
'metadata'] = metadata
638 """Return a list of tables containing the provenance.
640 This seems inefficient and slow, so this may not be the best
641 way to store the data.
645 tableList : `list` [`lsst.afw.table.Table`]
646 List of tables containing the provenance information
653 filteredMetadata = {k: v
for k, v
in self.
getMetadata().
toDict().items()
if v
is not None}
654 catalog.meta = filteredMetadata
655 tableList.append(catalog)