def _createMetadata(bbox, wcs, filterName):
    """
    Create match metadata entries required for regenerating the catalog

    @param bbox  bounding box of image (pixels)
    @param filterName Name of filter, used for magnitudes
    @return Metadata
    """
    meta = dafBase.PropertyList()

    bboxD = geom.Box2D(bbox)
    cx, cy = bboxD.getCenter()
    radec = wcs.pixelToSky(cx, cy)
    meta.add('RA', radec.getRa().asDegrees(), 'field center in degrees')
    meta.add('DEC', radec.getDec().asDegrees(), 'field center in degrees')
    pixelRadius = math.hypot(*bboxD.getDimensions())/2.0
    skyRadius = wcs.getPixelScale() * pixelRadius
    meta.add('RADIUS', skyRadius.asDegrees(),
             'field radius in degrees, approximate')
    meta.add('SMATCHV', 1, 'SourceMatchVector version number')
    if filterName is not None:
        meta.add('FILTER', str(filterName), 'LSST filter name for tagalong data')
    return meta
示例#2
0
    def setUp(self):
        simpleMapConfig = skyMap.discreteSkyMap.DiscreteSkyMapConfig()
        simpleMapConfig.raList = [10, 11]
        simpleMapConfig.decList = [-1, -1]
        simpleMapConfig.radiusList = [0.1, 0.1]

        self.simpleMap = skyMap.DiscreteSkyMap(simpleMapConfig)
        self.tractId = 0
        self.patchId = 10

        self.skyInfo = makeSkyInfo(self.simpleMap, self.tractId, self.patchId)
        self.innerPatchBox = geom.Box2D(self.skyInfo.patchInfo.getInnerBBox())

        self.nSources = 100
        xs = np.linspace(self.innerPatchBox.getMinX() + 1,
                         self.innerPatchBox.getMaxX() - 1, self.nSources)
        ys = np.linspace(self.innerPatchBox.getMinY() + 1,
                         self.innerPatchBox.getMaxY() - 1, self.nSources)

        dataIn = []
        dataOut = []
        for x, y in zip(xs, ys):
            coordIn = self.skyInfo.wcs.pixelToSky(x, y)
            coordOut = self.skyInfo.wcs.pixelToSky(
                x + 10 * self.innerPatchBox.getWidth(),
                y + 10 * self.innerPatchBox.getHeight())
            dataIn.append({
                "ra": coordIn.getRa().asDegrees(),
                "decl": coordIn.getDec().asDegrees()
            })
            dataOut.append({
                "ra": coordOut.getRa().asDegrees(),
                "decl": coordOut.getDec().asDegrees()
            })

        self.diaSrcCatIn = pd.DataFrame(data=dataIn)
        self.diaSrcCatOut = pd.DataFrame(data=dataOut)
示例#3
0
    def getValidImageCorners(self, imageWcs, imageBox, patchPoly, dataId=None):
        "Return corners or None if bad"
        try:
            imageCorners = [
                imageWcs.pixelToSky(pix)
                for pix in geom.Box2D(imageBox).getCorners()
            ]
        except (pexExceptions.DomainError, pexExceptions.RuntimeError) as e:
            # Protecting ourselves from awful Wcs solutions in input images
            self.log.debug("WCS error in testing calexp %s (%s): deselecting",
                           dataId, e)
            return

        imagePoly = lsst.sphgeom.ConvexPolygon.convexHull(
            [coord.getVector() for coord in imageCorners])
        if imagePoly is None:
            self.log.debug(
                "Unable to create polygon from image %s: deselecting", dataId)
            return

        if patchPoly.intersects(imagePoly):
            # "intersects" also covers "contains" or "is contained by"
            self.log.info("Selecting calexp %s" % dataId)
            return imageCorners
示例#4
0
def plotSkyMap3d(skyMap):
    fig = plt.figure()
    ax = fig.gca(projection="3d")
    ax.set_axis_off()

    # make sure a complete 1x1x1 cube is shown -- what I really want is to constrain the aspect ratio
    # but that is not yet supported for 3D plots
    for direction in (-1, 1):
        for point in numpy.diag(direction * numpy.array([1, 1, 1])):
            ax.plot([point[0]], [point[1]], [point[2]], 'w')

    for tractInfo in skyMap:
        # display outer edge; scale to be approximately in the same plane as the inner region
        wcs = tractInfo.getWcs()
        posBox = geom.Box2D(tractInfo.getBBox())
        xRange = posBox.getMinX(), posBox.getMaxX()
        yRange = posBox.getMinY(), posBox.getMaxY()

        numX = min(50, max(1, ((xRange[1] - xRange[0]) // 100)))
        numY = min(50, max(1, ((yRange[1] - yRange[0]) // 100)))

        outerPixPosList = \
            [(x1, yRange[0]) for x1 in numpy.linspace(xRange[0], xRange[1], num=numX, endpoint=False)] \
            + [(xRange[1], y1) for y1 in numpy.linspace(yRange[0], yRange[1], num=numY, endpoint=False)] \
            + [(x2, yRange[1]) for x2 in numpy.linspace(xRange[1], xRange[0], num=numX, endpoint=False)] \
            + [(xRange[0], y2) for y2 in numpy.linspace(yRange[1], yRange[0], num=numY, endpoint=False)]
        outerPixPosList.append(outerPixPosList[0])

        outerPoints = [
            numpy.array(wcs.pixelToSky(p[0], p[1]).getVector())
            for p in outerPixPosList
        ]
        outX, outY, outZ = zip(*outerPoints)
        ax.plot(outX, outY, outZ)

    plt.show()
示例#5
0
 def runQuantum(self, butlerQC, inputRefs, outputRefs):
     inputs = butlerQC.get(inputRefs)
     oid = outputRefs.outputCatalog.dataId.byName()
     skymap = inputs['skyMap']
     del inputs['skyMap']
     box, wcs = self.get_box_wcs(skymap, oid)
     # Cast to float to handle fractional pixels
     box = geom.Box2D(box)
     inputs['vIds'] = [
         butlerQC.registry.expandDataId(el.dataId)
         for el in inputRefs.source_catalogs
     ]
     inputs['wcs'] = wcs
     inputs['box'] = box
     inputs['apply_external_wcs'] = self.config.apply_external_wcs
     if inputs['apply_external_wcs'] and not inputs['astrom_calibs']:
         self.log.warn(
             'Task configured to apply an external WCS, but no external WCS datasets found.'
         )
     if not inputs[
             'astrom_calibs']:  # Fill with None if jointcal wcs doesn't exist
         inputs['astrom_calibs'] = [None for el in inputs['photo_calibs']]
     outputs = self.run(**inputs)
     butlerQC.put(outputs, outputRefs)
示例#6
0
    def warpSources(self, inputSources, newWcs, templateWcs, templateBBox):
        """Warp sources to the new frame

        It would be difficult to transform all possible quantities of potential
        interest between the two frames.  We therefore update only the sky and
        pixel coordinates.

        @param inputSources: Sources on input exposure, to be warped
        @param newWcs: Revised Wcs for input exposure
        @param templateWcs: Target Wcs
        @param templateBBox: Target bounding box
        @return Warped sources
        """
        alignedSources = inputSources.copy(True)
        if not isinstance(templateBBox, geom.Box2D):
            # There is no method Box2I::contains(Point2D)
            templateBBox = geom.Box2D(templateBBox)
        table = alignedSources.getTable()
        coordKey = table.getCoordKey()
        centroidKey = table.getCentroidSlot().getMeasKey()
        deleteList = []
        for i, s in enumerate(alignedSources):
            oldCentroid = s.get(centroidKey)
            newCoord = newWcs.pixelToSky(oldCentroid)
            newCentroid = templateWcs.skyToPixel(newCoord)
            if not templateBBox.contains(newCentroid):
                deleteList.append(i)
                continue
            s.set(coordKey, newCoord)
            s.set(centroidKey, newCentroid)

        for i in reversed(
                deleteList):  # Delete from back so we don't change indices
            del alignedSources[i]

        return alignedSources
示例#7
0
    def matchBackgrounds(self, refExposure, sciExposure):
        """
        Match science exposure's background level to that of reference exposure.

        Process creates a difference image of the reference exposure minus the science exposure, and then
        generates an afw.math.Background object. It assumes (but does not require/check) that the mask plane
        already has detections set. If detections have not been set/masked, sources will bias the
        background estimation.
        The 'background' of the difference image is smoothed by spline interpolation (by the Background class)
        or by polynomial interpolation by the Approximate class. This model of difference image
        is added to the science exposure in memory.
        Fit diagnostics are also calculated and returned.

        @param[in] refExposure: reference exposure
        @param[in,out] sciExposure: science exposure; modified by changing the background level
            to match that of the reference exposure
        @returns a pipBase.Struct with fields:
            - backgroundModel: an afw.math.Approximate or an afw.math.Background.
            - fitRMS: rms of the fit. This is the sqrt(mean(residuals**2)).
            - matchedMSE: the MSE of the reference and matched images: mean((refImage - matchedSciImage)**2);
              should be comparable to difference image's mean variance.
            - diffImVar: the mean variance of the difference image.
        """

        if lsstDebug.Info(__name__).savefits:
            refExposure.writeFits(
                lsstDebug.Info(__name__).figpath + 'refExposure.fits')
            sciExposure.writeFits(
                lsstDebug.Info(__name__).figpath + 'sciExposure.fits')

        # Check Configs for polynomials:
        if self.config.usePolynomial:
            x, y = sciExposure.getDimensions()
            shortSideLength = min(x, y)
            if shortSideLength < self.config.binSize:
                raise ValueError(
                    "%d = config.binSize > shorter dimension = %d" %
                    (self.config.binSize, shortSideLength))
            npoints = shortSideLength // self.config.binSize
            if shortSideLength % self.config.binSize != 0:
                npoints += 1

            if self.config.order > npoints - 1:
                raise ValueError("%d = config.order > npoints - 1 = %d" %
                                 (self.config.order, npoints - 1))

        # Check that exposures are same shape
        if (sciExposure.getDimensions() != refExposure.getDimensions()):
            wSci, hSci = sciExposure.getDimensions()
            wRef, hRef = refExposure.getDimensions()
            raise RuntimeError(
                "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)"
                % (wSci, hSci, wRef, hRef))

        statsFlag = getattr(afwMath, self.config.gridStatistic)
        self.sctrl.setNumSigmaClip(self.config.numSigmaClip)
        self.sctrl.setNumIter(self.config.numIter)

        im = refExposure.getMaskedImage()
        diffMI = im.Factory(im, True)
        diffMI -= sciExposure.getMaskedImage()

        width = diffMI.getWidth()
        height = diffMI.getHeight()
        nx = width // self.config.binSize
        if width % self.config.binSize != 0:
            nx += 1
        ny = height // self.config.binSize
        if height % self.config.binSize != 0:
            ny += 1

        bctrl = afwMath.BackgroundControl(nx, ny, self.sctrl, statsFlag)
        bctrl.setUndersampleStyle(self.config.undersampleStyle)

        bkgd = afwMath.makeBackground(diffMI, bctrl)

        # Some config and input checks if config.usePolynomial:
        # 1) Check that order/bin size make sense:
        # 2) Change binsize or order if underconstrained.
        if self.config.usePolynomial:
            order = self.config.order
            bgX, bgY, bgZ, bgdZ = self._gridImage(diffMI, self.config.binSize,
                                                  statsFlag)
            minNumberGridPoints = min(len(set(bgX)), len(set(bgY)))
            if len(bgZ) == 0:
                raise ValueError("No overlap with reference. Nothing to match")
            elif minNumberGridPoints <= self.config.order:
                # must either lower order or raise number of bins or throw exception
                if self.config.undersampleStyle == "THROW_EXCEPTION":
                    raise ValueError(
                        "Image does not cover enough of ref image for order and binsize"
                    )
                elif self.config.undersampleStyle == "REDUCE_INTERP_ORDER":
                    self.log.warning("Reducing order to %d",
                                     (minNumberGridPoints - 1))
                    order = minNumberGridPoints - 1
                elif self.config.undersampleStyle == "INCREASE_NXNYSAMPLE":
                    newBinSize = (minNumberGridPoints * self.config.binSize
                                  ) // (self.config.order + 1)
                    bctrl.setNxSample(newBinSize)
                    bctrl.setNySample(newBinSize)
                    bkgd = afwMath.makeBackground(diffMI, bctrl)  # do over
                    self.log.warning("Decreasing binsize to %d", newBinSize)

            # If there is no variance in any image pixels, do not weight bins by inverse variance
            isUniformImageDiff = not numpy.any(
                bgdZ > self.config.gridStdevEpsilon)
            weightByInverseVariance = False if isUniformImageDiff else self.config.approxWeighting

        # Add offset to sciExposure
        try:
            if self.config.usePolynomial:
                actrl = afwMath.ApproximateControl(
                    afwMath.ApproximateControl.CHEBYSHEV, order, order,
                    weightByInverseVariance)
                undersampleStyle = getattr(afwMath,
                                           self.config.undersampleStyle)
                approx = bkgd.getApproximate(actrl, undersampleStyle)
                bkgdImage = approx.getImage()
            else:
                bkgdImage = bkgd.getImageF(self.config.interpStyle,
                                           self.config.undersampleStyle)
        except Exception as e:
            raise RuntimeError(
                "Background/Approximation failed to interp image %s: %s" %
                (self.debugDataIdString, e))

        sciMI = sciExposure.getMaskedImage()
        sciMI += bkgdImage
        del sciMI

        # Need RMS from fit: 2895 will replace this:
        rms = 0.0
        X, Y, Z, dZ = self._gridImage(diffMI, self.config.binSize, statsFlag)
        x0, y0 = diffMI.getXY0()
        modelValueArr = numpy.empty(len(Z))
        for i in range(len(X)):
            modelValueArr[i] = bkgdImage[int(X[i] - x0),
                                         int(Y[i] - y0), afwImage.LOCAL]
        resids = Z - modelValueArr
        rms = numpy.sqrt(numpy.mean(resids[~numpy.isnan(resids)]**2))

        if lsstDebug.Info(__name__).savefits:
            sciExposure.writeFits(
                lsstDebug.Info(__name__).figpath + 'sciMatchedExposure.fits')

        if lsstDebug.Info(__name__).savefig:
            bbox = geom.Box2D(refExposure.getMaskedImage().getBBox())
            try:
                self._debugPlot(X, Y, Z, dZ, bkgdImage, bbox, modelValueArr,
                                resids)
            except Exception as e:
                self.log.warning('Debug plot not generated: %s', e)

        meanVar = afwMath.makeStatistics(diffMI.getVariance(),
                                         diffMI.getMask(), afwMath.MEANCLIP,
                                         self.sctrl).getValue()

        diffIm = diffMI.getImage()
        diffIm -= bkgdImage  # diffMI should now have a mean ~ 0
        del diffIm
        mse = afwMath.makeStatistics(diffMI, afwMath.MEANSQUARE,
                                     self.sctrl).getValue()

        outBkgd = approx if self.config.usePolynomial else bkgd

        return pipeBase.Struct(backgroundModel=outBkgd,
                               fitRMS=rms,
                               matchedMSE=mse,
                               diffImVar=meanVar)
示例#8
0
 def testMutators(self):
     box = geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 1), True)
     box.grow(1)
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(3, 2), True))
     box.grow(geom.Extent2D(2, 3))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-5, -7), geom.Point2D(5, 5), True))
     box.shift(geom.Extent2D(3, 2))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-2, -5), geom.Point2D(8, 7), True))
     box.include(geom.Point2D(-4, 2))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-4, -5), geom.Point2D(8, 7), True))
     self.assertTrue(box.contains(geom.Point2D(-4, 2)))
     box.include(geom.Point2D(0, -6))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-4, -6), geom.Point2D(8, 7), True))
     box.include(geom.Box2D(geom.Point2D(0, 0), geom.Point2D(10, 11), True))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(-4, -6), geom.Point2D(10, 11), True))
     box.clip(geom.Box2D(geom.Point2D(0, 0), geom.Point2D(11, 12), True))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(0, 0), geom.Point2D(10, 11), True))
     box.clip(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(5, 4), True))
     self.assertEqual(
         box, geom.Box2D(geom.Point2D(0, 0), geom.Point2D(5, 4), True))
