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:
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.
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.
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.fromMask(mi,
"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.
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.
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.
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):
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
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
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.
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
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")
546 shape = record[
'SHAPE'].upper().rstrip()
551 elif shape ==
"POINT":
555 elif shape ==
"ROTBOX":
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
649 Footprint list to process.
659 for fp
in fpList), normalize_on_init=
False)
663 """Compute a defect list from a specified mask plane.
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.
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.
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 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...