예제 #1
0
    def run(self, scienceExposure, templateExposure, doWarping=True, spatiallyVarying=False):
        """Register, PSF-match, and subtract two Exposures, ``scienceExposure - templateExposure``
        using the ZOGY algorithm.

        Parameters
        ----------
        templateExposure : `lsst.afw.image.Exposure`
            exposure to be warped to scienceExposure.
        scienceExposure : `lsst.afw.image.Exposure`
            reference Exposure.
        doWarping : `bool`
            what to do if templateExposure's and scienceExposure's WCSs do not match:
            - if True then warp templateExposure to match scienceExposure
            - if False then raise an Exception
        spatiallyVarying : `bool`
            If True, perform the operation over a grid of patches across the two exposures

        Notes
        -----
        Do the following, in order:
            - Warp templateExposure to match scienceExposure, if their WCSs do not already match
            - Compute subtracted exposure ZOGY image subtraction algorithm on the two exposures

        This is the new entry point of the task as of DM-25115.


        Returns
        -------
        results : `lsst.pipe.base.Struct` containing these fields:
            - subtractedExposure: `lsst.afw.image.Exposure`
                The subtraction result.
            - warpedExposure: `lsst.afw.image.Exposure` or `None`
                templateExposure after warping to match scienceExposure
        """

        if spatiallyVarying:
            raise NotImplementedError(
                "DM-25115 Spatially varying zogy subtraction is not implemented.")

        if not self._validateWcs(scienceExposure, templateExposure):
            if doWarping:
                self.log.info("Warping templateExposure to scienceExposure")
                xyTransform = afwGeom.makeWcsPairTransform(templateExposure.getWcs(),
                                                           scienceExposure.getWcs())
                psfWarped = measAlg.WarpedPsf(templateExposure.getPsf(), xyTransform)
                templateExposure = self._warper.warpExposure(
                    scienceExposure.getWcs(), templateExposure, destBBox=scienceExposure.getBBox())
                templateExposure.setPsf(psfWarped)
            else:
                raise RuntimeError("Input images are not registered. Consider setting doWarping=True.")

        config = self.config.zogyConfig
        task = ZogyTask(config=config)
        results = task.run(scienceExposure, templateExposure)
        results.warpedExposure = templateExposure
        return results
예제 #2
0
 def testSameWcs(self):
     """Confirm that pairing two identical Wcs gives an identity transform.
     """
     for wcs in self.wcsList:
         transform = makeWcsPairTransform(wcs, wcs)
         # check that the transform has been simplified
         self.assertTrue(transform.getMapping().isSimple)
         # check the transform
         outPoints1 = transform.applyForward(self.pixelPoints)
         outPoints2 = transform.applyInverse(outPoints1)
         self.assertPairListsAlmostEqual(self.pixelPoints, outPoints1)
         self.assertPairListsAlmostEqual(outPoints1, outPoints2)
예제 #3
0
    def testGenericWcs(self):
        """Test that input and output points represent the same sky position.

        Would prefer a black-box test, but don't have the numbers for it.
        """
        inPoints = self.pixelPoints
        for wcs1 in self.wcsList:
            for wcs2 in self.wcsList:
                transform = makeWcsPairTransform(wcs1, wcs2)
                outPoints = transform.applyForward(inPoints)
                inPointsRoundTrip = transform.applyInverse(outPoints)
                self.assertPairListsAlmostEqual(inPoints, inPointsRoundTrip)
                self.assertSpherePointListsAlmostEqual(
                    wcs1.pixelToSky(inPoints), wcs2.pixelToSky(outPoints))
