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. 258 x = schema.addField(
"X", type=
"D", units=
"pix", doc=
"X coordinate of center of shape")
259 y = schema.addField(
"Y", type=
"D", units=
"pix", doc=
"Y coordinate of center of shape")
260 shape = schema.addField(
"SHAPE", type=
"String", size=16, doc=
"Shape defined by these values")
261 r = schema.addField(
"R", type="ArrayD", size=2, units="pix", doc="Extents")
262 rotang = schema.addField(
"ROTANG", type=
"D", units=
"deg", doc=
"Rotation angle")
263 component = schema.addField(
"COMPONENT", type=
"I", doc=
"Index of this region")
274 for i, defect
in enumerate(self.
_defects):
275 box = defect.getBBox()
276 center = box.getCenter()
278 xCol.append(center.getX() + 1.0)
279 yCol.append(center.getY() + 1.0)
284 if width == 1
and height == 1:
291 table[i][shape] = shapeType
293 rCol.append(np.array([width, height], dtype=np.float64))
296 table[x] = np.array(xCol, dtype=np.float64)
297 table[y] = np.array(yCol, dtype=np.float64)
299 table[r] = np.array(rCol)
300 table[rotang] = np.zeros(nrows, dtype=np.float64)
301 table[component] = np.arange(nrows)
306 metadata[SCHEMA_NAME_KEY] =
"FITS Region" 307 metadata[SCHEMA_VERSION_KEY] = 1
308 table.setMetadata(metadata)
313 """Write defect list to FITS. 318 Arguments to be forwarded to 319 `lsst.afw.table.BaseCatalog.writeFits`. 324 metadata = table.getMetadata()
325 now = datetime.datetime.utcnow()
326 metadata[
"DATE"] = now.isoformat()
327 metadata[
"CALIB_CREATION_DATE"] = now.strftime(
"%Y-%m-%d")
328 metadata[
"CALIB_CREATION_TIME"] = now.strftime(
"%T %Z").strip()
330 table.writeFits(*args)
333 """Convert defects to a simple table form that we use to write 338 table : `lsst.afw.table.BaseCatalog` 339 Defects in simple tabular form. 343 These defect tables are used as the human readable definitions 344 of defects in calibration data definition repositories. The format 345 is to use four columns defined as follows: 348 X coordinate of bottom left corner of box. 350 Y coordinate of bottom left corner of box. 357 x = schema.addField(
"x0", type=
"I", units=
"pix",
358 doc=
"X coordinate of bottom left corner of box")
359 y = schema.addField(
"y0", type=
"I", units=
"pix",
360 doc=
"Y coordinate of bottom left corner of box")
361 width = schema.addField(
"width", type=
"I", units=
"pix",
362 doc=
"X extent of box")
363 height = schema.addField(
"height", type=
"I", units=
"pix",
364 doc=
"Y extent of box")
378 box = defect.getBBox()
379 xCol.append(box.getBeginX())
380 yCol.append(box.getBeginY())
381 widthCol.append(box.getWidth())
382 heightCol.append(box.getHeight())
384 table[x] = np.array(xCol, dtype=np.int64)
385 table[y] = np.array(yCol, dtype=np.int64)
386 table[width] = np.array(widthCol, dtype=np.int64)
387 table[height] = np.array(heightCol, dtype=np.int64)
392 metadata[SCHEMA_NAME_KEY] =
"Simple" 393 metadata[SCHEMA_VERSION_KEY] = 1
394 table.setMetadata(metadata)
399 """Write the defects out to a text file with the specified name. 404 Name of the file to write. The file extension ".ecsv" will 410 The name of the file used to write the data (which may be 411 different from the supplied name given the change to file 416 The file is written to ECSV format and will include any metadata 417 associated with the `Defects`. 422 table = afwTable.asAstropy()
424 metadata = afwTable.getMetadata()
425 now = datetime.datetime.utcnow()
426 metadata[
"DATE"] = now.isoformat()
427 metadata[
"CALIB_CREATION_DATE"] = now.strftime(
"%Y-%m-%d")
428 metadata[
"CALIB_CREATION_TIME"] = now.strftime(
"%T %Z").strip()
430 table.meta = metadata.toDict()
433 path, ext = os.path.splitext(filename)
434 filename = path +
".ecsv" 435 table.write(filename, format=
"ascii.ecsv")
439 def _get_values(values, n=1):
440 """Retrieve N values from the supplied values. 444 values : `numbers.Number` or `list` or `np.array` 447 Number of values to retrieve. 451 vals : `list` or `np.array` or `numbers.Number` 452 Single value from supplied list if ``n`` is 1, or `list` 453 containing first ``n`` values from supplied values. 457 Some supplied tables have vectors in some columns that can also 458 be scalars. This method can be used to get the first number as 459 a scalar or the first N items from a vector as a vector. 462 if isinstance(values, numbers.Number):
471 """Construct a `Defects` from the contents of a 472 `~lsst.afw.table.BaseCatalog`. 476 table : `lsst.afw.table.BaseCatalog` 477 Table with one row per defect. 486 Two table formats are recognized. The first is the 487 `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_ 488 definition tabular format written by `toFitsRegionTable` where the 489 pixel origin is corrected from FITS 1-based to a 0-based origin. 490 The second is the legacy defects format using columns ``x0``, ``y0`` 491 (bottom left hand pixel of box in 0-based coordinates), ``width`` 494 The FITS standard regions can only read BOX, POINT, or ROTBOX with 495 a zero degree rotation. 500 schema = table.getSchema()
503 if "X" in schema
and "Y" in schema
and "R" in schema and "SHAPE" in schema:
508 xKey = schema[
"X"].asKey()
509 yKey = schema[
"Y"].asKey()
510 shapeKey = schema[
"SHAPE"].asKey()
511 rKey = schema[
"R"].asKey() 512 rotangKey = schema["ROTANG"].asKey()
514 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
519 xKey = schema[
"x0"].asKey()
520 yKey = schema[
"y0"].asKey()
521 widthKey = schema[
"width"].asKey()
522 heightKey = schema[
"height"].asKey()
525 raise ValueError(
"Unsupported schema for defects extraction")
535 shape = record[shapeKey].upper()
540 elif shape ==
"POINT":
544 elif shape ==
"ROTBOX":
548 if math.isclose(rotang % 90.0, 0.0):
551 if math.isclose(rotang % 180.0, 0.0):
560 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
563 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
571 defectList.append(box)
573 defects = cls(defectList)
574 defects.setMetadata(table.getMetadata())
577 metadata = defects.getMetadata()
578 for k
in (SCHEMA_NAME_KEY, SCHEMA_VERSION_KEY):
586 """Read defect list from FITS table. 591 Arguments to be forwarded to 592 `lsst.afw.table.BaseCatalog.writeFits`. 597 Defects read from a FITS table. 599 table = lsst.afw.table.BaseCatalog.readFits(*args)
604 """Read defect list from standard format text table file. 609 Name of the file containing the defects definitions. 614 Defects read from a FITS table. 616 table = astropy.table.Table.read(filename)
620 for colName
in table.columns:
621 schema.addField(colName, units=str(table[colName].unit),
622 type=table[colName].dtype.type)
627 afwTable.resize(len(table))
628 for colName
in table.columns:
630 afwTable[colName] = table[colName]
634 for k, v
in table.meta.items():
636 afwTable.setMetadata(metadata)
643 """Read defects information from a legacy LSST format text file. 648 Name of text file containing the defect information. 657 These defect text files are used as the human readable definitions 658 of defects in calibration data definition repositories. The format 659 is to use four columns defined as follows: 662 X coordinate of bottom left corner of box. 664 Y coordinate of bottom left corner of box. 670 Files of this format were used historically to represent defects 671 in simple text form. Use `Defects.readText` and `Defects.writeText` 672 to use the more modern format. 676 defect_array = np.loadtxt(filename,
677 dtype=[(
"x0",
"int"), (
"y0",
"int"),
678 (
"x_extent",
"int"), (
"y_extent",
"int")])
682 for row
in defect_array)
686 """Compute a defect list from a footprint list, optionally growing 691 fpList : `list` of `lsst.afw.detection.Footprint` 692 Footprint list to process. 704 """Compute a defect list from a specified mask plane. 708 maskedImage : `lsst.afw.image.MaskedImage` 710 maskName : `str` or `list` 711 Mask plane name, or list of names to convert. 716 Defect list constructed from masked pixels. 718 mask = maskedImage.getMask()
720 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)