예제 #1
0
    def testSymDifference(self):
        """Test Polygon.symDifference"""
        poly1 = self.square(2.0, -1.0, -1.0)
        poly2 = self.square(2.0, +1.0, +1.0)

        poly3 = afwGeom.Polygon([
            lsst.geom.Point2D(x, y)
            for x, y in ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +1.0),
                         (-1.0, -1.0), (+1.0, -1.0), (1.0, -3.0))
        ])
        poly4 = afwGeom.Polygon([
            lsst.geom.Point2D(x, y)
            for x, y in ((-1.0, +1.0), (-1.0, +3.0), (+3.0, +3.0),
                         (+3.0, -1.0), (+1.0, -1.0), (1.0, +1.0))
        ])

        diff1 = poly1.symDifference(poly2)
        diff2 = poly2.symDifference(poly1)

        self.assertEqual(len(diff1), 2)
        self.assertEqual(len(diff2), 2)
        self.assertTrue((diff1[0] == diff2[0] and diff1[1] == diff2[1])
                        or (diff1[1] == diff2[0] and diff1[0] == diff2[1]))
        self.assertTrue((diff1[0] == poly3 and diff1[1] == poly4)
                        or (diff1[1] == poly3 and diff1[0] == poly4))
예제 #2
0
 def setUp(self):
     self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
                                 lsst.geom.Point2I(20, 20))
     x = [0, 0, 10, 10]
     y = [0, 10, 10, 0]
     self.polygon = afwGeom.Polygon(
         [lsst.geom.Point2D(xc, yc) for xc, yc in zip(x, y)])
예제 #3
0
    def testUnion(self):
        """Test Polygon.union"""
        poly1 = self.square(2.0, -1.0, -1.0)
        poly2 = self.square(2.0, +1.0, +1.0)
        poly3 = afwGeom.Polygon([
            lsst.geom.Point2D(x, y)
            for x, y in ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +1.0),
                         (-1.0, +3.0), (+3.0, +3.0), (+3.0, -1.0),
                         (+1.0, -1.0), (+1.0, -3.0))
        ])
        poly4 = self.square(1.0, +5.0, +5.0)

        # unionSingle: assumes there's a single union (intersecting polygons)
        self.assertEqual(poly1.unionSingle(poly2), poly3)
        self.assertEqual(poly2.unionSingle(poly1), poly3)
        self.assertRaises(afwGeom.SinglePolygonException, poly1.unionSingle,
                          poly4)
        self.assertRaises(afwGeom.SinglePolygonException, poly4.unionSingle,
                          poly1)

        # union: no assumptions
        polyList1 = poly1.union(poly2)
        polyList2 = poly2.union(poly1)
        self.assertEqual(polyList1, polyList2)
        self.assertEqual(len(polyList1), 1)
        self.assertEqual(polyList1[0], poly3)
        polyList3 = poly1.union(poly4)
        polyList4 = poly4.union(poly1)
        self.assertEqual(len(polyList3), 2)
        self.assertEqual(len(polyList3), len(polyList4))
        self.assertTrue(
            (polyList3[0] == polyList4[0] and polyList3[1] == polyList4[1])
            or (polyList3[0] == polyList4[1] and polyList3[1] == polyList4[0]))
        self.assertTrue((polyList3[0] == poly1 and polyList3[1] == poly4)
                        or (polyList3[0] == poly4 and polyList3[1] == poly1))
예제 #4
0
    def testValidPolygonPsf(self):
        """Demonstrate that we can use the validPolygon on Exposures in the CoaddPsf."""
        # Create 9 separate records, each with its own peculiar Psf, Wcs,
        # weight, bounding box, and valid region.
        for i in range(1, 10):
            record = self.mycatalog.getTable().makeRecord()
            record.setPsf(measAlg.DoubleGaussianPsf(100, 100, i, 1.00, 0.0))
            crpix = lsst.geom.PointD(1000 - 10.0 * i, 1000.0 - 10.0 * i)
            wcs = afwGeom.makeSkyWcs(crpix=crpix,
                                     crval=self.crval,
                                     cdMatrix=self.cdMatrix)
            record.setWcs(wcs)
            record['weight'] = 1.0 * (i + 1)
            record['id'] = i
            record.setBBox(
                lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
                                lsst.geom.Extent2I(1000, 1000)))
            validPolygon = afwGeom.Polygon(
                lsst.geom.Box2D(lsst.geom.Point2D(0, 0),
                                lsst.geom.Extent2D(i * 100, i * 100)))
            record.setValidPolygon(validPolygon)
            self.mycatalog.append(record)

        # Create the CoaddPsf and check at three different points to ensure that the validPolygon is working
        mypsf = measAlg.CoaddPsf(self.mycatalog, self.wcsref, 'weight')

        for position in [
                lsst.geom.Point2D(50, 50),
                lsst.geom.Point2D(500, 500),
                lsst.geom.Point2D(850, 850)
        ]:
            m1coadd, m2coadd = getCoaddSecondMoments(mypsf, position, True)
            m1, m2 = getPsfSecondMoments(mypsf, position)
            self.assertAlmostEqual(m1, m1coadd, delta=0.01)
            self.assertAlmostEqual(m2, m2coadd, delta=0.01)
예제 #5
0
    def setUp(self):
        super().setUp()

        afwImage.Filter.reset()
        afwImage.FilterProperty.reset()
        defineFilter("g", 470.0)

        self.wcs = afwGeom.makeSkyWcs(
            lsst.geom.Point2D(0.0, 0.0),
            lsst.geom.SpherePoint(2.0, 34.0, lsst.geom.degrees),
            np.identity(2),
        )
        self.photoCalib = afwImage.PhotoCalib(1.5)
        self.psf = DummyPsf(2.0)
        self.detector = DetectorWrapper().detector
        self.summaryStats = afwImage.ExposureSummaryStats(ra=100.0)
        self.polygon = afwGeom.Polygon(
            lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
                            lsst.geom.Point2D(25.0, 20.0)))
        self.coaddInputs = afwImage.CoaddInputs()
        self.apCorrMap = afwImage.ApCorrMap()
        self.transmissionCurve = afwImage.TransmissionCurve.makeIdentity()

        self.exposureInfo = afwImage.ExposureInfo()
        gFilter = afwImage.Filter("g")
        gFilterLabel = afwImage.FilterLabel(band="g")
        self.exposureInfo.setFilter(gFilter)
        self.exposureInfo.setFilterLabel(gFilterLabel)
예제 #6
0
def getCoaddSecondMoments(coaddpsf, point, useValidPolygon=False):
    count = coaddpsf.getComponentCount()
    coaddWcs = coaddpsf.getCoaddWcs()
    weight_sum = 0.0
    m1_sum = 0.0
    m2_sum = 0.0
    for i in range(count):
        wcs = coaddpsf.getWcs(i)
        psf = coaddpsf.getPsf(i)
        bbox = lsst.geom.Box2D(coaddpsf.getBBox(i))
        if useValidPolygon:
            validPolygon = coaddpsf.getValidPolygon(i)
        else:
            validPolygon = afwGeom.Polygon(bbox)

        point_rel = wcs.skyToPixel(
            coaddWcs.pixelToSky(lsst.geom.Point2D(point)))
        if bbox.contains(point_rel) and validPolygon.contains(point_rel):
            weight = coaddpsf.getWeight(i)
            m0, xbar, ybar, mxx, myy, x0, y0 = getPsfMoments(
                psf, point)  # , extent)
            m1_sum += mxx * weight
            m2_sum += myy * weight
            weight_sum += weight
    if weight_sum == 0.0:
        return 0, 0
    else:
        return m1_sum / weight_sum, m2_sum / weight_sum