예제 #4
0
    def testTransformBasedWarp(self):
        """Test warping using TransformPoint2ToPoint2
        """
        for interpLength in (0, 1, 2, 4):
            kernelName = "lanczos3"
            rtol = 4e-5
            atol = 1e-2
            warpingControl = afwMath.WarpingControl(
                warpingKernelName=kernelName,
                interpLength=interpLength,
            )

            originalExposure = afwImage.ExposureF(originalExposurePath)
            originalMetadata = afwImage.DecoratedImageF(
                originalExposurePath).getMetadata()
            originalSkyWcs = afwGeom.makeSkyWcs(originalMetadata)

            swarpedImageName = f"medswarp1{kernelName}.fits"
            swarpedImagePath = os.path.join(dataDir, swarpedImageName)
            swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath)
            swarpedImage = swarpedDecoratedImage.getImage()

            swarpedMetadata = swarpedDecoratedImage.getMetadata()
            warpedSkyWcs = afwGeom.makeSkyWcs(swarpedMetadata)

            # original image is source, warped image is destination
            srcToDest = afwGeom.makeWcsPairTransform(originalSkyWcs,
                                                     warpedSkyWcs)

            afwWarpedMaskedImage = afwImage.MaskedImageF(
                swarpedImage.getDimensions())
            originalMaskedImage = originalExposure.getMaskedImage()

            numGoodPix = afwMath.warpImage(afwWarpedMaskedImage,
                                           originalMaskedImage, srcToDest,
                                           warpingControl)
            self.assertGreater(numGoodPix, 50)

            afwWarpedImage = afwWarpedMaskedImage.getImage()
            afwWarpedImageArr = afwWarpedImage.getArray()
            noDataMaskArr = np.isnan(afwWarpedImageArr)
            self.assertImagesAlmostEqual(afwWarpedImage,
                                         swarpedImage,
                                         skipMask=noDataMaskArr,
                                         rtol=rtol,
                                         atol=atol)
예제 #5
0
    def testTransformBasedWarp(self):
        """Test warping using TransformPoint2ToPoint2
        """
        for interpLength in (0, 1, 2, 4):
            kernelName = "lanczos3"
            rtol = 4e-5
            atol = 1e-2
            warpingControl = afwMath.WarpingControl(
                warpingKernelName=kernelName,
                interpLength=interpLength,
            )

            originalExposure = afwImage.ExposureF(originalExposurePath)
            originalMetadata = afwImage.DecoratedImageF(originalExposurePath).getMetadata()
            originalSkyWcs = afwGeom.makeSkyWcs(originalMetadata)

            swarpedImageName = "medswarp1%s.fits" % (kernelName,)
            swarpedImagePath = os.path.join(dataDir, swarpedImageName)
            swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath)
            swarpedImage = swarpedDecoratedImage.getImage()

            swarpedMetadata = swarpedDecoratedImage.getMetadata()
            warpedSkyWcs = afwGeom.makeSkyWcs(swarpedMetadata)

            # original image is source, warped image is destination
            srcToDest = afwGeom.makeWcsPairTransform(originalSkyWcs, warpedSkyWcs)

            afwWarpedMaskedImage = afwImage.MaskedImageF(swarpedImage.getDimensions())
            originalMaskedImage = originalExposure.getMaskedImage()

            numGoodPix = afwMath.warpImage(afwWarpedMaskedImage, originalMaskedImage,
                                           srcToDest, warpingControl)
            self.assertGreater(numGoodPix, 50)

            afwWarpedImage = afwWarpedMaskedImage.getImage()
            afwWarpedImageArr = afwWarpedImage.getArray()
            noDataMaskArr = np.isnan(afwWarpedImageArr)
            self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage,
                                         skipMask=noDataMaskArr, rtol=rtol, atol=atol)
