Example #1
0
    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
Example #2
0
    def _check_value(self, value):
        """Check that the supplied value is a `~lsst.meas.algorithms.Defect`
        or can be converted to one.

        Parameters
        ----------
        value : `object`
            Value to check.

        Returns
        -------
        new : `~lsst.meas.algorithms.Defect`
            Either the supplied value or a new object derived from it.

        Raises
        ------
        ValueError
            Raised if the supplied value can not be converted to
            `~lsst.meas.algorithms.Defect`
        """
        if isinstance(value, Defect):
            pass
        elif isinstance(value, geom.BoxI):
            value = Defect(value)
        elif isinstance(value, geom.PointI):
            value = Defect(geom.Box2I(value, geom.Extent2I(1, 1)))
        elif isinstance(value, afwImage.DefectBase):
            value = Defect(value.getBBox())
        else:
            raise ValueError(f"Defects must be of type Defect, BoxI, or PointI, not '{value!r}'")
        return value
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)
Example #4
0
    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)
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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