示例#9
0
 def testRelations(self):
     box = geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 1), True)
     inPoints = [
         geom.Point2D(0, 0),
         geom.Point2D(-2, -3),
     ]
     outPoints = [
         geom.Point2D(2, -3),
         geom.Point2D(2, 1),
         geom.Point2D(-2, 1),
     ]
     for point in inPoints:
         with self.subTest(point=point):
             self.assertTrue(box.contains(point))
     for point in outPoints:
         with self.subTest(point=point):
             self.assertFalse(box.contains(point))
     inX, inY = zip(*inPoints)
     outX, outY = zip(*outPoints)
     self.assertTrue(
         box.contains(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(1, 0))))
     self.assertTrue(box.contains(box))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 2))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(3, 1))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-3, -3), geom.Point2D(2, 1))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(2, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 2))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(3, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-3, -3), geom.Point2D(2, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-3, -4), geom.Point2D(2, 1))))
     self.assertTrue(
         box.overlaps(geom.Box2D(geom.Point2D(-1, -2), geom.Point2D(1, 0))))
     self.assertTrue(box.overlaps(box))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-5, -3), geom.Point2D(-3,
                                                                    1))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -6), geom.Point2D(2,
                                                                    -4))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(3, -3), geom.Point2D(4, 1))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-2, 2), geom.Point2D(2, 2))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-2, -5), geom.Point2D(2,
                                                                    -3))))
     self.assertFalse(
         box.overlaps(geom.Box2D(geom.Point2D(-4, -3), geom.Point2D(-2,
                                                                    1))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(-2, 1), geom.Point2D(2, 3))))
     self.assertFalse(
         box.contains(geom.Box2D(geom.Point2D(2, -3), geom.Point2D(4, 1))))
