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:
472 elif "x0" in schema
and "y0" in schema
and "width" in schema
and "height" in schema:
477 raise ValueError(
"Unsupported schema for defects extraction")
480 record = r.extract(
"*")
488 shape = record[
"SHAPE"].upper()
493 elif shape ==
"POINT":
497 elif shape ==
"ROTBOX":
501 if math.isclose(rotang % 90.0, 0.0):
504 if math.isclose(rotang % 180.0, 0.0):
513 log.warning(
"Defect can not be defined using ROTBOX with non-aligned rotation angle")
516 log.warning(
"Defect lists can only be defined using BOX or POINT not %s", shape)
519 elif "x0" in record
and "y0" in record
and "width" in record
and "height" in record:
524 defectList.append(box)
526 defects =
cls(defectList)
527 defects.setMetadata(table.getMetadata())
530 metadata = defects.getMetadata()
531 for k
in (SCHEMA_NAME_KEY, SCHEMA_VERSION_KEY):
539 """Read defect list from FITS table. 544 Arguments to be forwarded to 545 `lsst.afw.table.BaseCatalog.writeFits`. 550 Defects read from a FITS table. 552 table = lsst.afw.table.BaseCatalog.readFits(*args)
557 """Read defect list from standard format text table file. 562 Name of the file containing the defects definitions. 567 Defects read from a FITS table. 569 table = astropy.table.Table.read(filename)
573 for colName
in table.columns:
574 schema.addField(colName, units=str(table[colName].unit),
575 type=table[colName].dtype.type)
580 afwTable.resize(len(table))
581 for colName
in table.columns:
583 afwTable[colName] = table[colName]
587 for k, v
in table.meta.items():
589 afwTable.setMetadata(metadata)
596 """Read defects information from a legacy LSST format text file. 601 Name of text file containing the defect information. 610 These defect text files are used as the human readable definitions 611 of defects in calibration data definition repositories. The format 612 is to use four columns defined as follows: 615 X coordinate of bottom left corner of box. 617 Y coordinate of bottom left corner of box. 623 Files of this format were used historically to represent defects 624 in simple text form. Use `Defects.readText` and `Defects.writeText` 625 to use the more modern format. 629 defect_array = np.loadtxt(filename,
630 dtype=[(
"x0",
"int"), (
"y0",
"int"),
631 (
"x_extent",
"int"), (
"y_extent",
"int")])
635 for row
in defect_array)
639 """Compute a defect list from a footprint list, optionally growing 644 fpList : `list` of `lsst.afw.detection.Footprint` 645 Footprint list to process. 657 """Compute a defect list from a specified mask plane. 661 maskedImage : `lsst.afw.image.MaskedImage` 663 maskName : `str` or `list` 664 Mask plane name, or list of names to convert. 669 Defect list constructed from masked pixels. 671 mask = maskedImage.getMask()
673 lsst.afw.detection.Threshold.BITMASK)
def fromFootprintList(cls, fpList)
Defines the fields and offsets for a table.
def readText(cls, filename)
Encapsulate information about a bad portion of a detector.
def maskPixels(self, maskedImage, maskName="BAD")
Class for storing ordered metadata with comments.
A compact representation of a collection of pixels.
def toFitsRegionTable(self)
A Threshold is used to pass a threshold value to detection algorithms.
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)
Return a list of BBoxs, whose union contains exactly the pixels in the footprint, neither more nor le...
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)
Create a box centered as closely as possible on a particular point.
def _get_values(values, n=1)
def writeText(self, filename)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
An integer coordinate rectangle.
def fromMask(cls, maskedImage, maskName)