예제 #6
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)
예제 #7
0
    def compareToSwarp(self,
                       kernelName,
                       useWarpExposure=True,
                       useSubregion=False,
                       useDeepCopy=False,
                       interpLength=10,
                       cacheSize=100000,
                       rtol=4e-05,
                       atol=1e-2):
        """Compare warpExposure to swarp for given warping kernel.

        Note that swarp only warps the image plane, so only test that plane.

        Inputs:
        - kernelName: name of kernel in the form used by afwImage.makeKernel
        - useWarpExposure: if True, call warpExposure to warp an ExposureF,
            else call warpImage to warp an ImageF and also call the Transform version
        - useSubregion: if True then the original source exposure (from which the usual
            test exposure was extracted) is read and the correct subregion extracted
        - useDeepCopy: if True then the copy of the subimage is a deep copy,
            else it is a shallow copy; ignored if useSubregion is False
        - interpLength: interpLength argument for lsst.afw.math.WarpingControl
        - cacheSize: cacheSize argument for lsst.afw.math.WarpingControl;
            0 disables the cache
            10000 gives some speed improvement but less accurate results (atol must be increased)
            100000 gives better accuracy but no speed improvement in this test
        - rtol: relative tolerance as used by np.allclose
        - atol: absolute tolerance as used by np.allclose
        """
        warpingControl = afwMath.WarpingControl(
            kernelName,
            "",  # there is no point to a separate mask kernel since we aren't testing the mask plane
            cacheSize,
            interpLength,
        )
        if useSubregion:
            originalFullExposure = afwImage.ExposureF(originalExposurePath)
            # "medsub" is a subregion of med starting at 0-indexed pixel (40, 150) of size 145 x 200
            bbox = lsst.geom.Box2I(lsst.geom.Point2I(40, 150),
                                   lsst.geom.Extent2I(145, 200))
            originalExposure = afwImage.ExposureF(originalFullExposure, bbox,
                                                  afwImage.LOCAL, useDeepCopy)
            swarpedImageName = f"medsubswarp1{kernelName}.fits"
        else:
            originalExposure = afwImage.ExposureF(originalExposurePath)
            swarpedImageName = f"medswarp1{kernelName}.fits"

        swarpedImagePath = os.path.join(dataDir, swarpedImageName)
        swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath)
        swarpedImage = swarpedDecoratedImage.getImage()
        swarpedMetadata = swarpedDecoratedImage.getMetadata()
        warpedWcs = afwGeom.makeSkyWcs(swarpedMetadata)

        if useWarpExposure:
            # path for saved afw-warped image
            afwWarpedImagePath = f"afwWarpedExposure1{kernelName}.fits"

            afwWarpedMaskedImage = afwImage.MaskedImageF(
                swarpedImage.getDimensions())
            afwWarpedExposure = afwImage.ExposureF(afwWarpedMaskedImage,
                                                   warpedWcs)
            afwMath.warpExposure(afwWarpedExposure, originalExposure,
                                 warpingControl)
            afwWarpedMask = afwWarpedMaskedImage.getMask()
            if SAVE_FITS_FILES:
                afwWarpedExposure.writeFits(afwWarpedImagePath)
            if display:
                afwDisplay.Display(frame=1).mtv(afwWarpedExposure,
                                                title="Warped")

            swarpedMaskedImage = afwImage.MaskedImageF(swarpedImage)

            if display:
                afwDisplay.Display(frame=2).mtv(swarpedMaskedImage,
                                                title="SWarped")

            msg = f"afw and swarp {kernelName}-warped differ (ignoring bad pixels)"
            try:
                self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage,
                                                   swarpedMaskedImage,
                                                   doImage=True,
                                                   doMask=False,
                                                   doVariance=False,
                                                   skipMask=afwWarpedMask,
                                                   rtol=rtol,
                                                   atol=atol,
                                                   msg=msg)
            except Exception:
                if SAVE_FAILED_FITS_FILES:
                    afwWarpedExposure.writeFits(afwWarpedImagePath)
                    print(
                        f"Saved failed afw-warped exposure as: {afwWarpedImagePath}"
                    )
                raise
        else:
            # path for saved afw-warped image
            afwWarpedImagePath = f"afwWarpedImage1{kernelName}.fits"
            afwWarpedImage2Path = f"afwWarpedImage1{kernelName}_xyTransform.fits"

            afwWarpedImage = afwImage.ImageF(swarpedImage.getDimensions())
            originalImage = originalExposure.getMaskedImage().getImage()
            originalWcs = originalExposure.getWcs()
            afwMath.warpImage(afwWarpedImage, warpedWcs, originalImage,
                              originalWcs, warpingControl)
            if display:
                afwDisplay.Display(frame=1).mtv(afwWarpedImage, title="Warped")
                afwDisplay.Display(frame=2).mtv(swarpedImage, title="SWarped")
                diff = swarpedImage.Factory(swarpedImage, True)
                diff -= afwWarpedImage
                afwDisplay.Display(frame=3).mtv(diff, title="swarp - afw")
            if SAVE_FITS_FILES:
                afwWarpedImage.writeFits(afwWarpedImagePath)

            afwWarpedImageArr = afwWarpedImage.getArray()
            noDataMaskArr = np.isnan(afwWarpedImageArr)
            msg = f"afw and swarp {kernelName}-warped images do not match (ignoring NaN pixels)"
            try:
                self.assertImagesAlmostEqual(afwWarpedImage,
                                             swarpedImage,
                                             skipMask=noDataMaskArr,
                                             rtol=rtol,
                                             atol=atol,
                                             msg=msg)
            except Exception:
                if SAVE_FAILED_FITS_FILES:
                    # save the image anyway
                    afwWarpedImage.writeFits(afwWarpedImagePath)
                    print(
                        f"Saved failed afw-warped image as: {afwWarpedImagePath}"
                    )
                raise

            afwWarpedImage2 = afwImage.ImageF(swarpedImage.getDimensions())
            srcToDest = afwGeom.makeWcsPairTransform(originalWcs, warpedWcs)
            afwMath.warpImage(afwWarpedImage2, originalImage, srcToDest,
                              warpingControl)
            msg = f"afw transform-based and WCS-based {kernelName}-warped images do not match"
            try:
                self.assertImagesAlmostEqual(afwWarpedImage2,
                                             afwWarpedImage,
                                             rtol=rtol,
                                             atol=atol,
                                             msg=msg)
            except Exception:
                if SAVE_FAILED_FITS_FILES:
                    # save the image anyway
                    afwWarpedImage.writeFits(afwWarpedImage2)
                    print(
                        f"Saved failed afw-warped image as: {afwWarpedImage2Path}"
                    )
                raise
