Example #1
0
    def testTwoDisplays(self):
        """Test that we can do things with two frames"""

        exp = afwImage.ExposureF(300, 350)

        for frame in (0, 1):
            ds9.setMaskTransparency(50, frame=frame)

            if frame == 1:
                ds9.setMaskPlaneColor("CROSSTALK", "ignore", frame=frame)
            ds9.mtv(exp, title="parent", frame=frame)

            ds9.erase(frame=frame)
            ds9.dot('o', 205, 180, size=6, ctype=ds9.RED, frame=frame)
Example #2
0
    def _displayDebug(self, kernelCellSet, spatialKernel, spatialBackground):
        """!Provide visualization of the inputs and ouputs to the Psf-matching code

        @param kernelCellSet: the SpatialCellSet used in determining the matching kernel and background 
        @param spatialKernel: spatially varying Psf-matching kernel
        @param spatialBackground: spatially varying background-matching function

        """
        import lsstDebug
        displayCandidates = lsstDebug.Info(__name__).displayCandidates
        displayKernelBasis = lsstDebug.Info(__name__).displayKernelBasis
        displayKernelMosaic = lsstDebug.Info(__name__).displayKernelMosaic
        plotKernelSpatialModel = lsstDebug.Info(__name__).plotKernelSpatialModel
        showBadCandidates = lsstDebug.Info(__name__).showBadCandidates
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        ds9.setMaskTransparency(maskTransparency)

        if displayCandidates:
            diUtils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, 
                                         frame=lsstDebug.frame,
                                         showBadCandidates=showBadCandidates)
            lsstDebug.frame += 1
            diUtils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, 
                                         frame=lsstDebug.frame,
                                         showBadCandidates=showBadCandidates,
                                         kernels=True)
            lsstDebug.frame += 1
            diUtils.showKernelCandidates(kernelCellSet, kernel=spatialKernel, background=spatialBackground, 
                                         frame=lsstDebug.frame,
                                         showBadCandidates=showBadCandidates,
                                         resids=True)
            lsstDebug.frame += 1

        if displayKernelBasis:
            diUtils.showKernelBasis(spatialKernel, frame=lsstDebug.frame)
            lsstDebug.frame += 1

        if displayKernelMosaic:
            diUtils.showKernelMosaic(kernelCellSet.getBBox(), spatialKernel, frame=lsstDebug.frame)
            lsstDebug.frame += 1

        if plotKernelSpatialModel:
            diUtils.plotKernelSpatialModel(spatialKernel, kernelCellSet, showBadCandidates=showBadCandidates)
Example #3
0
    def displayDipoles(self, exposure, sources):
        """!Display debugging information on the detected dipoles

        @param exposure  Image the dipoles were measured on
        @param sources   The set of diaSources that were measured"""

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayDiaSources = lsstDebug.Info(__name__).displayDiaSources
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 90
        ds9.setMaskTransparency(maskTransparency)
        ds9.mtv(exposure, frame=lsstDebug.frame)

        if display and displayDiaSources:
            with ds9.Buffering():
                for source in sources:
                    cenX, cenY = source.get("ipdiffim_DipolePsfFlux_centroid")
                    if np.isinf(cenX) or np.isinf(cenY):
                        cenX, cenY = source.getCentroid()

                    isdipole = source.get("classification.dipole")
                    if isdipole and np.isfinite(isdipole):
                        # Dipole
                        ctype= "green"
                    else:
                        # Not dipole
                        ctype = "red"

                    ds9.dot("o", cenX, cenY, size=2, ctype=ctype, frame=lsstDebug.frame)

                    negCenX = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_x")
                    negCenY = source.get("ip_diffim_PsfDipoleFlux_neg_centroid_y")
                    posCenX = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_x")
                    posCenY = source.get("ip_diffim_PsfDipoleFlux_pos_centroid_y")
                    if (np.isinf(negCenX) or np.isinf(negCenY) or np.isinf(posCenX) or np.isinf(posCenY)):
                        continue

                    ds9.line([(negCenX, negCenY), (posCenX, posCenY)], ctype="yellow", frame=lsstDebug.frame)

            lsstDebug.frame += 1
