23 """Support for image defects""" 25 __all__ = (
"Defects",)
29 import collections.abc
47 log = logging.getLogger(__name__)
49 SCHEMA_NAME_KEY =
"DEFECTS_SCHEMA" 50 SCHEMA_VERSION_KEY =
"DEFECTS_SCHEMA_VERSION" 53 class Defects(collections.abc.MutableSequence):
54 """Collection of `lsst.meas.algorithms.Defect`. 58 defectList : iterable of `lsst.meas.algorithms.Defect` 59 or `lsst.geom.BoxI`, optional 60 Collections of defects to apply to the image. 64 """The calibration type used for ingest.""" 66 def __init__(self, defectList=None, metadata=None):
69 if metadata
is not None:
74 if defectList
is None:
81 def _check_value(self, value):
82 """Check that the supplied value is a `~lsst.meas.algorithms.Defect` 83 or can be converted to one. 92 new : `~lsst.meas.algorithms.Defect` 93 Either the supplied value or a new object derived from it. 98 Raised if the supplied value can not be converted to 99 `~lsst.meas.algorithms.Defect` 101 if isinstance(value, Defect):
104 value = Defect(value)
108 value = Defect(value.getBBox())
110 raise ValueError(f
"Defects must be of type Defect, BoxI, or PointI, not '{value!r}'")
120 """Can be given a `~lsst.meas.algorithms.Defect` or a `lsst.geom.BoxI` 131 """Compare if two `Defects` are equal. 133 Two `Defects` are equal if their bounding boxes are equal and in 134 the same order. Metadata content is ignored. 136 if not isinstance(other, self.__class__):
140 if len(self) != len(other):
144 for d1, d2
in zip(self, other):
145 if d1.getBBox() != d2.getBBox():
151 return "Defects(" +
",".join(str(d.getBBox())
for d
in self) +
")" 157 """Retrieve metadata associated with these `Defects`. 161 meta : `lsst.daf.base.PropertyList` 162 Metadata. The returned `~lsst.daf.base.PropertyList` can be 163 modified by the caller and the changes will be written to 169 """Store a copy of the supplied metadata with the defects. 173 metadata : `lsst.daf.base.PropertyList`, optional 174 Metadata to associate with the defects. Will be copied and 175 overwrite existing metadata. If not supplied the existing 176 metadata will be reset. 187 """Copy the defects to a new list, creating new defects from the 193 New list with new `Defect` entries. 197 This is not a shallow copy in that new `Defect` instances are 198 created from the original bounding boxes. It's also not a deep 199 copy since the bounding boxes are not recreated. 201 return self.__class__(d.getBBox()
for d
in self)
204 """Make a transposed copy of this defect list. 208 retDefectList : `Defects` 209 Transposed list of defects. 211 retDefectList = self.__class__()
213 bbox = defect.getBBox()
214 dimensions = bbox.getDimensions()
217 retDefectList.append(nbbox)
221 """Set mask plane based on these defects. 225 maskedImage : `lsst.afw.image.MaskedImage` 226 Image to process. Only the mask plane is updated. 227 maskName : str, optional 228 Mask plane name to use. 231 mask = maskedImage.getMask()
232 bitmask = mask.getPlaneBitMask(maskName)
234 bbox = defect.getBBox()
238 """Convert defect list to `~lsst.afw.table.BaseCatalog` using the 239 FITS region standard. 243 table : `lsst.afw.table.BaseCatalog` 244 Defects in tabular form. 248 The table created uses the 249 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_ 250 definition tabular format. The ``X`` and ``Y`` coordinates are 251 converted to FITS Physical coordinates that have origin pixel (1, 1) 252 rather than the (0, 0) used in LSST software. 255 x = schema.addField(
"X", type=
"D", units=
"pix", doc=
"X coordinate of center of shape")
256 y = schema.addField(
"Y", type=
"D", units=
"pix", doc=
"Y coordinate of center of shape")
257 shape = schema.addField(
"SHAPE", type=
"String", size=16, doc=
"Shape defined by these values")
258 r = schema.addField(
"R", type="ArrayD", size=2, units="pix", doc="Extents")
259 rotang = schema.addField(
"ROTANG", type=
"D", units=
"deg", doc=
"Rotation angle")
260 component = schema.addField(
"COMPONENT", type=
"I", doc=
"Index of this region")
264 for i, defect
in enumerate(self.
_defects):
265 box = defect.getBBox()
267 table[i][x] = box.getCenterX() + 1.0
268 table[i][y] = box.getCenterY() + 1.0
269 width = box.getWidth()
270 height = box.getHeight()
272 if width == 1
and height == 1:
277 table[i][shape] = shapeType
278 table[i][r] = np.array([width, height], dtype=np.float64)
279 table[i][rotang] = 0.0
280 table[i][component] = i
285 metadata[SCHEMA_NAME_KEY] =
"FITS Region" 286 metadata[SCHEMA_VERSION_KEY] = 1
287 table.setMetadata(metadata)
292 """Write defect list to FITS. 297 Arguments to be forwarded to 298 `lsst.afw.table.BaseCatalog.writeFits`. 303 metadata = table.getMetadata()
304 now = datetime.datetime.utcnow()
305 metadata[
"DATE"] = now.isoformat()
306 metadata[
"CALIB_CREATION_DATE"] = now.strftime(
"%Y-%m-%d")
307 metadata[
"CALIB_CREATION_TIME"] = now.strftime(
"%T %Z").strip()
309 table.writeFits(*args)
312 """Convert defects to a simple table form that we use to write 317 table : `lsst.afw.table.BaseCatalog` 318 Defects in simple tabular form. 322 These defect tables are used as the human readable definitions 323 of defects in calibration data definition repositories. The format 324 is to use four columns defined as follows: 327 X coordinate of bottom left corner of box. 329 Y coordinate of bottom left corner of box. 336 x = schema.addField(
"x0", type=
"I", units=
"pix",
337 doc=
"X coordinate of bottom left corner of box")
338 y = schema.addField(
"y0", type=
"I", units=
"pix",
339 doc=
"Y coordinate of bottom left corner of box")
340 width = schema.addField(
"width", type=
"I", units=
"pix",
341 doc=
"X extent of box")
342 height = schema.addField(
"height", type=
"I", units=
"pix",
343 doc=
"Y extent of box")
347 for i, defect
in enumerate(self.
_defects):
348 box = defect.getBBox()
349 table[i][x] = box.getBeginX()
350 table[i][y] = box.getBeginY()
351 table[i][width] = box.getWidth()
352 table[i][height] = box.getHeight()
357 metadata[SCHEMA_NAME_KEY] =
"Simple" 358 metadata[SCHEMA_VERSION_KEY] = 1
359 table.setMetadata(metadata)
364 """Write the defects out to a text file with the specified name. 369 Name of the file to write. The file extension ".ecsv" will 375 The name of the file used to write the data (which may be 376 different from the supplied name given the change to file 381 The file is written to ECSV format and will include any metadata 382 associated with the `Defects`. 387 table = afwTable.asAstropy()
389 metadata = afwTable.getMetadata()
390 now = datetime.datetime.utcnow()
391 metadata[
"DATE"] = now.isoformat()
392 metadata[
"CALIB_CREATION_DATE"] = now.strftime(
"%Y-%m-%d")
393 metadata[
"CALIB_CREATION_TIME"] = now.strftime(
"%T %Z").strip()
395 table.meta = metadata.toDict()
398 path, ext = os.path.splitext(filename)
399 filename = path +
".ecsv" 400 table.write(filename, format=
"ascii.ecsv")
404 def _get_values(values, n=1):
405 """Retrieve N values from the supplied values. 409 values : `numbers.Number` or `list` or `np.array` 412 Number of values to retrieve. 416 vals : `list` or `np.array` or `numbers.Number` 417 Single value from supplied list if ``n`` is 1, or `list` 418 containing first ``n`` values from supplied values. 422 Some supplied tables have vectors in some columns that can also 423 be scalars. This method can be used to get the first number as 424 a scalar or the first N items from a vector as a vector. 427 if isinstance(values, numbers.Number):
436 """Construct a `Defects` from the contents of a 437 `~lsst.afw.table.BaseCatalog`. 441 table : `lsst.afw.table.BaseCatalog` 442 Table with one row per defect. 451 Two table formats are recognized. The first is the 452 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_ 453 definition tabular format written by `toFitsRegionTable` where the 454 pixel origin is corrected from FITS 1-based to a 0-based origin. 455 The second is the legacy defects format using columns ``x0``, ``y0`` 456 (bottom left hand pixel of box in 0-based coordinates), ``width`` 459 The FITS standard regions can only read BOX, POINT, or ROTBOX with 460 a zero degree rotation. 465 schema = table.getSchema()
468 if "X" in schema
and "Y" in schema
and "R" in schema and "SHAPE" in schema:
473 xKey = schema[
"X"].asKey()
474 yKey = schema[
"Y"].asKey()
475 shapeKey = schema[
"SHAPE"].asKey()
476 rKey = schema[
"R"].asKey() 477 rotangKey = schema["ROTANG"].asKey()
479 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
484 xKey = schema[
"x0"].asKey()
485 yKey = schema[
"y0"].asKey()
486 widthKey = schema[
"width"].asKey()
487 heightKey = schema[
"height"].asKey()
490 raise ValueError(
"Unsupported schema for defects extraction")
500 shape = record[shapeKey].upper()
505 elif shape ==
"POINT":
509 elif shape ==
"ROTBOX":
513 if math.isclose(rotang % 90.0, 0.0):
516 if math.isclose(rotang % 180.0, 0.0):
525 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
528 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
536 defectList.append(box)
538 defects = cls(defectList)
539 defects.setMetadata(table.getMetadata())
542 metadata = defects.getMetadata()
543 for k
in (SCHEMA_NAME_KEY, SCHEMA_VERSION_KEY):
551 """Read defect list from FITS table. 556 Arguments to be forwarded to 557 `lsst.afw.table.BaseCatalog.writeFits`. 562 Defects read from a FITS table. 564 table = lsst.afw.table.BaseCatalog.readFits(*args)
569 """Read defect list from standard format text table file. 574 Name of the file containing the defects definitions. 579 Defects read from a FITS table. 581 table = astropy.table.Table.read(filename)
585 for colName
in table.columns:
586 schema.addField(colName, units=str(table[colName].unit),
587 type=table[colName].dtype.type)
592 afwTable.resize(len(table))
593 for colName
in table.columns:
595 afwTable[colName] = table[colName]
599 for k, v
in table.meta.items():
601 afwTable.setMetadata(metadata)
608 """Read defects information from a legacy LSST format text file. 613 Name of text file containing the defect information. 622 These defect text files are used as the human readable definitions 623 of defects in calibration data definition repositories. The format 624 is to use four columns defined as follows: 627 X coordinate of bottom left corner of box. 629 Y coordinate of bottom left corner of box. 635 Files of this format were used historically to represent defects 636 in simple text form. Use `Defects.readText` and `Defects.writeText` 637 to use the more modern format. 641 defect_array = np.loadtxt(filename,
642 dtype=[(
"x0",
"int"), (
"y0",
"int"),
643 (
"x_extent",
"int"), (
"y_extent",
"int")])
647 for row
in defect_array)
651 """Compute a defect list from a footprint list, optionally growing 656 fpList : `list` of `lsst.afw.detection.Footprint` 657 Footprint list to process. 669 """Compute a defect list from a specified mask plane. 673 maskedImage : `lsst.afw.image.MaskedImage` 675 maskName : `str` or `list` 676 Mask plane name, or list of names to convert. 681 Defect list constructed from masked pixels. 683 mask = maskedImage.getMask()
685 lsst.afw.detection.Threshold.BITMASK)
def fromFootprintList(cls, fpList)
def readText(cls, filename)
def maskPixels(self, maskedImage, maskName="BAD")
def toFitsRegionTable(self)
def setMetadata(self, metadata=None)
def _check_value(self, value)
def __setitem__(self, index, value)
def __init__(self, defectList=None, metadata=None)
def readLsstDefectsFile(cls, filename)
def __getitem__(self, index)
std::vector< lsst::geom::Box2I > footprintToBBoxList(Footprint const &footprint)
def writeFits(self, args)
def __delitem__(self, index)
def fromTable(cls, table)
def insert(self, index, value)
static Box2I makeCenteredBox(Point2D const ¢er, Extent const &size)
def _get_values(values, n=1)
def writeText(self, filename)
def fromMask(cls, maskedImage, maskName)