예제 #8
0
    def matchExposures(self,
                       templateExposure,
                       scienceExposure,
                       templateFwhmPix=None,
                       scienceFwhmPix=None,
                       candidateList=None,
                       doWarping=True,
                       convolveTemplate=True):
        """Warp and PSF-match an exposure to the reference.

        Do the following, in order:

        - Warp templateExposure to match scienceExposure,
            if doWarping True and their WCSs do not already match
        - Determine a PSF matching kernel and differential background model
            that matches templateExposure to scienceExposure
        - Convolve templateExposure by PSF matching kernel

        Parameters
        ----------
        templateExposure : `lsst.afw.image.Exposure`
            Exposure to warp and PSF-match to the reference masked image
        scienceExposure : `lsst.afw.image.Exposure`
            Exposure whose WCS and PSF are to be matched to
        templateFwhmPix :`float`
            FWHM (in pixels) of the Psf in the template image (image to convolve)
        scienceFwhmPix : `float`
            FWHM (in pixels) of the Psf in the science image
        candidateList : `list`, optional
            a list of footprints/maskedImages for kernel candidates;
            if `None` then source detection is run.

            - Currently supported: list of Footprints or measAlg.PsfCandidateF

        doWarping : `bool`
            what to do if ``templateExposure`` and ``scienceExposure`` WCSs do not match:

            - if `True` then warp ``templateExposure`` to match ``scienceExposure``
            - if `False` then raise an Exception

        convolveTemplate : `bool`
            Whether to convolve the template image or the science image:

            - if `True`, ``templateExposure`` is warped if doWarping,
              ``templateExposure`` is convolved
            - if `False`, ``templateExposure`` is warped if doWarping,
              ``scienceExposure`` is convolved

        Returns
        -------
        results : `lsst.pipe.base.Struct`
            An `lsst.pipe.base.Struct` containing these fields:

            - ``matchedImage`` : the PSF-matched exposure =
                Warped ``templateExposure`` convolved by psfMatchingKernel. This has:

                - the same parent bbox, Wcs and PhotoCalib as scienceExposure
                - the same filter as templateExposure
                - no Psf (because the PSF-matching process does not compute one)

            - ``psfMatchingKernel`` : the PSF matching kernel
            - ``backgroundModel`` : differential background model
            - ``kernelCellSet`` : SpatialCellSet used to solve for the PSF matching kernel

        Raises
        ------
        RuntimeError
           Raised if doWarping is False and ``templateExposure`` and
           ``scienceExposure`` WCSs do not match
        """
        if not self._validateWcs(templateExposure, scienceExposure):
            if doWarping:
                self.log.info(
                    "Astrometrically registering template to science image")
                templatePsf = templateExposure.getPsf()
                # Warp PSF before overwriting exposure
                xyTransform = afwGeom.makeWcsPairTransform(
                    templateExposure.getWcs(), scienceExposure.getWcs())
                psfWarped = WarpedPsf(templatePsf, xyTransform)
                templateExposure = self._warper.warpExposure(
                    scienceExposure.getWcs(),
                    templateExposure,
                    destBBox=scienceExposure.getBBox())
                templateExposure.setPsf(psfWarped)
            else:
                self.log.error("ERROR: Input images not registered")
                raise RuntimeError("Input images not registered")

        if templateFwhmPix is None:
            if not templateExposure.hasPsf():
                self.log.warning("No estimate of Psf FWHM for template image")
            else:
                templateFwhmPix = self.getFwhmPix(templateExposure.getPsf())
                self.log.info("templateFwhmPix: %s", templateFwhmPix)

        if scienceFwhmPix is None:
            if not scienceExposure.hasPsf():
                self.log.warning("No estimate of Psf FWHM for science image")
            else:
                scienceFwhmPix = self.getFwhmPix(scienceExposure.getPsf())
                self.log.info("scienceFwhmPix: %s", scienceFwhmPix)

        if convolveTemplate:
            kernelSize = self.makeKernelBasisList(
                templateFwhmPix, scienceFwhmPix)[0].getWidth()
            candidateList = self.makeCandidateList(templateExposure,
                                                   scienceExposure, kernelSize,
                                                   candidateList)
            results = self.matchMaskedImages(templateExposure.getMaskedImage(),
                                             scienceExposure.getMaskedImage(),
                                             candidateList,
                                             templateFwhmPix=templateFwhmPix,
                                             scienceFwhmPix=scienceFwhmPix)
        else:
            kernelSize = self.makeKernelBasisList(
                scienceFwhmPix, templateFwhmPix)[0].getWidth()
            candidateList = self.makeCandidateList(templateExposure,
                                                   scienceExposure, kernelSize,
                                                   candidateList)
            results = self.matchMaskedImages(scienceExposure.getMaskedImage(),
                                             templateExposure.getMaskedImage(),
                                             candidateList,
                                             templateFwhmPix=scienceFwhmPix,
                                             scienceFwhmPix=templateFwhmPix)

        psfMatchedExposure = afwImage.makeExposure(results.matchedImage,
                                                   scienceExposure.getWcs())
        psfMatchedExposure.setFilter(templateExposure.getFilter())
        psfMatchedExposure.setPhotoCalib(scienceExposure.getPhotoCalib())
        results.warpedExposure = templateExposure
        results.matchedExposure = psfMatchedExposure
        return results
