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))
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)])
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))
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)
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)
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
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
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
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)
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))])
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)
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)))
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
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)])
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." )
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
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
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)
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)
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
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)
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)
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)
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
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)