Example #4
0
    def _buildCellSet(self, exposure, referencePsfModel):
        """!Build a SpatialCellSet for use with the solve method

        @param exposure: The science exposure that will be convolved; must contain a Psf
        @param referencePsfModel: Psf model to match to

        @return kernelCellSet: a SpatialCellSet to be used by self._solve

        Raise a RuntimeError if the reference Psf model and science Psf model have different dimensions
        """
        scienceBBox = exposure.getBBox()
        sciencePsfModel = exposure.getPsf()
        # The Psf base class does not support getKernel() in general, as there are some Psf
        # classes for which this is not meaningful.
        # Many Psfs we use in practice are KernelPsfs, and this algorithm will work fine for them,
        # but someday it should probably be modified to support arbitrary Psfs.
        referencePsfModel = measAlg.KernelPsf.swigConvert(referencePsfModel)
        sciencePsfModel = measAlg.KernelPsf.swigConvert(sciencePsfModel)
        if referencePsfModel is None or sciencePsfModel is None:
            raise RuntimeError("ERROR: Psf matching is only implemented for KernelPsfs")
        if (referencePsfModel.getKernel().getDimensions() != sciencePsfModel.getKernel().getDimensions()):
            pexLog.Trace(self.log.getName(), 1,
                         "ERROR: Dimensions of reference Psf and science Psf different; exiting")
            raise RuntimeError, "ERROR: Dimensions of reference Psf and science Psf different; exiting"

        psfWidth, psfHeight = referencePsfModel.getKernel().getDimensions()
        maxKernelSize = min(psfWidth, psfHeight) - 1
        if maxKernelSize % 2 == 0:
            maxKernelSize -= 1
        if self.kConfig.kernelSize > maxKernelSize:
            raise ValueError, "Kernel size (%d) too big to match Psfs of size %d; reduce to at least %d" % (
                self.kConfig.kernelSize, psfWidth, maxKernelSize)

        # Infer spatial order of Psf model!
        #
        # Infer from the number of spatial parameters.
        # (O + 1) * (O + 2) / 2 = N
        # O^2 + 3 * O + 2 * (1 - N) = 0
        #
        # Roots are [-3 +/- sqrt(9 - 8 * (1 - N))] / 2
        #
        nParameters = sciencePsfModel.getKernel().getNSpatialParameters()
        root        = num.sqrt(9 - 8 * (1 - nParameters))
        if (root != root // 1):            # We know its an integer solution
            pexLog.Trace(self.log.getName(), 3, "Problem inferring spatial order of image's Psf")
        else:
            order       = (root - 3) / 2
            if (order != order // 1):
                pexLog.Trace(self.log.getName(), 3, "Problem inferring spatial order of image's Psf")
            else:
                pexLog.Trace(self.log.getName(), 2, "Spatial order of Psf = %d; matching kernel order = %d" % (
                        order, self.kConfig.spatialKernelOrder))

        regionSizeX, regionSizeY = scienceBBox.getDimensions()
        scienceX0,   scienceY0   = scienceBBox.getMin()

        sizeCellX = self.kConfig.sizeCellX
        sizeCellY = self.kConfig.sizeCellY

        kernelCellSet = afwMath.SpatialCellSet(
            afwGeom.Box2I(afwGeom.Point2I(scienceX0, scienceY0),
                          afwGeom.Extent2I(regionSizeX, regionSizeY)),
            sizeCellX, sizeCellY
            )

        nCellX    = regionSizeX // sizeCellX
        nCellY    = regionSizeY // sizeCellY
        dimenR    = referencePsfModel.getKernel().getDimensions()
        dimenS    = sciencePsfModel.getKernel().getDimensions()

        policy = pexConfig.makePolicy(self.kConfig)
        for row in range(nCellY):
            # place at center of cell
            posY = sizeCellY * row + sizeCellY // 2 + scienceY0

            for col in range(nCellX):
                # place at center of cell
                posX = sizeCellX * col + sizeCellX // 2 + scienceX0

                pexLog.Trace(self.log.getName(), 5, "Creating Psf candidate at %.1f %.1f" % (posX, posY))

                # reference kernel image, at location of science subimage
                kernelImageR = referencePsfModel.computeImage(afwGeom.Point2D(posX, posY)).convertF()
                kernelMaskR   = afwImage.MaskU(dimenR)
                kernelMaskR.set(0)
                kernelVarR    = afwImage.ImageF(dimenR)
                kernelVarR.set(1.0)
                referenceMI   = afwImage.MaskedImageF(kernelImageR, kernelMaskR, kernelVarR)

                # kernel image we are going to convolve
                kernelImageS = sciencePsfModel.computeImage(afwGeom.Point2D(posX, posY)).convertF()
                kernelMaskS   = afwImage.MaskU(dimenS)
                kernelMaskS.set(0)
                kernelVarS    = afwImage.ImageF(dimenS)
                kernelVarS.set(1.0)
                scienceMI     = afwImage.MaskedImageF(kernelImageS, kernelMaskS, kernelVarS)

                # The image to convolve is the science image, to the reference Psf.
                kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, policy)
                kernelCellSet.insertCandidate(kc)
        
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displaySpatialCells = lsstDebug.Info(__name__).displaySpatialCells
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)
        if display and displaySpatialCells:
            diUtils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet,
                symb="o", ctype=ds9.CYAN, ctypeUnused=ds9.YELLOW, ctypeBad=ds9.RED,
                size=4, frame=lsstDebug.frame, title="Image to be convolved")
            lsstDebug.frame += 1
        return kernelCellSet