예제 #9
0
    def matchExposures(self, templateExposure, scienceExposure,
                       templateFwhmPix=None, scienceFwhmPix=None,
                       candidateList=None, doWarping=True, convolveTemplate=True):
        """Warp and PSF-match an exposure to the reference.

        Do the following, in order:

        - Warp templateExposure to match scienceExposure,
            if doWarping True and their WCSs do not already match
        - Determine a PSF matching kernel and differential background model
            that matches templateExposure to scienceExposure
        - Convolve templateExposure by PSF matching kernel

        Parameters
        ----------
        templateExposure : `lsst.afw.image.Exposure`
            Exposure to warp and PSF-match to the reference masked image
        scienceExposure : `lsst.afw.image.Exposure`
            Exposure whose WCS and PSF are to be matched to
        templateFwhmPix :`float`
            FWHM (in pixels) of the Psf in the template image (image to convolve)
        scienceFwhmPix : `float`
            FWHM (in pixels) of the Psf in the science image
        candidateList : `list`, optional
            a list of footprints/maskedImages for kernel candidates;
            if `None` then source detection is run.

            - Currently supported: list of Footprints or measAlg.PsfCandidateF

        doWarping : `bool`
            what to do if ``templateExposure`` and ``scienceExposure`` WCSs do not match:

            - if `True` then warp ``templateExposure`` to match ``scienceExposure``
            - if `False` then raise an Exception

        convolveTemplate : `bool`
            Whether to convolve the template image or the science image:

            - if `True`, ``templateExposure`` is warped if doWarping,
              ``templateExposure`` is convolved
            - if `False`, ``templateExposure`` is warped if doWarping,
              ``scienceExposure`` is convolved

        Returns
        -------
        results : `lsst.pipe.base.Struct`
            An `lsst.pipe.base.Struct` containing these fields:

            - ``matchedImage`` : the PSF-matched exposure =
                Warped ``templateExposure`` convolved by psfMatchingKernel. This has:

                - the same parent bbox, Wcs and PhotoCalib as scienceExposure
                - the same filter as templateExposure
                - no Psf (because the PSF-matching process does not compute one)

            - ``psfMatchingKernel`` : the PSF matching kernel
            - ``backgroundModel`` : differential background model
            - ``kernelCellSet`` : SpatialCellSet used to solve for the PSF matching kernel

        Raises
        ------
        RuntimeError
           Raised if doWarping is False and ``templateExposure`` and
           ``scienceExposure`` WCSs do not match
        """
        if not self._validateWcs(templateExposure, scienceExposure):
            if doWarping:
                self.log.info("Astrometrically registering template to science image")
                templatePsf = templateExposure.getPsf()
                # Warp PSF before overwriting exposure
                xyTransform = afwGeom.makeWcsPairTransform(templateExposure.getWcs(),
                                                           scienceExposure.getWcs())
                psfWarped = WarpedPsf(templatePsf, xyTransform)
                templateExposure = self._warper.warpExposure(scienceExposure.getWcs(),
                                                             templateExposure,
                                                             destBBox=scienceExposure.getBBox())
                templateExposure.setPsf(psfWarped)
            else:
                self.log.error("ERROR: Input images not registered")
                raise RuntimeError("Input images not registered")

        if templateFwhmPix is None:
            if not templateExposure.hasPsf():
                self.log.warn("No estimate of Psf FWHM for template image")
            else:
                templateFwhmPix = self.getFwhmPix(templateExposure.getPsf())
                self.log.info("templateFwhmPix: {}".format(templateFwhmPix))

        if scienceFwhmPix is None:
            if not scienceExposure.hasPsf():
                self.log.warn("No estimate of Psf FWHM for science image")
            else:
                scienceFwhmPix = self.getFwhmPix(scienceExposure.getPsf())
                self.log.info("scienceFwhmPix: {}".format(scienceFwhmPix))

        kernelSize = makeKernelBasisList(self.kConfig, templateFwhmPix, scienceFwhmPix)[0].getWidth()
        candidateList = self.makeCandidateList(templateExposure, scienceExposure, kernelSize, candidateList)

        if convolveTemplate:
            results = self.matchMaskedImages(
                templateExposure.getMaskedImage(), scienceExposure.getMaskedImage(), candidateList,
                templateFwhmPix=templateFwhmPix, scienceFwhmPix=scienceFwhmPix)
        else:
            results = self.matchMaskedImages(
                scienceExposure.getMaskedImage(), templateExposure.getMaskedImage(), candidateList,
                templateFwhmPix=scienceFwhmPix, scienceFwhmPix=templateFwhmPix)

        psfMatchedExposure = afwImage.makeExposure(results.matchedImage, scienceExposure.getWcs())
        psfMatchedExposure.setFilter(templateExposure.getFilter())
        psfMatchedExposure.setPhotoCalib(scienceExposure.getPhotoCalib())
        results.warpedExposure = templateExposure
        results.matchedExposure = psfMatchedExposure
        return results