예제 #7
0
    def run(self, exposure):
        """Generate circular vignette pattern.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            Exposure to construct vignette for.

        Returns
        -------
        polygon : `lsst.afw.Geom.Polygon`
            Polygon defining the boundary of the vignetted region.
        """

        if self.config.doWriteVignettePolygon:
            theta = numpy.linspace(0,
                                   2 * numpy.pi,
                                   num=self.config.numPolygonPoints,
                                   endpoint=False)
            x = self.config.radius * numpy.cos(theta) + self.config.xCenter
            y = self.config.radius * numpy.sin(theta) + self.config.yCenter
            points = numpy.array([x, y]).transpose()
            polygon = afwGeom.Polygon(
                [lsst.geom.Point2D(x1, y1) for x1, y1 in reversed(points)])
            return polygon
        else:
            return None
def makeCircularPolygon(fpCenterX, fpCenterY, fpRadius, numPolygonPoints):
    theta = np.linspace(0, 2*np.pi, num=numPolygonPoints, endpoint=False)
    xx = fpRadius*np.cos(theta) + fpCenterX
    yy = fpRadius*np.sin(theta) + fpCenterY
    points = np.array([xx, yy]).transpose()
    polygon = afwGeom.Polygon([afwGeom.Point2D(x, y) for x, y in reversed(points)])
    return polygon
예제 #9
0
 def testTransform(self):
     """Test constructor for Polygon involving transforms"""
     box = lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
                           lsst.geom.Point2D(123.4, 567.8))
     poly1 = afwGeom.Polygon(box)
     scale = 1.5
     shift = lsst.geom.Extent2D(3.0, 4.0)
     affineTransform = lsst.geom.AffineTransform.makeTranslation(shift) * \
         lsst.geom.AffineTransform.makeScaling(scale)
     transform22 = afwGeom.makeTransform(affineTransform)
     transformedVertices = transform22.applyForward(box.getCorners())
     expect = afwGeom.Polygon(transformedVertices)
     poly1 = afwGeom.Polygon(box, affineTransform)
     poly2 = afwGeom.Polygon(box, transform22)
     self.assertEqual(poly1, expect)
     self.assertEqual(poly2, expect)
    def runDataRef(self, sensorRef):
        """Process one CCD
        """
        dataid = sensorRef.dataId
        self.log.info("Processing %s" % (dataid))

        raw = self.butler.get('raw', dataid)
        wcsRaw = raw.getWcs()
        bbox = raw.getBBox()
        ra, dec = self.bboxToRaDec(bbox, wcsRaw)
        minPoint = afwGeom.Point2D(min(ra), min(dec))
        maxPoint = afwGeom.Point2D(max(ra), max(dec))
        bboxRaDec = afwGeom.Box2D(minPoint, maxPoint)

        # DC2 footprint
        NE = afwGeom.Point2D(71.46, -27.25)
        NW = afwGeom.Point2D(52.25, -27.25)
        SE = afwGeom.Point2D(73.79, -44.33)
        SW = afwGeom.Point2D(49.92, -44.33)
        polyList = [NE, NW, SW, SE]
        poly = afwGeom.Polygon(polyList)

        interArea = poly.intersection(bboxRaDec)[0].calculateArea()
        ccdArea = bboxRaDec.getArea()
        diff = abs(ccdArea - interArea)

        print(ccdArea, interArea, abs(ccdArea - interArea))

        if diff > self.config.onEdgeCut:
            self.log.info("CCD on the edge of DC2 footprint, diff area: %f" %
                          diff)
        else:
            self.log.info("CCD is fully contained within the DC2 footprint")

        return diff
예제 #11
0
 def testFromBox(self):
     size = 1.0
     poly1 = self.square(size=size)
     box = lsst.geom.Box2D(lsst.geom.Point2D(-1.0, -1.0),
                           lsst.geom.Point2D(1.0, 1.0))
     poly2 = afwGeom.Polygon(box)
     self.assertEqual(poly1, poly2)
예제 #12
0
    def square(self, size=1.0, x0=0, y0=0):
        """Generate a square

        @param size: Half-length of the sides
        @param x0,y0: Offset of center
        """
        return afwGeom.Polygon([lsst.geom.Point2D(size*x + x0, size*y + y0) for
                               x, y in ((-1, -1), (-1, 1), (1, 1), (1, -1))])
예제 #13
0
 def testConvexHull(self):
     """Test Polygon.convexHull"""
     poly1 = self.square(2.0, -1.0, -1.0)
     poly2 = self.square(2.0, +1.0, +1.0)
     poly = poly1.unionSingle(poly2)
     expected = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
                                ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +3.0),
                                 (+3.0, +3.0), (+3.0, -1.0), (+1.0, -3.0))])
     self.assertEqual(poly.convexHull(), expected)