示例#10
0
    def runAssociation(self, cache, dataIdList, selectDataList):
        """! Run association on a patch
        For all of the visits that overlap this patch in the band create a DIAObject
        catalog.  Only the objects in the non-overlaping area of the tract and patch
        are included.
        """
        dataRefList = [
            getDataRef(cache.butler, dataId,
                       self.config.coaddName + "Coadd_calexp")
            for dataId in dataIdList
        ]

        # We need the WCS for the patch, so we can use the first entry in the dataIdList
        dataRef = dataRefList[0]
        tract = dataRef.dataId['tract']
        skyInfo = getSkyInfo(coaddName=self.config.coaddName, patchRef=dataRef)
        skyMap = skyInfo.skyMap
        try:
            calexp = dataRef.get(f"{self.config.coaddName}Coadd_calexp")
        except Exception:
            self.log.info('Cannot read coadd data for %s' % (dataRef.dataId))
            return

        coaddWcs = calexp.getWcs()
        innerPatchBox = geom.Box2D(skyInfo.patchInfo.getInnerBBox())

        expBits = dataRef.get("deepMergedCoaddId_bits")
        expId = int(dataRef.get("deepMergedCoaddId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)

        if len(selectDataList) == 0:
            differenceImages = self.catalogGenerator
        else:
            differenceImages = self.idListGenerator

        initializeSelector = False
        for diffIm in differenceImages(cache, dataRefList, selectDataList):

            if initializeSelector is False:
                self.associator.initialize(diffIm.src.schema, idFactory)
                initializeSelector = True

            if len(diffIm.src) == 0:
                continue

            srcWcs = diffIm.exp.getWcs()
            isInside = np.array([
                innerPatchBox.contains(
                    coaddWcs.skyToPixel(srcWcs.pixelToSky(a.getCentroid())))
                for a in diffIm.src
            ],
                                dtype=bool)

            isGood = np.array([
                rec.getFootprint().contains(geom.Point2I(rec.getCentroid()))
                for rec in diffIm.src
            ], )

            isInnerTract = np.array([
                skyMap.findTract(srcWcs.pixelToSky(
                    a.getCentroid())).getId() == tract for a in diffIm.src
            ])

            mask = (isInside) & (isGood) & (isInnerTract)

            src = diffIm.src[mask]
            if len(src) == 0:
                continue

            self.log.info(
                'Reading difference image %d %d, %s with %d possible sources' %
                (diffIm.visit, diffIm.ccd, diffIm.filter, len(src)))

            footprints = []
            region = calexp.getBBox(afwImage.PARENT)
            for ii, rec in enumerate(src):
                # transformations on large footprints can take a long time
                # We truncate the footprint since we will rarely be interested
                # in such large footprints
                if rec.getFootprint().getArea() > self.config.maxFootprintArea:
                    spans = afwGeom.SpanSet.fromShape(
                        self.config.defaultFootprintRadius,
                        afwGeom.Stencil.CIRCLE,
                        geom.Point2I(rec.getCentroid()))
                    foot = afwDet.Footprint(spans)
                    foot.addPeak(int(rec.getX()), int(rec.getY()), 1)
                else:
                    foot = rec.getFootprint()
                footprints.append(foot.transform(srcWcs, coaddWcs, region))

            self.associator.addCatalog(src, diffIm.filter, diffIm.visit,
                                       diffIm.ccd, diffIm.calib, footprints)

        result = self.associator.finalize(idFactory)

        if len(dataRefList) > 0 and result is not None:
            dataRefList[0].put(result,
                               self.config.coaddName + 'Diff_diaObject')
            self.log.info('Total objects found %d' % len(result))

            idCatalog = self.associator.getObjectIds()
            dataRefList[0].put(idCatalog,
                               self.config.coaddName + 'Diff_diaObjectId')
示例#11
0
def _findDetectorsListLSST(focalPointList,
                           detectorList,
                           possible_points,
                           allow_multiple_chips=False):
    """!Find the detectors that cover a list of points specified by x and y coordinates in any system

    This is based one afw.camerGeom.camera.findDetectorsList.  It has been optimized for the LSST
    camera in the following way:

        - it accepts a limited list of detectors to check in advance (this list should be
          constructed by comparing the pupil coordinates in question and comparing to the
          pupil coordinates of the center of each detector)

       - it will stop looping through detectors one it has found one that is correct (the LSST
         camera does not allow an object to fall on more than one detector)

    @param[in] focalPointList  a list of points in FOCAL_PLANE coordinates

    @param[in] detectorList is a list of the afwCameraGeom detector objects being considered

    @param[in] possible_points is a list of lists.  possible_points[ii] is a list of integers
    corresponding to the indices in focalPointList of the pupilPoints that may be on detectorList[ii].

    @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not
    this method will allow objects to be visible on more than one chip.  If it is 'False'
    and an object appears on more than one chip, only the first chip will appear in the list of
    chipNames but NO WARNING WILL BE EMITTED.  If it is 'True' and an object falls on more than one
    chip, a list of chipNames will appear for that object.

    @return outputNameList is a numpy array of the names of the detectors
    """
    # transform the points to the native coordinate system
    #
    # The conversion to a numpy array looks a little clunky.
    # The problem, if you do the naive thing (nativePointList = np.array(lsst_camera().....),
    # the conversion to a numpy array gets passed down to the contents of nativePointList
    # and they end up in a form that the afwCameraGeom code does not know how to handle
    nativePointList = np.zeros(len(focalPointList), dtype=object)
    for ii in range(len(focalPointList)):
        nativePointList[ii] = focalPointList[ii]

    # initialize output and some caching lists
    outputNameList = [None] * len(focalPointList)
    chip_has_found = np.array([-1] * len(focalPointList))
    unfound_pts = len(chip_has_found)

    # Figure out if any of these (RA, Dec) pairs could be
    # on more than one chip.  This is possible on the
    # wavefront sensors, since adjoining wavefront sensors
    # are kept one in focus, one out of focus.
    # See figure 2 of arXiv:1506.04839v2
    # (This might actually be a bug in obs_lsstSim
    # I opened DM-8075 on 25 October 2016 to investigate)
    could_be_multiple = [False] * len(focalPointList)
    if allow_multiple_chips:
        for ipt in range(len(focalPointList)):
            for det in detectorList[ipt]:
                if det.getType() == WAVEFRONT:
                    could_be_multiple[ipt] = True

    # t_assemble_list = 0.0
    # loop over detectors
    for i_detector, detector in enumerate(detectorList):
        if len(possible_points[i_detector]) == 0:
            continue

        if unfound_pts <= 0:
            if unfound_pts < 0:
                raise RuntimeError(
                    "Somehow, unfound_pts = %d in _findDetectorsListLSST" %
                    unfound_pts)
            # we have already found all of the (RA, Dec) pairs
            for ix, name in enumerate(outputNameList):
                if isinstance(name, list):
                    outputNameList[ix] = str(name)
            return np.array(outputNameList)

        # find all of the pupil points that could be on this detector
        valid_pt_dexes = possible_points[i_detector][np.where(
            chip_has_found[possible_points[i_detector]] < 0)]

        if len(valid_pt_dexes) > 0:
            valid_pt_list = nativePointList[valid_pt_dexes]
            transform = detector.getTransform(lsst_camera()._nativeCameraSys,
                                              PIXELS)
            detectorPointList = transform.applyForward(valid_pt_list)

            box = geom.Box2D(detector.getBBox())
            for ix, pt in zip(valid_pt_dexes, detectorPointList):
                if box.contains(pt):
                    if not could_be_multiple[ix]:
                        # because this (RA, Dec) pair is not marked as could_be_multiple,
                        # the fact that this (RA, Dec) pair is on the current chip
                        # means this (RA, Dec) pair no longer needs to be considered.
                        # You can set chip_has_found[ix] to unity.
                        outputNameList[ix] = detector.getName()
                        chip_has_found[ix] = 1
                        unfound_pts -= 1
                    else:
                        # Since this (RA, Dec) pair has been makred could_be_multiple,
                        # finding this (RA, Dec) pair on the chip does not remove the
                        # (RA, Dec) pair from contention.
                        if outputNameList[ix] is None:
                            outputNameList[ix] = detector.getName()
                        elif isinstance(outputNameList[ix], list):
                            outputNameList[ix].append(detector.getName())
                        else:
                            outputNameList[ix] = [
                                outputNameList[ix],
                                detector.getName()
                            ]

    # convert entries corresponding to multiple chips into strings
    # (i.e. [R:2,2 S:0,0, R:2,2 S:0,1] becomes `[R:2,2 S:0,0, R:2,2 S:0,1]`)
    for ix, name in enumerate(outputNameList):
        if isinstance(name, list):
            outputNameList[ix] = str(name)

    # print('t_assemble %.2e' % t_assemble_list)

    return np.array(outputNameList)
示例#12
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)
示例#13
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)
示例#14
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)
示例#15
0
    def run(self,
            tractInfo,
            patchList,
            skyCorners,
            availableCoaddRefs,
            sensorRef=None,
            visitInfo=None):
        """Gen2 and gen3 shared code: determination of exposure dimensions and
        copying of pixels from overlapping patch regions.

        Parameters
        ----------
        skyMap : `lsst.skymap.BaseSkyMap`
            SkyMap object that corresponds to the template coadd.
        tractInfo : `lsst.skymap.TractInfo`
            The selected tract.
        patchList : iterable of `lsst.skymap.patchInfo.PatchInfo`
            Patches to consider for making the template exposure.
        skyCorners : list of `lsst.geom.SpherePoint`
            Sky corner coordinates to be covered by the template exposure.
        availableCoaddRefs : `dict` [`int`]
            Dictionary of spatially relevant retrieved coadd patches,
            indexed by their sequential patch number. In Gen3 mode, values are
            `lsst.daf.butler.DeferredDatasetHandle` and ``.get()`` is called,
            in Gen2 mode, ``sensorRef.get(**coaddef)`` is called to retrieve the coadd.
        sensorRef : `lsst.daf.persistence.ButlerDataRef`, Gen2 only
            Butler data reference to get coadd data.
            Must be `None` for Gen3.
        visitInfo : `lsst.afw.image.VisitInfo`, Gen2 only
            VisitInfo to make dcr model.

        Returns
        -------
        templateExposure: `lsst.afw.image.ExposureF`
            The created template exposure.
        """
        coaddWcs = tractInfo.getWcs()

        # compute coadd bbox
        coaddBBox = geom.Box2D()
        for skyPos in skyCorners:
            coaddBBox.include(coaddWcs.skyToPixel(skyPos))
        coaddBBox = geom.Box2I(coaddBBox)
        self.log.info("coadd dimensions=%s", coaddBBox.getDimensions())

        coaddExposure = afwImage.ExposureF(coaddBBox, coaddWcs)
        coaddExposure.maskedImage.set(np.nan,
                                      afwImage.Mask.getPlaneBitMask("NO_DATA"),
                                      np.nan)
        nPatchesFound = 0
        coaddFilterLabel = None
        coaddPsf = None
        coaddPhotoCalib = None
        for patchInfo in patchList:
            patchNumber = tractInfo.getSequentialPatchIndex(patchInfo)
            patchSubBBox = patchInfo.getOuterBBox()
            patchSubBBox.clip(coaddBBox)
            if patchNumber not in availableCoaddRefs:
                self.log.warning(
                    "skip patch=%d; patch does not exist for this coadd",
                    patchNumber)
                continue
            if patchSubBBox.isEmpty():
                if isinstance(availableCoaddRefs[patchNumber],
                              DeferredDatasetHandle):
                    tract = availableCoaddRefs[patchNumber].dataId['tract']
                else:
                    tract = availableCoaddRefs[patchNumber]['tract']
                self.log.info("skip tract=%d patch=%d; no overlapping pixels",
                              tract, patchNumber)
                continue

            if self.config.coaddName == 'dcr':
                patchInnerBBox = patchInfo.getInnerBBox()
                patchInnerBBox.clip(coaddBBox)
                if np.min(patchInnerBBox.getDimensions()
                          ) <= 2 * self.config.templateBorderSize:
                    self.log.info(
                        "skip tract=%(tract)s, patch=%(patch)s; too few pixels.",
                        availableCoaddRefs[patchNumber])
                    continue
                self.log.info("Constructing DCR-matched template for patch %s",
                              availableCoaddRefs[patchNumber])

                dcrModel = DcrModel.fromQuantum(
                    availableCoaddRefs[patchNumber],
                    self.config.effectiveWavelength, self.config.bandwidth)
                # The edge pixels of the DcrCoadd may contain artifacts due to missing data.
                # Each patch has significant overlap, and the contaminated edge pixels in
                # a new patch will overwrite good pixels in the overlap region from
                # previous patches.
                # Shrink the BBox to remove the contaminated pixels,
                # but make sure it is only the overlap region that is reduced.
                dcrBBox = geom.Box2I(patchSubBBox)
                dcrBBox.grow(-self.config.templateBorderSize)
                dcrBBox.include(patchInnerBBox)
                coaddPatch = dcrModel.buildMatchedExposure(bbox=dcrBBox,
                                                           visitInfo=visitInfo)
            else:
                if sensorRef is None:
                    # Gen3
                    coaddPatch = availableCoaddRefs[patchNumber].get()
                else:
                    # Gen2
                    coaddPatch = sensorRef.get(
                        **availableCoaddRefs[patchNumber])
            nPatchesFound += 1

            # Gen2 get() seems to clip based on bbox kwarg but we removed bbox
            # calculation from caller code. Gen3 also does not do this.
            overlapBox = coaddPatch.getBBox()
            overlapBox.clip(coaddBBox)
            coaddExposure.maskedImage.assign(
                coaddPatch.maskedImage[overlapBox], overlapBox)

            if coaddFilterLabel is None:
                coaddFilterLabel = coaddPatch.getFilter()

            # Retrieve the PSF for this coadd tract, if not already retrieved
            if coaddPsf is None and coaddPatch.hasPsf():
                coaddPsf = coaddPatch.getPsf()

            # Retrieve the calibration for this coadd tract, if not already retrieved
            if coaddPhotoCalib is None:
                coaddPhotoCalib = coaddPatch.getPhotoCalib()

        if coaddPhotoCalib is None:
            raise RuntimeError("No coadd PhotoCalib found!")
        if nPatchesFound == 0:
            raise RuntimeError("No patches found!")
        if coaddPsf is None:
            raise RuntimeError("No coadd Psf found!")

        coaddExposure.setPhotoCalib(coaddPhotoCalib)
        coaddExposure.setPsf(coaddPsf)
        coaddExposure.setFilter(coaddFilterLabel)
        return coaddExposure
