27 from astropy.table
import Table
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 detectorName : `str`, optional
53 Name of the detector this calibration is for.
54 detectorSerial : `str`, optional
55 Identifier for the detector.
56 detector : `lsst.afw.cameraGeom.Detector`, optional
57 Detector to extract metadata from.
58 log : `lsst.log.Log`, optional
66 def __init__(self, detectorName=None, detectorSerial=None, detector=None, log=None, **kwargs):
75 self.
log = log
if log
else Log.getLogger(__name__.partition(
".")[2])
82 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, detector={self._detectorName}, )"
85 """Calibration equivalence.
87 Subclasses will need to check specific sub-properties. The
88 default is only to check common entries.
90 if not isinstance(other, self.__class__):
94 if getattr(self, attr) != getattr(other, attr):
103 @requiredAttributes.setter
109 """Retrieve metadata associated with this calibration.
113 meta : `lsst.daf.base.PropertyList`
114 Metadata. The returned `~lsst.daf.base.PropertyList` can be
115 modified by the caller and the changes will be written to
121 """Store a copy of the supplied metadata with this calibration.
125 metadata : `lsst.daf.base.PropertyList`
126 Metadata to associate with the calibration. Will be copied and
127 overwrite existing metadata.
129 if metadata
is not None:
138 """Update metadata keywords with new values.
142 setDate : `bool`, optional
143 Ensure the metadata CALIBDATE fields are set to the current datetime.
145 Set of key=value pairs to assign to the metadata.
148 mdSupplemental = dict()
154 date = datetime.datetime.now()
155 mdSupplemental[
'CALIBDATE'] = date.isoformat()
156 mdSupplemental[
'CALIB_CREATION_DATE'] = date.date().isoformat()
157 mdSupplemental[
'CALIB_CREATION_TIME'] = date.time().isoformat()
159 mdSupplemental.update(kwargs)
160 mdOriginal.update(mdSupplemental)
164 """Read calibration representation from a yaml/ecsv file.
169 Name of the file containing the calibration definition.
173 calib : `~lsst.ip.isr.IsrCalibType`
179 Raised if the filename does not end in ".ecsv" or ".yaml".
181 if filename.endswith((
".ecsv",
".ECSV")):
182 data = Table.read(filename, format=
'ascii.ecsv')
185 elif filename.endswith((
".yaml",
".YAML")):
186 with open(filename,
'r')
as f:
187 data = yaml.load(f, Loader=yaml.CLoader)
190 raise RuntimeError(f
"Unknown filename extension: {filename}")
193 """Write the calibration data to a text file.
198 Name of the file to write.
200 Format to write the file as. Supported values are:
201 ``"auto"`` : Determine filetype from filename.
202 ``"yaml"`` : Write as yaml.
203 ``"ecsv"`` : Write as ecsv.
207 The name of the file used to write the data. This may
208 differ from the input if the format is explicitly chosen.
213 Raised if filename does not end in a known extension, or
214 if all information cannot be written.
218 The file is written to YAML/ECSV format and will include any
222 if format ==
'yaml' or (format ==
'auto' and filename.lower().endswith((
".yaml",
".YAML"))):
224 path, ext = os.path.splitext(filename)
225 filename = path +
".yaml"
226 with open(filename,
'w')
as f:
227 yaml.dump(outDict, f)
228 elif format ==
'ecsv' or (format ==
'auto' and filename.lower().endswith((
".ecsv",
".ECSV"))):
230 if len(tableList) > 1:
233 raise RuntimeError(f
"Unable to persist {len(tableList)}tables in ECSV format.")
236 path, ext = os.path.splitext(filename)
237 filename = path +
".ecsv"
238 table.write(filename, format=
"ascii.ecsv")
240 raise RuntimeError(f
"Attempt to write to a file {filename} "
241 "that does not end in '.yaml' or '.ecsv'")
247 """Read calibration data from a FITS file.
252 Filename to read data from.
256 calib : `lsst.ip.isr.IsrCalib`
257 Calibration contained within the file.
260 tableList.append(Table.read(filename, hdu=1))
263 with warnings.catch_warnings(
"error"):
264 newTable = Table.read(filename, hdu=extNum)
265 tableList.append(newTable)
273 """Write calibration data to a FITS file.
278 Filename to write data to.
283 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 tableList[0].write(filename)
291 for table
in tableList[1:]:
292 table.write(filename, append=
True)
297 """Modify the calibration parameters to match the supplied detector.
301 detector : `lsst.afw.cameraGeom.Detector`
302 Detector to use to set parameters from.
307 This needs to be implemented by subclasses for each
310 raise NotImplementedError(
"Must be implemented by subclass.")
314 """Construct a calibration from a dictionary of properties.
316 Must be implemented by the specific calibration subclasses.
321 Dictionary of properties.
325 calib : `lsst.ip.isr.CalibType`
326 Constructed calibration.
330 NotImplementedError :
331 Raised if not implemented.
333 raise NotImplementedError(
"Must be implemented by subclass.")
336 """Return a dictionary containing the calibration properties.
338 The dictionary should be able to be round-tripped through
344 Dictionary of properties.
348 NotImplementedError :
349 Raised if not implemented.
351 raise NotImplementedError(
"Must be implemented by subclass.")
355 """Construct a calibration from a dictionary of properties.
357 Must be implemented by the specific calibration subclasses.
361 tableList : `list` [`lsst.afw.table.Table`]
362 List of tables of properties.
366 calib : `lsst.ip.isr.CalibType`
367 Constructed calibration.
371 NotImplementedError :
372 Raised if not implemented.
374 raise NotImplementedError(
"Must be implemented by subclass.")
377 """Return a list of tables containing the calibration properties.
379 The table list should be able to be round-tripped through
384 tableList : `list` [`lsst.afw.table.Table`]
385 List of tables of properties.
389 NotImplementedError :
390 Raised if not implemented.
392 raise NotImplementedError(
"Must be implemented by subclass.")
395 """Validate that this calibration is defined and can be used.
399 other : `object`, optional
400 Thing to validate against.
405 Returns true if the calibration is valid and appropriate.
410 """Method to apply the calibration to the target object.
415 Thing to validate against.
420 Returns true if the calibration was applied correctly.
424 NotImplementedError :
425 Raised if not implemented.
427 raise NotImplementedError(
"Must be implemented by subclass.")
431 """Class for the provenance of data used to construct calibration.
433 Provenance is not really a calibration, but we would like to
434 record this when constructing the calibration, and it provides an
435 example of the base calibration class.
439 instrument : `str`, optional
440 Name of the instrument the data was taken with.
441 calibType : `str`, optional
442 Type of calibration this provenance was generated for.
443 detectorName : `str`, optional
444 Name of the detector this calibration is for.
445 detectorSerial : `str`, optional
446 Identifier for the detector.
449 _OBSTYPE =
'IsrProvenance'
451 def __init__(self, instrument="unknown", calibType="unknown",
460 self.
requiredAttributes.update([
'instrument',
'calibType',
'dimensions',
'dataIdList'])
463 return f
"{self.__class__.__name__}(obstype={self._OBSTYPE}, calibType={self.calibType}, )"
466 return super().
__eq__(other)
469 """Update calibration metadata.
473 setDate : `bool, optional
474 Update the CALIBDATE fields in the metadata to the current
475 time. Defaults to False.
477 Other keyword parameters to set in the metadata.
487 """Update provenance from dataId List.
491 dataIdList : `list` [`lsst.daf.butler.DataId`]
492 List of dataIds used in generating this calibration.
494 for dataId
in dataIdList:
502 """Construct provenance from table list.
506 tableList : `list` [`lsst.afw.table.Table`]
507 List of tables to construct the provenance from.
511 provenance : `lsst.ip.isr.IsrProvenance`
512 The provenance defined in the tables.
515 metadata = table.meta
517 inDict[
'metadata'] = metadata
518 inDict[
'detectorName'] = metadata[
'DETECTOR']
519 inDict[
'detectorSerial'] = metadata[
'DETECTOR_SERIAL']
520 inDict[
'instrument'] = metadata[
'INSTRUME']
521 inDict[
'calibType'] = metadata[
'calibType']
522 inDict[
'dimensions'] = set()
523 inDict[
'dataIdList'] = list()
526 for colName
in table.columns:
527 schema[colName.lower()] = colName
528 inDict[
'dimensions'].add(colName.lower())
529 inDict[
'dimensions'] = sorted(inDict[
'dimensions'])
533 for dim
in sorted(inDict[
'dimensions']):
534 entry[dim] = row[schema[dim]]
535 inDict[
'dataIdList'].append(entry)
541 """Construct provenance from a dictionary.
546 Dictionary of provenance parameters.
550 provenance : `lsst.ip.isr.IsrProvenance`
551 The provenance defined in the tables.
554 calib.updateMetadata(setDate=
False, **dictionary[
'metadata'])
555 calib._detectorName = dictionary[
'detectorName']
556 calib._detectorSerial = dictionary[
'detectorSerial']
557 calib.instrument = dictionary[
'instrument']
558 calib.calibType = dictionary[
'calibType']
559 calib.dimensions = set(dictionary[
'dimensions'])
560 calib.dataIdList = dictionary[
'dataIdList']
562 calib.updateMetadata()
566 """Return a dictionary containing the provenance information.
571 Dictionary of provenance.
578 outDict[
'metadata'] = metadata
589 """Return a list of tables containing the provenance.
591 This seems inefficient and slow, so this may not be the best
592 way to store the data.
596 tableList : `list` [`lsst.afw.table.Table`]
597 List of tables containing the provenance information
605 tableList.append(catalog)