예제 #14
0
 def __init__(self, *args, **kwargs):
     super(SubaruIsrTask, self).__init__(*args, **kwargs)
     self.makeSubtask("crosstalk")
     self.makeSubtask("strayLight")
     if self.config.doWriteVignettePolygon:
         theta = numpy.linspace(0, 2*numpy.pi, num=self.config.numPolygonPoints, endpoint=False)
         x = self.config.vignette.radius*numpy.cos(theta) + self.config.vignette.xCenter
         y = self.config.vignette.radius*numpy.sin(theta) + self.config.vignette.yCenter
         points = numpy.array([x, y]).transpose()
         self.vignettePolygon = afwGeom.Polygon([afwGeom.Point2D(x1, y1) for x1, y1 in reversed(points)])
    def testSetPolygonIntersect(self):
        # Create a detector
        detector = DetectorWrapper().detector
        numPolygonPoints = 50
        # Create an exposure with bounding box defined by detector
        exposure = afwImage.ExposureF(detector.getBBox())
        exposure.setDetector(detector)

        pixelSizeMm = exposure.getDetector().getPixelSize()[0]

        pixX0 = exposure.getX0()
        pixY0 = exposure.getY0()
        pixX1 = pixX0 + exposure.getWidth() - 1
        pixY1 = pixY0 + exposure.getHeight() - 1

        fpCenter = exposure.getDetector().getCenter(FOCAL_PLANE)
        fpCenterX = fpCenter[0]
        fpCenterY = fpCenter[1]
        pixCenter = exposure.getDetector().getCenter(PIXELS)

        # Create an instance of IsrTask
        task = IsrTask()

        # Make a polygon that encompases entire ccd (radius of 2*max of width/height)
        fpRadius = 2.0*max(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeCircularPolygon(fpCenterX, fpCenterY, fpRadius, numPolygonPoints)
        # Set the polygon that is the intersection of fpPolygon and ccd
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # Since the ccd is fully contained in the fpPolygon, the intersection should be the ccdPolygon itself
        ccdPolygonPix = afwGeom.Polygon(exposure.getDetector().getCorners(PIXELS))
        self.assertEqual(exposure.getInfo().getValidPolygon(), ccdPolygonPix)

        # Make a polygon that is entirely within, but smaller than, the ccd
        # (radius of 0.2*min of width/height)
        fpRadius = 0.2*min(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeCircularPolygon(fpCenterX, fpCenterY, fpRadius, numPolygonPoints)
        # Set the polygon that is the intersection of fpPolygon and ccd
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # all vertices of polygon should be contained within the ccd
        for x in exposure.getInfo().getValidPolygon():
            self.assertTrue(ccdPolygonPix.contains(afwGeom.Point2D(x)))
        # intersection is smaller than the ccd
        self.assertNotEqual(exposure.getInfo().getValidPolygon(), ccdPolygonPix)

        # make a simple square polygon that partly intersects the ccd, centered at ccd center
        fpPolygonSize = max(exposure.getWidth()*pixelSizeMm, exposure.getHeight()*pixelSizeMm)
        fpPolygon = makeSquarePolygon(fpCenterX, fpCenterY, fpPolygonSize)
        task.setValidPolygonIntersect(exposure, fpPolygon)
        # Check that the polygon contains the central pixel (offset by one to actually be "contained")
        pixCenterPlusOne = afwGeom.Point2D(pixCenter[0] + 1, pixCenter[1] + 1)
        self.assertTrue(exposure.getInfo().getValidPolygon().contains(afwGeom.Point2D(pixCenterPlusOne)))
        # Check that the polygon contains the upper right ccd edge
        self.assertTrue(exposure.getInfo().getValidPolygon().contains(afwGeom.Point2D(pixX1, pixY1)))
예제 #16
0
파일: vignette.py 프로젝트: lsst/ip_isr
    def run(self,
            exposure=None,
            doUpdateMask=True,
            maskPlane="NO_DATA",
            vignetteValue=None,
            log=None):
        """Generate circular vignette pattern.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`, optional
            Exposure to construct, apply, and optionally mask vignette for.
        doUpdateMask : `bool`, optional
            If true, the mask will be updated to mask the vignetted region.
        maskPlane : `str`, optional
            Mask plane to assign vignetted pixels to.
        vignetteValue : `float` or `None`, optional
            Value to assign to the image array pixels within the ``polygon``
            region.  If `None`, image pixel values are not replaced.
        log : `logging.Logger`, optional
            Log object to write to.

        Returns
        -------
        polygon : `lsst.afw.geom.Polygon`
            Polygon defining the boundary of the vignetted region.
        """
        theta = np.linspace(0,
                            2 * np.pi,
                            num=self.config.numPolygonPoints,
                            endpoint=False)
        x = self.config.radius * np.cos(theta) + self.config.xCenter
        y = self.config.radius * np.sin(theta) + self.config.yCenter
        points = np.array([x, y]).transpose()
        fpPolygon = afwGeom.Polygon(
            [geom.Point2D(x1, y1) for x1, y1 in reversed(points)])
        if exposure is None:
            return fpPolygon

        # Exposure was provided, so attach the validPolygon associated with the
        # vignetted region.
        setValidPolygonCcdIntersect(exposure, fpPolygon, log=log)

        if doUpdateMask:
            polygon = exposure.getInfo().getValidPolygon()
            maskVignettedRegion(exposure,
                                polygon,
                                maskPlane="NO_DATA",
                                vignetteValue=vignetteValue,
                                log=log)
        return fpPolygon
예제 #17
0
    def polygon(self, num, radius=1.0, x0=None, y0=None):
        """Generate a polygon

        @param num: Number of points
        @param radius: Radius of polygon
        @param x0,y0: Offset of center
        @return polygon
        """
        if x0 is None:
            x0 = self.x0
        if y0 is None:
            y0 = self.y0
        points = circle(radius, num, x0=x0, y0=y0)
        return afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in reversed(points)])
예제 #18
0
파일: vignette.py 프로젝트: lsst/ip_isr
def setValidPolygonCcdIntersect(ccdExposure, fpPolygon, log=None):
    """Set valid polygon on ccdExposure associated with focal plane polygon.

    The ccd exposure's valid polygon is the intersection of fpPolygon,
    a valid polygon in focal plane coordinates, and the ccd corners,
    in ccd pixel coordinates.

    Parameters
    ----------
    ccdExposure : `lsst.afw.image.Exposure`
        Exposure to process.
    fpPolygon : `lsst.afw.geom.Polygon`
        Polygon in focal plane coordinates.
    log : `logging.Logger`, optional
        Log object to write to.
    """
    # Get ccd corners in focal plane coordinates
    ccd = ccdExposure.getDetector()
    fpCorners = ccd.getCorners(cameraGeom.FOCAL_PLANE)
    ccdPolygon = afwGeom.Polygon(fpCorners)
    # Get intersection of ccd corners with fpPolygon
    try:
        intersect = ccdPolygon.intersectionSingle(fpPolygon)
    except afwGeom.SinglePolygonException:
        intersect = None
    if intersect is not None:
        # Transform back to pixel positions and build new polygon
        ccdPoints = ccd.transform(intersect, cameraGeom.FOCAL_PLANE,
                                  cameraGeom.PIXELS)
        validPolygon = afwGeom.Polygon(ccdPoints)
        ccdExposure.getInfo().setValidPolygon(validPolygon)
    else:
        if log is not None:
            log.info(
                "Ccd exposure does not overlap with focal plane polygon.  Not setting validPolygon."
            )
예제 #19
0
    def _preparePlugin(self, addCoaddInputs):
        """Prepare a `SingleFrameInputCountPlugin` for running.

        Sets up the plugin to run on an empty catalog together with a
        synthetic, content-free `~lsst.afw.image.ExposureF`.

        Parameters
        ----------
        addCoaddInputs : `bool`
            Should we add the coadd inputs?

        Returns
        -------
        inputCount : `SingleFrameInputCountPlugin`
            Initialized measurement plugin.
        catalog : `lsst.afw.table.SourceCatalog`
            Empty Catalog.
        exp : `lsst.afw.image.ExposureF`
            Synthetic exposure.
        """
        exp = afwImage.ExposureF(20, 20)
        scale = 1.0e-5 * lsst.geom.degrees
        wcs = afwGeom.makeSkyWcs(crpix=lsst.geom.Point2D(0, 0),
                                 crval=lsst.geom.SpherePoint(
                                     0.0, 0.0, lsst.geom.degrees),
                                 cdMatrix=afwGeom.makeCdMatrix(scale=scale))
        exp.setWcs(wcs)
        if addCoaddInputs:
            exp.getInfo().setCoaddInputs(
                afwImage.CoaddInputs(
                    afwTable.ExposureTable.makeMinimalSchema(),
                    afwTable.ExposureTable.makeMinimalSchema()))
            ccds = exp.getInfo().getCoaddInputs().ccds
            record = ccds.addNew()
            record.setWcs(wcs)
            record.setBBox(exp.getBBox())
            record.setValidPolygon(
                afwGeom.Polygon(lsst.geom.Box2D(exp.getBBox())))

        schema = afwTable.SourceTable.makeMinimalSchema()
        measBase.SingleFramePeakCentroidPlugin(
            measBase.SingleFramePeakCentroidConfig(), "centroid", schema, None)
        schema.getAliasMap().set("slot_Centroid", "centroid")
        inputCount = measBase.SingleFrameInputCountPlugin(
            measBase.InputCountConfig(), "inputCount", schema, None)
        catalog = afwTable.SourceCatalog(schema)
        return inputCount, catalog, exp
예제 #20
0
    def _preparePlugin(self, addCoaddInputs):
        """
        Prepare a SingleFrameInputCountPlugin for running.

        Sets up an InputCount plugin to run on an empty catalog together with a synthetic, content-free
        Exposure.

        @param[in] addCoaddInputs  Should we add the coadd inputs?
        @returns   tuple of (initialized plugin, empty catalog, synthetic exposure)
        """
        exp = afwImage.ExposureF(20, 20)
        scale = 1.0e-5 * afwGeom.degrees
        wcs = afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(0, 0),
                                 crval=afwGeom.SpherePoint(
                                     0.0, 0.0, afwGeom.degrees),
                                 cdMatrix=afwGeom.makeCdMatrix(scale=scale))
        exp.setWcs(wcs)
        if addCoaddInputs:
            exp.getInfo().setCoaddInputs(
                afwImage.CoaddInputs(
                    afwTable.ExposureTable.makeMinimalSchema(),
                    afwTable.ExposureTable.makeMinimalSchema()))
            ccds = exp.getInfo().getCoaddInputs().ccds
            record = ccds.addNew()
            record.setWcs(wcs)
            record.setBBox(exp.getBBox())
            record.setValidPolygon(
                afwGeom.Polygon(afwGeom.Box2D(exp.getBBox())))

        schema = afwTable.SourceTable.makeMinimalSchema()
        measBase.SingleFramePeakCentroidPlugin(
            measBase.SingleFramePeakCentroidConfig(), "centroid", schema, None)
        schema.getAliasMap().set("slot_Centroid", "centroid")
        inputCount = measBase.SingleFrameInputCountPlugin(
            measBase.InputCountConfig(), "inputCount", schema, None)
        catalog = afwTable.SourceCatalog(schema)
        return inputCount, catalog, exp