Example #5
0
    def runDebug(self, exposure, subtractRes, selectSources, kernelSources, diaSources):
        """@todo Test and update for current debug display and slot names
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        showSubtracted = lsstDebug.Info(__name__).showSubtracted
        showPixelResiduals = lsstDebug.Info(__name__).showPixelResiduals
        showDiaSources = lsstDebug.Info(__name__).showDiaSources
        showDipoles = lsstDebug.Info(__name__).showDipoles
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if display:
            import lsst.afw.display.ds9 as ds9
            if not maskTransparency:
                maskTransparency = 0
            ds9.setMaskTransparency(maskTransparency)

        if display and showSubtracted:
            ds9.mtv(subtractRes.subtractedExposure, frame=lsstDebug.frame, title="Subtracted image")
            mi = subtractRes.subtractedExposure.getMaskedImage()
            x0, y0 = mi.getX0(), mi.getY0()
            with ds9.Buffering():
                for s in diaSources:
                    x, y = s.getX() - x0, s.getY() - y0
                    ctype = "red" if s.get("flags.negative") else "yellow"
                    if (s.get("flags.pixel.interpolated.center") or s.get("flags.pixel.saturated.center") or
                        s.get("flags.pixel.cr.center")):
                        ptype = "x"
                    elif (s.get("flags.pixel.interpolated.any") or s.get("flags.pixel.saturated.any") or
                          s.get("flags.pixel.cr.any")):
                        ptype = "+"
                    else:
                        ptype = "o"
                    ds9.dot(ptype, x, y, size=4, frame=lsstDebug.frame, ctype=ctype)
            lsstDebug.frame += 1

        if display and showPixelResiduals and selectSources:
            nonKernelSources = []
            for source in selectSources:
                if not source in kernelSources:
                    nonKernelSources.append(source)

            diUtils.plotPixelResiduals(exposure,
                                       subtractRes.warpedExposure,
                                       subtractRes.subtractedExposure,
                                       subtractRes.kernelCellSet,
                                       subtractRes.psfMatchingKernel,
                                       subtractRes.backgroundModel,
                                       nonKernelSources,
                                       self.subtract.config.kernel.active.detectionConfig,
                                       origVariance=False)
            diUtils.plotPixelResiduals(exposure,
                                       subtractRes.warpedExposure,
                                       subtractRes.subtractedExposure,
                                       subtractRes.kernelCellSet,
                                       subtractRes.psfMatchingKernel,
                                       subtractRes.backgroundModel,
                                       nonKernelSources,
                                       self.subtract.config.kernel.active.detectionConfig,
                                       origVariance=True)
        if display and showDiaSources:
            flagChecker   = SourceFlagChecker(diaSources)
            isFlagged     = [flagChecker(x) for x in diaSources]
            isDipole      = [x.get("classification.dipole") for x in diaSources]
            diUtils.showDiaSources(diaSources, subtractRes.subtractedExposure, isFlagged, isDipole,
                                   frame=lsstDebug.frame)
            lsstDebug.frame += 1

        if display and showDipoles:
            DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure, diaSources,
                                            frame=lsstDebug.frame)
            lsstDebug.frame += 1
Example #6
0
 def testMaskPlanes(self):
     """Test basic image display"""
     ds9.setMaskTransparency(50)
     ds9.setMaskPlaneColor("CROSSTALK", "orange")
Example #7
0
    def runDebug(self, exposure, subtractRes, selectSources, kernelSources,
                 diaSources):
        """@todo Test and update for current debug display and slot names
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        showSubtracted = lsstDebug.Info(__name__).showSubtracted
        showPixelResiduals = lsstDebug.Info(__name__).showPixelResiduals
        showDiaSources = lsstDebug.Info(__name__).showDiaSources
        showDipoles = lsstDebug.Info(__name__).showDipoles
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if display:
            import lsst.afw.display.ds9 as ds9
            if not maskTransparency:
                maskTransparency = 0
            ds9.setMaskTransparency(maskTransparency)

        if display and showSubtracted:
            ds9.mtv(subtractRes.subtractedExposure,
                    frame=lsstDebug.frame,
                    title="Subtracted image")
            mi = subtractRes.subtractedExposure.getMaskedImage()
            x0, y0 = mi.getX0(), mi.getY0()
            with ds9.Buffering():
                for s in diaSources:
                    x, y = s.getX() - x0, s.getY() - y0
                    ctype = "red" if s.get("flags.negative") else "yellow"
                    if (s.get("flags.pixel.interpolated.center")
                            or s.get("flags.pixel.saturated.center")
                            or s.get("flags.pixel.cr.center")):
                        ptype = "x"
                    elif (s.get("flags.pixel.interpolated.any")
                          or s.get("flags.pixel.saturated.any")
                          or s.get("flags.pixel.cr.any")):
                        ptype = "+"
                    else:
                        ptype = "o"
                    ds9.dot(ptype,
                            x,
                            y,
                            size=4,
                            frame=lsstDebug.frame,
                            ctype=ctype)
            lsstDebug.frame += 1

        if display and showPixelResiduals and selectSources:
            nonKernelSources = []
            for source in selectSources:
                if not source in kernelSources:
                    nonKernelSources.append(source)

            diUtils.plotPixelResiduals(
                exposure,
                subtractRes.warpedExposure,
                subtractRes.subtractedExposure,
                subtractRes.kernelCellSet,
                subtractRes.psfMatchingKernel,
                subtractRes.backgroundModel,
                nonKernelSources,
                self.subtract.config.kernel.active.detectionConfig,
                origVariance=False)
            diUtils.plotPixelResiduals(
                exposure,
                subtractRes.warpedExposure,
                subtractRes.subtractedExposure,
                subtractRes.kernelCellSet,
                subtractRes.psfMatchingKernel,
                subtractRes.backgroundModel,
                nonKernelSources,
                self.subtract.config.kernel.active.detectionConfig,
                origVariance=True)
        if display and showDiaSources:
            flagChecker = SourceFlagChecker(diaSources)
            isFlagged = [flagChecker(x) for x in diaSources]
            isDipole = [x.get("classification.dipole") for x in diaSources]
            diUtils.showDiaSources(diaSources,
                                   subtractRes.subtractedExposure,
                                   isFlagged,
                                   isDipole,
                                   frame=lsstDebug.frame)
            lsstDebug.frame += 1

        if display and showDipoles:
            DipoleAnalysis().displayDipoles(subtractRes.subtractedExposure,
                                            diaSources,
                                            frame=lsstDebug.frame)
            lsstDebug.frame += 1