示例#16
0
    def fetchInPatches(self, butler, exposure, tract, patchList, band):
        """!
        Get the reference catalogs from a given tract,patchlist
        This will remove objects where the child is inside the catalog boundary, but
        the parent is outside the boundary.

        @param[in]  butler     A Butler used to get the reference catalogs
        @param[in]  exposure   A deepDiff_exposure on which to run the measurements
        @param[in]  tract      The tract
        @param[in]  patchList  A list of patches that need to be checked
.

        @return    Combined SourceCatalog from all the patches
        """
        dataset = f"{self.config.coaddName}Coadd_meas"
        catalog = None

        for patch in patchList:
            dataId = {
                'tract': tract.getId(),
                'patch': "%d,%d" % patch.getIndex()
            }

            dataId['filter'] = band
            self.log.info("Getting references in %s" % (dataId, ))
            if not butler.datasetExists(dataset, dataId):
                if self.config.skipMissing:
                    self.log.info("Could not find %s for dataset %s" %
                                  (dataId, dataset))
                    continue
                raise pipeBase.TaskError("Reference %s doesn't exist" %
                                         (dataId, ))

            new_catalog = butler.get(dataset, dataId, immediate=True)
            patchBox = geom.Box2D(patch.getOuterBBox())
            tractWcs = tract.getWcs()
            expBox = geom.Box2D(exposure.getBBox())
            expWcs = exposure.getWcs()

            # only use objects that overlap patch bounding box and overlap exposure
            validPatch = np.array([
                patchBox.contains(tractWcs.skyToPixel(s.getCoord()))
                for s in new_catalog
            ])

            validExposure = np.array([
                expBox.contains(expWcs.skyToPixel(s.getCoord()))
                for s in new_catalog
            ])

            if validPatch.size == 0 or validExposure.size == 0:
                self.log.debug("No valid sources %s for dataset %s" %
                               (dataId, dataset))
                continue

            if catalog is None:
                catalog = new_catalog[validPatch & validExposure]
            else:
                catalog.extend(new_catalog[validPatch & validExposure])

        if catalog is None:
            return None
        # if the parent is not in the catalog remove
        refCatIdDict = {ref.getId(): ref.getParent() for ref in catalog}
        refCatIdDict[0] = 0
        parentGood = np.array(
            [refCatIdDict[ref.getId()] in refCatIdDict for ref in catalog])
        if np.sum(parentGood == False) > 1:
            self.log.info("Removing %d/%d objects without parents" %
                          (np.sum(parentGood == False), len(parentGood)))
            catalog = catalog.copy(deep=True)[parentGood]

        return catalog
示例#17
0
    def __init__(self, detectorName, cameraWrapper, obs_metadata, epoch, photParams=None):
        """
        @param [in] detectorName is the name of the detector as stored
        by afw

        @param [in] cameraWrapper is an instantionat of a GalSimCameraWrapper

        @param [in] photParams is an instantiation of the PhotometricParameters class that carries
        details about the photometric response of the telescope.

        This class will generate its own internal variable self.fileName which is
        the name of the detector as it will appear in the output FITS files
        """

        if not isinstance(cameraWrapper, GalSimCameraWrapper):
            raise RuntimeError("You must pass GalSimDetector an instantiation "
                               "of GalSimCameraWrapper or one of its daughter "
                               "classes")

        if detectorName not in cameraWrapper.camera:
            raise RuntimeError("detectorName needs to be in the camera wrapped by "
                               "cameraWrapper when instantiating a GalSimDetector\n"
                               "%s is not in your cameraWrapper.camera" % detectorName)

        if photParams is None:
            raise RuntimeError("You need to specify an instantiation of PhotometricParameters " +
                               "when constructing a GalSimDetector")

        self._wcs = None  # this will be created when it is actually called for
        self._name = detectorName
        self._cameraWrapper = cameraWrapper
        self._obs_metadata = obs_metadata
        self._epoch = epoch
        self._detector_type = self._cameraWrapper.camera[self._name].getType()

        # Default Tree Ring properties, i.e., no tree rings:
        self._tree_rings = TreeRingInfo(galsim.PositionD(0, 0), None)

        # We are transposing the coordinates because of the difference
        # between how DM defines pixel coordinates and how the
        # Camera team defines pixel coordinates
        bbox = self._cameraWrapper.getBBox(self._name)
        self._xMinPix = bbox.getMinX()
        self._xMaxPix = bbox.getMaxX()
        self._yMinPix = bbox.getMinY()
        self._yMaxPix = bbox.getMaxY()

        self._bbox = LsstGeom.Box2D(bbox)

        centerPupil = self._cameraWrapper.getCenterPupil(self._name)
        self._xCenterArcsec = arcsecFromRadians(centerPupil.getX())
        self._yCenterArcsec = arcsecFromRadians(centerPupil.getY())

        centerPixel = self._cameraWrapper.getCenterPixel(self._name)
        self._xCenterPix = centerPixel.getX()
        self._yCenterPix = centerPixel.getY()

        self._xMinArcsec = None
        self._yMinArcsec = None
        self._xMaxArcsec = None
        self._yMaxArcsec = None

        for cameraPointPupil in self._cameraWrapper.getCornerPupilList(self._name):

            xx = arcsecFromRadians(cameraPointPupil.getX())
            yy = arcsecFromRadians(cameraPointPupil.getY())
            if self._xMinArcsec is None or xx < self._xMinArcsec:
                self._xMinArcsec = xx
            if self._xMaxArcsec is None or xx > self._xMaxArcsec:
                self._xMaxArcsec = xx
            if self._yMinArcsec is None or yy < self._yMinArcsec:
                self._yMinArcsec = yy
            if self._yMaxArcsec is None or yy > self._yMaxArcsec:
                self._yMaxArcsec = yy

        self._photParams = photParams
        self._fileName = self._getFileName()
示例#18
0
def main(rootDir,
         tracts,
         visits,
         ccds=None,
         ccdKey='ccd',
         showPatch=False,
         saveFile=None,
         showCcds=False):
    butler = dafPersist.Butler(rootDir)
    camera = butler.get("camera")

    # draw the CCDs
    ras, decs = [], []
    bboxesPlotted = []
    print('Number of visits = ', len(visits))
    cmap = get_cmap(len(visits))
    for i_v, visit in enumerate(visits):
        print("%r visit=%r" % (i_v + 1, visit))
        inLegend = False
        color = cmap(i_v)
        for ccd in camera:
            bbox = ccd.getBBox()
            ccdId = int(ccd.getSerial())

            if (ccds is None
                    or ccdId in ccds) and ccd.getType() == cameraGeom.SCIENCE:
                dataId = {'visit': visit, ccdKey: ccdId}
                try:
                    md = butler.get("calexp_md", dataId)
                    wcs = afwGeom.makeSkyWcs(md)

                    ra, dec = bboxToRaDec(bbox, wcs)
                    ras += ra
                    decs += dec
                    if not inLegend:
                        pyplot.fill(ra,
                                    dec,
                                    fill=True,
                                    alpha=0.2,
                                    color=color,
                                    edgecolor=color,
                                    label=str(visit))
                        inLegend = True
                    else:
                        pyplot.fill(ra,
                                    dec,
                                    fill=True,
                                    alpha=0.2,
                                    color=color,
                                    edgecolor=color)

                    # add CCD serial numbers
                    if showCcds:
                        minPoint = geom.Point2D(min(ra), min(dec))
                        maxPoint = geom.Point2D(max(ra), max(dec))
                        # Use doubles in Box2D to check overlap
                        bboxDouble = geom.Box2D(minPoint, maxPoint)
                        overlaps = [
                            not bboxDouble.overlaps(otherBbox)
                            for otherBbox in bboxesPlotted
                        ]
                        if all(overlaps):
                            pyplot.text(percent(ra),
                                        percent(dec),
                                        str(ccdId),
                                        fontsize=6,
                                        horizontalalignment='center',
                                        verticalalignment='center',
                                        color=color)
                            pyplot.fill(ra,
                                        dec,
                                        fill=False,
                                        alpha=0.5,
                                        color=color,
                                        edgecolor=color)
                        bboxesPlotted.append(bboxDouble)
                except Exception:
                    pass

    buff = 0.1
    xlim = max(ras) + buff, min(ras) - buff
    ylim = min(decs) - buff, max(decs) + buff

    skymap = butler.get("deepCoadd_skyMap")
    # draw the skymap
    if showPatch:
        alpha0 = 1.0
        for i_t, tract in enumerate(tracts):
            alpha = max(0.1, alpha0 - i_t * 1.0 / len(tracts))
            inLegend = False
            for tractInfo in skymap:
                if tractInfo.getId() == tract:
                    for patch in tractInfo:
                        ra, dec = bboxToRaDec(patch.getInnerBBox(),
                                              tractInfo.getWcs())
                        if not inLegend:
                            pyplot.fill(ra,
                                        dec,
                                        fill=False,
                                        edgecolor='k',
                                        lw=1,
                                        linestyle='dashed',
                                        alpha=alpha,
                                        label=str(tract))
                            inLegend = True
                        else:
                            pyplot.fill(ra,
                                        dec,
                                        fill=False,
                                        edgecolor='k',
                                        lw=1,
                                        linestyle='dashed',
                                        alpha=alpha)
                        if xlim[1] < percent(ra) < xlim[0] and ylim[
                                0] < percent(dec) < ylim[1]:
                            pyplot.text(percent(ra),
                                        percent(dec),
                                        ((str(patch.getIndex()))[1:-1].replace(
                                            " ", "")),
                                        fontsize=6,
                                        horizontalalignment='center',
                                        verticalalignment='center',
                                        alpha=alpha)

    # add labels and save
    ax = pyplot.gca()
    ax.set_xlabel("R.A. (deg)")
    ax.set_ylabel("Decl. (deg)")
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    ax.legend(loc='center left',
              bbox_to_anchor=(1.0, 0.5),
              fancybox=True,
              shadow=True,
              fontsize=6)
    fig = pyplot.gcf()
    if saveFile is not None:
        fig.savefig(saveFile)
    else:
        fig.show()
示例#19
0
def getCornerCoords(wcs, bbox):
    """Return the coords of the four corners of a bounding box
    """
    cornerPosList = geom.Box2D(bbox).getCorners()
    return wcs.pixelToSky(cornerPosList)
