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
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:
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.
110 Either the supplied value
or a new object derived
from it.
115 Raised
if the supplied value can
not be converted to
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.
223 def insert(self, index, value):
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.
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 """Convert defect list to `~lsst.afw.table.BaseCatalog` using the
281 FITS region standard.
286 Defects in tabular form.
290 The table created uses the
291 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
292 definition tabular format. The ``X``
and ``Y`` coordinates are
293 converted to FITS Physical coordinates that have origin pixel (1, 1)
294 rather than the (0, 0) used
in LSST software.
306 for i, defect
in enumerate(self.
_defects):
307 box = defect.getBBox()
308 center = box.getCenter()
310 xCol.append(center.getX() + 1.0)
311 yCol.append(center.getY() + 1.0)
316 if width == 1
and height == 1:
323 shapes.append(shapeType)
325 rCol.append(np.array([width, height], dtype=np.float64))
327 table = astropy.table.Table({
'X': xCol,
'Y': yCol,
'SHAPE': shapes,
328 'R': rCol,
'ROTANG': np.zeros(nrows),
329 'COMPONENT': np.arange(nrows)})
335 """Construct a calibration from a dictionary of properties.
337 Must be implemented by the specific calibration subclasses.
342 Dictionary of properties.
346 calib : `lsst.ip.isr.CalibType`
347 Constructed calibration.
352 Raised if the supplied dictionary
is for a different
357 if calib._OBSTYPE != dictionary[
'metadata'][
'OBSTYPE']:
358 raise RuntimeError(f
"Incorrect crosstalk supplied. Expected {calib._OBSTYPE}, "
359 f
"found {dictionary['metadata']['OBSTYPE']}")
361 calib.setMetadata(dictionary[
'metadata'])
362 calib.calibInfoFromDict(dictionary)
364 xCol = dictionary[
'x0']
365 yCol = dictionary[
'y0']
366 widthCol = dictionary[
'width']
367 heightCol = dictionary[
'height']
369 with calib.bulk_update:
370 for x0, y0, width, height
in zip(xCol, yCol, widthCol, heightCol):
376 """Return a dictionary containing the calibration properties.
378 The dictionary should be able to be round-tripped through
384 Dictionary of properties.
390 outDict['metadata'] = metadata
400 box = defect.getBBox()
401 xCol.append(box.getBeginX())
402 yCol.append(box.getBeginY())
403 widthCol.append(box.getWidth())
404 heightCol.append(box.getHeight())
408 outDict[
'width'] = widthCol
409 outDict[
'height'] = heightCol
414 """Convert defects to a simple table form that we use to write
420 Defects in simple tabular form.
424 These defect tables are used
as the human readable definitions
425 of defects
in calibration data definition repositories. The format
426 is to use four columns defined
as follows:
429 X coordinate of bottom left corner of box.
431 Y coordinate of bottom left corner of box.
448 box = defect.getBBox()
449 xCol.append(box.getBeginX())
450 yCol.append(box.getBeginY())
451 widthCol.append(box.getWidth())
452 heightCol.append(box.getHeight())
454 catalog = astropy.table.Table({
'x0': xCol,
'y0': yCol,
'width': widthCol,
'height': heightCol})
456 outMeta = {k: v
for k, v
in inMeta.items()
if v
is not None}
457 catalog.meta = outMeta
458 tableList.append(catalog)
464 """Retrieve N values from the supplied values.
468 values : `numbers.Number` or `list`
or `np.array`
471 Number of values to retrieve.
475 vals : `list`
or `np.array`
or `numbers.Number`
476 Single value
from supplied list
if ``n``
is 1,
or `list`
477 containing first ``n`` values
from supplied values.
481 Some supplied tables have vectors
in some columns that can also
482 be scalars. This method can be used to get the first number
as
483 a scalar
or the first N items
from a vector
as a vector.
486 if isinstance(values, numbers.Number):
495 """Construct a `Defects` from the contents of a
501 Table with one row per defect.
502 normalize_on_init : `bool`, optional
503 If `
True`, normalization
is applied to the defects listed
in the
504 table to remove duplicates, eliminate overlaps, etc. Otherwise
505 the defects
in the returned object exactly match those
in the
515 Two table formats are recognized. The first
is the
516 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
517 definition tabular format written by `toFitsRegionTable` where the
518 pixel origin
is corrected
from FITS 1-based to a 0-based origin.
519 The second
is the legacy defects format using columns ``x0``, ``y0``
520 (bottom left hand pixel of box
in 0-based coordinates), ``width``
523 The FITS standard regions can only read BOX, POINT,
or ROTBOX
with
524 a zero degree rotation.
529 schema = table.columns
531 if "X" in schema
and "Y" in schema
and "R" in schema
and "SHAPE" in schema:
534 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
538 raise ValueError(
"Unsupported schema for defects extraction")
547 shape = record[
'SHAPE'].upper().rstrip()
552 elif shape ==
"POINT":
556 elif shape ==
"ROTBOX":
560 if math.isclose(rotang % 90.0, 0.0):
563 if math.isclose(rotang % 180.0, 0.0):
572 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
575 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
583 defectList.append(box)
585 defects = cls(defectList, normalize_on_init=normalize_on_init)
586 newMeta = dict(table.meta)
587 defects.updateMetadata(setCalibInfo=
True, **newMeta)
593 """Read defects information from a legacy LSST format text file.
598 Name of text file containing the defect information.
600 normalize_on_init : `bool`, optional
601 If `True`, normalization
is applied to the defects listed
in the
602 table to remove duplicates, eliminate overlaps, etc. Otherwise
603 the defects
in the returned object exactly match those
in the
613 These defect text files are used
as the human readable definitions
614 of defects
in calibration data definition repositories. The format
615 is to use four columns defined
as follows:
618 X coordinate of bottom left corner of box.
620 Y coordinate of bottom left corner of box.
626 Files of this format were used historically to represent defects
627 in simple text form. Use `Defects.readText`
and `Defects.writeText`
628 to use the more modern format.
632 defect_array = np.loadtxt(filename,
633 dtype=[(
"x0",
"int"), (
"y0",
"int"),
634 (
"x_extent",
"int"), (
"y_extent",
"int")])
638 for row
in defect_array)
640 return cls(defects, normalize_on_init=normalize_on_init)
644 """Compute a defect list from a footprint list, optionally growing
650 Footprint list to process.
660 for fp
in fpList), normalize_on_init=
False)
664 """Compute a defect list from a specified mask plane.
670 maskName : `str`
or `list`
671 Mask plane name,
or list of names to convert.
676 Defect list constructed
from masked pixels.
678 if hasattr(mask,
"getMask"):
679 mask = mask.getMask()
681 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.
Represent a 2-dimensional array of bitmask pixels.
A class to manipulate images, masks, and variance as a single object.
Class for storing ordered metadata with comments.
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.
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)
fromDict(cls, dictionary)
_check_value(self, value)
__setitem__(self, index, value)
maskPixels(self, mask, maskName="BAD")
fromMask(cls, mask, maskName)
readLsstDefectsFile(cls, filename, normalize_on_init=False)
fromFootprintList(cls, fpList)
__init__(self, defectList=None, metadata=None, *normalize_on_init=True, **kwargs)
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...