23 """Support for image defects"""
25 __all__ = (
"Defects",)
29 import collections.abc
48 log = logging.getLogger(__name__)
50 SCHEMA_NAME_KEY =
"DEFECTS_SCHEMA"
51 SCHEMA_VERSION_KEY =
"DEFECTS_SCHEMA_VERSION"
54 class Defects(collections.abc.MutableSequence):
55 """Collection of `lsst.meas.algorithms.Defect`.
59 defectList : iterable of `lsst.meas.algorithms.Defect`
60 or `lsst.geom.BoxI`, optional
61 Collections of defects to apply to the image.
65 """The calibration type used for ingest."""
67 def __init__(self, defectList=None, metadata=None):
70 if metadata
is not None:
75 if defectList
is None:
82 def _check_value(self, value):
83 """Check that the supplied value is a `~lsst.meas.algorithms.Defect`
84 or can be converted to one.
93 new : `~lsst.meas.algorithms.Defect`
94 Either the supplied value or a new object derived from it.
99 Raised if the supplied value can not be converted to
100 `~lsst.meas.algorithms.Defect`
102 if isinstance(value, Defect):
105 value = Defect(value)
109 value = Defect(value.getBBox())
111 raise ValueError(f
"Defects must be of type Defect, BoxI, or PointI, not '{value!r}'")
121 """Can be given a `~lsst.meas.algorithms.Defect` or a `lsst.geom.BoxI`
132 """Compare if two `Defects` are equal.
134 Two `Defects` are equal if their bounding boxes are equal and in
135 the same order. Metadata content is ignored.
137 if not isinstance(other, self.__class__):
141 if len(self) != len(other):
145 for d1, d2
in zip(self, other):
146 if d1.getBBox() != d2.getBBox():
152 return "Defects(" +
",".join(str(d.getBBox())
for d
in self) +
")"
158 """Retrieve metadata associated with these `Defects`.
162 meta : `lsst.daf.base.PropertyList`
163 Metadata. The returned `~lsst.daf.base.PropertyList` can be
164 modified by the caller and the changes will be written to
170 """Store a copy of the supplied metadata with the defects.
174 metadata : `lsst.daf.base.PropertyList`, optional
175 Metadata to associate with the defects. Will be copied and
176 overwrite existing metadata. If not supplied the existing
177 metadata will be reset.
188 """Copy the defects to a new list, creating new defects from the
194 New list with new `Defect` entries.
198 This is not a shallow copy in that new `Defect` instances are
199 created from the original bounding boxes. It's also not a deep
200 copy since the bounding boxes are not recreated.
202 return self.__class__(d.getBBox()
for d
in self)
205 """Make a transposed copy of this defect list.
209 retDefectList : `Defects`
210 Transposed list of defects.
212 retDefectList = self.__class__()
214 bbox = defect.getBBox()
215 dimensions = bbox.getDimensions()
218 retDefectList.append(nbbox)
222 """Set mask plane based on these defects.
226 maskedImage : `lsst.afw.image.MaskedImage`
227 Image to process. Only the mask plane is updated.
228 maskName : str, optional
229 Mask plane name to use.
232 mask = maskedImage.getMask()
233 bitmask = mask.getPlaneBitMask(maskName)
235 bbox = defect.getBBox()
239 """Convert defect list to `~lsst.afw.table.BaseCatalog` using the
240 FITS region standard.
244 table : `lsst.afw.table.BaseCatalog`
245 Defects in tabular form.
249 The table created uses the
250 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
251 definition tabular format. The ``X`` and ``Y`` coordinates are
252 converted to FITS Physical coordinates that have origin pixel (1, 1)
253 rather than the (0, 0) used in LSST software.
259 x = schema.addField(
"X", type=
"D", units=
"pix", doc=
"X coordinate of center of shape")
260 y = schema.addField(
"Y", type=
"D", units=
"pix", doc=
"Y coordinate of center of shape")
261 shape = schema.addField(
"SHAPE", type=
"String", size=16, doc=
"Shape defined by these values")
262 r = schema.addField(
"R", type=
"ArrayD", size=2, units=
"pix", doc=
"Extents")
263 rotang = schema.addField(
"ROTANG", type=
"D", units=
"deg", doc=
"Rotation angle")
264 component = schema.addField(
"COMPONENT", type=
"I", doc=
"Index of this region")
275 for i, defect
in enumerate(self.
_defects):
276 box = defect.getBBox()
277 center = box.getCenter()
279 xCol.append(center.getX() + 1.0)
280 yCol.append(center.getY() + 1.0)
285 if width == 1
and height == 1:
292 table[i][shape] = shapeType
294 rCol.append(np.array([width, height], dtype=np.float64))
297 table[x] = np.array(xCol, dtype=np.float64)
298 table[y] = np.array(yCol, dtype=np.float64)
300 table[r] = np.array(rCol)
301 table[rotang] = np.zeros(nrows, dtype=np.float64)
302 table[component] = np.arange(nrows)
307 metadata[SCHEMA_NAME_KEY] =
"FITS Region"
308 metadata[SCHEMA_VERSION_KEY] = 1
309 table.setMetadata(metadata)
314 """Write defect list to FITS.
319 Arguments to be forwarded to
320 `lsst.afw.table.BaseCatalog.writeFits`.
325 metadata = table.getMetadata()
326 now = datetime.datetime.utcnow()
327 metadata[
"DATE"] = now.isoformat()
328 metadata[
"CALIB_CREATION_DATE"] = now.strftime(
"%Y-%m-%d")
329 metadata[
"CALIB_CREATION_TIME"] = now.strftime(
"%T %Z").strip()
331 table.writeFits(*args)
334 """Convert defects to a simple table form that we use to write
339 table : `lsst.afw.table.BaseCatalog`
340 Defects in simple tabular form.
344 These defect tables are used as the human readable definitions
345 of defects in calibration data definition repositories. The format
346 is to use four columns defined as follows:
349 X coordinate of bottom left corner of box.
351 Y coordinate of bottom left corner of box.
358 x = schema.addField(
"x0", type=
"I", units=
"pix",
359 doc=
"X coordinate of bottom left corner of box")
360 y = schema.addField(
"y0", type=
"I", units=
"pix",
361 doc=
"Y coordinate of bottom left corner of box")
362 width = schema.addField(
"width", type=
"I", units=
"pix",
363 doc=
"X extent of box")
364 height = schema.addField(
"height", type=
"I", units=
"pix",
365 doc=
"Y extent of box")
379 box = defect.getBBox()
380 xCol.append(box.getBeginX())
381 yCol.append(box.getBeginY())
382 widthCol.append(box.getWidth())
383 heightCol.append(box.getHeight())
385 table[x] = np.array(xCol, dtype=np.int64)
386 table[y] = np.array(yCol, dtype=np.int64)
387 table[width] = np.array(widthCol, dtype=np.int64)
388 table[height] = np.array(heightCol, dtype=np.int64)
393 metadata[SCHEMA_NAME_KEY] =
"Simple"
394 metadata[SCHEMA_VERSION_KEY] = 1
395 table.setMetadata(metadata)
400 """Write the defects out to a text file with the specified name.
405 Name of the file to write. The file extension ".ecsv" will
411 The name of the file used to write the data (which may be
412 different from the supplied name given the change to file
417 The file is written to ECSV format and will include any metadata
418 associated with the `Defects`.
423 table = afwTable.asAstropy()
425 metadata = afwTable.getMetadata()
426 now = datetime.datetime.utcnow()
427 metadata[
"DATE"] = now.isoformat()
428 metadata[
"CALIB_CREATION_DATE"] = now.strftime(
"%Y-%m-%d")
429 metadata[
"CALIB_CREATION_TIME"] = now.strftime(
"%T %Z").strip()
431 table.meta = metadata.toDict()
434 path, ext = os.path.splitext(filename)
435 filename = path +
".ecsv"
436 table.write(filename, format=
"ascii.ecsv")
440 def _get_values(values, n=1):
441 """Retrieve N values from the supplied values.
445 values : `numbers.Number` or `list` or `np.array`
448 Number of values to retrieve.
452 vals : `list` or `np.array` or `numbers.Number`
453 Single value from supplied list if ``n`` is 1, or `list`
454 containing first ``n`` values from supplied values.
458 Some supplied tables have vectors in some columns that can also
459 be scalars. This method can be used to get the first number as
460 a scalar or the first N items from a vector as a vector.
463 if isinstance(values, numbers.Number):
472 """Construct a `Defects` from the contents of a
473 `~lsst.afw.table.BaseCatalog`.
477 table : `lsst.afw.table.BaseCatalog`
478 Table with one row per defect.
487 Two table formats are recognized. The first is the
488 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_
489 definition tabular format written by `toFitsRegionTable` where the
490 pixel origin is corrected from FITS 1-based to a 0-based origin.
491 The second is the legacy defects format using columns ``x0``, ``y0``
492 (bottom left hand pixel of box in 0-based coordinates), ``width``
495 The FITS standard regions can only read BOX, POINT, or ROTBOX with
496 a zero degree rotation.
501 schema = table.getSchema()
504 if "X" in schema
and "Y" in schema
and "R" in schema
and "SHAPE" in schema:
509 xKey = schema[
"X"].asKey()
510 yKey = schema[
"Y"].asKey()
511 shapeKey = schema[
"SHAPE"].asKey()
512 rKey = schema[
"R"].asKey()
513 rotangKey = schema[
"ROTANG"].asKey()
515 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
520 xKey = schema[
"x0"].asKey()
521 yKey = schema[
"y0"].asKey()
522 widthKey = schema[
"width"].asKey()
523 heightKey = schema[
"height"].asKey()
526 raise ValueError(
"Unsupported schema for defects extraction")
536 shape = record[shapeKey].upper()
541 elif shape ==
"POINT":
545 elif shape ==
"ROTBOX":
549 if math.isclose(rotang % 90.0, 0.0):
552 if math.isclose(rotang % 180.0, 0.0):
561 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
564 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
572 defectList.append(box)
574 defects = cls(defectList)
575 defects.setMetadata(table.getMetadata())
578 metadata = defects.getMetadata()
579 for k
in (SCHEMA_NAME_KEY, SCHEMA_VERSION_KEY):
587 """Read defect list from FITS table.
592 Arguments to be forwarded to
593 `lsst.afw.table.BaseCatalog.writeFits`.
598 Defects read from a FITS table.
600 table = lsst.afw.table.BaseCatalog.readFits(*args)
605 """Read defect list from standard format text table file.
610 Name of the file containing the defects definitions.
615 Defects read from a FITS table.
617 with warnings.catch_warnings():
621 warnings.filterwarnings(
"ignore", category=ResourceWarning, module=
"astropy.io.ascii")
622 table = astropy.table.Table.read(filename)
626 for colName
in table.columns:
627 schema.addField(colName, units=str(table[colName].unit),
628 type=table[colName].dtype.type)
633 afwTable.resize(len(table))
634 for colName
in table.columns:
636 afwTable[colName] = table[colName]
640 for k, v
in table.meta.items():
642 afwTable.setMetadata(metadata)
649 """Read defects information from a legacy LSST format text file.
654 Name of text file containing the defect information.
663 These defect text files are used as the human readable definitions
664 of defects in calibration data definition repositories. The format
665 is to use four columns defined as follows:
668 X coordinate of bottom left corner of box.
670 Y coordinate of bottom left corner of box.
676 Files of this format were used historically to represent defects
677 in simple text form. Use `Defects.readText` and `Defects.writeText`
678 to use the more modern format.
682 defect_array = np.loadtxt(filename,
683 dtype=[(
"x0",
"int"), (
"y0",
"int"),
684 (
"x_extent",
"int"), (
"y_extent",
"int")])
688 for row
in defect_array)
692 """Compute a defect list from a footprint list, optionally growing
697 fpList : `list` of `lsst.afw.detection.Footprint`
698 Footprint list to process.
710 """Compute a defect list from a specified mask plane.
714 maskedImage : `lsst.afw.image.MaskedImage`
716 maskName : `str` or `list`
717 Mask plane name, or list of names to convert.
722 Defect list constructed from masked pixels.
724 mask = maskedImage.getMask()
726 lsst.afw.detection.Threshold.BITMASK)