예제 #21
0
    def testInputCounts(self, showPlot=False):
        # Generate a simulated coadd of four overlapping-but-offset CCDs.
        # Populate it with three sources.
        # Demonstrate that we can correctly recover the number of images which
        # contribute to each source.

        size = 20  # Size of images (pixels)
        value = 100.0  # Source flux

        ccdPositions = [
            lsst.geom.Point2D(8, 0),
            lsst.geom.Point2D(10, 10),
            lsst.geom.Point2D(-8, -8),
            lsst.geom.Point2D(-8, 8)
        ]

        # Represent sources by a tuple of position and expected number of
        # contributing CCDs (based on the size/positions given above).
        Source = namedtuple("Source", ["pos", "count"])
        sources = [
            Source(pos=lsst.geom.Point2D(6, 6), count=2),
            Source(pos=lsst.geom.Point2D(10, 10), count=3),
            Source(pos=lsst.geom.Point2D(14, 14), count=1)
        ]

        # These lines are used in the creation of WCS information
        scale = 1.0e-5 * lsst.geom.degrees
        cdMatrix = afwGeom.makeCdMatrix(scale=scale)
        crval = lsst.geom.SpherePoint(0.0, 0.0, lsst.geom.degrees)

        # Construct the info needed to set the exposure object
        imageBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
                                   lsst.geom.Extent2I(size, size))
        wcsRef = afwGeom.makeSkyWcs(crpix=lsst.geom.Point2D(0, 0),
                                    crval=crval,
                                    cdMatrix=cdMatrix)

        # Create the exposure object, and set it up to be the output of a coadd
        exp = afwImage.ExposureF(size, size)
        exp.setWcs(wcsRef)
        exp.getInfo().setCoaddInputs(
            afwImage.CoaddInputs(afwTable.ExposureTable.makeMinimalSchema(),
                                 afwTable.ExposureTable.makeMinimalSchema()))

        # Set the fake CCDs that "went into" making this coadd, using the
        # differing wcs objects created above.
        ccds = exp.getInfo().getCoaddInputs().ccds
        for pos in ccdPositions:
            record = ccds.addNew()
            record.setWcs(
                afwGeom.makeSkyWcs(crpix=pos, crval=crval, cdMatrix=cdMatrix))
            record.setBBox(imageBox)
            record.setValidPolygon(afwGeom.Polygon(lsst.geom.Box2D(imageBox)))

        # Configure a SingleFrameMeasurementTask to run InputCounts.
        measureSourcesConfig = measBase.SingleFrameMeasurementConfig()
        measureSourcesConfig.plugins.names = [
            "base_PeakCentroid", "base_InputCount"
        ]
        measureSourcesConfig.slots.centroid = "base_PeakCentroid"
        measureSourcesConfig.slots.psfFlux = None
        measureSourcesConfig.slots.apFlux = None
        measureSourcesConfig.slots.modelFlux = None
        measureSourcesConfig.slots.gaussianFlux = None
        measureSourcesConfig.slots.calibFlux = None
        measureSourcesConfig.slots.shape = None
        measureSourcesConfig.validate()
        schema = afwTable.SourceTable.makeMinimalSchema()
        task = measBase.SingleFrameMeasurementTask(schema,
                                                   config=measureSourcesConfig)
        catalog = afwTable.SourceCatalog(schema)

        # Add simulated sources to the measurement catalog.
        for src in sources:
            spans = afwGeom.SpanSet.fromShape(1)
            spans = spans.shiftedBy(int(src.pos.getX()), int(src.pos.getY()))
            foot = afwDetection.Footprint(spans)
            peak = foot.getPeaks().addNew()
            peak.setFx(src.pos[0])
            peak.setFy(src.pos[1])
            peak.setPeakValue(value)
            catalog.addNew().setFootprint(foot)

        task.run(catalog, exp)

        for src, rec in zip(sources, catalog):
            self.assertEqual(rec.get("base_InputCount_value"), src.count)

        if display:
            ccdVennDiagram(exp)
예제 #22
0
    def build_patch_input_map(self, butler, tract_wcs, patch_info, ccds,
                              nside_coverage_patch):
        """
        Build the patch input map.

        Parameters
        ----------
        butler : `lsst.daf.persistence.Butler`
           gen2 butler
        tract_wcs : `lsst.afw.geom.SkyWcs`
           WCS object for the tract
        patch_info : `lsst.skymap.PatchInfo`
           Patch info object
        ccds : `lsst.afw.table.ExposureCatalog`
           Catalog of ccd information
        nside_coverage_patch : `int`
           Healpix nside for the coverage map

        Returns
        -------
        patch_input_map : `healsparse.HealSparseMap`
           Healsparse map encoding input ccd information.
        """
        patch_input_map = healsparse.HealSparseMap.make_empty(
            nside_coverage=nside_coverage_patch,
            nside_sparse=self.config.nside,
            dtype=healsparse.WIDE_MASK,
            wide_mask_maxbits=len(ccds))

        if self.config.use_calexp_mask:
            # pixel_scale = tract_wcs.getPixelScale().asArcseconds()
            hpix_area_arcsec2 = hp.nside2pixarea(patch_input_map.nside_sparse,
                                                 degrees=True) * (3600.**2.)
            bad_mask = afwImage.Mask.getPlaneBitMask(
                self.config.bad_mask_planes)

        metadata = {}
        for bit, ccd in enumerate(ccds):
            metadata['B%04dCCD' % (bit)] = ccd['ccd']
            metadata['B%04dVIS' % (bit)] = ccd['visit']
            metadata['B%04dWT' % (bit)] = ccd['weight']

            wcs = ccd.getWcs()
            ccd_poly = ccd.getValidPolygon()
            if ccd_poly is None:
                ccd_poly = afwGeom.Polygon(lsst.geom.Box2D(ccd.getBBox()))
            ccd_poly_radec = pixels_to_radec(
                wcs,
                ccd_poly.convexHull().getVertices())

            # Use polygons for all of these
            poly = healsparse.Polygon(ra=ccd_poly_radec[:-1, 0],
                                      dec=ccd_poly_radec[:-1, 1],
                                      value=[bit])
            poly_map = poly.get_map_like(patch_input_map)

            dataId = {
                self.config.detector_id_name: int(ccd['ccd']),
                self.config.visit_id_name: int(ccd['visit'])
            }

            if self.config.use_calexp_mask:
                calexp = butler.get('calexp', dataId=dataId)
                calexp_metadata = calexp.getMetadata()

                mask = calexp.getMask()

                pixel_scale = tract_wcs.getPixelScale().asArcseconds()
                min_bad = self.config.bad_mask_coverage * hpix_area_arcsec2 / (
                    pixel_scale**2.)
                bad_pixels = np.where(mask.getArray() & bad_mask)

                # Convert bad pixels to position
                bad_radec = xy_to_radec(wcs, bad_pixels[1].astype(np.float64),
                                        bad_pixels[0].astype(np.float64))

                # Convert position to healpixel
                bad_hpix = hp.ang2pix(patch_input_map.nside_sparse,
                                      bad_radec[:, 0],
                                      bad_radec[:, 1],
                                      lonlat=True,
                                      nest=True)

                min_bad_hpix = bad_hpix.min()
                bad_hpix_count = np.zeros(bad_hpix.max() - min_bad_hpix + 1,
                                          dtype=np.int32)
                np.add.at(bad_hpix_count, bad_hpix - min_bad_hpix, 1)
                hpix_to_mask = min_bad_hpix + np.where(
                    bad_hpix_count > min_bad)[0]

                poly_map.clear_bits_pix(hpix_to_mask, [bit])

                if 'SKYLEVEL' not in calexp_metadata:
                    # We must recompute skylevel, skysigma
                    skylevel, skysigma = self._compute_skylevel(
                        butler, dataId, calexp)
                else:
                    skylevel = calexp_metadata['SKYLEVEL']
                    skysigma = calexp_metadata['SKYSIGMA']
            else:
                calexp_metadata = butler.get('calexp_md', dataId=dataId)
                if 'SKYLEVEL' not in calexp_metadata:
                    # We want to log this
                    skylevel = 0.0
                    skysigma = 0.0
                else:
                    skylevel = calexp_metadata['SKYLEVEL']
                    skysigma = calexp_metadata['SKYSIGMA']

            metadata['B%04dSLV' % (bit)] = skylevel
            metadata['B%04dSSG' % (bit)] = skysigma
            metadata['B%04dBGM' % (bit)] = calexp_metadata['BGMEAN']
            metadata['B%04dBGV' % (bit)] = calexp_metadata['BGVAR']

            # Now we have the full masked ccd map, set the appropriate bit
            patch_input_map.set_bits_pix(poly_map.valid_pixels, [bit])

        # Now cut down to the inner tract polygon
        poly_vertices = patch_info.getInnerSkyPolygon(tract_wcs).getVertices()
        patch_radec = vertices_to_radec(poly_vertices)
        patch_poly = healsparse.Polygon(ra=patch_radec[:, 0],
                                        dec=patch_radec[:, 1],
                                        value=np.arange(
                                            patch_input_map.wide_mask_maxbits))

        # Realize the patch polygon
        patch_poly_map = patch_poly.get_map_like(patch_input_map)
        patch_input_map = healsparse.and_intersection(
            [patch_input_map, patch_poly_map])

        # And set the metadata
        patch_input_map.metadata = metadata

        return patch_input_map
