106 """Calibration equivalence.
108 Running ``calib.log.setLevel(0)`` enables debug statements to
109 identify problematic fields.
111 if not isinstance(other, self.
__class__):
112 self.
log.debug(
"Incorrect class type: %s %s", self.
__class__, other.__class__)
116 attrSelf = getattr(self, attr)
117 attrOther = getattr(other, attr)
119 if isinstance(attrSelf, dict):
121 if attrSelf.keys() != attrOther.keys():
122 self.
log.debug(
"Dict Key Failure: %s %s %s", attr, attrSelf.keys(), attrOther.keys())
126 if not np.allclose(attrSelf[key], attrOther[key], equal_nan=
True):
127 self.
log.debug(
"Array Failure: %s %s %s", key, attrSelf[key], attrOther[key])
134 if np.all(attrSelf[key] != attrOther[key]):
136 elif (isinstance(attrSelf, np.ndarray)
or isinstance(attrSelf, Column)
137 or isinstance(attrOther, np.ndarray)
or isinstance(attrOther, Column)):
139 if isinstance(attrSelf[0], (str, np.str_, np.bytes_)):
140 if not np.all(attrSelf == attrOther):
141 self.
log.debug(
"Array Failure: %s %s %s", attr, attrSelf, attrOther)
144 if not np.allclose(attrSelf, attrOther, equal_nan=
True):
145 self.
log.debug(
"Array Failure: %s %s %s", attr, attrSelf, attrOther)
147 elif type(attrSelf)
is not type(attrOther):
148 if set([attrSelf, attrOther]) == set([
None,
""]):
151 self.
log.debug(
"Type Failure: %s %s %s %s %s", attr, type(attrSelf), type(attrOther),
155 if attrSelf != attrOther:
156 self.
log.debug(
"Value Failure: %s %s %s", attr, attrSelf, attrOther)
209 setCalibId=False, setCalibInfo=False, setDate=False,
211 """Update metadata keywords with new values.
215 camera : `lsst.afw.cameraGeom.Camera`, optional
216 Reference camera to use to set ``_instrument`` field.
217 detector : `lsst.afw.cameraGeom.Detector`, optional
218 Reference detector to use to set ``_detector*`` fields.
219 filterName : `str`, optional
220 Filter name to assign to this calibration.
221 setCalibId : `bool`, optional
222 Construct the ``_calibId`` field from other fields.
223 setCalibInfo : `bool`, optional
224 Set calibration parameters from metadata.
225 setDate : `bool`, optional
226 Ensure the metadata ``CALIBDATE`` fields are set to the current
228 kwargs : `dict` or `collections.abc.Mapping`, optional
229 Set of ``key=value`` pairs to assign to the metadata.
232 mdSupplemental = dict()
234 for k, v
in kwargs.items():
235 if isinstance(v, fits.card.Undefined):
260 date = datetime.datetime.now()
261 mdSupplemental[
"CALIBDATE"] = date.isoformat()
262 mdSupplemental[
"CALIB_CREATION_DATE"] = date.date().isoformat()
263 mdSupplemental[
"CALIB_CREATION_TIME"] = date.time().isoformat()
267 values.append(f
"instrument={self._instrument}")
if self.
_instrument else None
268 values.append(f
"raftName={self._raftName}")
if self.
_raftName else None
269 values.append(f
"detectorName={self._detectorName}")
if self.
_detectorName else None
270 values.append(f
"detector={self._detectorId}")
if self.
_detectorId else None
271 values.append(f
"filter={self._filter}")
if self.
_filter else None
273 calibDate = mdOriginal.get(
"CALIBDATE", mdSupplemental.get(
"CALIBDATE",
None))
274 values.append(f
"calibDate={calibDate}")
if calibDate
else None
289 self.
_metadata[
"CALIBCLS"] = get_full_type_name(self)
291 mdSupplemental.update(kwargs)
292 mdOriginal.update(mdSupplemental)
295 """Extract and unify metadata information.
300 Exposures or other calibrations to scan.
306 keywords = [
"SEQNAME",
"SEQFILE",
"SEQCKSUM",
"ODP",
"AP0_RC"]
309 for exp
in exposures:
311 expMeta = exp.getMetadata()
312 except AttributeError:
317 if metadata[key] != expMeta[key]:
318 self.
log.warning(
"Metadata mismatch! Have: %s Found %s",
319 metadata[key], expMeta[key])
321 metadata[key] = expMeta[key]
326 """Handle common keywords.
328 This isn't an ideal solution, but until all calibrations
329 expect to find everything in the metadata, they still need to
330 search through dictionaries.
334 dictionary : `dict` or `lsst.daf.base.PropertyList`
335 Source for the common keywords.
340 Raised if the dictionary does not match the expected OBSTYPE.
343 def search(haystack, needles):
344 """Search dictionary 'haystack' for an entry in 'needles'
346 test = [haystack.get(x)
for x
in needles]
347 test = set([x
for x
in test
if x
is not None])
349 if "metadata" in haystack:
350 return search(haystack[
"metadata"], needles)
354 value = list(test)[0]
360 raise ValueError(f
"Too many values found: {len(test)} {test} {needles}")
362 if "metadata" in dictionary:
363 metadata = dictionary[
"metadata"]
365 if self.
_OBSTYPE != metadata[
"OBSTYPE"]:
366 raise RuntimeError(f
"Incorrect calibration supplied. Expected {self._OBSTYPE}, "
367 f
"found {metadata['OBSTYPE']}")
369 if (value := search(dictionary, [
"INSTRUME",
"instrument"]))
is not None:
371 if (value := search(dictionary, [
"RAFTNAME"]))
is not None:
373 if (value := search(dictionary, [
"DETECTOR",
"detectorId"]))
is not None:
375 if (value := search(dictionary, [
"DET_NAME",
"DETECTOR_NAME",
"detectorName"]))
is not None:
377 if (value := search(dictionary, [
"DET_SER",
"DETECTOR_SERIAL",
"detectorSerial"]))
is not None:
379 if (value := search(dictionary, [
"FILTER",
"filterName"]))
is not None:
381 if (value := search(dictionary, [
"CALIB_ID"]))
is not None:
383 if (value := search(dictionary, [
"SEQFILE"]))
is not None:
385 if (value := search(dictionary, [
"SEQNAME"]))
is not None:
387 if (value := search(dictionary, [
"SEQCKSUM"]))
is not None:
392 """Attempt to find calibration class in metadata.
396 metadata : `dict` or `lsst.daf.base.PropertyList`
397 Metadata possibly containing a calibration class entry.
399 Message to include in any errors.
403 calibClass : `object`
404 The class to use to read the file contents. Should be an
405 `lsst.ip.isr.IsrCalib` subclass.
410 Raised if the resulting calibClass is the base
411 `lsst.ip.isr.IsrClass` (which does not implement the
414 calibClassName = metadata.get(
"CALIBCLS")
415 if calibClassName
is None:
416 calibClassName = metadata.get(
"fileType")
417 if calibClassName ==
"shutterMotionProfile":
418 calibClassName =
"lsst.ip.isr.ShutterMotionProfile"
419 calibClass = doImport(calibClassName)
if calibClassName
is not None else cls
420 if calibClass
is IsrCalib:
421 raise ValueError(f
"Cannot use base class to read calibration data: {message}")
426 """Read calibration representation from a yaml/ecsv file.
431 Name of the file containing the calibration definition.
432 kwargs : `dict` or collections.abc.Mapping`, optional
433 Set of key=value pairs to pass to the ``fromDict`` or
434 ``fromTable`` methods.
438 calib : `~lsst.ip.isr.IsrCalibType`
444 Raised if the filename does not end in ".ecsv" or ".yaml".
446 if filename.endswith((
".ecsv",
".ECSV")):
447 data = Table.read(filename, format=
"ascii.ecsv")
449 return calibClass.fromTable([data], **kwargs)
450 elif filename.endswith((
".yaml",
".YAML")):
451 with open(filename,
"r")
as f:
452 data = yaml.load(f, Loader=yaml.CLoader)
454 return calibClass.fromDict(data, **kwargs)
455 elif filename.endswith((
".json",
".JSON")):
456 with open(filename,
"r")
as f:
459 return calibClass.fromDict(data, **kwargs)
461 raise RuntimeError(f
"Unknown filename extension: {filename}")
464 """Write the calibration data to a text file.
469 Name of the file to write.
471 Format to write the file as. Supported values are:
472 ``"auto"`` : Determine filetype from filename.
473 ``"yaml"`` : Write as yaml.
474 ``"ecsv"`` : Write as ecsv.
479 The name of the file used to write the data. This may
480 differ from the input if the format is explicitly chosen.
485 Raised if filename does not end in a known extension, or
486 if all information cannot be written.
490 The file is written to YAML/ECSV format and will include any
493 if format ==
"yaml" or (format ==
"auto" and filename.lower().endswith((
".yaml",
".YAML"))):
495 path, ext = os.path.splitext(filename)
496 filename = path +
".yaml"
497 with open(filename,
"w")
as f:
498 yaml.dump(outDict, f)
499 elif format ==
"ecsv" or (format ==
"auto" and filename.lower().endswith((
".ecsv",
".ECSV"))):
501 if len(tableList) > 1:
504 raise RuntimeError(f
"Unable to persist {len(tableList)}tables in ECSV format.")
507 path, ext = os.path.splitext(filename)
508 filename = path +
".ecsv"
509 table.write(filename, format=
"ascii.ecsv")
511 raise RuntimeError(f
"Attempt to write to a file {filename} "
512 "that does not end in '.yaml' or '.ecsv'")
518 """Read calibration data from a FITS file.
523 Filename to read data from.
524 kwargs : `dict` or collections.abc.Mapping`, optional
525 Set of key=value pairs to pass to the ``fromTable``
530 calib : `lsst.ip.isr.IsrCalib`
531 Calibration contained within the file.
534 tableList.append(Table.read(filename, hdu=1, mask_invalid=
False))
539 with warnings.catch_warnings():
540 warnings.simplefilter(
"error")
542 newTable = Table.read(filename, hdu=extNum, mask_invalid=
False)
543 tableList.append(newTable)
549 for table
in tableList:
550 if calibClass._OBSTYPE ==
"BF_DISTORTION_MATRIX":
551 table.convert_bytestring_to_unicode()
552 for k, v
in table.meta.items():
553 if isinstance(v, fits.card.Undefined):
556 if calibClass._OBSTYPE
in (
"PHOTODIODE", ):
559 with fits.open(filename)
as hdul:
560 primaryHeader = hdul[0].header
561 tableList[0].meta = merge_headers([tableList[0].meta, primaryHeader], mode=
"first")
563 return calibClass.fromTable(tableList, **kwargs)
566 """Write calibration data to a FITS file.
571 Filename to write data to.
576 The name of the file used to write the data.
579 with warnings.catch_warnings():
580 warnings.filterwarnings(
"ignore", category=Warning, module=
"astropy.io")
581 astropyList = [fits.table_to_hdu(table)
for table
in tableList]
582 astropyList.insert(0, fits.PrimaryHDU())
584 writer = fits.HDUList(astropyList)
585 writer.writeto(filename, overwrite=
True)