示例#20
0
    def run(self, exposure, sensorRef, templateIdList=None):
        """Retrieve and mosaic a template coadd that overlaps the exposure where
        the template spans multiple tracts.

        The resulting template image will be an average of all the input templates from
        the separate tracts.

        The PSF on the template is created by combining the CoaddPsf on each template image
        into a meta-CoaddPsf.

        Parameters
        ----------
        exposure: `lsst.afw.image.Exposure`
            an exposure for which to generate an overlapping template
        sensorRef : TYPE
            a Butler data reference that can be used to obtain coadd data
        templateIdList : TYPE, optional
            list of data ids (unused)
        Returns
        -------
        result : `struct`
            return a pipeBase.Struct:
            - ``exposure`` : a template coadd exposure assembled out of patches
            - ``sources`` :  None for this subtask
        """

        # 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)

        skyMap = sensorRef.get(datasetType=self.config.coaddName +
                               "Coadd_skyMap")
        expWcs = exposure.getWcs()
        expBoxD = geom.Box2D(exposure.getBBox())
        expBoxD.grow(self.config.templateBorderSize)
        ctrSkyPos = expWcs.pixelToSky(expBoxD.getCenter())

        centralTractInfo = skyMap.findTract(ctrSkyPos)
        if not centralTractInfo:
            raise RuntimeError("No suitable tract found for central point")

        self.log.info("Central skyMap tract %s" % (centralTractInfo.getId(), ))

        skyCorners = [
            expWcs.pixelToSky(pixPos) for pixPos in expBoxD.getCorners()
        ]
        tractPatchList = skyMap.findTractPatchList(skyCorners)
        if not tractPatchList:
            raise RuntimeError("No suitable tract found")

        self.log.info("All overlapping skyMap tracts %s" %
                      ([a[0].getId() for a in tractPatchList]))

        # Move central tract to front of the list and use as the reference
        tracts = [tract[0].getId() for tract in tractPatchList]
        centralIndex = tracts.index(centralTractInfo.getId())
        tracts.insert(0, tracts.pop(centralIndex))
        tractPatchList.insert(0, tractPatchList.pop(centralIndex))

        coaddPsf = None
        coaddFilter = None
        nPatchesFound = 0

        maskedImageList = []
        weightList = []

        for itract, tract in enumerate(tracts):
            tractInfo = tractPatchList[itract][0]

            coaddWcs = tractInfo.getWcs()
            coaddBBox = geom.Box2D()
            for skyPos in skyCorners:
                coaddBBox.include(coaddWcs.skyToPixel(skyPos))
            coaddBBox = geom.Box2I(coaddBBox)

            if itract == 0:
                # Define final wcs and bounding box from the reference tract
                finalWcs = coaddWcs
                finalBBox = coaddBBox

            patchList = tractPatchList[itract][1]
            for patchInfo in patchList:
                self.log.info('Adding patch %s from tract %s' %
                              (patchInfo.getIndex(), tract))

                # Local patch information
                patchSubBBox = geom.Box2I(patchInfo.getInnerBBox())
                patchSubBBox.clip(coaddBBox)
                patchInt = int(
                    f"{patchInfo.getIndex()[0]}{patchInfo.getIndex()[1]}")
                innerBBox = geom.Box2I(tractInfo._minimumBoundingBox(finalWcs))

                if itract == 0:
                    # clip to image and tract boundaries
                    patchSubBBox.clip(finalBBox)
                    patchSubBBox.clip(innerBBox)
                    if patchSubBBox.getArea() == 0:
                        self.log.debug("No ovlerap for patch %s" % patchInfo)
                        continue

                    patchArgDict = dict(
                        datasetType="deepCoadd_sub",
                        bbox=patchSubBBox,
                        tract=tractInfo.getId(),
                        patch="%s,%s" %
                        (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
                        filter=exposure.getFilter().getName())
                    coaddPatch = sensorRef.get(**patchArgDict)
                    if coaddFilter is None:
                        coaddFilter = coaddPatch.getFilter()

                    # create full image from final bounding box
                    exp = afwImage.ExposureF(finalBBox, finalWcs)
                    exp.maskedImage.set(
                        np.nan, afwImage.Mask.getPlaneBitMask("NO_DATA"),
                        np.nan)
                    exp.maskedImage.assign(coaddPatch.maskedImage,
                                           patchSubBBox)

                    maskedImageList.append(exp.maskedImage)
                    weightList.append(1)

                    record = tractsCatalog.addNew()
                    record.setPsf(coaddPatch.getPsf())
                    record.setWcs(coaddPatch.getWcs())
                    record.setPhotoCalib(coaddPatch.getPhotoCalib())
                    record.setBBox(patchSubBBox)
                    record.set(tractKey, tract)
                    record.set(patchKey, patchInt)
                    record.set(weightKey, 1.)
                    nPatchesFound += 1
                else:
                    # compute the exposure bounding box in a tract that is not the reference tract
                    localBox = geom.Box2I()
                    for skyPos in skyCorners:
                        localBox.include(
                            geom.Point2I(
                                tractInfo.getWcs().skyToPixel(skyPos)))

                    # clip to patch bounding box
                    localBox.clip(patchSubBBox)

                    # grow border to deal with warping at edges
                    localBox.grow(self.config.templateBorderSize)

                    # clip to tract inner bounding box
                    localInnerBBox = geom.Box2I(
                        tractInfo._minimumBoundingBox(tractInfo.getWcs()))
                    localBox.clip(localInnerBBox)

                    patchArgDict = dict(
                        datasetType="deepCoadd_sub",
                        bbox=localBox,
                        tract=tractInfo.getId(),
                        patch="%s,%s" %
                        (patchInfo.getIndex()[0], patchInfo.getIndex()[1]),
                        filter=exposure.getFilter().getName())
                    coaddPatch = sensorRef.get(**patchArgDict)

                    # warp to reference tract wcs
                    xyTransform = afwGeom.makeWcsPairTransform(
                        coaddPatch.getWcs(), finalWcs)
                    psfWarped = WarpedPsf(coaddPatch.getPsf(), xyTransform)
                    warped = self.warper.warpExposure(finalWcs,
                                                      coaddPatch,
                                                      maxBBox=finalBBox)

                    # check if warpped image is viable
                    if warped.getBBox().getArea() == 0:
                        self.log.info(
                            "No ovlerap for warped patch %s. Skipping" %
                            patchInfo)
                        continue

                    warped.setPsf(psfWarped)

                    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(psfWarped)
                    record.setWcs(finalWcs)
                    record.setPhotoCalib(coaddPatch.getPhotoCalib())
                    record.setBBox(warped.getBBox())
                    record.set(tractKey, tract)
                    record.set(patchKey, patchInt)
                    record.set(weightKey, 1.)
                    nPatchesFound += 1

        if nPatchesFound == 0:
            raise RuntimeError("No patches found!")

        # Combine images from individual patches together

        # Do not mask any values
        statsFlags = afwMath.stringToStatisticsProperty(self.config.statistic)
        maskMap = []
        statsCtrl = afwMath.StatisticsControl()
        statsCtrl.setNanSafe(True)
        statsCtrl.setWeighted(True)
        statsCtrl.setCalcErrorFromInputVariance(True)

        coaddExposure = afwImage.ExposureF(finalBBox, finalWcs)
        coaddExposure.maskedImage.set(np.nan,
                                      afwImage.Mask.getPlaneBitMask("NO_DATA"),
                                      np.nan)
        xy0 = coaddExposure.getXY0()
        coaddExposure.maskedImage = afwMath.statisticsStack(
            maskedImageList, statsFlags, statsCtrl, weightList, 0, maskMap)
        coaddExposure.maskedImage.setXY0(xy0)

        coaddPsf = CoaddPsf(tractsCatalog, finalWcs,
                            self.config.coaddPsf.makeControl())
        if coaddPsf is None:
            raise RuntimeError("No coadd Psf found!")

        coaddExposure.setPsf(coaddPsf)
        coaddExposure.setFilter(coaddFilter)
        return pipeBase.Struct(exposure=coaddExposure, sources=None)
示例#21
0
def plot_tract_patches(skyMap,
                       tract=0,
                       title=None,
                       ax=None,
                       patch_colors=None):
    """
    Plot the patches for a tract from a skyMap.

    Parameters
    ----------
    skyMap: lsst.skyMap.SkyMap
        The SkyMap object containing the tract and patch information.
    tract: int [0]
        The tract id of the desired tract to plot.
    title: str [None]
        Title of the tract plot.  If None, the use `tract <id>`.
    ax: matplotlib.axes._subplots.AxesSubplot [None]
        The subplot object to contain the tract plot.  If None, then
        make a new one.
    patch_colors: dict [None]
        Dictionary of colors keyed by patchId.

    Returns
    -------
    matplotlib.axes._subplots.AxesSubplot: The subplot containing the
    tract plot.
    """
    if title is None:
        title = 'tract {}'.format(tract)
    tract_info = skyMap[tract]
    tractBox = lsst_geom.Box2D(tract_info.getBBox())
    tractPosList = tractBox.getCorners()
    wcs = tract_info.getWcs()
    xNum, yNum = tract_info.getNumPatches()

    if ax is None:
        fig = plt.figure(figsize=(12, 8))
        ax = fig.add_subplot(111)

    tract_center = wcs.pixelToSky(tractBox.getCenter())\
                      .getPosition(lsst_geom.degrees)
    ax.text(tract_center[0],
            tract_center[1],
            '%d' % tract,
            size=16,
            ha="center",
            va="center",
            color='blue')
    for x in range(xNum):
        for y in range(yNum):
            patch_info = tract_info.getPatchInfo([x, y])
            patchBox = lsst_geom.Box2D(patch_info.getOuterBBox())
            pixelPatchList = patchBox.getCorners()
            path = make_patch(pixelPatchList, wcs)
            try:
                color = patch_colors[(x, y)]
            except (TypeError, KeyError):
                color = 'blue'
            patch = patches.PathPatch(path, alpha=0.1, lw=1, color=color)
            ax.add_patch(patch)
            center = wcs.pixelToSky(patchBox.getCenter())\
                        .getPosition(lsst_geom.degrees)
            ax.text(center[0],
                    center[1],
                    '%d,%d' % (x, y),
                    size=6,
                    ha="center",
                    va="center")

    skyPosList = [
        wcs.pixelToSky(pos).getPosition(lsst_geom.degrees)
        for pos in tractPosList
    ]
    ax.set_xlim(
        max(coord[0] for coord in skyPosList) + 1,
        min(coord[0] for coord in skyPosList) - 1)
    ax.set_ylim(
        min(coord[1] for coord in skyPosList) - 1,
        max(coord[1] for coord in skyPosList) + 1)
    ax.grid(ls=':', color='gray')
    ax.set_xlabel("RA (deg.)")
    ax.set_ylabel("Dec (deg.)")
    ax.set_title(title)
    return ax
示例#22
0
    def run(self,
            diaSourceTables,
            skyMap,
            tractId,
            patchId,
            tractPatchId,
            skymapBits):
        """Trim DiaSources to the current Patch and run association.

        Takes in the set of DiaSource catalogs that covers the current patch,
        trims them to the dimensions of the patch, and [TODO: eventually]
        runs association on the concatenated DiaSource Catalog.

        Parameters
        ----------
        diaSourceTables : `list` of `lst.daf.butler.DeferredDatasetHandle`
            Set of DiaSource catalogs potentially covering this patch/tract.
        skyMap : `lsst.skymap.BaseSkyMap`
            SkyMap defining the patch/tract
        tractId : `int`
            Id of current tract being processed.
        patchId : `int`
            Id of current patch being processed

        Returns
        -------
        output : `lsst.pipe.base.Struct`
            Results struct with attributes:

            ``assocDiaSourceTable``
                Table of DiaSources with updated value for diaObjectId.
                (`pandas.DataFrame`)
            ``diaObjectTable``
                Table of DiaObjects from matching DiaSources
                (`pandas.DataFrame`).
        """
        self.log.info("Running DPR Association on patch %i, tract %i...",
                      patchId, tractId)

        skyInfo = makeSkyInfo(skyMap, tractId, patchId)

        # Get the patch bounding box.
        innerPatchBox = geom.Box2D(skyInfo.patchInfo.getInnerBBox())

        diaSourceHistory = []
        for catRef in diaSourceTables:
            cat = catRef.get(
                datasetType=self.config.connections.diaSourceTables,
                immediate=True)

            isInTractPatch = self._trimToPatch(cat,
                                               innerPatchBox,
                                               skyInfo.wcs)

            nDiaSrc = isInTractPatch.sum()
            self.log.info(
                "Read DiaSource catalog of length %i from visit %i, "
                "detector %i. Found %i sources within the patch/tract "
                "footprint.",
                len(cat), catRef.dataId["visit"],
                catRef.dataId["detector"], nDiaSrc)

            if nDiaSrc <= 0:
                continue

            cutCat = cat[isInTractPatch]
            diaSourceHistory.append(cutCat)

        if diaSourceHistory:
            diaSourceHistoryCat = pd.concat(diaSourceHistory)
        else:
            # No rows to associate
            if self.config.doWriteEmptyTables:
                self.log.info("Constructing empty table")
                # Construct empty table using last table and dropping all the rows
                diaSourceHistoryCat = cat.drop(cat.index)
            else:
                raise pipeBase.NoWorkFound("Found no overlapping DIASources to associate.")

        self.log.info("Found %i DiaSources overlapping patch %i, tract %i",
                      len(diaSourceHistoryCat), patchId, tractId)

        assocResult = self.associator.run(diaSourceHistoryCat,
                                          tractPatchId,
                                          skymapBits)

        self.log.info("Associated DiaSources into %i DiaObjects",
                      len(assocResult.diaObjects))

        if self.config.doAddDiaObjectCoords:
            assocResult.assocDiaSources = self._addDiaObjectCoords(assocResult.diaObjects,
                                                                   assocResult.assocDiaSources)

        return pipeBase.Struct(
            diaObjectTable=assocResult.diaObjects,
            assocDiaSourceTable=assocResult.assocDiaSources)
示例#23
0
    def setUp(self):
        """Create fake data to use in the tests.
        """
        self.bbox = geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1024, 1153))
        dataset = measTests.TestDataset(self.bbox)
        self.exposure = dataset.exposure

        simpleMapConfig = skyMap.discreteSkyMap.DiscreteSkyMapConfig()
        simpleMapConfig.raList = [
            dataset.exposure.getWcs().getSkyOrigin().getRa().asDegrees()
        ]
        simpleMapConfig.decList = [
            dataset.exposure.getWcs().getSkyOrigin().getDec().asDegrees()
        ]
        simpleMapConfig.radiusList = [0.1]

        self.simpleMap = skyMap.DiscreteSkyMap(simpleMapConfig)
        self.tractId = 0
        bCircle = self.simpleMap.generateTract(
            self.tractId).getInnerSkyPolygon().getBoundingCircle()
        bCenter = sphgeom.LonLat(bCircle.getCenter())
        bRadius = bCircle.getOpeningAngle().asRadians()
        targetSources = 10000
        self.sourceDensity = (targetSources / (bCircle.getArea() *
                                               (180 / np.pi)**2))
        self.rng = np.random.default_rng(1234)

        self.fakeCat = pd.DataFrame({
            "fakeId":
            [uuid.uuid4().int & (1 << 64) - 1 for n in range(targetSources)],
            # Quick-and-dirty values for testing
            "ra":
            bCenter.getLon().asRadians() + bRadius *
            (2.0 * self.rng.random(targetSources) - 1.0),
            "dec":
            bCenter.getLat().asRadians() + bRadius *
            (2.0 * self.rng.random(targetSources) - 1.0),
            "isVisitSource":
            np.concatenate([
                np.ones(targetSources // 2, dtype="bool"),
                np.zeros(targetSources - targetSources // 2, dtype="bool")
            ]),
            "isTemplateSource":
            np.concatenate([
                np.zeros(targetSources // 2, dtype="bool"),
                np.ones(targetSources - targetSources // 2, dtype="bool")
            ]),
            **{
                band: self.rng.uniform(20, 30, size=targetSources)
                for band in {"u", "g", "r", "i", "z", "y"}
            },
            "DiskHalfLightRadius":
            np.ones(targetSources, dtype="float"),
            "BulgeHalfLightRadius":
            np.ones(targetSources, dtype="float"),
            "disk_n":
            np.ones(targetSources, dtype="float"),
            "bulge_n":
            np.ones(targetSources, dtype="float"),
            "a_d":
            np.ones(targetSources, dtype="float"),
            "a_b":
            np.ones(targetSources, dtype="float"),
            "b_d":
            np.ones(targetSources, dtype="float"),
            "b_b":
            np.ones(targetSources, dtype="float"),
            "pa_disk":
            np.ones(targetSources, dtype="float"),
            "pa_bulge":
            np.ones(targetSources, dtype="float"),
            "sourceType":
            targetSources * ["star"],
        })

        self.inExp = np.zeros(len(self.fakeCat), dtype=bool)
        bbox = geom.Box2D(self.exposure.getBBox())
        for idx, row in self.fakeCat.iterrows():
            coord = geom.SpherePoint(row["ra"], row["dec"], geom.radians)
            cent = self.exposure.getWcs().skyToPixel(coord)
            self.inExp[idx] = bbox.contains(cent)

        tmpCat = self.fakeCat[self.inExp].iloc[:int(self.inExp.sum() / 2)]
        extraColumnData = self.rng.integers(0, 100, size=len(tmpCat))
        self.sourceCat = pd.DataFrame(
            data={
                "ra": np.degrees(tmpCat["ra"]),
                "decl": np.degrees(tmpCat["dec"]),
                "diaObjectId": np.arange(1, len(tmpCat) + 1, dtype=int),
                "filterName": "g",
                "diaSourceId": np.arange(1, len(tmpCat) + 1, dtype=int),
                "extraColumn": extraColumnData
            })
        self.sourceCat.set_index(["diaObjectId", "filterName", "extraColumn"],
                                 drop=False,
                                 inplace=True)
示例#24
0
def makeMockVisitSummary(visit,
                         ra_center=0.0,
                         dec_center=-45.0,
                         physical_filter='TEST-I',
                         band='i',
                         mjd=59234.7083333334,
                         psf_sigma=3.0,
                         zenith_distance=45.0,
                         zero_point=30.0,
                         sky_background=100.0,
                         sky_noise=10.0,
                         mean_var=100.0,
                         exposure_time=100.0,
                         detector_size=200,
                         pixel_scale=0.2):
    """Make a mock visit summary catalog.

    This will contain two square detectors with the same metadata,
    with a small (20 pixel) gap between the detectors.  There is no
    rotation, as each detector is simply offset in RA from the
    specified boresight.

    Parameters
    ----------
    visit : `int`
        Visit number.
    ra_center : `float`
        Right ascension of the center of the "camera" boresight (degrees).
    dec_center : `float`
        Declination of the center of the "camera" boresight (degrees).
    physical_filter : `str`
        Arbitrary name for the physical filter.
    band : `str`
        Name of the associated band.
    mjd : `float`
        Modified Julian Date.
    psf_sigma : `float`
        Sigma width of Gaussian psf.
    zenith_distance : `float`
        Distance from zenith of the visit (degrees).
    zero_point : `float`
        Constant zero point for the visit (magnitudes).
    sky_background : `float`
        Background level for the visit (counts).
    sky_noise : `float`
        Noise level for the background of the visit (counts).
    mean_var : `float`
        Mean of the variance plane of the visit (counts).
    exposure_time : `float`
        Exposure time of the visit (seconds).
    detector_size : `int`
        Size of each square detector in the visit (pixels).
    pixel_scale : `float`
        Size of the pixel in arcseconds per pixel.

    Returns
    -------
    visit_summary : `lsst.afw.table.ExposureCatalog`
    """
    # We are making a 2 detector "camera"
    n_detector = 2

    schema = ConsolidateVisitSummaryTask()._makeVisitSummarySchema()
    visit_summary = afwTable.ExposureCatalog(schema)
    visit_summary.resize(n_detector)

    bbox = geom.Box2I(x=geom.IntervalI(min=0, max=detector_size - 1),
                      y=geom.IntervalI(min=0, max=detector_size - 1))

    for detector_id in range(n_detector):
        row = visit_summary[detector_id]

        row['id'] = detector_id
        row.setBBox(bbox)
        row['visit'] = visit
        row['physical_filter'] = physical_filter
        row['band'] = band
        row['zenithDistance'] = zenith_distance
        row['zeroPoint'] = zero_point
        row['skyBg'] = sky_background
        row['skyNoise'] = sky_noise
        row['meanVar'] = mean_var

        # Generate a photocalib
        instFluxMag0 = 10.**(zero_point / 2.5)
        row.setPhotoCalib(
            afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0))

        # Generate a WCS and set values accordingly
        crpix = geom.Point2D(detector_size / 2., detector_size / 2.)
        # Create a 20 pixel gap between the two detectors (each offset 10 pixels).
        if detector_id == 0:
            delta_ra = -1.0 * ((detector_size + 10) * pixel_scale /
                               3600.) / np.cos(np.deg2rad(dec_center))
            delta_dec = 0.0
        elif detector_id == 1:
            delta_ra = ((detector_size + 10) * pixel_scale / 3600.) / np.cos(
                np.deg2rad(dec_center))
            delta_dec = 0.0
        crval = geom.SpherePoint(ra_center + delta_ra, dec_center + delta_dec,
                                 geom.degrees)
        cd_matrix = afwGeom.makeCdMatrix(scale=pixel_scale * geom.arcseconds,
                                         orientation=0.0 * geom.degrees)
        wcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cd_matrix)
        row.setWcs(wcs)

        sph_pts = wcs.pixelToSky(geom.Box2D(bbox).getCorners())
        row['raCorners'] = np.array(
            [float(sph.getRa().asDegrees()) for sph in sph_pts])
        row['decCorners'] = np.array(
            [float(sph.getDec().asDegrees()) for sph in sph_pts])
        sph_pt = wcs.pixelToSky(bbox.getCenter())
        row['ra'] = sph_pt.getRa().asDegrees()
        row['decl'] = sph_pt.getDec().asDegrees()

        # Generate a visitInfo.
        # This does not need to be consistent with the zenith angle in the table,
        # it just needs to be valid and have sufficient information to compute
        # exposure time and parallactic angle.
        date = DateTime(date=mjd, system=DateTime.DateSystem.MJD)
        visit_info = afwImage.VisitInfo(
            exposureId=visit,
            exposureTime=exposure_time,
            date=date,
            darkTime=0.0,
            boresightRaDec=geom.SpherePoint(ra_center, dec_center,
                                            geom.degrees),
            era=45.1 * geom.degrees,
            observatory=Observatory(11.1 * geom.degrees, 0.0 * geom.degrees,
                                    0.333),
            boresightRotAngle=0.0 * geom.degrees,
            rotType=afwImage.RotType.SKY)
        row.setVisitInfo(visit_info)

        # Generate a PSF and set values accordingly
        psf = GaussianPsf(15, 15, psf_sigma)
        row.setPsf(psf)
        psfAvgPos = psf.getAveragePosition()
        shape = psf.computeShape(psfAvgPos)
        row['psfSigma'] = psf.getSigma()
        row['psfIxx'] = shape.getIxx()
        row['psfIyy'] = shape.getIyy()
        row['psfIxy'] = shape.getIxy()
        row['psfArea'] = shape.getArea()

    return visit_summary
示例#25
0
def chipNameFromPupilCoordsLSST(xPupil_in,
                                yPupil_in,
                                allow_multiple_chips=False,
                                band='r'):
    """
    Return the names of LSST detectors that see the object specified by
    either (xPupil, yPupil).

    @param [in] xPupil_in is the x pupil coordinate in radians.
    Must be a numpy array.

    @param [in] yPupil_in is the y pupil coordinate in radians.
    Must be a numpy array.

    @param [in] allow_multiple_chips is a boolean (default False) indicating whether or not
    this method will allow objects to be visible on more than one chip.  If it is 'False'
    and an object appears on more than one chip, only the first chip will appear in the list of
    chipNames and warning will be emitted.  If it is 'True' and an object falls on more than one
    chip, a list of chipNames will appear for that object.

    @param[in] band is the bandpass being simulated (default='r')

    @param [out] a numpy array of chip names

    """
    if (not hasattr(chipNameFromPupilCoordsLSST, '_focal_map')
            or not hasattr(chipNameFromPupilCoordsLSST, '_detector_arr')
            or len(chipNameFromPupilCoordsLSST._detector_arr) == 0):
        focal_map = _build_lsst_focal_coord_map()
        chipNameFromPupilCoordsLSST._focal_map = focal_map
        camera = lsst_camera()
        detector_arr = np.zeros(len(focal_map['name']), dtype=object)
        for ii in range(len(focal_map['name'])):
            detector_arr[ii] = camera[focal_map['name'][ii]]

        chipNameFromPupilCoordsLSST._detector_arr = detector_arr

        # build a Box2D that contains all of the detectors in the camera
        focal_to_field = camera.getTransformMap().getTransform(
            FOCAL_PLANE, FIELD_ANGLE)
        focal_bbox = camera.getFpBBox()
        focal_corners = focal_bbox.getCorners()
        camera_bbox = geom.Box2D()
        x_focal_max = None
        x_focal_min = None
        y_focal_max = None
        y_focal_min = None
        for cc in focal_corners:
            xx = cc.getX()
            yy = cc.getY()
            if x_focal_max is None or xx > x_focal_max:
                x_focal_max = xx
            if x_focal_min is None or xx < x_focal_min:
                x_focal_min = xx
            if y_focal_max is None or yy > y_focal_max:
                y_focal_max = yy
            if y_focal_min is None or yy < y_focal_min:
                y_focal_min = yy

        chipNameFromPupilCoordsLSST._x_focal_center = 0.5 * (x_focal_max +
                                                             x_focal_min)
        chipNameFromPupilCoordsLSST._y_focal_center = 0.5 * (y_focal_max +
                                                             y_focal_min)

        radius_sq_max = None
        for cc in focal_corners:
            xx = cc.getX()
            yy = cc.getY()
            radius_sq = (
                (xx - chipNameFromPupilCoordsLSST._x_focal_center)**2 +
                (yy - chipNameFromPupilCoordsLSST._y_focal_center)**2)
            if radius_sq_max is None or radius_sq > radius_sq_max:
                radius_sq_max = radius_sq

        chipNameFromPupilCoordsLSST._camera_focal_radius_sq = radius_sq_max * 1.1

    are_arrays = _validate_inputs([xPupil_in, yPupil_in],
                                  ['xPupil_in', 'yPupil_in'],
                                  "chipNameFromPupilCoordsLSST")

    if not are_arrays:
        xPupil_in = np.array([xPupil_in])
        yPupil_in = np.array([yPupil_in])

    xFocal, yFocal = focalPlaneCoordsFromPupilCoordsLSST(xPupil_in,
                                                         yPupil_in,
                                                         band=band)

    radius_sq_list = (
        (xFocal - chipNameFromPupilCoordsLSST._x_focal_center)**2 +
        (yFocal - chipNameFromPupilCoordsLSST._y_focal_center)**2)

    with np.errstate(invalid='ignore'):
        good_radii = np.where(radius_sq_list < chipNameFromPupilCoordsLSST.
                              _camera_focal_radius_sq)

    if len(good_radii[0]) == 0:
        return np.array([None] * len(xPupil_in))

    xFocal_good = xFocal[good_radii]
    yFocal_good = yFocal[good_radii]

    ############################################################
    # in the code below, we will only consider those points which
    # passed the 'good_radii' test above; the other points will
    # be added in with chipName == None at the end
    #
    focalPointList = [
        geom.Point2D(xFocal[i_pt], yFocal[i_pt]) for i_pt in good_radii[0]
    ]

    # Loop through every detector on the camera.  For each detector, assemble a list of points
    # whose centers are within 1.1 detector radii of the center of the detector.

    x_cam_list = chipNameFromPupilCoordsLSST._focal_map['xx']
    y_cam_list = chipNameFromPupilCoordsLSST._focal_map['yy']
    rrsq_lim_list = (1.1 * chipNameFromPupilCoordsLSST._focal_map['dp'])**2

    possible_points = []
    for i_chip, (x_cam, y_cam, rrsq_lim) in \
    enumerate(zip(x_cam_list, y_cam_list, rrsq_lim_list)):

        local_possible_pts = np.where(((xFocal_good - x_cam)**2 +
                                       (yFocal_good - y_cam)**2) < rrsq_lim)[0]

        possible_points.append(local_possible_pts)

    nameList_good = _findDetectorsListLSST(
        focalPointList,
        chipNameFromPupilCoordsLSST._detector_arr,
        possible_points,
        allow_multiple_chips=allow_multiple_chips)

    ####################################################################
    # initialize output as an array of Nones, effectively adding back in
    # the points which failed the initial radius cut
    nameList = np.array([None] * len(xPupil_in))

    nameList[good_radii] = nameList_good

    if not are_arrays:
        return nameList[0]

    return nameList
示例#26
0
 def get(self, center_coord, width, height, filt, units):
     """Merge multiple patches from a SkyMap into a single image.
     This function takes advantage of the fact that all tracts in a SkyMap
     share the same WCS and should have identical pixels where they overlap.
     @center_coord: base coordinate of the box RA and Dec (minimum values,
     lower left corner)
     @width: in arcsec or pixel
     @height: in arcsec or pixel
     @filt: valid filter for the data set such as 'i', 'r', 'g'
     @units: 'pixel' or 'arcsecond' (defaults to 'pixel')
     """
     dest_tract_info = self._skymap.findTract(center_coord)
     dest_wcs = dest_tract_info.getWcs()
     # Determine target area.
     if units != "arcsec" and units != "arcsecond" and units != "pixel":
         units = "pixel"
     dest_bbox = self._bbox_for_coords(dest_wcs, center_coord, width,
                                       height, units)
     dest_corner_coords = [
         dest_wcs.pixelToSky(pixPos)
         for pixPos in geom.Box2D(dest_bbox).getCorners()
     ]
     # Collect patches of the SkyMap that are in the target region.
     # Create source exposures from the patches within each tract
     # as all patches from a tract share a WCS.
     exposure_list = []
     tract_patch_list = self._skymap.findTractPatchList(dest_corner_coords)
     for j, tract_patch in enumerate(tract_patch_list):
         tract_info = tract_patch[0]
         patch_list = tract_patch[1]
         self._log.info("tract_info[{}]={}".format(j, tract_info))
         self._log.info("patch_list[{}]={}".format(j, patch_list))
         src_wcs = tract_info.getWcs()
         src_bbox = geom.Box2I()
         for patch_info in patch_list:
             src_bbox.include(patch_info.getOuterBBox())
         src_exposure = afw_image.ExposureF(src_bbox, src_wcs)  # blank,
         # so far
         exposure_list.append(src_exposure)
         # load srcExposures with patches
         tract_id = tract_info.getId()
         for patch_info in patch_list:
             patch_index = patch_info.getIndex()
             patch_index_str = ','.join(str(i) for i in patch_index)
             self._log.info("butler.get dataId=filter:{}, tract:{}, "
                            "patch:{}".format(filt, tract_id,
                                              patch_index_str))
             patch_exposure = self._butler.get("deepCoadd",
                                               dataId={
                                                   "filter": filt,
                                                   "tract": tract_id,
                                                   "patch": patch_index_str
                                               })
             src_view = afw_image.ExposureF(src_exposure,
                                            patch_exposure.getBBox())
             src_view_img = src_view.getMaskedImage()
             patch_img = patch_exposure.getMaskedImage()
             src_view_img[:] = patch_img
     # Copy the pixels from the source exposures to the destination
     # exposures.
     dest_exposure_list = []
     for j, src_exposure in enumerate(exposure_list):
         src_image = src_exposure.getMaskedImage()
         src_wcs = src_exposure.getWcs()
         if j == 0:
             expo_bbox = dest_bbox  # dest_bbox only correct for first image
         else:
             # Determine the correct BBox (in pixels) for the current src_wcs
             ll_corner = geom.Point2I(
                 src_wcs.skyToPixel(dest_corner_coords[0]))
             ur_corner = geom.Point2I(
                 src_wcs.skyToPixel(dest_corner_coords[2]))
             # Handle negative values for in expo_bbox.
             if ll_corner.getX() < 0:
                 ll_corner.setX(0)
             if ll_corner.getY() < 0:
                 ll_corner.setY(0)
             if ur_corner.getX() < 0:
                 ur_corner.setX(0)
                 self._log.warn("getSkyMap negative X for ur_corner")
             if ur_corner.getY() < 0:
                 ur_corner.setY(0)
                 self._log.warn("getSkyMap negative Y for ur_corner")
             expo_bbox = geom.Box2I(ll_corner, ur_corner)
         self._log.info("j={} expo_bbox={} sBBox={}".format(
             j, expo_bbox, src_exposure.getBBox()))
         dest_exposure = afw_image.ExposureF(expo_bbox, src_wcs)
         dest_img = dest_exposure.getMaskedImage()
         begin_x = expo_bbox.getBeginX() - src_image.getX0()
         end_x = expo_bbox.getEndX() - src_image.getX0()
         begin_y = expo_bbox.getBeginY() - src_image.getY0()
         end_y = expo_bbox.getEndY() - src_image.getY0()
         new_width = src_exposure.getBBox().getEndX() - expo_bbox.getBeginX(
         )
         new_height = src_exposure.getBBox().getEndY(
         ) - expo_bbox.getBeginY()
         # Do a final check to make sure that the we're not going past the
         # end of src_image.
         src_img_len_x = src_image.getWidth()
         if end_x > src_img_len_x:
             new_width = src_img_len_x - begin_x
             end_x = src_img_len_x
         s_img_len_y = src_image.getHeight()
         if end_y > s_img_len_y:
             new_width = s_img_len_y - begin_y
             end_y = s_img_len_y
         self._log.debug("begin_x={} end_x={}".format(begin_x, end_x))
         self._log.debug(
             "new_width{} = sBBox.EndX{} - sBBox.BeginX{}".format(
                 new_width,
                 src_exposure.getBBox().getEndX(), expo_bbox.getBeginX()))
         self._log.debug("begin_y={} end_y={}".format(begin_y, end_y))
         self._log.debug(
             "new_height{} = sBBox.EndY{} - sBBox.BeginY{}".format(
                 new_height,
                 src_exposure.getBBox().getEndY(), expo_bbox.getBeginY()))
         if isinstance(src_exposure, afw_image.Exposure):
             dest_exposure = afw_image.ExposureF(src_exposure, expo_bbox)
         else:
             dest_img[0:new_width, 0:new_height] = src_image[begin_x:end_x,
                                                             begin_y:end_y]
         # notice that dest_img is operating on dest_exposure
         dest_exposure_list.append(dest_exposure)
     # If there's only one exposure in the list (and there usually is) just
     # return it.
     if len(dest_exposure_list) == 1:
         return dest_exposure_list[0]
     # Need to stitch together the multiple destination exposures.
     self._log.debug(
         "SkymapImage: stitching together multiple destExposures")
     warper_config = afw_math.WarperConfig()
     warper = afw_math.Warper.fromConfig(warper_config)
     stitched_exposure = self._stitch_exposures_good_pixel_copy(
         dest_wcs, dest_bbox, dest_exposure_list, warper)
     return stitched_exposure
    def setUp(self):
        """Create a sqlite3 database with default tables and schemas.
        """
        # CFHT Filters from the camera mapper.
        self.filter_names = ["u", "g", "r", "i", "z"]
        afwImageUtils.resetFilters()
        afwImageUtils.defineFilter('u', lambdaEff=374, alias="u.MP9301")
        afwImageUtils.defineFilter('g', lambdaEff=487, alias="g.MP9401")
        afwImageUtils.defineFilter('r', lambdaEff=628, alias="r.MP9601")
        afwImageUtils.defineFilter('i', lambdaEff=778, alias="i.MP9701")
        afwImageUtils.defineFilter('z', lambdaEff=1170, alias="z.MP9801")

        self.dia_object_schema = make_dia_object_schema()

        # metadata taken from CFHT data
        # v695856-e0/v695856-e0-c000-a00.sci_img.fits

        self.metadata = dafBase.PropertySet()

        self.metadata.set("SIMPLE", "T")
        self.metadata.set("BITPIX", -32)
        self.metadata.set("NAXIS", 2)
        self.metadata.set("NAXIS1", 1024)
        self.metadata.set("NAXIS2", 1153)
        self.metadata.set("RADECSYS", 'FK5')
        self.metadata.set("EQUINOX", 2000.)

        self.metadata.setDouble("CRVAL1", 215.604025685476)
        self.metadata.setDouble("CRVAL2", 53.1595451514076)
        self.metadata.setDouble("CRPIX1", 1109.99981456774)
        self.metadata.setDouble("CRPIX2", 560.018167811613)
        self.metadata.set("CTYPE1", 'RA---SIN')
        self.metadata.set("CTYPE2", 'DEC--SIN')

        self.metadata.setDouble("CD1_1", 5.10808596133527E-05)
        self.metadata.setDouble("CD1_2", 1.85579539217196E-07)
        self.metadata.setDouble("CD2_2", -5.10281493481982E-05)
        self.metadata.setDouble("CD2_1", -8.27440751733828E-07)

        self.wcs = afwGeom.makeSkyWcs(self.metadata)
        self.exposure = afwImage.makeExposure(
            afwImage.makeMaskedImageFromArrays(np.ones((1024, 1153))),
            self.wcs)
        detector = DetectorWrapper(id=23, bbox=self.exposure.getBBox()).detector
        visit = afwImage.VisitInfo(
            exposureId=1234,
            exposureTime=200.,
            date=dafBase.DateTime("2014-05-13T17:00:00.000000000",
                                  dafBase.DateTime.Timescale.TAI))
        self.exposure.setDetector(detector)
        self.exposure.getInfo().setVisitInfo(visit)
        self.exposure.setFilter(afwImage.Filter('g'))
        self.flux0 = 10000
        self.flux0_err = 100
        self.exposure.setPhotoCalib(
            afwImage.PhotoCalib(self.flux0, self.flux0_err))

        bbox = geom.Box2D(self.exposure.getBBox())
        wcs = self.exposure.getWcs()

        self.pixelator = sphgeom.HtmPixelization(20)
        region = sphgeom.ConvexPolygon([wcs.pixelToSky(pp).getVector()
                                        for pp in bbox.getCorners()])

        indices = self.pixelator.envelope(region, 64)
        # Index types must be cast to int to work with dax_apdb.
        self.index_ranges = indices.ranges()
示例#28
0
    def runDataRef(self,
                   dataRef,
                   coordList,
                   makeDataRefList=True,
                   selectDataList=[]):
        """Select images in the selectDataList that overlap the patch

        We use the "convexHull" method of lsst.sphgeom.ConvexPolygon to define
        polygons on the celestial sphere, and test the polygon of the
        patch for overlap with the polygon of the image.

        We use "convexHull" instead of generating a ConvexPolygon
        directly because the standard for the inputs to ConvexPolygon
        are pretty high and we don't want to be responsible for reaching them.

        @param dataRef: Data reference for coadd/tempExp (with tract, patch)
        @param coordList: List of ICRS coordinates (lsst.geom.SpherePoint) specifying boundary of patch
        @param makeDataRefList: Construct a list of data references?
        @param selectDataList: List of SelectStruct, to consider for selection
        """
        dataRefList = []
        exposureInfoList = []

        patchVertices = [coord.getVector() for coord in coordList]
        patchPoly = lsst.sphgeom.ConvexPolygon.convexHull(patchVertices)

        for data in selectDataList:
            dataRef = data.dataRef
            imageWcs = data.wcs
            imageBox = data.bbox

            try:
                imageCorners = [
                    imageWcs.pixelToSky(pix)
                    for pix in geom.Box2D(imageBox).getCorners()
                ]
            except (pexExceptions.DomainError,
                    pexExceptions.RuntimeError) as e:
                # Protecting ourselves from awful Wcs solutions in input images
                self.log.debug(
                    "WCS error in testing calexp %s (%s): deselecting",
                    dataRef.dataId, e)
                continue

            imagePoly = lsst.sphgeom.ConvexPolygon.convexHull(
                [coord.getVector() for coord in imageCorners])
            if imagePoly is None:
                self.log.debug(
                    "Unable to create polygon from image %s: deselecting",
                    dataRef.dataId)
                continue
            if patchPoly.intersects(
                    imagePoly
            ):  # "intersects" also covers "contains" or "is contained by"
                self.log.info("Selecting calexp %s" % dataRef.dataId)
                dataRefList.append(dataRef)
                exposureInfoList.append(
                    BaseExposureInfo(dataRef.dataId, imageCorners))

        return pipeBase.Struct(
            dataRefList=dataRefList if makeDataRefList else None,
            exposureInfoList=exposureInfoList,
        )
示例#29
0
 def makePolygon(wcs, bbox):
     """Return a polygon for the image, given Wcs and bounding box"""
     boxPixelCorners = geom.Box2D(bbox).getCorners()
     boxSkyCorners = wcs.pixelToSky(boxPixelCorners)
     return lsst.sphgeom.ConvexPolygon.convexHull(
         [coord.getVector() for coord in boxSkyCorners])
示例#30
0
 def testRelations(self):
     box = geom.Box2D(geom.Point2D(-2, -3), geom.Point2D(2, 1), True)
     self.assertTrue(box.contains(geom.Point2D(0, 0)))
     self.assertTrue(box.contains(geom.Point2D(-2, -3)))
     self.assertFalse(box.contains(geom.Point2D(2, -3)))
     self.assertFalse(box.contains(geom.Point2D(2, 1)))
     self.assertFalse(box.contains(geom.Point2D(-2, 1)))
     self.assertTrue(box.contains(geom.Box2D(
         geom.Point2D(-1, -2), geom.Point2D(1, 0))))
     self.assertTrue(box.contains(box))
     self.assertFalse(box.contains(geom.Box2D(
         geom.Point2D(-2, -3), geom.Point2D(2, 2))))
     self.assertFalse(box.contains(geom.Box2D(
         geom.Point2D(-2, -3), geom.Point2D(3, 1))))
     self.assertFalse(box.contains(geom.Box2D(
         geom.Point2D(-3, -3), geom.Point2D(2, 1))))
     self.assertFalse(box.contains(geom.Box2D(
         geom.Point2D(-3, -4), geom.Point2D(2, 1))))
     self.assertTrue(box.overlaps(geom.Box2D(
         geom.Point2D(-2, -3), geom.Point2D(2, 2))))
     self.assertTrue(box.overlaps(geom.Box2D(
         geom.Point2D(-2, -3), geom.Point2D(3, 1))))
     self.assertTrue(box.overlaps(geom.Box2D(
         geom.Point2D(-3, -3), geom.Point2D(2, 1))))
     self.assertTrue(box.overlaps(geom.Box2D(
         geom.Point2D(-3, -4), geom.Point2D(2, 1))))
     self.assertTrue(box.overlaps(geom.Box2D(
         geom.Point2D(-1, -2), geom.Point2D(1, 0))))
     self.assertTrue(box.overlaps(box))
     self.assertFalse(box.overlaps(geom.Box2D(
         geom.Point2D(-5, -3), geom.Point2D(-3, 1))))
     self.assertFalse(box.overlaps(geom.Box2D(
         geom.Point2D(-2, -6), geom.Point2D(2, -4))))
     self.assertFalse(box.overlaps(geom.Box2D(
         geom.Point2D(3, -3), geom.Point2D(4, 1))))
     self.assertFalse(box.overlaps(geom.Box2D(
         geom.Point2D(-2, 2), geom.Point2D(2, 2))))
     self.assertFalse(box.overlaps(geom.Box2D(
         geom.Point2D(-2, -5), geom.Point2D(2, -3))))
     self.assertFalse(box.overlaps(geom.Box2D(
         geom.Point2D(-4, -3), geom.Point2D(-2, 1))))
     self.assertFalse(box.contains(geom.Box2D(
         geom.Point2D(-2, 1), geom.Point2D(2, 3))))
     self.assertFalse(box.contains(geom.Box2D(
         geom.Point2D(2, -3), geom.Point2D(4, 1))))