def makeSquarePolygon(fpX0, fpY0, fpSize):
    xx = [fpX0, fpX0, fpX0 + fpSize - 1, fpX0 + fpSize - 1, fpX0]
    yy = [fpY0, fpY0 + fpSize - 1, fpY0 + fpSize - 1, fpY0, fpY0]
    points = np.array([xx, yy]).transpose()
    polygon = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in points])
    return polygon
    def testCoaddApCorrMap(self):
        """Check that we can create and use a coadd ApCorrMap."""
        coaddBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(100, 100))
        scale = 5.0e-5*afwGeom.degrees
        cdMatrix = afwGeom.makeCdMatrix(scale=scale)
        crval = afwGeom.SpherePoint(0.0, 0.0, afwGeom.degrees)
        center = afwGeom.Point2D(afwGeom.Extent2D(coaddBox.getDimensions())*0.5)
        coaddWcs = afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(0, 0), crval=crval, cdMatrix=cdMatrix)
        schema = afwTable.ExposureTable.makeMinimalSchema()
        weightKey = schema.addField("customweightname", type="D", doc="Coadd weight")
        catalog = afwTable.ExposureCatalog(schema)

        # Non-overlapping
        num = 5
        inputBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(10, 10))
        validBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(7, 7))
        pointList = []
        pointListValid = []

        for i in range(num):
            value = np.array([[1]], dtype=float)  # Constant with value = i+1
            apCorrMap = afwImage.ApCorrMap()
            bf = afwMath.ChebyshevBoundedField(inputBox, value*(i + 1))
            apCorrMap.set("only", bf)

            point = afwGeom.Point2D(0, 0) - afwGeom.Extent2D(coaddBox.getDimensions())*(i+0.5)/num
            wcs = afwGeom.makeSkyWcs(crpix=point, crval=crval, cdMatrix=cdMatrix)
            center = afwGeom.Box2D(inputBox).getCenter()
            pointList.append(coaddWcs.skyToPixel(wcs.pixelToSky(center)))

            # This point will only be valid for the second overlapping record
            pointValid = center + afwGeom.Extent2D(4, 4)
            pointListValid.append(coaddWcs.skyToPixel(wcs.pixelToSky(pointValid)))

            # A record with the valid polygon defining a limited region
            record = catalog.getTable().makeRecord()
            record.setWcs(wcs)
            record.setBBox(inputBox)
            record.setApCorrMap(apCorrMap)
            record.set(weightKey, i + 1)
            record['id'] = i
            record.setValidPolygon(afwGeom.Polygon(afwGeom.Box2D(validBox)))
            catalog.append(record)

            # An overlapping record with the whole region as valid
            record = catalog.getTable().makeRecord()
            record.setWcs(wcs)
            record.setBBox(inputBox)
            apCorrMap = afwImage.ApCorrMap()
            bf = afwMath.ChebyshevBoundedField(inputBox, value*(i + 2))
            apCorrMap.set("only", bf)
            record.setApCorrMap(apCorrMap)
            record.set(weightKey, i + 2)
            record['id'] = i + num
            record.setValidPolygon(afwGeom.Polygon(afwGeom.Box2D(inputBox)))
            catalog.append(record)

        apCorrMap = measAlg.makeCoaddApCorrMap(catalog, coaddBox, coaddWcs, "customweightname")
        # This will test a point where both records contribute
        self.assertApCorrMap(apCorrMap, pointList)
        # Only the second record will be valid for this point
        self.assertApCorrMapValid(apCorrMap, pointListValid)

        filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), "coaddApCorrMap.fits")
        exposure = afwImage.ExposureF(1, 1)
        exposure.getInfo().setApCorrMap(apCorrMap)
        exposure.writeFits(filename)
        exposure = afwImage.ExposureF(filename)
        self.assertApCorrMap(exposure.getInfo().getApCorrMap(), pointList)
        self.assertApCorrMapValid(exposure.getInfo().getApCorrMap(), pointListValid)
        os.unlink(filename)
