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():
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.
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())
204 mi = lsst.afw.image.MaskedImageF(region)
206 self.
_defects_defects = Defects.fromMask(mi,
"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.
242 return self.__class__(d.getBBox()
for d
in self)
245 """Make a transposed copy of this defect list.
249 retDefectList : `Defects`
250 Transposed list of defects.
252 retDefectList = self.__class__()
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`
267 Image to process. Only the mask plane is updated.
268 maskName : str, optional
269 Mask plane name to use.
272 mask = maskedImage.getMask()
273 bitmask = mask.getPlaneBitMask(maskName)
275 bbox = defect.getBBox()
279 """Convert defect list to `~lsst.afw.table.BaseCatalog` using the
280 FITS region standard.
284 table : `lsst.afw.table.BaseCatalog`
285 Defects in tabular form.
289 The table created uses the
290 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
291 definition tabular format. The ``X`` and ``Y`` coordinates are
292 converted to FITS Physical coordinates that have origin pixel (1, 1)
293 rather than the (0, 0) used in LSST software.
305 for i, defect
in enumerate(self.
_defects_defects):
306 box = defect.getBBox()
307 center = box.getCenter()
309 xCol.append(center.getX() + 1.0)
310 yCol.append(center.getY() + 1.0)
315 if width == 1
and height == 1:
322 shapes.append(shapeType)
324 rCol.append(np.array([width, height], dtype=np.float64))
326 table = astropy.table.Table({
'X': xCol,
'Y': yCol,
'SHAPE': shapes,
327 'R': rCol,
'ROTANG': np.zeros(nrows),
328 'COMPONENT': np.arange(nrows)})
334 """Construct a calibration from a dictionary of properties.
336 Must be implemented by the specific calibration subclasses.
341 Dictionary of properties.
345 calib : `lsst.ip.isr.CalibType`
346 Constructed calibration.
351 Raised if the supplied dictionary is for a different
356 if calib._OBSTYPE != dictionary[
'metadata'][
'OBSTYPE']:
357 raise RuntimeError(f
"Incorrect crosstalk supplied. Expected {calib._OBSTYPE}, "
358 f
"found {dictionary['metadata']['OBSTYPE']}")
360 calib.setMetadata(dictionary[
'metadata'])
361 calib.calibInfoFromDict(dictionary)
363 xCol = dictionary[
'x0']
364 yCol = dictionary[
'y0']
365 widthCol = dictionary[
'width']
366 heightCol = dictionary[
'height']
368 with calib.bulk_update:
369 for x0, y0, width, height
in zip(xCol, yCol, widthCol, heightCol):
375 """Return a dictionary containing the calibration properties.
377 The dictionary should be able to be round-tripped through
383 Dictionary of properties.
389 outDict[
'metadata'] = metadata
398 for defect
in self.
_defects_defects:
399 box = defect.getBBox()
400 xCol.append(box.getBeginX())
401 yCol.append(box.getBeginY())
402 widthCol.append(box.getWidth())
403 heightCol.append(box.getHeight())
407 outDict[
'width'] = widthCol
408 outDict[
'height'] = heightCol
413 """Convert defects to a simple table form that we use to write
418 table : `lsst.afw.table.BaseCatalog`
419 Defects in simple tabular form.
423 These defect tables are used as the human readable definitions
424 of defects in calibration data definition repositories. The format
425 is to use four columns defined as follows:
428 X coordinate of bottom left corner of box.
430 Y coordinate of bottom left corner of box.
446 for defect
in self.
_defects_defects:
447 box = defect.getBBox()
448 xCol.append(box.getBeginX())
449 yCol.append(box.getBeginY())
450 widthCol.append(box.getWidth())
451 heightCol.append(box.getHeight())
453 catalog = astropy.table.Table({
'x0': xCol,
'y0': yCol,
'width': widthCol,
'height': heightCol})
455 outMeta = {k: v
for k, v
in inMeta.items()
if v
is not None}
456 catalog.meta = outMeta
457 tableList.append(catalog)
462 def _get_values(values, n=1):
463 """Retrieve N values from the supplied values.
467 values : `numbers.Number` or `list` or `np.array`
470 Number of values to retrieve.
474 vals : `list` or `np.array` or `numbers.Number`
475 Single value from supplied list if ``n`` is 1, or `list`
476 containing first ``n`` values from supplied values.
480 Some supplied tables have vectors in some columns that can also
481 be scalars. This method can be used to get the first number as
482 a scalar or the first N items from a vector as a vector.
485 if isinstance(values, numbers.Number):
494 """Construct a `Defects` from the contents of a
495 `~lsst.afw.table.BaseCatalog`.
499 table : `lsst.afw.table.BaseCatalog`
500 Table with one row per defect.
501 normalize_on_init : `bool`, optional
502 If `True`, normalization is applied to the defects listed in the
503 table to remove duplicates, eliminate overlaps, etc. Otherwise
504 the defects in the returned object exactly match those in the
514 Two table formats are recognized. The first is the
515 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
516 definition tabular format written by `toFitsRegionTable` where the
517 pixel origin is corrected from FITS 1-based to a 0-based origin.
518 The second is the legacy defects format using columns ``x0``, ``y0``
519 (bottom left hand pixel of box in 0-based coordinates), ``width``
522 The FITS standard regions can only read BOX, POINT, or ROTBOX with
523 a zero degree rotation.
528 schema = table.columns
530 if "X" in schema
and "Y" in schema
and "R" in schema
and "SHAPE" in schema:
533 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
537 raise ValueError(
"Unsupported schema for defects extraction")
544 xcen = cls.
_get_values_get_values(record[
'X']) - 1.0
545 ycen = cls.
_get_values_get_values(record[
'Y']) - 1.0
546 shape = record[
'SHAPE'].upper().rstrip()
551 elif shape ==
"POINT":
555 elif shape ==
"ROTBOX":
557 rotang = cls.
_get_values_get_values(record[
'ROTANG'])
559 if math.isclose(rotang % 90.0, 0.0):
562 if math.isclose(rotang % 180.0, 0.0):
571 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
574 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
582 defectList.append(box)
584 defects = cls(defectList, normalize_on_init=normalize_on_init)
585 newMeta = dict(table.meta)
586 defects.updateMetadata(setCalibInfo=
True, **newMeta)
592 """Read defects information from a legacy LSST format text file.
597 Name of text file containing the defect information.
599 normalize_on_init : `bool`, optional
600 If `True`, normalization is applied to the defects listed in the
601 table to remove duplicates, eliminate overlaps, etc. Otherwise
602 the defects in the returned object exactly match those in the
612 These defect text files are used as the human readable definitions
613 of defects in calibration data definition repositories. The format
614 is to use four columns defined as follows:
617 X coordinate of bottom left corner of box.
619 Y coordinate of bottom left corner of box.
625 Files of this format were used historically to represent defects
626 in simple text form. Use `Defects.readText` and `Defects.writeText`
627 to use the more modern format.
631 defect_array = np.loadtxt(filename,
632 dtype=[(
"x0",
"int"), (
"y0",
"int"),
633 (
"x_extent",
"int"), (
"y_extent",
"int")])
637 for row
in defect_array)
639 return cls(defects, normalize_on_init=normalize_on_init)
643 """Compute a defect list from a footprint list, optionally growing
648 fpList : `list` of `lsst.afw.detection.Footprint`
649 Footprint list to process.
659 for fp
in fpList), normalize_on_init=
False)
663 """Compute a defect list from a specified mask plane.
667 maskedImage : `lsst.afw.image.MaskedImage`
669 maskName : `str` or `list`
670 Mask plane name, or list of names to convert.
675 Defect list constructed from masked pixels.
677 mask = maskedImage.getMask()
679 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.