예제 #10
0
    def run(self,
            exposure,
            wcs,
            modelPsf=None,
            maxBBox=None,
            destBBox=None,
            makeDirect=True,
            makePsfMatched=False):
        """Warp and optionally PSF-match exposure

        Parameters
        ----------
        exposure : :cpp:class: `lsst::afw::image::Exposure`
            Exposure to preprocess.
        wcs : :cpp:class:`lsst::afw::image::Wcs`
            Desired WCS of temporary images.
        modelPsf : :cpp:class: `lsst::meas::algorithms::KernelPsf` or None
            Target PSF to which to match.
        maxBBox : :cpp:class:`lsst::afw::geom::Box2I` or None
            Maximum allowed parent bbox of warped exposure.
            If None then the warped exposure will be just big enough to contain all warped pixels;
            if provided then the warped exposure may be smaller, and so missing some warped pixels;
            ignored if destBBox is not None.
        destBBox: :cpp:class: `lsst::afw::geom::Box2I` or None
            Exact parent bbox of warped exposure.
            If None then maxBBox is used to determine the bbox, otherwise maxBBox is ignored.
        makeDirect : bool
            Return an exposure that has been only warped?
        makePsfMatched : bool
            Return an exposure that has been warped and PSF-matched?

        Returns
        -------
        An lsst.pipe.base.Struct with the following fields:

        direct : :cpp:class:`lsst::afw::image::Exposure`
            warped exposure
        psfMatched : :cpp:class: `lsst::afw::image::Exposure`
            warped and psf-Matched temporary exposure
        """
        if makePsfMatched and modelPsf is None:
            raise RuntimeError(
                "makePsfMatched=True, but no model PSF was provided")

        if not makePsfMatched and not makeDirect:
            self.log.warn("Neither makeDirect nor makePsfMatched requested")

        # Warp PSF before overwriting exposure
        xyTransform = afwGeom.makeWcsPairTransform(exposure.getWcs(), wcs)
        psfWarped = WarpedPsf(exposure.getPsf(), xyTransform)

        if makePsfMatched and maxBBox is not None:
            # grow warped region to provide sufficient area for PSF-matching
            pixToGrow = 2 * max(self.psfMatch.kConfig.sizeCellX,
                                self.psfMatch.kConfig.sizeCellY)
            # replace with copy
            maxBBox = geom.Box2I(maxBBox)
            maxBBox.grow(pixToGrow)

        with self.timer("warp"):
            exposure = self.warper.warpExposure(wcs,
                                                exposure,
                                                maxBBox=maxBBox,
                                                destBBox=destBBox)
            exposure.setPsf(psfWarped)

        if makePsfMatched:
            try:
                exposurePsfMatched = self.psfMatch.run(
                    exposure, modelPsf).psfMatchedExposure
            except Exception as e:
                exposurePsfMatched = None
                self.log.info("Cannot PSF-Match: %s" % (e))

        return pipeBase.Struct(
            direct=exposure if makeDirect else None,
            psfMatched=exposurePsfMatched if makePsfMatched else None)