Example #8
0
 def testMaskPlanes(self):
     """Test basic image display"""
     ds9.setMaskTransparency(50)
     ds9.setMaskPlaneColor("CROSSTALK", "orange")
Example #9
0
    def subtractExposures(self,
                          templateExposure,
                          scienceExposure,
                          templateFwhmPix=None,
                          scienceFwhmPix=None,
                          candidateList=None,
                          doWarping=True,
                          convolveTemplate=True):
        """!Register, Psf-match and subtract two Exposures

        Do the following, in order:
        - Warp templateExposure to match scienceExposure, if their WCSs do not already match
        - Determine a PSF matching kernel and differential background model
            that matches templateExposure to scienceExposure
        - PSF-match templateExposure to scienceExposure
        - Compute subtracted exposure (see return values for equation).

        @param templateExposure: exposure to PSF-match to scienceExposure
        @param scienceExposure: reference Exposure
        @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
        @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
        @param candidateList: a list of footprints/maskedImages for kernel candidates;
                              if None then source detection is run.
            - Currently supported: list of Footprints or measAlg.PsfCandidateF
        @param doWarping: 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
        @param convolveTemplate: 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

        @return a pipeBase.Struct containing these fields:
        - subtractedExposure: subtracted Exposure = scienceExposure - (matchedImage + backgroundModel)
        - matchedImage: templateExposure after warping to match templateExposure (if doWarping true),
            and convolving with psfMatchingKernel
        - psfMatchingKernel: PSF matching kernel
        - backgroundModel: differential background model
        - kernelCellSet: SpatialCellSet used to determine PSF matching kernel
        """
        results = self.matchExposures(templateExposure=templateExposure,
                                      scienceExposure=scienceExposure,
                                      templateFwhmPix=templateFwhmPix,
                                      scienceFwhmPix=scienceFwhmPix,
                                      candidateList=candidateList,
                                      doWarping=doWarping,
                                      convolveTemplate=convolveTemplate)

        subtractedExposure = afwImage.ExposureF(scienceExposure, True)
        if convolveTemplate:
            subtractedMaskedImage = subtractedExposure.getMaskedImage()
            subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
            subtractedMaskedImage -= results.backgroundModel
        else:
            subtractedExposure.setMaskedImage(
                results.warpedExposure.getMaskedImage())
            subtractedMaskedImage = subtractedExposure.getMaskedImage()
            subtractedMaskedImage -= results.matchedExposure.getMaskedImage()
            subtractedMaskedImage -= results.backgroundModel

            # Preserve polarity of differences
            subtractedMaskedImage *= -1

            # Place back on native photometric scale
            subtractedMaskedImage /= results.psfMatchingKernel.computeImage(
                afwImage.ImageD(results.psfMatchingKernel.getDimensions()),
                False)

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayDiffIm = lsstDebug.Info(__name__).displayDiffIm
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)
        if display and displayDiffIm:
            ds9.mtv(templateExposure, frame=lsstDebug.frame, title="Template")
            lsstDebug.frame += 1
            ds9.mtv(results.matchedExposure,
                    frame=lsstDebug.frame,
                    title="Matched template")
            lsstDebug.frame += 1
            ds9.mtv(scienceExposure,
                    frame=lsstDebug.frame,
                    title="Science Image")
            lsstDebug.frame += 1
            ds9.mtv(subtractedExposure,
                    frame=lsstDebug.frame,
                    title="Difference Image")
            lsstDebug.frame += 1

        results.subtractedExposure = subtractedExposure
        return results
