21 """Support for image defects"""
23 __all__ = (
"Defects",)
39 from .calibType
import IsrCalib
41 log = logging.getLogger(__name__)
43 SCHEMA_NAME_KEY =
"DEFECTS_SCHEMA"
44 SCHEMA_VERSION_KEY =
"DEFECTS_SCHEMA_VERSION"
48 """Calibration handler for collections of `lsst.meas.algorithms.Defect`.
52 defectList : iterable of `lsst.meas.algorithms.Defect`
53 or `lsst.geom.BoxI`, optional
54 Collections of defects to apply to the image.
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:
98 def _check_value(self, value):
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.
156 if not isinstance(other, self.__class__):
160 if len(self) != len(other):
164 for d1, d2
in zip(self, other):
165 if d1.getBBox() != d2.getBBox():
171 baseStr = super().
__str__(self)
172 return baseStr +
",".join(str(d.getBBox())
for d
in self) +
")"
174 def _normalize(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.
189 minX, minY, maxX, maxY = float(
'inf'), float(
'inf'), float(
'-inf'), float(
'-inf')
191 bbox = defect.getBBox()
192 minX =
min(minX, bbox.getMinX())
193 minY =
min(minY, bbox.getMinY())
194 maxX =
max(maxX, bbox.getMaxX())
195 maxY =
max(maxY, bbox.getMaxY())
200 mi = lsst.afw.image.MaskedImageF(region)
202 self.
_defects_defects = Defects.fromMask(mi,
"BAD")._defects
204 @contextlib.contextmanager
206 """Temporarily suspend normalization of the defect list.
224 """Copy the defects to a new list, creating new defects from the
230 New list with new `Defect` entries.
234 This is not a shallow copy in that new `Defect` instances are
235 created from the original bounding boxes. It's also not a deep
236 copy since the bounding boxes are not recreated.
238 return self.__class__(d.getBBox()
for d
in self)
241 """Make a transposed copy of this defect list.
245 retDefectList : `Defects`
246 Transposed list of defects.
248 retDefectList = self.__class__()
250 bbox = defect.getBBox()
251 dimensions = bbox.getDimensions()
254 retDefectList.append(nbbox)
258 """Set mask plane based on these defects.
262 maskedImage : `lsst.afw.image.MaskedImage`
263 Image to process. Only the mask plane is updated.
264 maskName : str, optional
265 Mask plane name to use.
268 mask = maskedImage.getMask()
269 bitmask = mask.getPlaneBitMask(maskName)
271 bbox = defect.getBBox()
275 """Convert defect list to `~lsst.afw.table.BaseCatalog` using the
276 FITS region standard.
280 table : `lsst.afw.table.BaseCatalog`
281 Defects in tabular form.
285 The table created uses the
286 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
287 definition tabular format. The ``X`` and ``Y`` coordinates are
288 converted to FITS Physical coordinates that have origin pixel (1, 1)
289 rather than the (0, 0) used in LSST software.
301 for i, defect
in enumerate(self.
_defects_defects):
302 box = defect.getBBox()
303 center = box.getCenter()
305 xCol.append(center.getX() + 1.0)
306 yCol.append(center.getY() + 1.0)
311 if width == 1
and height == 1:
318 shapes.append(shapeType)
320 rCol.append(np.array([width, height], dtype=np.float64))
322 table = astropy.table.Table({
'X': xCol,
'Y': yCol,
'SHAPE': shapes,
323 'R': rCol,
'ROTANG': np.zeros(nrows),
324 'COMPONENT': np.arange(nrows)})
330 """Construct a calibration from a dictionary of properties.
332 Must be implemented by the specific calibration subclasses.
337 Dictionary of properties.
341 calib : `lsst.ip.isr.CalibType`
342 Constructed calibration.
347 Raised if the supplied dictionary is for a different
352 if calib._OBSTYPE != dictionary[
'metadata'][
'OBSTYPE']:
353 raise RuntimeError(f
"Incorrect crosstalk supplied. Expected {calib._OBSTYPE}, "
354 f
"found {dictionary['metadata']['OBSTYPE']}")
356 calib.setMetadata(dictionary[
'metadata'])
357 calib.calibInfoFromDict(dictionary)
359 xCol = dictionary[
'x0']
360 yCol = dictionary[
'y0']
361 widthCol = dictionary[
'width']
362 heightCol = dictionary[
'height']
364 with calib.bulk_update:
365 for x0, y0, width, height
in zip(xCol, yCol, widthCol, heightCol):
371 """Return a dictionary containing the calibration properties.
373 The dictionary should be able to be round-tripped through
379 Dictionary of properties.
385 outDict[
'metadata'] = metadata
394 for defect
in self.
_defects_defects:
395 box = defect.getBBox()
396 xCol.append(box.getBeginX())
397 yCol.append(box.getBeginY())
398 widthCol.append(box.getWidth())
399 heightCol.append(box.getHeight())
403 outDict[
'width'] = widthCol
404 outDict[
'height'] = heightCol
409 """Convert defects to a simple table form that we use to write
414 table : `lsst.afw.table.BaseCatalog`
415 Defects in simple tabular form.
419 These defect tables are used as the human readable definitions
420 of defects in calibration data definition repositories. The format
421 is to use four columns defined as follows:
424 X coordinate of bottom left corner of box.
426 Y coordinate of bottom left corner of box.
442 for defect
in self.
_defects_defects:
443 box = defect.getBBox()
444 xCol.append(box.getBeginX())
445 yCol.append(box.getBeginY())
446 widthCol.append(box.getWidth())
447 heightCol.append(box.getHeight())
449 catalog = astropy.table.Table({
'x0': xCol,
'y0': yCol,
'width': widthCol,
'height': heightCol})
451 outMeta = {k: v
for k, v
in inMeta.items()
if v
is not None}
452 catalog.meta = outMeta
453 tableList.append(catalog)
458 def _get_values(values, n=1):
459 """Retrieve N values from the supplied values.
463 values : `numbers.Number` or `list` or `np.array`
466 Number of values to retrieve.
470 vals : `list` or `np.array` or `numbers.Number`
471 Single value from supplied list if ``n`` is 1, or `list`
472 containing first ``n`` values from supplied values.
476 Some supplied tables have vectors in some columns that can also
477 be scalars. This method can be used to get the first number as
478 a scalar or the first N items from a vector as a vector.
481 if isinstance(values, numbers.Number):
490 """Construct a `Defects` from the contents of a
491 `~lsst.afw.table.BaseCatalog`.
495 table : `lsst.afw.table.BaseCatalog`
496 Table with one row per defect.
497 normalize_on_init : `bool`, optional
498 If `True`, normalization is applied to the defects listed in the
499 table to remove duplicates, eliminate overlaps, etc. Otherwise
500 the defects in the returned object exactly match those in the
510 Two table formats are recognized. The first is the
511 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
512 definition tabular format written by `toFitsRegionTable` where the
513 pixel origin is corrected from FITS 1-based to a 0-based origin.
514 The second is the legacy defects format using columns ``x0``, ``y0``
515 (bottom left hand pixel of box in 0-based coordinates), ``width``
518 The FITS standard regions can only read BOX, POINT, or ROTBOX with
519 a zero degree rotation.
524 schema = table.columns
526 if "X" in schema
and "Y" in schema
and "R" in schema
and "SHAPE" in schema:
529 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
533 raise ValueError(
"Unsupported schema for defects extraction")
540 xcen = cls.
_get_values_get_values(record[
'X']) - 1.0
541 ycen = cls.
_get_values_get_values(record[
'Y']) - 1.0
542 shape = record[
'SHAPE'].upper().rstrip()
547 elif shape ==
"POINT":
551 elif shape ==
"ROTBOX":
553 rotang = cls.
_get_values_get_values(record[
'ROTANG'])
555 if math.isclose(rotang % 90.0, 0.0):
558 if math.isclose(rotang % 180.0, 0.0):
567 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
570 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
578 defectList.append(box)
580 defects =
cls(defectList, normalize_on_init=normalize_on_init)
581 newMeta = dict(table.meta)
582 defects.updateMetadata(setCalibInfo=
True, **newMeta)
588 """Read defects information from a legacy LSST format text file.
593 Name of text file containing the defect information.
595 normalize_on_init : `bool`, optional
596 If `True`, normalization is applied to the defects listed in the
597 table to remove duplicates, eliminate overlaps, etc. Otherwise
598 the defects in the returned object exactly match those in the
608 These defect text files are used as the human readable definitions
609 of defects in calibration data definition repositories. The format
610 is to use four columns defined as follows:
613 X coordinate of bottom left corner of box.
615 Y coordinate of bottom left corner of box.
621 Files of this format were used historically to represent defects
622 in simple text form. Use `Defects.readText` and `Defects.writeText`
623 to use the more modern format.
627 defect_array = np.loadtxt(filename,
628 dtype=[(
"x0",
"int"), (
"y0",
"int"),
629 (
"x_extent",
"int"), (
"y_extent",
"int")])
633 for row
in defect_array)
635 return cls(defects, normalize_on_init=normalize_on_init)
639 """Compute a defect list from a footprint list, optionally growing
644 fpList : `list` of `lsst.afw.detection.Footprint`
645 Footprint list to process.
655 for fp
in fpList), normalize_on_init=
False)
659 """Compute a defect list from a specified mask plane.
663 maskedImage : `lsst.afw.image.MaskedImage`
665 maskName : `str` or `list`
666 Mask plane name, or list of names to convert.
671 Defect list constructed from masked pixels.
673 mask = maskedImage.getMask()
675 lsst.afw.detection.Threshold.BITMASK)
A Threshold is used to pass a threshold value to detection algorithms.
A compact representation of a collection of pixels.
Encapsulate information about a bad portion of a detector.
An integer coordinate rectangle.
static Box2I makeCenteredBox(Point2D const ¢er, Extent const &size)
Create a box centered as closely as possible on a particular point.
def requiredAttributes(self, value)
def updateMetadata(self, camera=None, detector=None, filterName=None, setCalibId=False, setCalibInfo=False, setDate=False, **kwargs)
def requiredAttributes(self)
def readLsstDefectsFile(cls, filename, normalize_on_init=False)
def toFitsRegionTable(self)
def maskPixels(self, maskedImage, maskName="BAD")
def insert(self, index, value)
def fromMask(cls, maskedImage, maskName)
def _get_values(values, n=1)
def fromTable(cls, tableList, normalize_on_init=True)
def _check_value(self, value)
def fromFootprintList(cls, fpList)
def __getitem__(self, index)
def fromDict(cls, dictionary)
def __setitem__(self, index, value)
def __init__(self, defectList=None, metadata=None, *normalize_on_init=True, **kwargs)
def __delitem__(self, index)
Encapsulate information about a bad portion of a detector.
std::vector< lsst::geom::Box2I > footprintToBBoxList(Footprint const &footprint)
Return a list of BBoxs, whose union contains exactly the pixels in the footprint, neither more nor le...
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.