26 from astropy.table
import Table
27 from astropy.io
import fits
33 __all__ = [
"IsrCalib",
"IsrProvenance"]
37 """Generic calibration type.
39 Subclasses must implement the toDict, fromDict, toTable, fromTable
40 methods that allow the calibration information to be converted
41 from dictionaries and afw tables. This will allow the calibration
42 to be persisted using the base class read/write methods.
44 The validate method is intended to provide a common way to check
45 that the calibration is valid (internally consistent) and
46 appropriate (usable with the intended data). The apply method is
47 intended to allow the calibration to be applied in a consistent
52 camera : `lsst.afw.cameraGeom.Camera`, optional
53 Camera to extract metadata from.
54 detector : `lsst.afw.cameraGeom.Detector`, optional
55 Detector to extract metadata from.
56 log : `lsst.log.Log`, optional
63 def __init__(self, camera=None, detector=None, detectorName=None, detectorId=None, log=None, **kwargs):
78 '_detectorName',
'_detectorSerial',
'_detectorId',
79 '_filter',
'_calibId',
'_metadata'])
81 self.
log = log
if log
else Log.getLogger(__name__.partition(
".")[2])
85 self.
updateMetadata(camera=camera, detector=detector, setDate=
False)
88 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, detector={self._detectorName}, )"
91 """Calibration equivalence.
93 Subclasses will need to check specific sub-properties. The
94 default is only to check common entries.
96 if not isinstance(other, self.__class__):
100 if getattr(self, attr) != getattr(other, attr):
109 @requiredAttributes.setter
114 """Retrieve metadata associated with this calibration.
118 meta : `lsst.daf.base.PropertyList`
119 Metadata. The returned `~lsst.daf.base.PropertyList` can be
120 modified by the caller and the changes will be written to
126 """Store a copy of the supplied metadata with this calibration.
130 metadata : `lsst.daf.base.PropertyList`
131 Metadata to associate with the calibration. Will be copied and
132 overwrite existing metadata.
134 if metadata
is not None:
143 setCalibId=False, setDate=False,
145 """Update metadata keywords with new values.
149 camera : `lsst.afw.cameraGeom.Camera`, optional
150 Reference camera to use to set _instrument field.
151 detector : `lsst.afw.cameraGeom.Detector`, optional
152 Reference detector to use to set _detector* fields.
153 filterName : `str`, optional
154 Filter name to assign to this calibration.
155 setCalibId : `bool` optional
156 Construct the _calibId field from other fields.
157 setDate : `bool`, optional
158 Ensure the metadata CALIBDATE fields are set to the current datetime.
159 kwargs : `dict` or `collections.abc.Mapping`, optional
160 Set of key=value pairs to assign to the metadata.
163 mdSupplemental = dict()
183 values.append(f
"instrument={self._instrument}")
if self.
_instrument else None
184 values.append(f
"raftName={self._raftName}")
if self.
_raftName else None
185 values.append(f
"detectorName={self._detectorName}")
if self.
_detectorName else None
186 values.append(f
"detector={self.detector}")
if self._detector
else None
187 values.append(f
"filter={self._filter}")
if self.
_filter else None
191 date = datetime.datetime.now()
192 mdSupplemental[
'CALIBDATE'] = date.isoformat()
193 mdSupplemental[
'CALIB_CREATION_DATE'] = date.date().isoformat()
194 mdSupplemental[
'CALIB_CREATION_TIME'] = date.time().isoformat()
205 mdSupplemental.update(kwargs)
206 mdOriginal.update(mdSupplemental)
210 """Read calibration representation from a yaml/ecsv file.
215 Name of the file containing the calibration definition.
219 calib : `~lsst.ip.isr.IsrCalibType`
225 Raised if the filename does not end in ".ecsv" or ".yaml".
227 if filename.endswith((
".ecsv",
".ECSV")):
228 data = Table.read(filename, format=
'ascii.ecsv')
231 elif filename.endswith((
".yaml",
".YAML")):
232 with open(filename,
'r')
as f:
233 data = yaml.load(f, Loader=yaml.CLoader)
236 raise RuntimeError(f
"Unknown filename extension: {filename}")
239 """Write the calibration data to a text file.
244 Name of the file to write.
246 Format to write the file as. Supported values are:
247 ``"auto"`` : Determine filetype from filename.
248 ``"yaml"`` : Write as yaml.
249 ``"ecsv"`` : Write as ecsv.
253 The name of the file used to write the data. This may
254 differ from the input if the format is explicitly chosen.
259 Raised if filename does not end in a known extension, or
260 if all information cannot be written.
264 The file is written to YAML/ECSV format and will include any
268 if format ==
'yaml' or (format ==
'auto' and filename.lower().endswith((
".yaml",
".YAML"))):
270 path, ext = os.path.splitext(filename)
271 filename = path +
".yaml"
272 with open(filename,
'w')
as f:
273 yaml.dump(outDict, f)
274 elif format ==
'ecsv' or (format ==
'auto' and filename.lower().endswith((
".ecsv",
".ECSV"))):
276 if len(tableList) > 1:
279 raise RuntimeError(f
"Unable to persist {len(tableList)}tables in ECSV format.")
282 path, ext = os.path.splitext(filename)
283 filename = path +
".ecsv"
284 table.write(filename, format=
"ascii.ecsv")
286 raise RuntimeError(f
"Attempt to write to a file {filename} "
287 "that does not end in '.yaml' or '.ecsv'")
293 """Read calibration data from a FITS file.
298 Filename to read data from.
302 calib : `lsst.ip.isr.IsrCalib`
303 Calibration contained within the file.
306 tableList.append(Table.read(filename, hdu=1))
311 with warnings.catch_warnings():
312 warnings.simplefilter(
"error")
314 newTable = Table.read(filename, hdu=extNum)
315 tableList.append(newTable)
323 """Write calibration data to a FITS file.
328 Filename to write data to.
333 The name of the file used to write the data.
337 with warnings.catch_warnings():
338 warnings.filterwarnings(
"ignore", category=Warning, module=
"astropy.io")
339 astropyList = [fits.table_to_hdu(table)
for table
in tableList]
340 astropyList.insert(0, fits.PrimaryHDU())
342 writer = fits.HDUList(astropyList)
343 writer.writeto(filename, overwrite=
True)
347 """Modify the calibration parameters to match the supplied detector.
351 detector : `lsst.afw.cameraGeom.Detector`
352 Detector to use to set parameters from.
357 This needs to be implemented by subclasses for each
360 raise NotImplementedError(
"Must be implemented by subclass.")
364 """Construct a calibration from a dictionary of properties.
366 Must be implemented by the specific calibration subclasses.
371 Dictionary of properties.
375 calib : `lsst.ip.isr.CalibType`
376 Constructed calibration.
380 NotImplementedError :
381 Raised if not implemented.
383 raise NotImplementedError(
"Must be implemented by subclass.")
386 """Return a dictionary containing the calibration properties.
388 The dictionary should be able to be round-tripped through
394 Dictionary of properties.
398 NotImplementedError :
399 Raised if not implemented.
401 raise NotImplementedError(
"Must be implemented by subclass.")
405 """Construct a calibration from a dictionary of properties.
407 Must be implemented by the specific calibration subclasses.
411 tableList : `list` [`lsst.afw.table.Table`]
412 List of tables of properties.
416 calib : `lsst.ip.isr.CalibType`
417 Constructed calibration.
421 NotImplementedError :
422 Raised if not implemented.
424 raise NotImplementedError(
"Must be implemented by subclass.")
427 """Return a list of tables containing the calibration properties.
429 The table list should be able to be round-tripped through
434 tableList : `list` [`lsst.afw.table.Table`]
435 List of tables of properties.
439 NotImplementedError :
440 Raised if not implemented.
442 raise NotImplementedError(
"Must be implemented by subclass.")
445 """Validate that this calibration is defined and can be used.
449 other : `object`, optional
450 Thing to validate against.
455 Returns true if the calibration is valid and appropriate.
460 """Method to apply the calibration to the target object.
465 Thing to validate against.
470 Returns true if the calibration was applied correctly.
474 NotImplementedError :
475 Raised if not implemented.
477 raise NotImplementedError(
"Must be implemented by subclass.")
481 """Class for the provenance of data used to construct calibration.
483 Provenance is not really a calibration, but we would like to
484 record this when constructing the calibration, and it provides an
485 example of the base calibration class.
489 instrument : `str`, optional
490 Name of the instrument the data was taken with.
491 calibType : `str`, optional
492 Type of calibration this provenance was generated for.
493 detectorName : `str`, optional
494 Name of the detector this calibration is for.
495 detectorSerial : `str`, optional
496 Identifier for the detector.
499 _OBSTYPE =
'IsrProvenance'
512 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, calibType={self.calibType}, )"
515 return super().
__eq__(other)
518 """Update calibration metadata.
522 setDate : `bool, optional
523 Update the CALIBDATE fields in the metadata to the current
524 time. Defaults to False.
525 kwargs : `dict` or `collections.abc.Mapping`, optional
526 Other keyword parameters to set in the metadata.
532 """Update provenance from dataId List.
536 dataIdList : `list` [`lsst.daf.butler.DataId`]
537 List of dataIds used in generating this calibration.
539 for dataId
in dataIdList:
547 """Construct provenance from table list.
551 tableList : `list` [`lsst.afw.table.Table`]
552 List of tables to construct the provenance from.
556 provenance : `lsst.ip.isr.IsrProvenance`
557 The provenance defined in the tables.
560 metadata = table.meta
562 inDict[
'metadata'] = metadata
563 inDict[
'detectorName'] = metadata.get(
'DET_NAME',
None)
564 inDict[
'detectorSerial'] = metadata.get(
'DET_SER',
None)
565 inDict[
'instrument'] = metadata.get(
'INSTRUME',
None)
566 inDict[
'calibType'] = metadata[
'calibType']
567 inDict[
'dimensions'] = set()
568 inDict[
'dataIdList'] = list()
571 for colName
in table.columns:
572 schema[colName.lower()] = colName
573 inDict[
'dimensions'].add(colName.lower())
574 inDict[
'dimensions'] = sorted(inDict[
'dimensions'])
578 for dim
in sorted(inDict[
'dimensions']):
579 entry[dim] = row[schema[dim]]
580 inDict[
'dataIdList'].append(entry)
586 """Construct provenance from a dictionary.
591 Dictionary of provenance parameters.
595 provenance : `lsst.ip.isr.IsrProvenance`
596 The provenance defined in the tables.
599 calib.updateMetadata(setDate=
False, **dictionary[
'metadata'])
604 calib._detectorName = dictionary.get(
'detectorName',
605 dictionary[
'metadata'].get(
'DET_NAME',
None))
606 calib._detectorSerial = dictionary.get(
'detectorSerial',
607 dictionary[
'metadata'].get(
'DET_SER',
None))
608 calib._instrument = dictionary.get(
'instrument',
609 dictionary[
'metadata'].get(
'INSTRUME',
None))
610 calib.calibType = dictionary[
'calibType']
611 calib.dimensions = set(dictionary[
'dimensions'])
612 calib.dataIdList = dictionary[
'dataIdList']
614 calib.updateMetadata()
618 """Return a dictionary containing the provenance information.
623 Dictionary of provenance.
630 outDict[
'metadata'] = metadata
641 """Return a list of tables containing the provenance.
643 This seems inefficient and slow, so this may not be the best
644 way to store the data.
648 tableList : `list` [`lsst.afw.table.Table`]
649 List of tables containing the provenance information
656 filteredMetadata = {k: v
for k, v
in self.
getMetadata().
toDict().items()
if v
is not None}
657 catalog.meta = filteredMetadata
658 tableList.append(catalog)