21"""Support for image defects"""
39from .calibType
import IsrCalib
41log = logging.getLogger(__name__)
43SCHEMA_NAME_KEY =
"DEFECTS_SCHEMA"
44SCHEMA_VERSION_KEY =
"DEFECTS_SCHEMA_VERSION"
48 """Calibration handler for collections of `lsst.meas.algorithms.Defect`.
52 defectList : iterable, optional
53 Collections of defects to apply to the image. Can be an iterable of
54 `lsst.meas.algorithms.Defect` or `lsst.geom.BoxI`.
55 metadata : `lsst.daf.base.PropertyList`, optional
56 Metadata to associate with the defects. Will be copied and
57 overwrite existing metadata, if any. If not supplied the existing
58 metadata will be reset.
59 normalize_on_init : `bool`
60 If True, normalization is applied to the defects in ``defectList`` to
61 remove duplicates, eliminate overlaps, etc.
65 Defects are stored within this collection in a "reduced" or "normalized"
66 form: rather than simply storing the bounding boxes which are added to the
67 collection, we eliminate overlaps and duplicates. This normalization
68 procedure may introduce overhead when adding many new defects; it may be
69 temporarily disabled using the `Defects.bulk_update` context manager if
72 The attributes stored in this calibration are:
74 _defects : `list` [`lsst.meas.algorithms.Defect`]
75 The collection of Defect objects.
78 """The calibration type used for ingest."""
83 def __init__(self, defectList=None, metadata=None, *, normalize_on_init=True, **kwargs):
86 if defectList
is not None:
99 """Check that the supplied value is a `~lsst.meas.algorithms.Defect`
100 or can be converted to one.
109 new : `~lsst.meas.algorithms.Defect`
110 Either the supplied value or a new object derived from it.
115 Raised if the supplied value can not be converted to
116 `~lsst.meas.algorithms.Defect`
118 if isinstance(value, Defect):
125 value =
Defect(value.getBBox())
127 raise ValueError(f
"Defects must be of type Defect, BoxI, or PointI, not '{value!r}'")
137 """Can be given a `~lsst.meas.algorithms.Defect` or a `lsst.geom.BoxI`
149 """Compare if two `Defects` are equal.
151 Two `Defects` are equal if their bounding boxes are equal and in
152 the same order. Metadata content is ignored.
160 if len(self) != len(other):
164 for d1, d2
in zip(self, other):
165 if d1.getBBox() != d2.getBBox():
172 return baseStr +
",".join(str(d.getBBox())
for d
in self) +
")"
175 """Recalculate defect bounding boxes for efficiency.
179 Ideally, this would generate the provably-minimal set of bounding
180 boxes necessary to represent the defects. At present, however, that
181 doesn't happen: see DM-24781. In the cases of substantial overlaps or
182 duplication, though, this will produce a much reduced set.
193 minX, minY, maxX, maxY = float(
'inf'), float(
'inf'), float(
'-inf'), float(
'-inf')
195 bbox = defect.getBBox()
196 minX = min(minX, bbox.getMinX())
197 minY = min(minY, bbox.getMinY())
198 maxX = max(maxX, bbox.getMaxX())
199 maxY = max(maxY, bbox.getMaxY())
206 self.
_defects = Defects.fromMask(mask,
"BAD")._defects
208 @contextlib.contextmanager
210 """Temporarily suspend normalization of the defect list.
228 """Copy the defects to a new list, creating new defects from the
234 New list with new `Defect` entries.
238 This is not a shallow copy in that new `Defect` instances are
239 created from the original bounding boxes. It's also not a deep
240 copy since the bounding boxes are not recreated.
245 """Make a transposed copy of this defect list.
249 retDefectList : `Defects`
250 Transposed list of defects.
254 bbox = defect.getBBox()
255 dimensions = bbox.getDimensions()
258 retDefectList.append(nbbox)
262 """Set mask plane based on these defects.
266 maskedImage : `lsst.afw.image.MaskedImage` or `lsst.afw.image.Mask`
267 Image to process. Only the mask plane is updated.
268 maskName : str, optional
269 Mask plane name to use.
272 if hasattr(mask,
"getMask"):
273 mask = mask.getMask()
274 bitmask = mask.getPlaneBitMask(maskName)
276 bbox = defect.getBBox()
280 """Update metadata with pixel and column counts.
284 columns : `int`, optional
285 Number of full columns masked.
286 hot : `dict` [`str`, `int`], optional
287 Dictionary with the count of hot pixels, indexed by amplifier name.
288 cold : `dict` [`str`, `int`], optional
289 Dictionary with the count of hot pixels, indexed by amplifier name.
291 mdSupplemental = dict()
293 mdSupplemental[
"LSST CALIB DEFECTS N_BAD_COLUMNS"] = columns
295 for amp, count
in hot.items():
296 mdSupplemental[f
"LSST CALIB DEFECTS {amp} N_HOT"] = count
298 for amp, count
in cold.items():
299 mdSupplemental[f
"LSST CALIB DEFECTS {amp} N_COLD"] = count
303 """Convert defect list to `~lsst.afw.table.BaseCatalog` using the
304 FITS region standard.
308 table : `lsst.afw.table.BaseCatalog`
309 Defects in tabular form.
313 The table created uses the
314 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
315 definition tabular format. The ``X`` and ``Y`` coordinates are
316 converted to FITS Physical coordinates that have origin pixel (1, 1)
317 rather than the (0, 0) used in LSST software.
329 for i, defect
in enumerate(self.
_defects):
330 box = defect.getBBox()
331 center = box.getCenter()
333 xCol.append(center.getX() + 1.0)
334 yCol.append(center.getY() + 1.0)
339 if width == 1
and height == 1:
346 shapes.append(shapeType)
348 rCol.append(np.array([width, height], dtype=np.float64))
350 table = astropy.table.Table({
'X': xCol,
'Y': yCol,
'SHAPE': shapes,
351 'R': rCol,
'ROTANG': np.zeros(nrows),
352 'COMPONENT': np.arange(nrows)})
358 """Construct a calibration from a dictionary of properties.
360 Must be implemented by the specific calibration subclasses.
365 Dictionary of properties.
369 calib : `lsst.ip.isr.CalibType`
370 Constructed calibration.
375 Raised if the supplied dictionary is for a different
380 if calib._OBSTYPE != dictionary[
'metadata'][
'OBSTYPE']:
381 raise RuntimeError(f
"Incorrect crosstalk supplied. Expected {calib._OBSTYPE}, "
382 f
"found {dictionary['metadata']['OBSTYPE']}")
384 calib.setMetadata(dictionary[
'metadata'])
385 calib.calibInfoFromDict(dictionary)
387 xCol = dictionary[
'x0']
388 yCol = dictionary[
'y0']
389 widthCol = dictionary[
'width']
390 heightCol = dictionary[
'height']
392 with calib.bulk_update:
393 for x0, y0, width, height
in zip(xCol, yCol, widthCol, heightCol):
399 """Return a dictionary containing the calibration properties.
401 The dictionary should be able to be round-tripped through
407 Dictionary of properties.
413 outDict[
'metadata'] = metadata
423 box = defect.getBBox()
424 xCol.append(box.getBeginX())
425 yCol.append(box.getBeginY())
426 widthCol.append(box.getWidth())
427 heightCol.append(box.getHeight())
431 outDict[
'width'] = widthCol
432 outDict[
'height'] = heightCol
437 """Convert defects to a simple table form that we use to write
442 table : `lsst.afw.table.BaseCatalog`
443 Defects in simple tabular form.
447 These defect tables are used as the human readable definitions
448 of defects in calibration data definition repositories. The format
449 is to use four columns defined as follows:
452 X coordinate of bottom left corner of box.
454 Y coordinate of bottom left corner of box.
471 box = defect.getBBox()
472 xCol.append(box.getBeginX())
473 yCol.append(box.getBeginY())
474 widthCol.append(box.getWidth())
475 heightCol.append(box.getHeight())
477 catalog = astropy.table.Table({
'x0': xCol,
'y0': yCol,
'width': widthCol,
'height': heightCol})
479 outMeta = {k: v
for k, v
in inMeta.items()
if v
is not None}
480 catalog.meta = outMeta
481 tableList.append(catalog)
487 """Retrieve N values from the supplied values.
491 values : `numbers.Number` or `list` or `np.array`
494 Number of values to retrieve.
498 vals : `list` or `np.array` or `numbers.Number`
499 Single value from supplied list if ``n`` is 1, or `list`
500 containing first ``n`` values from supplied values.
504 Some supplied tables have vectors in some columns that can also
505 be scalars. This method can be used to get the first number as
506 a scalar or the first N items from a vector as a vector.
509 if isinstance(values, numbers.Number):
518 """Construct a `Defects` from the contents of a
519 `~lsst.afw.table.BaseCatalog`.
523 table : `lsst.afw.table.BaseCatalog`
524 Table with one row per defect.
525 normalize_on_init : `bool`, optional
526 If `True`, normalization is applied to the defects listed in the
527 table to remove duplicates, eliminate overlaps, etc. Otherwise
528 the defects in the returned object exactly match those in the
538 Two table formats are recognized. The first is the
539 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
540 definition tabular format written by `toFitsRegionTable` where the
541 pixel origin is corrected from FITS 1-based to a 0-based origin.
542 The second is the legacy defects format using columns ``x0``, ``y0``
543 (bottom left hand pixel of box in 0-based coordinates), ``width``
546 The FITS standard regions can only read BOX, POINT, or ROTBOX with
547 a zero degree rotation.
552 schema = table.columns
554 if "X" in schema
and "Y" in schema
and "R" in schema
and "SHAPE" in schema:
557 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
561 raise ValueError(
"Unsupported schema for defects extraction")
570 shape = record[
'SHAPE'].upper().rstrip()
575 elif shape ==
"POINT":
579 elif shape ==
"ROTBOX":
583 if math.isclose(rotang % 90.0, 0.0):
586 if math.isclose(rotang % 180.0, 0.0):
595 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
598 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
606 defectList.append(box)
608 defects = cls(defectList, normalize_on_init=normalize_on_init)
609 newMeta = dict(table.meta)
610 defects.updateMetadata(setCalibInfo=
True, **newMeta)
616 """Read defects information from a legacy LSST format text file.
621 Name of text file containing the defect information.
623 normalize_on_init : `bool`, optional
624 If `True`, normalization is applied to the defects listed in the
625 table to remove duplicates, eliminate overlaps, etc. Otherwise
626 the defects in the returned object exactly match those in the
636 These defect text files are used as the human readable definitions
637 of defects in calibration data definition repositories. The format
638 is to use four columns defined as follows:
641 X coordinate of bottom left corner of box.
643 Y coordinate of bottom left corner of box.
649 Files of this format were used historically to represent defects
650 in simple text form. Use `Defects.readText` and `Defects.writeText`
651 to use the more modern format.
655 defect_array = np.loadtxt(filename,
656 dtype=[(
"x0",
"int"), (
"y0",
"int"),
657 (
"x_extent",
"int"), (
"y_extent",
"int")])
661 for row
in defect_array)
663 return cls(defects, normalize_on_init=normalize_on_init)
667 """Compute a defect list from a footprint list, optionally growing
672 fpList : `list` of `lsst.afw.detection.Footprint`
673 Footprint list to process.
683 for fp
in fpList), normalize_on_init=
False)
687 """Compute a defect list from a specified mask plane.
691 mask : `lsst.afw.image.Mask` or `lsst.afw.image.MaskedImage`
693 maskName : `str` or `list`
694 Mask plane name, or list of names to convert.
699 Defect list constructed from masked pixels.
701 if hasattr(mask,
"getMask"):
702 mask = mask.getMask()
704 lsst.afw.detection.Threshold.BITMASK)
static Box2I makeCenteredBox(Point2D const ¢er, Extent const &size)
requiredAttributes(self, value)
updateMetadata(self, camera=None, detector=None, filterName=None, setCalibId=False, setCalibInfo=False, setDate=False, **kwargs)
fromTable(cls, tableList, normalize_on_init=True)
insert(self, index, value)
fromDict(cls, dictionary)
_check_value(self, value)
__setitem__(self, index, value)
maskPixels(self, mask, maskName="BAD")
fromMask(cls, mask, maskName)
updateCounters(self, columns=None, hot=None, cold=None)
readLsstDefectsFile(cls, filename, normalize_on_init=False)
fromFootprintList(cls, fpList)
__init__(self, defectList=None, metadata=None, *normalize_on_init=True, **kwargs)
std::vector< lsst::geom::Box2I > footprintToBBoxList(Footprint const &footprint)