Example #10
0
    def matchMaskedImages(self,
                          templateMaskedImage,
                          scienceMaskedImage,
                          candidateList,
                          templateFwhmPix=None,
                          scienceFwhmPix=None):
        """!PSF-match a MaskedImage (templateMaskedImage) to a reference MaskedImage (scienceMaskedImage)

        Do the following, in order:
        - Determine a PSF matching kernel and differential background model
            that matches templateMaskedImage to scienceMaskedImage
        - Convolve templateMaskedImage by the PSF matching kernel

        @param templateMaskedImage: masked image to PSF-match to the reference masked image;
            must be warped to match the reference masked image
        @param scienceMaskedImage: maskedImage whose PSF is to be matched to
        @param templateFwhmPix: FWHM (in pixels) of the Psf in the template image (image to convolve)
        @param scienceFwhmPix: FWHM (in pixels) of the Psf in the science image
        @param candidateList: a list of footprints/maskedImages for kernel candidates;
                              if None then source detection is run.
            - Currently supported: list of Footprints or measAlg.PsfCandidateF

        @return a pipeBase.Struct containing these fields:
        - psfMatchedMaskedImage: the PSF-matched masked image =
            templateMaskedImage convolved with psfMatchingKernel.
            This has the same xy0, dimensions and wcs as scienceMaskedImage.
        - psfMatchingKernel: the PSF matching kernel
        - backgroundModel: differential background model
        - kernelCellSet: SpatialCellSet used to solve for the PSF matching kernel

        Raise a RuntimeError if input images have different dimensions
        """

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayTemplate = lsstDebug.Info(__name__).displayTemplate
        displaySciIm = lsstDebug.Info(__name__).displaySciIm
        displaySpatialCells = lsstDebug.Info(__name__).displaySpatialCells
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)

        if not candidateList:
            raise RuntimeError(
                "Candidate list must be populated by makeCandidateList")

        if not self._validateSize(templateMaskedImage, scienceMaskedImage):
            self.log.error("ERROR: Input images different size")
            raise RuntimeError("Input images different size")

        if display and displayTemplate:
            ds9.mtv(templateMaskedImage,
                    frame=lsstDebug.frame,
                    title="Image to convolve")
            lsstDebug.frame += 1

        if display and displaySciIm:
            ds9.mtv(scienceMaskedImage,
                    frame=lsstDebug.frame,
                    title="Image to not convolve")
            lsstDebug.frame += 1

        kernelCellSet = self._buildCellSet(templateMaskedImage,
                                           scienceMaskedImage, candidateList)

        if display and displaySpatialCells:
            diUtils.showKernelSpatialCells(scienceMaskedImage,
                                           kernelCellSet,
                                           symb="o",
                                           ctype=ds9.CYAN,
                                           ctypeUnused=ds9.YELLOW,
                                           ctypeBad=ds9.RED,
                                           size=4,
                                           frame=lsstDebug.frame,
                                           title="Image to not convolve")
            lsstDebug.frame += 1

        if templateFwhmPix and scienceFwhmPix:
            self.log.info("Matching Psf FWHM %.2f -> %.2f pix",
                          templateFwhmPix, scienceFwhmPix)

        if self.kConfig.useBicForKernelBasis:
            tmpKernelCellSet = self._buildCellSet(templateMaskedImage,
                                                  scienceMaskedImage,
                                                  candidateList)
            nbe = diffimTools.NbasisEvaluator(self.kConfig, templateFwhmPix,
                                              scienceFwhmPix)
            bicDegrees = nbe(tmpKernelCellSet, self.log)
            basisList = makeKernelBasisList(self.kConfig,
                                            templateFwhmPix,
                                            scienceFwhmPix,
                                            alardDegGauss=bicDegrees[0],
                                            metadata=self.metadata)
            del tmpKernelCellSet
        else:
            basisList = makeKernelBasisList(self.kConfig,
                                            templateFwhmPix,
                                            scienceFwhmPix,
                                            metadata=self.metadata)

        spatialSolution, psfMatchingKernel, backgroundModel = self._solve(
            kernelCellSet, basisList)

        psfMatchedMaskedImage = afwImage.MaskedImageF(
            templateMaskedImage.getBBox())
        doNormalize = False
        afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage,
                         psfMatchingKernel, doNormalize)
        return pipeBase.Struct(
            matchedImage=psfMatchedMaskedImage,
            psfMatchingKernel=psfMatchingKernel,
            backgroundModel=backgroundModel,
            kernelCellSet=kernelCellSet,
        )
    def _buildCellSet(self, exposure, referencePsfModel):
        """!Build a SpatialCellSet for use with the solve method

        @param exposure: The science exposure that will be convolved; must contain a Psf
        @param referencePsfModel: Psf model to match to

        @return kernelCellSet: a SpatialCellSet to be used by self._solve

        Raise a RuntimeError if the reference Psf model and science Psf model have different dimensions
        """
        scienceBBox = exposure.getBBox()
        sciencePsfModel = exposure.getPsf()
        # The Psf base class does not support getKernel() in general, as there are some Psf
        # classes for which this is not meaningful.
        # Many Psfs we use in practice are KernelPsfs, and this algorithm will work fine for them,
        # but someday it should probably be modified to support arbitrary Psfs.
        referencePsfModel = measAlg.KernelPsf.swigConvert(referencePsfModel)
        sciencePsfModel = measAlg.KernelPsf.swigConvert(sciencePsfModel)
        if referencePsfModel is None or sciencePsfModel is None:
            raise RuntimeError(
                "ERROR: Psf matching is only implemented for KernelPsfs")
        if (referencePsfModel.getKernel().getDimensions() !=
                sciencePsfModel.getKernel().getDimensions()):
            pexLog.Trace(
                self.log.getName(), 1,
                "ERROR: Dimensions of reference Psf and science Psf different; exiting"
            )
            raise RuntimeError, "ERROR: Dimensions of reference Psf and science Psf different; exiting"

        psfWidth, psfHeight = referencePsfModel.getKernel().getDimensions()
        maxKernelSize = min(psfWidth, psfHeight) - 1
        if maxKernelSize % 2 == 0:
            maxKernelSize -= 1
        if self.kConfig.kernelSize > maxKernelSize:
            raise ValueError, "Kernel size (%d) too big to match Psfs of size %d; reduce to at least %d" % (
                self.kConfig.kernelSize, psfWidth, maxKernelSize)

        # Infer spatial order of Psf model!
        #
        # Infer from the number of spatial parameters.
        # (O + 1) * (O + 2) / 2 = N
        # O^2 + 3 * O + 2 * (1 - N) = 0
        #
        # Roots are [-3 +/- sqrt(9 - 8 * (1 - N))] / 2
        #
        nParameters = sciencePsfModel.getKernel().getNSpatialParameters()
        root = num.sqrt(9 - 8 * (1 - nParameters))
        if (root != root // 1):  # We know its an integer solution
            pexLog.Trace(self.log.getName(), 3,
                         "Problem inferring spatial order of image's Psf")
        else:
            order = (root - 3) / 2
            if (order != order // 1):
                pexLog.Trace(self.log.getName(), 3,
                             "Problem inferring spatial order of image's Psf")
            else:
                pexLog.Trace(
                    self.log.getName(), 2,
                    "Spatial order of Psf = %d; matching kernel order = %d" %
                    (order, self.kConfig.spatialKernelOrder))

        regionSizeX, regionSizeY = scienceBBox.getDimensions()
        scienceX0, scienceY0 = scienceBBox.getMin()

        sizeCellX = self.kConfig.sizeCellX
        sizeCellY = self.kConfig.sizeCellY

        kernelCellSet = afwMath.SpatialCellSet(
            afwGeom.Box2I(afwGeom.Point2I(scienceX0, scienceY0),
                          afwGeom.Extent2I(regionSizeX, regionSizeY)),
            sizeCellX, sizeCellY)

        nCellX = regionSizeX // sizeCellX
        nCellY = regionSizeY // sizeCellY
        dimenR = referencePsfModel.getKernel().getDimensions()
        dimenS = sciencePsfModel.getKernel().getDimensions()

        policy = pexConfig.makePolicy(self.kConfig)
        for row in range(nCellY):
            # place at center of cell
            posY = sizeCellY * row + sizeCellY // 2 + scienceY0

            for col in range(nCellX):
                # place at center of cell
                posX = sizeCellX * col + sizeCellX // 2 + scienceX0

                pexLog.Trace(
                    self.log.getName(), 5,
                    "Creating Psf candidate at %.1f %.1f" % (posX, posY))

                # reference kernel image, at location of science subimage
                kernelImageR = referencePsfModel.computeImage(
                    afwGeom.Point2D(posX, posY)).convertF()
                kernelMaskR = afwImage.MaskU(dimenR)
                kernelMaskR.set(0)
                kernelVarR = afwImage.ImageF(dimenR)
                kernelVarR.set(1.0)
                referenceMI = afwImage.MaskedImageF(kernelImageR, kernelMaskR,
                                                    kernelVarR)

                # kernel image we are going to convolve
                kernelImageS = sciencePsfModel.computeImage(
                    afwGeom.Point2D(posX, posY)).convertF()
                kernelMaskS = afwImage.MaskU(dimenS)
                kernelMaskS.set(0)
                kernelVarS = afwImage.ImageF(dimenS)
                kernelVarS.set(1.0)
                scienceMI = afwImage.MaskedImageF(kernelImageS, kernelMaskS,
                                                  kernelVarS)

                # The image to convolve is the science image, to the reference Psf.
                kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI,
                                                   referenceMI, policy)
                kernelCellSet.insertCandidate(kc)

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displaySpatialCells = lsstDebug.Info(__name__).displaySpatialCells
        maskTransparency = lsstDebug.Info(__name__).maskTransparency
        if not maskTransparency:
            maskTransparency = 0
        if display:
            ds9.setMaskTransparency(maskTransparency)
        if display and displaySpatialCells:
            diUtils.showKernelSpatialCells(exposure.getMaskedImage(),
                                           kernelCellSet,
                                           symb="o",
                                           ctype=ds9.CYAN,
                                           ctypeUnused=ds9.YELLOW,
                                           ctypeBad=ds9.RED,
                                           size=4,
                                           frame=lsstDebug.frame,
                                           title="Image to be convolved")
            lsstDebug.frame += 1
        return kernelCellSet