def _normalize(self): """Recalculate defect bounding boxes for efficiency. Notes ----- Ideally, this would generate the provably-minimal set of bounding boxes necessary to represent the defects. At present, however, that doesn't happen: see DM-24781. In the cases of substantial overlaps or duplication, though, this will produce a much reduced set. """ # In bulk-update mode, normalization is a no-op. if self._bulk_update: return # work out the minimum and maximum bounds from all defect regions. minX, minY, maxX, maxY = float('inf'), float('inf'), float('-inf'), float('-inf') for defect in self: bbox = defect.getBBox() minX = min(minX, bbox.getMinX()) minY = min(minY, bbox.getMinY()) maxX = max(maxX, bbox.getMaxX()) maxY = max(maxY, bbox.getMaxY()) region = geom.Box2I(geom.Point2I(minX, minY), geom.Point2I(maxX, maxY)) mi = afwImage.MaskedImageF(region) self.maskPixels(mi, maskName="BAD") self._defects = Defects.fromMask(mi, "BAD")._defects
def readLsstDefectsFile(cls, filename, normalize_on_init=False): """Read defects information from a legacy LSST format text file. Parameters ---------- filename : `str` Name of text file containing the defect information. normalize_on_init : `bool`, optional If `True`, normalization is applied to the defects listed in the table to remove duplicates, eliminate overlaps, etc. Otherwise the defects in the returned object exactly match those in the table. Returns ------- defects : `Defects` The defects. Notes ----- These defect text files are used as the human readable definitions of defects in calibration data definition repositories. The format is to use four columns defined as follows: x0 : `int` X coordinate of bottom left corner of box. y0 : `int` Y coordinate of bottom left corner of box. width : `int` X extent of the box. height : `int` Y extent of the box. Files of this format were used historically to represent defects in simple text form. Use `Defects.readText` and `Defects.writeText` to use the more modern format. """ # Use loadtxt so that ValueError is thrown if the file contains a # non-integer value. genfromtxt converts bad values to -1. defect_array = np.loadtxt(filename, dtype=[("x0", "int"), ("y0", "int"), ("x_extent", "int"), ("y_extent", "int")]) defects = (geom.Box2I(geom.Point2I(row["x0"], row["y0"]), geom.Extent2I(row["x_extent"], row["y_extent"])) for row in defect_array) return cls(defects, normalize_on_init=normalize_on_init)
def transpose(self): """Make a transposed copy of this defect list. Returns ------- retDefectList : `Defects` Transposed list of defects. """ retDefectList = self.__class__() for defect in self: bbox = defect.getBBox() dimensions = bbox.getDimensions() nbbox = geom.Box2I(geom.Point2I(bbox.getMinY(), bbox.getMinX()), geom.Extent2I(dimensions[1], dimensions[0])) retDefectList.append(nbbox) return retDefectList
def imageReadFitsWithOptions(cls, source, options): """Read an Image, Mask, MaskedImage or Exposure from a FITS file, with options. Parameters ---------- source : `str` Fits file path from which to read image, mask, masked image or exposure. options : `lsst.daf.base.PropertySet` Read options: - llcX: bbox minimum x (int) - llcY: bbox minimum y (int, must be present if llcX is present) - width: bbox width (int, must be present if llcX is present) - height: bbox height (int, must be present if llcX is present) - imageOrigin: one of "LOCAL" or "PARENT" (has no effect unless a bbox is specified by llcX, etc.) Raises ------ RuntimeError If options contains an unknown value for "imageOrigin" lsst.pex.exceptions.NotFoundError If options contains "llcX" and is missing any of "llcY", "width", or "height". """ bbox = geom.Box2I() if options.exists("llcX"): llcX = options.getInt("llcX") llcY = options.getInt("llcY") width = options.getInt("width") height = options.getInt("height") bbox = geom.Box2I(geom.Point2I(llcX, llcY), geom.Extent2I(width, height)) origin = image.PARENT if options.exists("imageOrigin"): originStr = options.getString("imageOrigin") if (originStr == "LOCAL"): origin = image.LOCAL elif (originStr == "PARENT"): origin = image.PARENT else: raise RuntimeError("Unknown ImageOrigin type {}".format(originStr)) return cls(source, bbox=bbox, origin=origin)
def fromDict(cls, dictionary): """Construct a calibration from a dictionary of properties. Must be implemented by the specific calibration subclasses. Parameters ---------- dictionary : `dict` Dictionary of properties. Returns ------- calib : `lsst.ip.isr.CalibType` Constructed calibration. Raises ------ RuntimeError : Raised if the supplied dictionary is for a different calibration. """ calib = cls() if calib._OBSTYPE != dictionary['metadata']['OBSTYPE']: raise RuntimeError(f"Incorrect crosstalk supplied. Expected {calib._OBSTYPE}, " f"found {dictionary['metadata']['OBSTYPE']}") calib.setMetadata(dictionary['metadata']) calib.calibInfoFromDict(dictionary) xCol = dictionary['x0'] yCol = dictionary['y0'] widthCol = dictionary['width'] heightCol = dictionary['height'] with calib.bulk_update: for x0, y0, width, height in zip(xCol, yCol, widthCol, heightCol): calib.append(geom.Box2I(geom.Point2I(x0, y0), geom.Extent2I(width, height))) return calib
def fromTable(cls, tableList, normalize_on_init=True): """Construct a `Defects` from the contents of a `~lsst.afw.table.BaseCatalog`. Parameters ---------- table : `lsst.afw.table.BaseCatalog` Table with one row per defect. normalize_on_init : `bool`, optional If `True`, normalization is applied to the defects listed in the table to remove duplicates, eliminate overlaps, etc. Otherwise the defects in the returned object exactly match those in the table. Returns ------- defects : `Defects` A `Defects` list. Notes ----- Two table formats are recognized. The first is the `FITS regions <https://fits.gsfc.nasa.gov/registry/region.html>`_ definition tabular format written by `toFitsRegionTable` where the pixel origin is corrected from FITS 1-based to a 0-based origin. The second is the legacy defects format using columns ``x0``, ``y0`` (bottom left hand pixel of box in 0-based coordinates), ``width`` and ``height``. The FITS standard regions can only read BOX, POINT, or ROTBOX with a zero degree rotation. """ table = tableList[0] defectList = [] schema = table.columns # Check schema to see which definitions we have if "X" in schema and "Y" in schema and "R" in schema and "SHAPE" in schema: # This is a FITS region style table isFitsRegion = True elif "x0" in schema and "y0" in schema and "width" in schema and "height" in schema: # This is a classic LSST-style defect table isFitsRegion = False else: raise ValueError("Unsupported schema for defects extraction") for record in table: if isFitsRegion: # Coordinates can be arrays (some shapes in the standard # require this) # Correct for FITS 1-based origin xcen = cls._get_values(record['X']) - 1.0 ycen = cls._get_values(record['Y']) - 1.0 shape = record['SHAPE'].upper().rstrip() if shape == "BOX": box = geom.Box2I.makeCenteredBox(geom.Point2D(xcen, ycen), geom.Extent2I(cls._get_values(record['R'], n=2))) elif shape == "POINT": # Handle the case where we have an externally created # FITS file. box = geom.Point2I(xcen, ycen) elif shape == "ROTBOX": # Astropy regions always writes ROTBOX rotang = cls._get_values(record['ROTANG']) # We can support 0 or 90 deg if math.isclose(rotang % 90.0, 0.0): # Two values required r = cls._get_values(record['R'], n=2) if math.isclose(rotang % 180.0, 0.0): width = r[0] height = r[1] else: width = r[1] height = r[0] box = geom.Box2I.makeCenteredBox(geom.Point2D(xcen, ycen), geom.Extent2I(width, height)) else: log.warning("Defect can not be defined using ROTBOX with non-aligned rotation angle") continue else: log.warning("Defect lists can only be defined using BOX or POINT not %s", shape) continue else: # This is a classic LSST-style defect table box = geom.Box2I(geom.Point2I(record['x0'], record['y0']), geom.Extent2I(record['width'], record['height'])) defectList.append(box) defects = cls(defectList, normalize_on_init=normalize_on_init) newMeta = dict(table.meta) defects.updateMetadata(setCalibInfo=True, **newMeta) return defects