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))
309 with warnings.catch_warnings(
"error"):
310 newTable = Table.read(filename, hdu=extNum)
311 tableList.append(newTable)
319 """Write calibration data to a FITS file.
324 Filename to write data to.
329 The name of the file used to write the data.
333 with warnings.catch_warnings():
334 warnings.filterwarnings(
"ignore", category=Warning, module=
"astropy.io")
335 astropyList = [fits.table_to_hdu(table)
for table
in tableList]
336 astropyList.insert(0, fits.PrimaryHDU())
338 writer = fits.HDUList(astropyList)
339 writer.writeto(filename, overwrite=
True)
343 """Modify the calibration parameters to match the supplied detector.
347 detector : `lsst.afw.cameraGeom.Detector`
348 Detector to use to set parameters from.
353 This needs to be implemented by subclasses for each
356 raise NotImplementedError(
"Must be implemented by subclass.")
360 """Construct a calibration from a dictionary of properties.
362 Must be implemented by the specific calibration subclasses.
367 Dictionary of properties.
371 calib : `lsst.ip.isr.CalibType`
372 Constructed calibration.
376 NotImplementedError :
377 Raised if not implemented.
379 raise NotImplementedError(
"Must be implemented by subclass.")
382 """Return a dictionary containing the calibration properties.
384 The dictionary should be able to be round-tripped through
390 Dictionary of properties.
394 NotImplementedError :
395 Raised if not implemented.
397 raise NotImplementedError(
"Must be implemented by subclass.")
401 """Construct a calibration from a dictionary of properties.
403 Must be implemented by the specific calibration subclasses.
407 tableList : `list` [`lsst.afw.table.Table`]
408 List of tables of properties.
412 calib : `lsst.ip.isr.CalibType`
413 Constructed calibration.
417 NotImplementedError :
418 Raised if not implemented.
420 raise NotImplementedError(
"Must be implemented by subclass.")
423 """Return a list of tables containing the calibration properties.
425 The table list should be able to be round-tripped through
430 tableList : `list` [`lsst.afw.table.Table`]
431 List of tables of properties.
435 NotImplementedError :
436 Raised if not implemented.
438 raise NotImplementedError(
"Must be implemented by subclass.")
441 """Validate that this calibration is defined and can be used.
445 other : `object`, optional
446 Thing to validate against.
451 Returns true if the calibration is valid and appropriate.
456 """Method to apply the calibration to the target object.
461 Thing to validate against.
466 Returns true if the calibration was applied correctly.
470 NotImplementedError :
471 Raised if not implemented.
473 raise NotImplementedError(
"Must be implemented by subclass.")
477 """Class for the provenance of data used to construct calibration.
479 Provenance is not really a calibration, but we would like to
480 record this when constructing the calibration, and it provides an
481 example of the base calibration class.
485 instrument : `str`, optional
486 Name of the instrument the data was taken with.
487 calibType : `str`, optional
488 Type of calibration this provenance was generated for.
489 detectorName : `str`, optional
490 Name of the detector this calibration is for.
491 detectorSerial : `str`, optional
492 Identifier for the detector.
495 _OBSTYPE =
'IsrProvenance'
508 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, calibType={self.calibType}, )"
511 return super().
__eq__(other)
514 """Update calibration metadata.
518 setDate : `bool, optional
519 Update the CALIBDATE fields in the metadata to the current
520 time. Defaults to False.
521 kwargs : `dict` or `collections.abc.Mapping`, optional
522 Other keyword parameters to set in the metadata.
528 """Update provenance from dataId List.
532 dataIdList : `list` [`lsst.daf.butler.DataId`]
533 List of dataIds used in generating this calibration.
535 for dataId
in dataIdList:
543 """Construct provenance from table list.
547 tableList : `list` [`lsst.afw.table.Table`]
548 List of tables to construct the provenance from.
552 provenance : `lsst.ip.isr.IsrProvenance`
553 The provenance defined in the tables.
556 metadata = table.meta
558 inDict[
'metadata'] = metadata
559 inDict[
'detectorName'] = metadata.get(
'DET_NAME',
None)
560 inDict[
'detectorSerial'] = metadata.get(
'DET_SER',
None)
561 inDict[
'instrument'] = metadata.get(
'INSTRUME',
None)
562 inDict[
'calibType'] = metadata[
'calibType']
563 inDict[
'dimensions'] = set()
564 inDict[
'dataIdList'] = list()
567 for colName
in table.columns:
568 schema[colName.lower()] = colName
569 inDict[
'dimensions'].add(colName.lower())
570 inDict[
'dimensions'] = sorted(inDict[
'dimensions'])
574 for dim
in sorted(inDict[
'dimensions']):
575 entry[dim] = row[schema[dim]]
576 inDict[
'dataIdList'].append(entry)
582 """Construct provenance from a dictionary.
587 Dictionary of provenance parameters.
591 provenance : `lsst.ip.isr.IsrProvenance`
592 The provenance defined in the tables.
595 calib.updateMetadata(setDate=
False, **dictionary[
'metadata'])
600 calib._detectorName = dictionary.get(
'detectorName',
601 dictionary[
'metadata'].get(
'DET_NAME',
None))
602 calib._detectorSerial = dictionary.get(
'detectorSerial',
603 dictionary[
'metadata'].get(
'DET_SER',
None))
604 calib._instrument = dictionary.get(
'instrument',
605 dictionary[
'metadata'].get(
'INSTRUME',
None))
606 calib.calibType = dictionary[
'calibType']
607 calib.dimensions = set(dictionary[
'dimensions'])
608 calib.dataIdList = dictionary[
'dataIdList']
610 calib.updateMetadata()
614 """Return a dictionary containing the provenance information.
619 Dictionary of provenance.
626 outDict[
'metadata'] = metadata
637 """Return a list of tables containing the provenance.
639 This seems inefficient and slow, so this may not be the best
640 way to store the data.
644 tableList : `list` [`lsst.afw.table.Table`]
645 List of tables containing the provenance information
652 filteredMetadata = {k: v
for k, v
in self.
getMetadata().
toDict().items()
if v
is not None}
653 catalog.meta = filteredMetadata
654 tableList.append(catalog)