예제 #25
0
    def setUp(self):
        tract = 0
        band = 'r'
        patch = 0
        visits = [100, 101]
        # Good to test crossing 0.
        ra_center = 0.0
        dec_center = -45.0
        pixel_scale = 0.2
        coadd_zp = 27.0

        # Generate a mock skymap with one patch
        config = DiscreteSkyMap.ConfigClass()
        config.raList = [ra_center]
        config.decList = [dec_center]
        config.radiusList = [150 * pixel_scale / 3600.]
        config.patchInnerDimensions = (350, 350)
        config.patchBorder = 50
        config.tractOverlap = 0.0
        config.pixelScale = pixel_scale
        sky_map = DiscreteSkyMap(config)

        visit_summaries = [
            makeMockVisitSummary(visit,
                                 ra_center=ra_center,
                                 dec_center=dec_center) for visit in visits
        ]
        visit_summary_refs = [
            MockVisitSummaryReference(visit_summary, visit)
            for visit_summary, visit in zip(visit_summaries, visits)
        ]
        self.visit_summary_dict = {
            visit: ref.get()
            for ref, visit in zip(visit_summary_refs, visits)
        }

        # Generate an input map.  Note that this does not need to be consistent
        # with the visit_summary projections, we're just tracking values.
        input_map = hsp.HealSparseMap.make_empty(
            nside_coverage=256,
            nside_sparse=32768,
            dtype=hsp.WIDE_MASK,
            wide_mask_maxbits=len(visits) * 2)
        patch_poly = afwGeom.Polygon(
            geom.Box2D(sky_map[tract][patch].getOuterBBox()))
        sph_pts = sky_map[tract].getWcs().pixelToSky(
            patch_poly.convexHull().getVertices())
        patch_poly_radec = np.array([(sph.getRa().asDegrees(),
                                      sph.getDec().asDegrees())
                                     for sph in sph_pts])
        poly = hsp.Polygon(ra=patch_poly_radec[:-1, 0],
                           dec=patch_poly_radec[:-1, 1],
                           value=[0])
        poly_pixels = poly.get_pixels(nside=input_map.nside_sparse)
        # The input map has full coverage for bits 0 and 1
        input_map.set_bits_pix(poly_pixels, [0])
        input_map.set_bits_pix(poly_pixels, [1])

        input_map_ref = MockInputMapReference(input_map,
                                              patch=patch,
                                              tract=tract)
        self.input_map_dict = {patch: input_map_ref}

        coadd = afwImage.ExposureF(sky_map[tract][patch].getOuterBBox(),
                                   sky_map[tract].getWcs())
        instFluxMag0 = 10.**(coadd_zp / 2.5)
        pc = afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0)
        coadd.setPhotoCalib(pc)

        # Mock the coadd input ccd table
        schema = afwTable.ExposureTable.makeMinimalSchema()
        schema.addField("ccd", type="I")
        schema.addField("visit", type="I")
        schema.addField("weight", type="F")
        ccds = afwTable.ExposureCatalog(schema)
        ccds.resize(2)
        ccds['id'] = np.arange(2)
        ccds['visit'][0] = visits[0]
        ccds['visit'][1] = visits[1]
        ccds['ccd'][0] = 0
        ccds['ccd'][1] = 1
        ccds['weight'] = 10.0
        for ccd_row in ccds:
            summary = self.visit_summary_dict[ccd_row['visit']].find(
                ccd_row['ccd'])
            ccd_row.setWcs(summary.getWcs())
            ccd_row.setPsf(summary.getPsf())
            ccd_row.setBBox(summary.getBBox())
            ccd_row.setPhotoCalib(summary.getPhotoCalib())

        inputs = afwImage.CoaddInputs()
        inputs.ccds = ccds
        coadd.getInfo().setCoaddInputs(inputs)

        coadd_ref = MockCoaddReference(coadd, patch=patch, tract=tract)
        self.coadd_dict = {patch: coadd_ref}

        self.tract = tract
        self.band = band
        self.sky_map = sky_map
        self.input_map = input_map
예제 #26
0
    def getOverlappingExposures(self, inputs):
        """Return lists of coadds and their corresponding dataIds that overlap the detector.

        The spatial index in the registry has generous padding and often supplies
        patches near, but not directly overlapping the detector.
        Filters inputs so that we don't have to read in all input coadds.

        Parameters
        ----------
        inputs : `dict` of task Inputs, containing:
            - coaddExposureRefs : `list` of elements of type
                                  `lsst.daf.butler.DeferredDatasetHandle` of
                                  `lsst.afw.image.Exposure`
                Data references to exposures that might overlap the detector.
            - bbox : `lsst.geom.Box2I`
                Template Bounding box of the detector geometry onto which to
                resample the coaddExposures
            - skyMap : `lsst.skymap.SkyMap`
                Input definition of geometry/bbox and projection/wcs for template exposures
            - wcs : `lsst.afw.geom.SkyWcs`
                Template WCS onto which to resample the coaddExposures
            - visitInfo : `lsst.afw.image.VisitInfo`
                Metadata for the science image.

        Returns
        -------
        result : `lsst.pipe.base.Struct` containing these fields:
            - coaddExposures : `list` of elements of type `lsst.afw.image.Exposure`
                Coadd exposures that overlap the detector.
            - dataIds : `list` of `lsst.daf.butler.DataCoordinate`
                Data IDs of the coadd exposures that overlap the detector.

        Raises
        ------
        NoWorkFound
            Raised if no patches overlatp the input detector bbox
        """
        # Check that the patches actually overlap the detector
        # Exposure's validPolygon would be more accurate
        detectorPolygon = geom.Box2D(inputs["bbox"])
        overlappingArea = 0
        coaddExposureRefList = []
        dataIds = []
        patchList = dict()
        for coaddRef in inputs["dcrCoadds"]:
            dataId = coaddRef.dataId
            patchWcs = inputs["skyMap"][dataId['tract']].getWcs()
            patchBBox = inputs["skyMap"][dataId['tract']][
                dataId['patch']].getOuterBBox()
            patchCorners = patchWcs.pixelToSky(
                geom.Box2D(patchBBox).getCorners())
            patchPolygon = afwGeom.Polygon(
                inputs["wcs"].skyToPixel(patchCorners))
            if patchPolygon.intersection(detectorPolygon):
                overlappingArea += patchPolygon.intersectionSingle(
                    detectorPolygon).calculateArea()
                self.log.info(
                    "Using template input tract=%s, patch=%s, subfilter=%s" %
                    (dataId['tract'], dataId['patch'], dataId["subfilter"]))
                coaddExposureRefList.append(coaddRef)
                if dataId['tract'] in patchList:
                    patchList[dataId['tract']].append(dataId['patch'])
                else:
                    patchList[dataId['tract']] = [
                        dataId['patch'],
                    ]
                dataIds.append(dataId)

        if not overlappingArea:
            raise pipeBase.NoWorkFound('No patches overlap detector')

        self.checkPatchList(patchList)

        coaddExposures = self.getDcrModel(patchList, inputs['dcrCoadds'],
                                          inputs['visitInfo'])
        return pipeBase.Struct(coaddExposures=coaddExposures, dataIds=dataIds)
