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 detectorName : `str`, optional
54 Name of the detector this calibration is for.
55 detectorSerial : `str`, optional
56 Identifier for the detector.
57 detector : `lsst.afw.cameraGeom.Detector`, optional
58 Detector to extract metadata from.
59 log : `lsst.log.Log`, optional
67 def __init__(self, detectorName=None, detectorSerial=None, detector=None, log=None, **kwargs):
76 self.
log = log
if log
else Log.getLogger(__name__.partition(
".")[2])
83 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, detector={self._detectorName}, )"
86 """Calibration equivalence.
88 Subclasses will need to check specific sub-properties. The
89 default is only to check common entries.
91 if not isinstance(other, self.__class__):
95 if getattr(self, attr) != getattr(other, attr):
104 @requiredAttributes.setter
110 """Retrieve metadata associated with this calibration.
114 meta : `lsst.daf.base.PropertyList`
115 Metadata. The returned `~lsst.daf.base.PropertyList` can be
116 modified by the caller and the changes will be written to
122 """Store a copy of the supplied metadata with this calibration.
126 metadata : `lsst.daf.base.PropertyList`
127 Metadata to associate with the calibration. Will be copied and
128 overwrite existing metadata.
130 if metadata
is not None:
139 """Update metadata keywords with new values.
143 setDate : `bool`, optional
144 Ensure the metadata CALIBDATE fields are set to the current datetime.
145 kwargs : `dict` or `collections.abc.Mapping`, optional
146 Set of key=value pairs to assign to the metadata.
149 mdSupplemental = dict()
155 date = datetime.datetime.now()
156 mdSupplemental[
'CALIBDATE'] = date.isoformat()
157 mdSupplemental[
'CALIB_CREATION_DATE'] = date.date().isoformat()
158 mdSupplemental[
'CALIB_CREATION_TIME'] = date.time().isoformat()
160 mdSupplemental.update(kwargs)
161 mdOriginal.update(mdSupplemental)
165 """Read calibration representation from a yaml/ecsv file.
170 Name of the file containing the calibration definition.
174 calib : `~lsst.ip.isr.IsrCalibType`
180 Raised if the filename does not end in ".ecsv" or ".yaml".
182 if filename.endswith((
".ecsv",
".ECSV")):
183 data = Table.read(filename, format=
'ascii.ecsv')
186 elif filename.endswith((
".yaml",
".YAML")):
187 with open(filename,
'r')
as f:
188 data = yaml.load(f, Loader=yaml.CLoader)
191 raise RuntimeError(f
"Unknown filename extension: {filename}")
194 """Write the calibration data to a text file.
199 Name of the file to write.
201 Format to write the file as. Supported values are:
202 ``"auto"`` : Determine filetype from filename.
203 ``"yaml"`` : Write as yaml.
204 ``"ecsv"`` : Write as ecsv.
208 The name of the file used to write the data. This may
209 differ from the input if the format is explicitly chosen.
214 Raised if filename does not end in a known extension, or
215 if all information cannot be written.
219 The file is written to YAML/ECSV format and will include any
223 if format ==
'yaml' or (format ==
'auto' and filename.lower().endswith((
".yaml",
".YAML"))):
225 path, ext = os.path.splitext(filename)
226 filename = path +
".yaml"
227 with open(filename,
'w')
as f:
228 yaml.dump(outDict, f)
229 elif format ==
'ecsv' or (format ==
'auto' and filename.lower().endswith((
".ecsv",
".ECSV"))):
231 if len(tableList) > 1:
234 raise RuntimeError(f
"Unable to persist {len(tableList)}tables in ECSV format.")
237 path, ext = os.path.splitext(filename)
238 filename = path +
".ecsv"
239 table.write(filename, format=
"ascii.ecsv")
241 raise RuntimeError(f
"Attempt to write to a file {filename} "
242 "that does not end in '.yaml' or '.ecsv'")
248 """Read calibration data from a FITS file.
253 Filename to read data from.
257 calib : `lsst.ip.isr.IsrCalib`
258 Calibration contained within the file.
261 tableList.append(Table.read(filename, hdu=1))
264 with warnings.catch_warnings(
"error"):
265 newTable = Table.read(filename, hdu=extNum)
266 tableList.append(newTable)
274 """Write calibration data to a FITS file.
279 Filename to write data to.
284 The name of the file used to write the data.
288 with warnings.catch_warnings():
289 warnings.filterwarnings(
"ignore", category=Warning, module=
"astropy.io")
290 astropyList = [fits.table_to_hdu(table)
for table
in tableList]
291 astropyList.insert(0, fits.PrimaryHDU())
293 writer = fits.HDUList(astropyList)
294 writer.writeto(filename, overwrite=
True)
298 """Modify the calibration parameters to match the supplied detector.
302 detector : `lsst.afw.cameraGeom.Detector`
303 Detector to use to set parameters from.
308 This needs to be implemented by subclasses for each
311 raise NotImplementedError(
"Must be implemented by subclass.")
315 """Construct a calibration from a dictionary of properties.
317 Must be implemented by the specific calibration subclasses.
322 Dictionary of properties.
326 calib : `lsst.ip.isr.CalibType`
327 Constructed calibration.
331 NotImplementedError :
332 Raised if not implemented.
334 raise NotImplementedError(
"Must be implemented by subclass.")
337 """Return a dictionary containing the calibration properties.
339 The dictionary should be able to be round-tripped through
345 Dictionary of properties.
349 NotImplementedError :
350 Raised if not implemented.
352 raise NotImplementedError(
"Must be implemented by subclass.")
356 """Construct a calibration from a dictionary of properties.
358 Must be implemented by the specific calibration subclasses.
362 tableList : `list` [`lsst.afw.table.Table`]
363 List of tables of properties.
367 calib : `lsst.ip.isr.CalibType`
368 Constructed calibration.
372 NotImplementedError :
373 Raised if not implemented.
375 raise NotImplementedError(
"Must be implemented by subclass.")
378 """Return a list of tables containing the calibration properties.
380 The table list should be able to be round-tripped through
385 tableList : `list` [`lsst.afw.table.Table`]
386 List of tables of properties.
390 NotImplementedError :
391 Raised if not implemented.
393 raise NotImplementedError(
"Must be implemented by subclass.")
396 """Validate that this calibration is defined and can be used.
400 other : `object`, optional
401 Thing to validate against.
406 Returns true if the calibration is valid and appropriate.
411 """Method to apply the calibration to the target object.
416 Thing to validate against.
421 Returns true if the calibration was applied correctly.
425 NotImplementedError :
426 Raised if not implemented.
428 raise NotImplementedError(
"Must be implemented by subclass.")
432 """Class for the provenance of data used to construct calibration.
434 Provenance is not really a calibration, but we would like to
435 record this when constructing the calibration, and it provides an
436 example of the base calibration class.
440 instrument : `str`, optional
441 Name of the instrument the data was taken with.
442 calibType : `str`, optional
443 Type of calibration this provenance was generated for.
444 detectorName : `str`, optional
445 Name of the detector this calibration is for.
446 detectorSerial : `str`, optional
447 Identifier for the detector.
450 _OBSTYPE =
'IsrProvenance'
452 def __init__(self, instrument="unknown", calibType="unknown",
461 self.
requiredAttributes.update([
'instrument',
'calibType',
'dimensions',
'dataIdList'])
464 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, calibType={self.calibType}, )"
467 return super().
__eq__(other)
470 """Update calibration metadata.
474 setDate : `bool, optional
475 Update the CALIBDATE fields in the metadata to the current
476 time. Defaults to False.
477 kwargs : `dict` or `collections.abc.Mapping`, optional
478 Other keyword parameters to set in the metadata.
488 """Update provenance from dataId List.
492 dataIdList : `list` [`lsst.daf.butler.DataId`]
493 List of dataIds used in generating this calibration.
495 for dataId
in dataIdList:
503 """Construct provenance from table list.
507 tableList : `list` [`lsst.afw.table.Table`]
508 List of tables to construct the provenance from.
512 provenance : `lsst.ip.isr.IsrProvenance`
513 The provenance defined in the tables.
516 metadata = table.meta
518 inDict[
'metadata'] = metadata
519 inDict[
'detectorName'] = metadata[
'DETECTOR']
520 inDict[
'detectorSerial'] = metadata[
'DETECTOR_SERIAL']
521 inDict[
'instrument'] = metadata[
'INSTRUME']
522 inDict[
'calibType'] = metadata[
'calibType']
523 inDict[
'dimensions'] = set()
524 inDict[
'dataIdList'] = list()
527 for colName
in table.columns:
528 schema[colName.lower()] = colName
529 inDict[
'dimensions'].add(colName.lower())
530 inDict[
'dimensions'] = sorted(inDict[
'dimensions'])
534 for dim
in sorted(inDict[
'dimensions']):
535 entry[dim] = row[schema[dim]]
536 inDict[
'dataIdList'].append(entry)
542 """Construct provenance from a dictionary.
547 Dictionary of provenance parameters.
551 provenance : `lsst.ip.isr.IsrProvenance`
552 The provenance defined in the tables.
555 calib.updateMetadata(setDate=
False, **dictionary[
'metadata'])
556 calib._detectorName = dictionary[
'detectorName']
557 calib._detectorSerial = dictionary[
'detectorSerial']
558 calib.instrument = dictionary[
'instrument']
559 calib.calibType = dictionary[
'calibType']
560 calib.dimensions = set(dictionary[
'dimensions'])
561 calib.dataIdList = dictionary[
'dataIdList']
563 calib.updateMetadata()
567 """Return a dictionary containing the provenance information.
572 Dictionary of provenance.
579 outDict[
'metadata'] = metadata
590 """Return a list of tables containing the provenance.
592 This seems inefficient and slow, so this may not be the best
593 way to store the data.
597 tableList : `list` [`lsst.afw.table.Table`]
598 List of tables containing the provenance information
606 tableList.append(catalog)