예제 #11
0
    def compareToSwarp(self, kernelName,
                       useWarpExposure=True, useSubregion=False, useDeepCopy=False,
                       interpLength=10, cacheSize=100000,
                       rtol=4e-05, atol=1e-2):
        """Compare warpExposure to swarp for given warping kernel.

        Note that swarp only warps the image plane, so only test that plane.

        Inputs:
        - kernelName: name of kernel in the form used by afwImage.makeKernel
        - useWarpExposure: if True, call warpExposure to warp an ExposureF,
            else call warpImage to warp an ImageF and also call the Transform version
        - useSubregion: if True then the original source exposure (from which the usual
            test exposure was extracted) is read and the correct subregion extracted
        - useDeepCopy: if True then the copy of the subimage is a deep copy,
            else it is a shallow copy; ignored if useSubregion is False
        - interpLength: interpLength argument for lsst.afw.math.WarpingControl
        - cacheSize: cacheSize argument for lsst.afw.math.WarpingControl;
            0 disables the cache
            10000 gives some speed improvement but less accurate results (atol must be increased)
            100000 gives better accuracy but no speed improvement in this test
        - rtol: relative tolerance as used by np.allclose
        - atol: absolute tolerance as used by np.allclose
        """
        warpingControl = afwMath.WarpingControl(
            kernelName,
            "",  # there is no point to a separate mask kernel since we aren't testing the mask plane
            cacheSize,
            interpLength,
        )
        if useSubregion:
            originalFullExposure = afwImage.ExposureF(originalExposurePath)
            # "medsub" is a subregion of med starting at 0-indexed pixel (40, 150) of size 145 x 200
            bbox = lsst.geom.Box2I(lsst.geom.Point2I(40, 150),
                                   lsst.geom.Extent2I(145, 200))
            originalExposure = afwImage.ExposureF(
                originalFullExposure, bbox, afwImage.LOCAL, useDeepCopy)
            swarpedImageName = "medsubswarp1%s.fits" % (kernelName,)
        else:
            originalExposure = afwImage.ExposureF(originalExposurePath)
            swarpedImageName = "medswarp1%s.fits" % (kernelName,)

        swarpedImagePath = os.path.join(dataDir, swarpedImageName)
        swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath)
        swarpedImage = swarpedDecoratedImage.getImage()
        swarpedMetadata = swarpedDecoratedImage.getMetadata()
        warpedWcs = afwGeom.makeSkyWcs(swarpedMetadata)

        if useWarpExposure:
            # path for saved afw-warped image
            afwWarpedImagePath = "afwWarpedExposure1%s.fits" % (kernelName,)

            afwWarpedMaskedImage = afwImage.MaskedImageF(
                swarpedImage.getDimensions())
            afwWarpedExposure = afwImage.ExposureF(
                afwWarpedMaskedImage, warpedWcs)
            afwMath.warpExposure(
                afwWarpedExposure, originalExposure, warpingControl)
            afwWarpedMask = afwWarpedMaskedImage.getMask()
            if SAVE_FITS_FILES:
                afwWarpedExposure.writeFits(afwWarpedImagePath)
            if display:
                afwDisplay.Display(frame=1).mtv(afwWarpedExposure, title="Warped")

            swarpedMaskedImage = afwImage.MaskedImageF(swarpedImage)

            if display:
                afwDisplay.Display(frame=2).mtv(swarpedMaskedImage, title="SWarped")

            msg = "afw and swarp %s-warped differ (ignoring bad pixels)" % (
                kernelName,)
            try:
                self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, swarpedMaskedImage,
                                                   doImage=True, doMask=False, doVariance=False,
                                                   skipMask=afwWarpedMask, rtol=rtol, atol=atol, msg=msg)
            except Exception:
                if SAVE_FAILED_FITS_FILES:
                    afwWarpedExposure.writeFits(afwWarpedImagePath)
                    print("Saved failed afw-warped exposure as: %s" %
                          (afwWarpedImagePath,))
                raise
        else:
            # path for saved afw-warped image
            afwWarpedImagePath = "afwWarpedImage1%s.fits" % (kernelName,)
            afwWarpedImage2Path = "afwWarpedImage1%s_xyTransform.fits" % (
                kernelName,)

            afwWarpedImage = afwImage.ImageF(swarpedImage.getDimensions())
            originalImage = originalExposure.getMaskedImage().getImage()
            originalWcs = originalExposure.getWcs()
            afwMath.warpImage(afwWarpedImage, warpedWcs, originalImage,
                              originalWcs, warpingControl)
            if display:
                afwDisplay.Display(frame=1).mtv(afwWarpedImage, title="Warped")
                afwDisplay.Display(frame=2).mtv(swarpedImage, title="SWarped")
                diff = swarpedImage.Factory(swarpedImage, True)
                diff -= afwWarpedImage
                afwDisplay.Display(frame=3).mtv(diff, title="swarp - afw")
            if SAVE_FITS_FILES:
                afwWarpedImage.writeFits(afwWarpedImagePath)

            afwWarpedImageArr = afwWarpedImage.getArray()
            noDataMaskArr = np.isnan(afwWarpedImageArr)
            msg = "afw and swarp %s-warped images do not match (ignoring NaN pixels)" % \
                (kernelName,)
            try:
                self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage,
                                             skipMask=noDataMaskArr, rtol=rtol, atol=atol, msg=msg)
            except Exception:
                if SAVE_FAILED_FITS_FILES:
                    # save the image anyway
                    afwWarpedImage.writeFits(afwWarpedImagePath)
                    print("Saved failed afw-warped image as: %s" %
                          (afwWarpedImagePath,))
                raise

            afwWarpedImage2 = afwImage.ImageF(swarpedImage.getDimensions())
            srcToDest = afwGeom.makeWcsPairTransform(originalWcs, warpedWcs)
            afwMath.warpImage(afwWarpedImage2, originalImage,
                              srcToDest, warpingControl)
            msg = "afw transform-based and WCS-based %s-warped images do not match" % (
                kernelName,)
            try:
                self.assertImagesAlmostEqual(afwWarpedImage2, afwWarpedImage,
                                             rtol=rtol, atol=atol, msg=msg)
            except Exception:
                if SAVE_FAILED_FITS_FILES:
                    # save the image anyway
                    afwWarpedImage.writeFits(afwWarpedImagePath)
                    print("Saved failed afw-warped image as: %s" %
                          (afwWarpedImage2Path,))
                raise