예제 #27
0
    def run(self, coaddExposures, bbox, wcs, dataIds, **kwargs):
        """Warp coadds from multiple tracts to form a template for image diff.

        Where the tracts overlap, the resulting template image is averaged.
        The PSF on the template is created by combining the CoaddPsf on each
        template image into a meta-CoaddPsf.

        Parameters
        ----------
        coaddExposures : `list` of `lsst.afw.image.Exposure`
            Coadds to be mosaicked
        bbox : `lsst.geom.Box2I`
            Template Bounding box of the detector geometry onto which to
            resample the coaddExposures
        wcs : `lsst.afw.geom.SkyWcs`
            Template WCS onto which to resample the coaddExposures
        dataIds : `list` of `lsst.daf.butler.DataCoordinate`
            Record of the tract and patch of each coaddExposure.
        **kwargs
            Any additional keyword parameters.

        Returns
        -------
        result : `lsst.pipe.base.Struct` containing
            - ``outputExposure`` : a template coadd exposure assembled out of patches
        """
        # Table for CoaddPSF
        tractsSchema = afwTable.ExposureTable.makeMinimalSchema()
        tractKey = tractsSchema.addField('tract',
                                         type=np.int32,
                                         doc='Which tract')
        patchKey = tractsSchema.addField('patch',
                                         type=np.int32,
                                         doc='Which patch')
        weightKey = tractsSchema.addField(
            'weight', type=float, doc='Weight for each tract, should be 1')
        tractsCatalog = afwTable.ExposureCatalog(tractsSchema)

        finalWcs = wcs
        bbox.grow(self.config.templateBorderSize)
        finalBBox = bbox

        nPatchesFound = 0
        maskedImageList = []
        weightList = []

        for coaddExposure, dataId in zip(coaddExposures, dataIds):

            # warp to detector WCS
            warped = self.warper.warpExposure(finalWcs,
                                              coaddExposure,
                                              maxBBox=finalBBox)

            # Check if warped image is viable
            if not np.any(np.isfinite(warped.image.array)):
                self.log.info("No overlap for warped %s. Skipping" % dataId)
                continue

            exp = afwImage.ExposureF(finalBBox, finalWcs)
            exp.maskedImage.set(np.nan,
                                afwImage.Mask.getPlaneBitMask("NO_DATA"),
                                np.nan)
            exp.maskedImage.assign(warped.maskedImage, warped.getBBox())

            maskedImageList.append(exp.maskedImage)
            weightList.append(1)
            record = tractsCatalog.addNew()
            record.setPsf(coaddExposure.getPsf())
            record.setWcs(coaddExposure.getWcs())
            record.setPhotoCalib(coaddExposure.getPhotoCalib())
            record.setBBox(coaddExposure.getBBox())
            record.setValidPolygon(
                afwGeom.Polygon(
                    geom.Box2D(coaddExposure.getBBox()).getCorners()))
            record.set(tractKey, dataId['tract'])
            record.set(patchKey, dataId['patch'])
            record.set(weightKey, 1.)
            nPatchesFound += 1

        if nPatchesFound == 0:
            raise pipeBase.NoWorkFound("No patches found to overlap detector")

        # Combine images from individual patches together
        statsFlags = afwMath.stringToStatisticsProperty('MEAN')
        statsCtrl = afwMath.StatisticsControl()
        statsCtrl.setNanSafe(True)
        statsCtrl.setWeighted(True)
        statsCtrl.setCalcErrorFromInputVariance(True)

        templateExposure = afwImage.ExposureF(finalBBox, finalWcs)
        templateExposure.maskedImage.set(
            np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"), np.nan)
        xy0 = templateExposure.getXY0()
        # Do not mask any values
        templateExposure.maskedImage = afwMath.statisticsStack(maskedImageList,
                                                               statsFlags,
                                                               statsCtrl,
                                                               weightList,
                                                               clipped=0,
                                                               maskMap=[])
        templateExposure.maskedImage.setXY0(xy0)

        # CoaddPsf centroid not only must overlap image, but must overlap the part of
        # image with data. Use centroid of region with data
        boolmask = templateExposure.mask.array & templateExposure.mask.getPlaneBitMask(
            'NO_DATA') == 0
        maskx = afwImage.makeMaskFromArray(boolmask.astype(afwImage.MaskPixel))
        centerCoord = afwGeom.SpanSet.fromMask(maskx, 1).computeCentroid()

        ctrl = self.config.coaddPsf.makeControl()
        coaddPsf = CoaddPsf(tractsCatalog, finalWcs, centerCoord,
                            ctrl.warpingKernelName, ctrl.cacheSize)
        if coaddPsf is None:
            raise RuntimeError("CoaddPsf could not be constructed")

        templateExposure.setPsf(coaddPsf)
        templateExposure.setFilter(coaddExposure.getFilter())
        templateExposure.setPhotoCalib(coaddExposure.getPhotoCalib())
        return pipeBase.Struct(outputExposure=templateExposure)
예제 #28
0
    def runQuantum(self, exposure, butlerQC, skyMapRef, coaddExposureRefs):
        """Gen3 task entry point. Retrieve and mosaic a template coadd exposure
        that overlaps the science exposure.

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            The science exposure to define the sky region of the template coadd.
        butlerQC : `lsst.pipe.base.ButlerQuantumContext`
            Butler like object that supports getting data by DatasetRef.
        skyMapRef : `lsst.daf.butler.DatasetRef`
            Reference to SkyMap object that corresponds to the template coadd.
        coaddExposureRefs : iterable of `lsst.daf.butler.DeferredDatasetRef`
            Iterable of references to the available template coadd patches.

        Returns
        -------
        result : `lsst.pipe.base.Struct`
            - ``exposure`` : `lsst.afw.image.ExposureF`
                a template coadd exposure assembled out of patches
            - ``sources`` :  `None` for this subtask
        """
        self.log.warn(
            "GetCoaddAsTemplateTask is deprecated. Use GetTemplateTask instead."
        )
        skyMap = butlerQC.get(skyMapRef)
        coaddExposureRefs = butlerQC.get(coaddExposureRefs)
        tracts = [ref.dataId['tract'] for ref in coaddExposureRefs]
        if tracts.count(tracts[0]) == len(tracts):
            tractInfo = skyMap[tracts[0]]
        else:
            raise RuntimeError(
                "Templates constructed from multiple Tracts not supported by this task. "
                "Use GetTemplateTask instead.")

        detectorBBox = exposure.getBBox()
        detectorWcs = exposure.getWcs()
        detectorCorners = detectorWcs.pixelToSky(
            geom.Box2D(detectorBBox).getCorners())
        validPolygon = exposure.getInfo().getValidPolygon()
        detectorPolygon = validPolygon if validPolygon else geom.Box2D(
            detectorBBox)

        availableCoaddRefs = dict()
        overlappingArea = 0
        for coaddRef in coaddExposureRefs:
            dataId = coaddRef.dataId
            patchWcs = skyMap[dataId['tract']].getWcs()
            patchBBox = skyMap[dataId['tract']][dataId['patch']].getOuterBBox()
            patchCorners = patchWcs.pixelToSky(
                geom.Box2D(patchBBox).getCorners())
            patchPolygon = afwGeom.Polygon(
                detectorWcs.skyToPixel(patchCorners))
            if patchPolygon.intersection(detectorPolygon):
                overlappingArea += patchPolygon.intersectionSingle(
                    detectorPolygon).calculateArea()
                if self.config.coaddName == 'dcr':
                    self.log.info(
                        "Using template input tract=%s, patch=%s, subfilter=%s",
                        dataId['tract'], dataId['patch'], dataId['subfilter'])
                    if dataId['patch'] in availableCoaddRefs:
                        availableCoaddRefs[dataId['patch']].append(coaddRef)
                    else:
                        availableCoaddRefs[dataId['patch']] = [
                            coaddRef,
                        ]
                else:
                    self.log.info("Using template input tract=%s, patch=%s",
                                  dataId['tract'], dataId['patch'])
                    availableCoaddRefs[dataId['patch']] = coaddRef

        if overlappingArea == 0:
            templateExposure = None
            pixGood = 0
            self.log.warning("No overlapping template patches found")
        else:
            patchList = [
                tractInfo[patch] for patch in availableCoaddRefs.keys()
            ]
            templateExposure = self.run(
                tractInfo,
                patchList,
                detectorCorners,
                availableCoaddRefs,
                visitInfo=exposure.getInfo().getVisitInfo())
            # Count the number of pixels with the NO_DATA mask bit set
            # counting NaN pixels is insufficient because pixels without data are often intepolated over)
            pixNoData = np.count_nonzero(
                templateExposure.mask.array
                & templateExposure.mask.getPlaneBitMask('NO_DATA'))
            pixGood = templateExposure.getBBox().getArea() - pixNoData
            self.log.info("template has %d good pixels (%.1f%%)", pixGood,
                          100 * pixGood / templateExposure.getBBox().getArea())
        return pipeBase.Struct(exposure=templateExposure,
                               sources=None,
                               area=pixGood)
예제 #29
0
    def cutout_from_pos(self, params: dict):
        """ Get cutout of source image by supported SODA shapes:
                POS: CIRCLE, RANGE, POLYGON
                LSST extension: BRECT

        Parameters
        ----------
        params: `dict`
            the POS parameter.
        Returns
        -------
        cutout: `afwImage.Exposure`

        """
        _pos = params["POS"]
        _id = params["ID"]
        db, ds, filt = _id.split(".")
        pos_items = _pos.split()
        shape = pos_items[0]
        if shape == "BRECT":
            if len(pos_items) < 6:
                raise Exception("BRECT: invalid parameters")
            ra, dec = float(pos_items[1]), float(pos_items[2])
            w, h = float(pos_items[3]), float(pos_items[4])
            unit_size = pos_items[5]
            cutout = self.cutout_from_nearest(ra, dec, w, h, unit_size, filt)
            return cutout
        elif shape == "CIRCLE":
            if len(pos_items) < 4:
                raise Exception("CIRCLE: invalid parameters")
            ra, dec = float(pos_items[1]), float(pos_items[2])
            radius = float(pos_items[3])
            # convert from deg to pixels by wcs (ICRS)
            q_result = self._metaservget.nearest_image_containing(ra, dec, filt)
            data_id = self._data_id_from_qr(q_result)
            metadata = self._metadata_from_data_id(data_id)
            wcs = afwGeom.makeSkyWcs(metadata, strip=False)
            pix_r = int(radius / wcs.getPixelScale().asArcseconds())
            ss = SpanSet.fromShape(pix_r)
            ss_width = ss.getBBox().getWidth()
            src_image = self.cutout_from_nearest(ra, dec, ss_width, ss_width,
                                                 "pixel", filt)
            src_cutout = src_image.getMaskedImage()
            circle_cutout = afwImage.MaskedImageF(src_cutout.getBBox())
            spanset = SpanSet.fromShape(pix_r, Stencil.CIRCLE,
                                      offset=src_cutout.getXY0() +
                                             afwGeom.Extent2I(pix_r, pix_r))
            spanset.copyMaskedImage(src_cutout, circle_cutout)
            # make an Exposure cutout with WCS info
            cutout = afwImage.ExposureF(circle_cutout,
                                            afwImage.ExposureInfo(wcs))
            return cutout
        elif shape == "RANGE":
            if len(pos_items) < 5:
                raise Exception("RANGE: invalid parameters")
            # convert the pair of (ra,dec) to bbox
            ra1, ra2 = float(pos_items[1]), float(pos_items[2])
            dec1, dec2 = float(pos_items[3]), float(pos_items[4])
            box = afwGeom.Box2D(afwGeom.Point2D(ra1, dec1),
                                  afwGeom.Point2D(ra2, dec2))
            # convert from deg to arcsec
            w = box.getWidth()*3600
            h = box.getHeight()*3600
            # compute the arithmetic center (ra, dec) of the range
            ra = (ra1 + ra2) / 2
            dec = (dec1 + dec2) / 2
            cutout = self.cutout_from_nearest(ra, dec, w, h, "arcsec", filt)
            return cutout
        elif shape == "POLYGON":
            if len(pos_items) < 7:
                raise Exception("POLYGON: invalid parameters")
            vertices = []
            pos_items.pop(0)
            for long, lat in zip(pos_items[::2], pos_items[1::2]):
                pt = afwGeom.Point2D(float(long), float(lat))
                vertices.append(pt)
            polygon = afwGeom.Polygon(vertices)
            center = polygon.calculateCenter()
            ra, dec = center.getX(), center.getY()
            # afw limitation: can only return the bbox of the polygon
            bbox = polygon.getBBox()
            # convert from 'deg' to 'arcsec'
            w = bbox.getWidth()*3600
            h = bbox.getHeight()*3600
            cutout = self.cutout_from_nearest(ra, dec, w, h, "arcsec", filt)
            return cutout
예제 #30
0
    def build_ccd_input_map(self, bbox, wcs, ccds):
        """Build a map from ccd valid polygons or bounding boxes.

        Parameters
        ----------
        bbox : `lsst.geom.Box2I`
            Bounding box for region to build input map.
        wcs : `lsst.afw.geom.SkyWcs`
            WCS object for region to build input map.
        ccds : `lsst.afw.table.ExposureCatalog`
            Exposure catalog with ccd data from coadd inputs.
        """
        with warnings.catch_warnings():
            # Healsparse will emit a warning if nside coverage is greater than
            # 128.  In the case of generating patch input maps, and not global
            # maps, high nside coverage works fine, so we can suppress this
            # warning.
            warnings.simplefilter("ignore")
            self.ccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage,
                                                              nside_sparse=self.config.nside,
                                                              dtype=hsp.WIDE_MASK,
                                                              wide_mask_maxbits=len(ccds))
        self._wcs = wcs
        self._bbox = bbox
        self._ccds = ccds

        pixel_scale = wcs.getPixelScale().asArcseconds()
        hpix_area_arcsec2 = hp.nside2pixarea(self.config.nside, degrees=True)*(3600.**2.)
        self._min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.)

        metadata = {}
        self._bits_per_visit_ccd = {}
        self._bits_per_visit = defaultdict(list)
        for bit, ccd_row in enumerate(ccds):
            metadata[f"B{bit:04d}CCD"] = ccd_row["ccd"]
            metadata[f"B{bit:04d}VIS"] = ccd_row["visit"]
            metadata[f"B{bit:04d}WT"] = ccd_row["weight"]

            self._bits_per_visit_ccd[(ccd_row["visit"], ccd_row["ccd"])] = bit
            self._bits_per_visit[ccd_row["visit"]].append(bit)

            ccd_poly = ccd_row.getValidPolygon()
            if ccd_poly is None:
                ccd_poly = afwGeom.Polygon(lsst.geom.Box2D(ccd_row.getBBox()))
            # Detectors need to be rendered with their own wcs.
            ccd_poly_radec = self._pixels_to_radec(ccd_row.getWcs(), ccd_poly.convexHull().getVertices())

            # Create a ccd healsparse polygon
            poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0],
                               dec=ccd_poly_radec[: -1, 1],
                               value=[bit])
            self.ccd_input_map.set_bits_pix(poly.get_pixels(nside=self.ccd_input_map.nside_sparse),
                                            [bit])

        # Cut down to the overall bounding box with associated wcs.
        bbox_afw_poly = afwGeom.Polygon(lsst.geom.Box2D(bbox))
        bbox_poly_radec = self._pixels_to_radec(self._wcs,
                                                bbox_afw_poly.convexHull().getVertices())
        bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1],
                                value=np.arange(self.ccd_input_map.wide_mask_maxbits))
        bbox_poly_map = bbox_poly.get_map_like(self.ccd_input_map)
        self.ccd_input_map = hsp.and_intersection([self.ccd_input_map, bbox_poly_map])
        self.ccd_input_map.metadata = metadata

        # Create a temporary map to hold the count of bad pixels in each healpix pixel
        self._ccd_input_pixels = self.ccd_input_map.valid_pixels

        dtype = [(f"v{visit}", np.int64) for visit in self._bits_per_visit.keys()]

        with warnings.catch_warnings():
            # Healsparse will emit a warning if nside coverage is greater than
            # 128.  In the case of generating patch input maps, and not global
            # maps, high nside coverage works fine, so we can suppress this
            # warning.
            warnings.simplefilter("ignore")
            self._ccd_input_bad_count_map = hsp.HealSparseMap.make_empty(
                nside_coverage=self.config.nside_coverage,
                nside_sparse=self.config.nside,
                dtype=dtype,
                primary=dtype[0][0])

        # Don't set input bad map if there are no ccds which overlap the bbox.
        if len(self._ccd_input_pixels) > 0:
            self._ccd_input_bad_count_map[self._ccd_input_pixels] = np.zeros(1, dtype=dtype)