Esempio n. 1
0
    def runStdTest(self, kernel, refKernel=None, kernelDescr="", rtol=1.0e-05, atol=1e-08,
        maxInterpDist=10):
        """Assert that afwMath::convolve gives the same result as reference convolution for a given kernel.
        
        Inputs:
        - kernel: convolution kernel
        - refKernel: kernel to use for refConvolve (if None then kernel is used)
        - kernelDescr: description of kernel
        - rtol: relative tolerance (see below)
        - atol: absolute tolerance (see below)
        - maxInterpDist: maximum allowed distance for linear interpolation during convolution
        
        rtol and atol are positive, typically very small numbers.
        The relative difference (rtol * abs(b)) and the absolute difference "atol" are added together
        to compare against the absolute difference between "a" and "b".
        """
        if VERBOSITY > 0:
            print "Test convolution with", kernelDescr
        
        convControl = afwMath.ConvolutionControl()
        convControl.setMaxInterpolationDistance(maxInterpDist)
        
        # verify dimension assertions:
        # - output image dimensions = input image dimensions
        # - input image width and height >= kernel width and height
        # Note: the assertion kernel size > 0 is tested elsewhere
        for inWidth in (kernel.getWidth() - 1, self.width-1, self.width, self.width + 1):
            for inHeight in (kernel.getHeight() - 1, self.width-1, self.width, self.width + 1):
                if (inWidth == self.width) and (inHeight == self.height):
                    continue
                inMaskedImage = afwImage.MaskedImageF(afwGeom.Extent2I(inWidth, inHeight))
                self.assertRaises(Exception, afwMath.convolve, self.cnvMaskedImage, inMaskedImage, kernel)

        for doNormalize in (True,): # (False, True):
            convControl.setDoNormalize(doNormalize)
            for doCopyEdge in (False,): # (False, True):
                convControl.setDoCopyEdge(doCopyEdge)
                self.runBasicTest(kernel, convControl=convControl, refKernel=refKernel,
                    kernelDescr=kernelDescr, rtol=rtol, atol=atol)

        # verify that basicConvolve does not write to edge pixels
        self.runBasicConvolveEdgeTest(kernel, kernelDescr)
Esempio n. 2
0
    def runBasicConvolveEdgeTest(self, kernel, kernelDescr):
        """Verify that basicConvolve does not write to edge pixels for this kind of kernel
        """
        fullBox = afwGeom.Box2I(
            afwGeom.Point2I(0, 0),
            ShiftedBBox.getDimensions(),
        )
        goodBox = kernel.shrinkBBox(fullBox)
        cnvMaskedImage = afwImage.MaskedImageF(FullMaskedImage, ShiftedBBox,
                                               afwImage.LOCAL, True)
        cnvMaskedImageCopy = afwImage.MaskedImageF(cnvMaskedImage, fullBox,
                                                   afwImage.LOCAL, True)
        cnvMaskedImageCopyViewOfGoodRegion = afwImage.MaskedImageF(
            cnvMaskedImageCopy, goodBox, afwImage.LOCAL, False)

        # convolve with basicConvolve, which should leave the edge pixels alone
        convControl = afwMath.ConvolutionControl()
        mathDetail.basicConvolve(cnvMaskedImage, self.maskedImage, kernel,
                                 convControl)

        # reset the good region to the original convolved image;
        # this should reset the entire convolved image to its original self
        cnvMaskedImageGoodView = afwImage.MaskedImageF(cnvMaskedImage, goodBox,
                                                       afwImage.LOCAL, False)
        cnvMaskedImageGoodView <<= cnvMaskedImageCopyViewOfGoodRegion

        # assert that these two are equal
        cnvImMaskVarArr = cnvMaskedImage.getArrays()
        desCnvImMaskVarArr = cnvMaskedImageCopy.getArrays()
        errStr = imTestUtils.maskedImagesDiffer(cnvImMaskVarArr,
                                                desCnvImMaskVarArr,
                                                doVariance=True,
                                                rtol=0,
                                                atol=0)
        shortKernelDescr = kernelDescr.translate(NullTranslator, GarbageChars)
        if errStr:
            cnvMaskedImage.writeFits("actBasicConvolve%s" %
                                     (shortKernelDescr, ))
            cnvMaskedImageCopy.writeFits("desBasicConvolve%s" %
                                         (shortKernelDescr, ))
            self.fail("basicConvolve(MaskedImage, kernel=%s) wrote to edge pixels:\n%s" % \
                (kernelDescr, errStr))
    def testBad(self):
        ti = afwImage.MaskedImageF(geom.Extent2I(100, 100))
        ti.getVariance().set(0.1)
        ti[50, 50, afwImage.LOCAL] = (1., 0x0, 1.)
        sKernel = self.makeSpatialKernel(2)
        si = afwImage.MaskedImageF(ti.getDimensions())
        convolutionControl = afwMath.ConvolutionControl()
        convolutionControl.setDoNormalize(True)
        afwMath.convolve(si, ti, sKernel, convolutionControl)

        bbox = geom.Box2I(geom.Point2I(25, 25), geom.Point2I(75, 75))
        si = afwImage.MaskedImageF(si, bbox, origin=afwImage.LOCAL)
        ti = afwImage.MaskedImageF(ti, bbox, origin=afwImage.LOCAL)
        kc = ipDiffim.KernelCandidateF(50., 50., ti, si, self.ps)

        badGaussian = afwMath.GaussianFunction2D(1., 1., 0.)
        badKernel = afwMath.AnalyticKernel(self.ksize, self.ksize, badGaussian)
        basisList = []
        basisList.append(badKernel)
        badSpatialKernelFunction = afwMath.PolynomialFunction2D(0)
        badSpatialKernel = afwMath.LinearCombinationKernel(
            basisList, badSpatialKernelFunction)
        badSpatialKernel.setSpatialParameters([[
            1,
        ]])

        sBg = afwMath.PolynomialFunction2D(1)
        bgCoeffs = [10., 10., 10.]
        sBg.setParameters(bgCoeffs)

        # must be initialized
        bskv = ipDiffim.BuildSingleKernelVisitorF(self.kList, self.ps)
        bskv.processCandidate(kc)
        self.assertEqual(kc.isInitialized(), True)

        askv = ipDiffim.AssessSpatialKernelVisitorF(badSpatialKernel, sBg,
                                                    self.ps)
        askv.processCandidate(kc)

        self.assertEqual(askv.getNProcessed(), 1)
        self.assertEqual(askv.getNRejected(), 1)
        self.assertEqual(kc.getStatus(), afwMath.SpatialCellCandidate.BAD)
Esempio n. 4
0
    def setUp(self):
        FWHM = 5
        psf = algorithms.DoubleGaussianPsf(
            15, 15, FWHM / (2 * math.sqrt(2 * math.log(2))))
        mi = afwImage.MaskedImageF(lsst.geom.ExtentI(100, 100))

        self.xc, self.yc, self.instFlux = 45, 55, 1000.0
        mi.image[self.xc, self.yc, afwImage.LOCAL] = self.instFlux

        cnvImage = mi.Factory(mi.getDimensions())
        afwMath.convolve(cnvImage, mi, psf.getKernel(),
                         afwMath.ConvolutionControl())

        self.exp = afwImage.makeExposure(cnvImage)
        self.exp.setPsf(psf)

        if display and False:
            afwDisplay.Display(frame=0).mtv(self.exp,
                                            title=self._testMethodName +
                                            ": image")
Esempio n. 5
0
    def setUp(self):
        self.config = ipDiffim.ImagePsfMatchTask.ConfigClass()
        self.config.kernel.name = "AL"
        self.subconfig = self.config.kernel.active
        self.kSize = self.subconfig.kernelSize

        # gaussian reference kernel
        self.gSize = self.kSize
        self.gaussFunction = afwMath.GaussianFunction2D(2, 3)
        self.gaussKernel = afwMath.AnalyticKernel(self.gSize, self.gSize, self.gaussFunction)

        if defDataDir:
            defImagePath = os.path.join(defDataDir, "DC3a-Sim", "sci", "v5-e0",
                                        "v5-e0-c011-a00.sci.fits")
            self.templateImage = afwImage.MaskedImageF(defImagePath)
            self.scienceImage = self.templateImage.Factory(self.templateImage.getDimensions())

            convolutionControl = afwMath.ConvolutionControl()
            convolutionControl.setDoNormalize(False)
            afwMath.convolve(self.scienceImage, self.templateImage, self.gaussKernel, convolutionControl)
Esempio n. 6
0
    def _doConvolve(exposure, kernel):
        """! Convolve an Exposure with a decorrelation convolution kernel.
        @param exposure Input afw.image.Exposure to be convolved.
        @param kernel Input 2-d numpy.array to convolve the image with
        @return a new Exposure with the convolved pixels and the (possibly
        re-centered) kernel.

        @note We re-center the kernel if necessary and return the possibly re-centered kernel
        """
        kernelImg = afwImage.ImageD(kernel.shape[0], kernel.shape[1])
        kernelImg.getArray()[:, :] = kernel
        kern = afwMath.FixedKernel(kernelImg)
        maxloc = np.unravel_index(np.argmax(kernel), kernel.shape)
        kern.setCtrX(maxloc[0])
        kern.setCtrY(maxloc[1])
        outExp = exposure.clone()  # Do this to keep WCS, PSF, masks, etc.
        convCntrl = afwMath.ConvolutionControl(False, True, 0)
        afwMath.convolve(outExp.getMaskedImage(), exposure.getMaskedImage(),
                         kern, convCntrl)

        return outExp, kern
Esempio n. 7
0
    def runBasicConvolveEdgeTest(self, kernel, kernelDescr):
        """Verify that basicConvolve does not write to edge pixels for this kind of kernel
        """
        fullBox = afwGeom.Box2I(
            afwGeom.Point2I(0, 0),
            ShiftedBBox.getDimensions(),
        )
        goodBox = kernel.shrinkBBox(fullBox)
        cnvMaskedImage = afwImage.MaskedImageF(
            FullMaskedImage, ShiftedBBox, afwImage.LOCAL, True)
        cnvMaskedImageCopy = afwImage.MaskedImageF(
            cnvMaskedImage, fullBox, afwImage.LOCAL, True)
        cnvMaskedImageCopyViewOfGoodRegion = afwImage.MaskedImageF(
            cnvMaskedImageCopy, goodBox, afwImage.LOCAL, False)

        # convolve with basicConvolve, which should leave the edge pixels alone
        convControl = afwMath.ConvolutionControl()
        mathDetail.basicConvolve(
            cnvMaskedImage, self.maskedImage, kernel, convControl)

        # reset the good region to the original convolved image;
        # this should reset the entire convolved image to its original self
        cnvMaskedImageGoodView = afwImage.MaskedImageF(
            cnvMaskedImage, goodBox, afwImage.LOCAL, False)
        cnvMaskedImageGoodView[:] = cnvMaskedImageCopyViewOfGoodRegion

        # assert that these two are equal
        msg = "basicConvolve(MaskedImage, kernel=%s) wrote to edge pixels" % (
            kernelDescr,)
        try:
            self.assertMaskedImagesAlmostEqual(cnvMaskedImage, cnvMaskedImageCopy,
                                               doVariance=True, rtol=0, atol=0, msg=msg)
        except Exception:
            # write out the images, then fail
            shortKernelDescr = self.removeGarbageChars(kernelDescr)
            cnvMaskedImage.writeFits(
                "actBasicConvolve%s" % (shortKernelDescr,))
            cnvMaskedImageCopy.writeFits(
                "desBasicConvolve%s" % (shortKernelDescr,))
            raise
Esempio n. 8
0
    def _doConvolve(self, exposure, kernel, recenterKernel=False):
        """! Convolve an Exposure with a decorrelation convolution kernel.

        Parameters
        ----------
        exposure : lsst.afw.image.Exposure to be convolved.
        kernel : 2D numpy.array to convolve the image with

        Returns
        -------
        A new lsst.afw.image.Exposure with the convolved pixels and the (possibly
        re-centered) kernel.

        Notes
        -----
        - We optionally re-center the kernel if necessary and return the possibly
          re-centered kernel
        """
        kernelImg = afwImage.ImageD(kernel.shape[0], kernel.shape[1])
        kernelImg.getArray()[:, :] = kernel
        kern = afwMath.FixedKernel(kernelImg)
        if recenterKernel:
            maxloc = np.unravel_index(np.argmax(kernel), kernel.shape)
            kern.setCtrX(maxloc[0])
            kern.setCtrY(maxloc[1])
        outExp = exposure.clone()  # Do this to keep WCS, PSF, masks, etc.
        convCntrl = afwMath.ConvolutionControl(doNormalize=False,
                                               doCopyEdge=False,
                                               maxInterpolationDistance=0)
        try:
            afwMath.convolve(outExp.getMaskedImage(),
                             exposure.getMaskedImage(), kern, convCntrl)
        except:
            # Allow exposure to actually be an image/maskedImage
            afwMath.convolve(outExp, exposure, kern, convCntrl)

        return outExp, kern
Esempio n. 9
0
def testAutoCorrelation(orderMake, orderFit, inMi=None, display=False):
    config = ipDiffim.ImagePsfMatchTask.ConfigClass()
    config.kernel.name = "AL"
    subconfig = config.kernel.active

    subconfig.fitForBackground = True

    stride = 100

    if inMi is None:
        width = 512
        height = 2048
        inMi = afwImage.MaskedImageF(afwGeom.Extent2I(width, height))
        for j in num.arange(stride // 2, height, stride):
            j = int(j)
            for i in num.arange(stride // 2, width, stride):
                i = int(i)
                inMi._set((i - 1, j - 1), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i - 1, j + 0), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i - 1, j + 1), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i + 0, j - 1), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i + 0, j + 0), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i + 0, j + 1), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i + 1, j - 1), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i + 1, j + 0), (100., 0x0, 1.), afwImage.LOCAL)
                inMi._set((i + 1, j + 1), (100., 0x0, 1.), afwImage.LOCAL)

    addNoise(inMi)

    kSize = subconfig.kernelSize

    basicGaussian1 = afwMath.GaussianFunction2D(2., 2., 0.)
    basicKernel1 = afwMath.AnalyticKernel(kSize, kSize, basicGaussian1)

    basicGaussian2 = afwMath.GaussianFunction2D(5., 3., 0.5 * num.pi)
    basicKernel2 = afwMath.AnalyticKernel(kSize, kSize, basicGaussian2)

    basisList = []
    basisList.append(basicKernel1)
    basisList.append(basicKernel2)

    spatialKernelFunction = afwMath.PolynomialFunction2D(orderMake)
    spatialKernel = afwMath.LinearCombinationKernel(basisList,
                                                    spatialKernelFunction)
    kCoeffs = [[
        1.0 for x in range(1,
                           spatialKernelFunction.getNParameters() + 1)
    ], [
        0.01 * x for x in range(1,
                                spatialKernelFunction.getNParameters() + 1)
    ]]
    spatialKernel.setSpatialParameters(kCoeffs)

    cMi = afwImage.MaskedImageF(inMi.getDimensions())
    convolutionControl = afwMath.ConvolutionControl()
    convolutionControl.setDoNormalize(True)
    afwMath.convolve(cMi, inMi, spatialKernel, convolutionControl)

    if display:
        afwDisplay.Display(frame=1).mtv(inMi.getImage())
        afwDisplay.Display(frame=2).mtv(inMi.getVariance())

        afwDisplay.Display(frame=3).mtv(cMi.getImage())
        afwDisplay.Display(frame=4).mtv(cMi.getVariance())

    subconfig.spatialKernelOrder = orderFit
    subconfig.sizeCellX = stride
    subconfig.sizeCellY = stride
    psfmatch = ipDiffim.ImagePsfMatchTask(config=config)
    candList = psfmatch.makeCandidateList(afwImage.ExposureF(inMi),
                                          afwImage.ExposureF(cMi), kSize)
    result = psfmatch.subtractMaskedImages(inMi, cMi, candList)

    spatialKernel = result.psfMatchingKernel
    kernelCellSet = result.kernelCellSet
    makeAutoCorrelation(kernelCellSet, spatialKernel, makePlot=True)
    def testGaussianWithNoise(self):
        # Convolve a real image with a gaussian and try and recover
        # it.  Add noise and perform the same test.

        gsize = self.ps["kernelSize"]
        gaussFunction = afwMath.GaussianFunction2D(2, 3)
        gaussKernel = afwMath.AnalyticKernel(gsize, gsize, gaussFunction)
        kImageIn = afwImage.ImageD(geom.Extent2I(gsize, gsize))
        kSumIn = gaussKernel.computeImage(kImageIn, False)

        imX, imY = self.templateExposure2.getMaskedImage().getDimensions()
        smi = afwImage.MaskedImageF(geom.Extent2I(imX, imY))

        convolutionControl = afwMath.ConvolutionControl()
        convolutionControl.setDoNormalize(False)
        afwMath.convolve(smi, self.templateExposure2.getMaskedImage(),
                         gaussKernel, convolutionControl)

        bbox = gaussKernel.shrinkBBox(smi.getBBox(afwImage.LOCAL))

        tmi2 = afwImage.MaskedImageF(self.templateExposure2.getMaskedImage(),
                                     bbox,
                                     origin=afwImage.LOCAL)
        smi2 = afwImage.MaskedImageF(smi, bbox, origin=afwImage.LOCAL)

        kc = ipDiffim.KernelCandidateF(self.x02, self.y02, tmi2, smi2, self.ps)
        kList = ipDiffim.makeKernelBasisList(self.subconfig)
        kc.build(kList)
        self.assertEqual(kc.isInitialized(), True)
        kImageOut = kc.getImage()

        soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT)
        self.assertAlmostEqual(soln.getKsum(), kSumIn)
        # 8.7499380640430563e-06 != 0.0 within 7 places
        self.assertAlmostEqual(soln.getBackground(), 0.0, 4)

        for j in range(kImageOut.getHeight()):
            for i in range(kImageOut.getWidth()):

                # in the outskirts of the kernel, the ratio can get screwed because of low S/N
                # e.g. 7.45817359824e-09 vs. 1.18062529402e-08
                # in the guts of the kernel it should look closer
                if kImageIn[i, j, afwImage.LOCAL] > 1e-4:
                    # sigh, too bad this sort of thing fails..
                    # 0.99941584433815966 != 1.0 within 3 places
                    self.assertAlmostEqual(
                        kImageOut[i, j, afwImage.LOCAL] /
                        kImageIn[i, j, afwImage.LOCAL], 1.0, 2)

        # now repeat with noise added; decrease precision of comparison
        self.addNoise(smi2)
        kc = ipDiffim.KernelCandidateF(self.x02, self.y02, tmi2, smi2, self.ps)
        kList = ipDiffim.makeKernelBasisList(self.subconfig)
        kc.build(kList)
        self.assertEqual(kc.isInitialized(), True)
        kImageOut = kc.getImage()

        soln = kc.getKernelSolution(ipDiffim.KernelCandidateF.RECENT)
        self.assertAlmostEqual(soln.getKsum(), kSumIn, 3)
        if not self.ps.get("fitForBackground"):
            self.assertEqual(soln.getBackground(), 0.0)

        for j in range(kImageOut.getHeight()):
            for i in range(kImageOut.getWidth()):
                if kImageIn[i, j, afwImage.LOCAL] > 1e-2:
                    self.assertAlmostEqual(kImageOut[i, j, afwImage.LOCAL],
                                           kImageIn[i, j, afwImage.LOCAL], 2)
Esempio n. 11
0
    def _makeAndTestUncorrectedDiffim(self):
        """Create the (un-decorrelated) diffim, and verify that its variance is too low.
        """
        # Create the matching kernel. We used Gaussian PSFs for im1 and im2, so we can compute the "expected"
        # matching kernel sigma.
        psf1pos = self.im1ex.getPsf().getAveragePosition()
        psf2pos = self.im2ex.getPsf().getAveragePosition()
        psf1_sig = self.im1ex.getPsf().computeShape(
            psf1pos).getDeterminantRadius()
        psf2_sig = self.im2ex.getPsf().computeShape(
            psf2pos).getDeterminantRadius()
        sig_match = np.sqrt((psf1_sig**2. - psf2_sig**2.))
        # Sanity check - make sure PSFs are correct.
        self.assertFloatsAlmostEqual(sig_match,
                                     np.sqrt((self.psf1_sigma**2. -
                                              self.psf2_sigma**2.)),
                                     rtol=2e-5)
        # mKernel = measAlg.SingleGaussianPsf(31, 31, sig_match)
        x0 = np.arange(-16, 16, 1)
        y0 = x0.copy()
        x0im, y0im = np.meshgrid(x0, y0)
        matchingKernel = singleGaussian2d(x0im,
                                          y0im,
                                          -1.,
                                          -1.,
                                          sigma_x=sig_match,
                                          sigma_y=sig_match)
        kernelImg = afwImage.ImageD(matchingKernel.shape[0],
                                    matchingKernel.shape[1])
        kernelImg.getArray()[:, :] = matchingKernel
        mKernel = afwMath.FixedKernel(kernelImg)

        # Create the matched template by convolving the template with the matchingKernel
        matched_im2ex = self.im2ex.clone()
        convCntrl = afwMath.ConvolutionControl(False, True, 0)
        afwMath.convolve(matched_im2ex.getMaskedImage(),
                         self.im2ex.getMaskedImage(), mKernel, convCntrl)

        # Expected (ideal) variance of difference image
        expected_var = self.svar + self.tvar
        if verbose:
            print('EXPECTED VARIANCE:', expected_var)

        # Create the diffim (uncorrected)
        # Uncorrected diffim exposure - variance plane is wrong (too low)
        tmp_diffExp = self.im1ex.getMaskedImage().clone()
        tmp_diffExp -= matched_im2ex.getMaskedImage()
        var = self._computeVarianceMean(tmp_diffExp)
        self.assertLess(var, expected_var)

        # Uncorrected diffim exposure - variance is wrong (too low) - same as above but on pixels
        diffExp = self.im1ex.clone()
        tmp = diffExp.getMaskedImage()
        tmp -= matched_im2ex.getMaskedImage()
        var = self._computePixelVariance(diffExp.getMaskedImage())
        self.assertLess(var, expected_var)

        # Uncorrected diffim exposure - variance plane is wrong (too low)
        mn = self._computeVarianceMean(diffExp.getMaskedImage())
        self.assertLess(mn, expected_var)
        if verbose:
            print('UNCORRECTED VARIANCE:', var, mn)

        return diffExp, mKernel, expected_var
Esempio n. 12
0
    def testPeakLikelihoodFlux(self):
        """Test measurement with PeakLikelihoodFlux
        """
        # make and measure a series of exposures containing just one star, approximately centered
        bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(100, 101))
        kernelWidth = 35
        var = 100
        fwhm = 3.0
        sigma = fwhm / FwhmPerSigma
        convolutionControl = afwMath.ConvolutionControl()
        psf = afwDetection.GaussianPsf(kernelWidth, kernelWidth, sigma)
        psfKernel = psf.getLocalKernel()
        psfImage = psf.computeKernelImage()
        sumPsfSq = numpy.sum(psfImage.getArray()**2)
        psfSqArr = psfImage.getArray()**2

        for flux in (1000, 10000):
            ctrInd = afwGeom.Point2I(50, 51)
            ctrPos = afwGeom.Point2D(ctrInd)

            kernelBBox = psfImage.getBBox()
            kernelBBox.shift(afwGeom.Extent2I(ctrInd))

            # compute predicted flux error
            unshMImage = makeFakeImage(bbox, [ctrPos], [flux], fwhm, var)

            # filter image by PSF
            unshFiltMImage = afwImage.MaskedImageF(unshMImage.getBBox())
            afwMath.convolve(unshFiltMImage, unshMImage, psfKernel,
                             convolutionControl)

            # compute predicted flux = value of image at peak / sum(PSF^2)
            # this is a sanity check of the algorithm, as much as anything
            predFlux = unshFiltMImage.getImage().get(ctrInd[0],
                                                     ctrInd[1]) / sumPsfSq
            self.assertLess(abs(flux - predFlux), flux * 0.01)

            # compute predicted flux error based on filtered pixels
            # = sqrt(value of filtered variance at peak / sum(PSF^2)^2)
            predFluxErr = math.sqrt(unshFiltMImage.getVariance().get(
                ctrInd[0], ctrInd[1])) / sumPsfSq

            # compute predicted flux error based on unfiltered pixels
            # = sqrt(sum(unfiltered variance * PSF^2)) / sum(PSF^2)
            # and compare to that derived from filtered pixels;
            # again, this is a test of the algorithm
            varView = afwImage.ImageF(unshMImage.getVariance(), kernelBBox)
            varArr = varView.getArray()
            unfiltPredFluxErr = math.sqrt(numpy.sum(
                varArr * psfSqArr)) / sumPsfSq
            self.assertLess(abs(unfiltPredFluxErr - predFluxErr),
                            predFluxErr * 0.01)

            for fracOffset in (afwGeom.Extent2D(0, 0),
                               afwGeom.Extent2D(0.2, -0.3)):
                adjCenter = ctrPos + fracOffset
                if fracOffset == (0, 0):
                    maskedImage = unshMImage
                    filteredImage = unshFiltMImage
                else:
                    maskedImage = makeFakeImage(bbox, [adjCenter], [flux],
                                                fwhm, var)
                    # filter image by PSF
                    filteredImage = afwImage.MaskedImageF(
                        maskedImage.getBBox())
                    afwMath.convolve(filteredImage, maskedImage, psfKernel,
                                     convolutionControl)

                exp = afwImage.makeExposure(filteredImage)
                exp.setPsf(psf)
                control = measBase.PeakLikelihoodFluxControl()
                plugin, cat = makePluginAndCat(
                    measBase.PeakLikelihoodFluxAlgorithm,
                    "test",
                    control,
                    centroid="centroid")
                source = cat.makeRecord()
                source.set("centroid_x", adjCenter.getX())
                source.set("centroid_y", adjCenter.getY())
                plugin.measure(source, exp)
                measFlux = source.get("test_flux")
                measFluxErr = source.get("test_fluxSigma")
                self.assertLess(abs(measFlux - flux), flux * 0.003)

                self.assertLess(abs(measFluxErr - predFluxErr),
                                predFluxErr * 0.2)

                # try nearby points and verify that the flux is smaller;
                # this checks that the sub-pixel shift is performed in the correct direction
                for dx in (-0.2, 0, 0.2):
                    for dy in (-0.2, 0, 0.2):
                        if dx == dy == 0:
                            continue
                        offsetCtr = afwGeom.Point2D(adjCenter[0] + dx,
                                                    adjCenter[1] + dy)
                        source = cat.makeRecord()
                        source.set("centroid_x", offsetCtr.getX())
                        source.set("centroid_y", offsetCtr.getY())
                        plugin.measure(source, exp)
                        self.assertLess(source.get("test_flux"), measFlux)

        # source so near edge of image that PSF does not overlap exposure should result in failure
        for edgePos in (
            (1, 50),
            (50, 1),
            (50, bbox.getHeight() - 1),
            (bbox.getWidth() - 1, 50),
        ):
            source = cat.makeRecord()
            source.set("centroid_x", edgePos[0])
            source.set("centroid_y", edgePos[1])
            self.assertRaises(
                lsst.pex.exceptions.RangeError,
                plugin.measure,
                source,
                exp,
            )

        # no PSF should result in failure: flags set
        noPsfExposure = afwImage.ExposureF(filteredImage)
        source = cat.makeRecord()
        source.set("centroid_x", edgePos[0])
        source.set("centroid_y", edgePos[1])
        self.assertRaises(
            lsst.pex.exceptions.InvalidParameterError,
            plugin.measure,
            source,
            noPsfExposure,
        )
Esempio n. 13
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

        Parameters
        ----------
        templateMaskedImage : `lsst.afw.image.MaskedImage`
            masked image to PSF-match to the reference masked image;
            must be warped to match the reference masked image
        scienceMaskedImage : `lsst.afw.image.MaskedImage`
            maskedImage whose PSF is 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

        Returns
        -------
        result : `callable`
        An `lsst.pipe.base.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

        Raises
        ------
        RuntimeError
            Raised 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:
            afwDisplay.setDefaultMaskTransparency(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:
            disp = afwDisplay.Display(frame=lsstDebug.frame)
            disp.mtv(templateMaskedImage, title="Image to convolve")
            lsstDebug.frame += 1

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

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

        if display and displaySpatialCells:
            diffimUtils.showKernelSpatialCells(scienceMaskedImage,
                                               kernelCellSet,
                                               symb="o",
                                               ctype=afwDisplay.CYAN,
                                               ctypeUnused=afwDisplay.YELLOW,
                                               ctypeBad=afwDisplay.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 = self.makeKernelBasisList(templateFwhmPix,
                                                 scienceFwhmPix,
                                                 basisDegGauss=bicDegrees[0],
                                                 metadata=self.metadata)
            del tmpKernelCellSet
        else:
            basisList = self.makeKernelBasisList(templateFwhmPix,
                                                 scienceFwhmPix,
                                                 metadata=self.metadata)

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

        psfMatchedMaskedImage = afwImage.MaskedImageF(
            templateMaskedImage.getBBox())
        convolutionControl = afwMath.ConvolutionControl()
        convolutionControl.setDoNormalize(False)
        afwMath.convolve(psfMatchedMaskedImage, templateMaskedImage,
                         psfMatchingKernel, convolutionControl)
        return pipeBase.Struct(
            matchedImage=psfMatchedMaskedImage,
            psfMatchingKernel=psfMatchingKernel,
            backgroundModel=backgroundModel,
            kernelCellSet=kernelCellSet,
        )
Esempio n. 14
0
    def run(self, exposure, referencePsfModel, kernelSum=1.0):
        """Psf-match an exposure to a model Psf

        Parameters
        ----------
        exposure : `lsst.afw.image.Exposure`
            Exposure to Psf-match to the reference Psf model;
            it must return a valid PSF model via exposure.getPsf()
        referencePsfModel : `lsst.afw.detection.Psf`
            The Psf model to match to
        kernelSum : `float`, optional
            A multipicative factor to apply to the kernel sum (default=1.0)

        Returns
        -------
        result : `struct`
            - ``psfMatchedExposure`` : the Psf-matched Exposure.
                This has the same parent bbox, Wcs, PhotoCalib and
                Filter as the input Exposure but no Psf.
                In theory the Psf should equal referencePsfModel but
                the match is likely not exact.
            - ``psfMatchingKernel`` : the spatially varying Psf-matching kernel
            - ``kernelCellSet`` : SpatialCellSet used to solve for the Psf-matching kernel
            - ``referencePsfModel`` : Validated and/or modified reference model used

        Raises
        ------
        RuntimeError
            if the Exposure does not contain a Psf model
        """
        if not exposure.hasPsf():
            raise RuntimeError("exposure does not contain a Psf model")

        maskedImage = exposure.getMaskedImage()

        self.log.info("compute Psf-matching kernel")
        result = self._buildCellSet(exposure, referencePsfModel)
        kernelCellSet = result.kernelCellSet
        referencePsfModel = result.referencePsfModel
        # TODO: This should be evaluated at (or close to) the center of the
        # exposure's bounding box in DM-32756.
        sciAvgPos = exposure.getPsf().getAveragePosition()
        modelAvgPos = referencePsfModel.getAveragePosition()
        fwhmScience = exposure.getPsf().computeShape(sciAvgPos).getDeterminantRadius()*sigma2fwhm
        fwhmModel = referencePsfModel.computeShape(modelAvgPos).getDeterminantRadius()*sigma2fwhm

        basisList = makeKernelBasisList(self.kConfig, fwhmScience, fwhmModel, metadata=self.metadata)
        spatialSolution, psfMatchingKernel, backgroundModel = self._solve(kernelCellSet, basisList)

        if psfMatchingKernel.isSpatiallyVarying():
            sParameters = np.array(psfMatchingKernel.getSpatialParameters())
            sParameters[0][0] = kernelSum
            psfMatchingKernel.setSpatialParameters(sParameters)
        else:
            kParameters = np.array(psfMatchingKernel.getKernelParameters())
            kParameters[0] = kernelSum
            psfMatchingKernel.setKernelParameters(kParameters)

        self.log.info("Psf-match science exposure to reference")
        psfMatchedExposure = afwImage.ExposureF(exposure.getBBox(), exposure.getWcs())
        psfMatchedExposure.info.id = exposure.info.id
        psfMatchedExposure.setFilter(exposure.getFilter())
        psfMatchedExposure.setPhotoCalib(exposure.getPhotoCalib())
        psfMatchedExposure.getInfo().setVisitInfo(exposure.getInfo().getVisitInfo())
        psfMatchedExposure.setPsf(referencePsfModel)
        psfMatchedMaskedImage = psfMatchedExposure.getMaskedImage()

        # Normalize the psf-matching kernel while convolving since its magnitude is meaningless
        # when PSF-matching one model to another.
        convolutionControl = afwMath.ConvolutionControl()
        convolutionControl.setDoNormalize(True)
        afwMath.convolve(psfMatchedMaskedImage, maskedImage, psfMatchingKernel, convolutionControl)

        self.log.info("done")
        return pipeBase.Struct(psfMatchedExposure=psfMatchedExposure,
                               psfMatchingKernel=psfMatchingKernel,
                               kernelCellSet=kernelCellSet,
                               metadata=self.metadata,
                               )
    def brighterFatterCorrection(self, exposure, kernel, maxIter, threshold, applyGain):
        """Apply brighter fatter correction in place for the image

        This correction takes a kernel that has been derived from flat field images to
        redistribute the charge.  The gradient of the kernel is the deflection
        field due to the accumulated charge.

        Given the original image I(x) and the kernel K(x) we can compute the corrected image  Ic(x)
        using the following equation:

        Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))

        To evaluate the derivative term we expand it as follows:

        0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y))) + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) )

        Because we use the measured counts instead of the incident counts we apply the correction
        iteratively to reconstruct the original counts and the correction.  We stop iterating when the
        summed difference between the current corrected image and the one from the previous iteration
        is below the threshold.  We do not require convergence because the number of iterations is
        too large a computational cost.  How we define the threshold still needs to be evaluated, the
        current default was shown to work reasonably well on a small set of images.  For more information
        on the method see DocuShare Document-19407.

        The edges as defined by the kernel are not corrected because they have spurious values
        due to the convolution.
        """
        self.log.info("Applying brighter fatter correction")

        image = exposure.getMaskedImage().getImage()

        # The image needs to be units of electrons/holes
        with self.gainContext(exposure, image, applyGain):

            kLx = numpy.shape(kernel)[0]
            kLy = numpy.shape(kernel)[1]
            kernelImage = afwImage.ImageD(kernel.astype(numpy.float64))
            tempImage = image.clone()

            nanIndex = numpy.isnan(tempImage.getArray())
            tempImage.getArray()[nanIndex] = 0.

            outImage = afwImage.ImageF(image.getDimensions())
            corr = numpy.zeros_like(image.getArray())
            prev_image = numpy.zeros_like(image.getArray())
            convCntrl = afwMath.ConvolutionControl(False, True, 1)
            fixedKernel = afwMath.FixedKernel(kernelImage)

            # Define boundary by convolution region.  The region that the correction will be
            # calculated for is one fewer in each dimension because of the second derivative terms.
            startX = kLx/2
            endX = -kLx/2
            startY = kLy/2
            endY = -kLy/2

            for iteration in range(maxIter):

                afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
                tmpArray = tempImage.getArray()
                outArray = outImage.getArray()

                # First derivative term
                gradTmp = numpy.gradient(tmpArray[startY:endY,startX:endX])
                gradOut = numpy.gradient(outArray[startY:endY,startX:endX])
                first = (gradTmp[0]*gradOut[0] + gradTmp[1]*gradOut[1])

                # Second derivative term
                diffOut20 = numpy.gradient(gradOut[0])
                diffOut21 = numpy.gradient(gradOut[1])
                second = tmpArray[startY:endY, startX:endX]*(diffOut20[0] + diffOut21[1])

                corr[startY:endY, startX:endX] = 0.5*(first + second)

                # reset tmp image and apply correction
                tmpArray[:,:] = image.getArray()[:,:]
                tmpArray[nanIndex] = 0.
                tmpArray[startY:endY, startX:endX] += corr[startY:endY,startX:endX]

                if iteration > 0:
                    diff = numpy.sum(numpy.abs(prev_image - tmpArray))

                    if diff < threshold:
                        break
                    prev_image[:,:] = tmpArray[:,:]

            if iteration == maxIter -1:
                self.log.warn("Brighter fatter correction did not converge, final difference %f" % diff)

            self.log.info("Finished brighter fatter in %d iterations" % (iteration))
            image.getArray()[startY:endY, startX:endX] += corr[startY:endY, startX:endX]
Esempio n. 16
0
    def measure(self):
        """Detect and measure sources"""
        mi = self.exposure.getMaskedImage()

        #
        # We do a pretty good job of interpolating, so don't propagagate the convolved CR/INTRP bits
        # (we'll keep them for the original CR/INTRP pixels)
        #
        savedMask = mi.getMask().Factory(mi.getMask(), True)
        saveBits = savedMask.getPlaneBitMask("CR") | \
                   savedMask.getPlaneBitMask("BAD") | \
                   savedMask.getPlaneBitMask("INTRP") # Bits to not convolve
        savedMask &= saveBits

        msk = mi.getMask(); msk &= ~saveBits; del msk # Clear the saved bits
        #
        # Smooth image
        #
        cnvImage = mi.Factory(mi.getBBox(afwImage.PARENT))
        afwMath.convolve(cnvImage, mi, self.psf.getKernel(), afwMath.ConvolutionControl())

        msk = cnvImage.getMask(); msk |= savedMask; del msk # restore the saved bits

        threshold = afwDetection.Threshold(3, afwDetection.Threshold.STDEV)
        #
        # Only search the part of the frame that was PSF-smoothed
        #        
        llc = afwGeom.PointI(self.psf.getKernel().getWidth()/2, self.psf.getKernel().getHeight()/2)
        urc = afwGeom.PointI(cnvImage.getWidth() - 1, cnvImage.getHeight() - 1) - afwGeom.ExtentI(llc[0], llc[1]);
        middle = cnvImage.Factory(cnvImage, afwGeom.BoxI(llc, urc), afwImage.LOCAL)
        ds = afwDetection.FootprintSetF(middle, threshold, "DETECTED")
        del middle
        #
        # ds only searched the middle but it belongs to the entire MaskedImage
        #
        ds.setRegion(mi.getBBox(afwImage.PARENT))
        #
        # We want to grow the detections into the edge by at least one pixel so that it sees the EDGE bit
        #
        grow, isotropic = 1, False
        ds = afwDetection.FootprintSetF(ds, grow, isotropic)
        ds.setMask(mi.getMask(), "DETECTED")
        #
        # Reinstate the saved (e.g. BAD) (and also the DETECTED | EDGE) bits in the unsmoothed image
        #
        savedMask <<= cnvImage.getMask()
        msk = mi.getMask(); msk |= savedMask; del msk
        del savedMask; savedMask = None

        #msk = mi.getMask(); msk &= ~0x10; del msk # XXXX

        if self.display:
            ds9.mtv(mi, frame = 0, lowOrderBits = True)
            ds9.mtv(cnvImage, frame = 1)

        objects = ds.getFootprints()
        #
        # Time to actually measure
        #
        msPolicy = policy.Policy.createPolicy(policy.DefaultPolicyFile("meas_algorithms",
            "examples/measureSources.paf"))
        msPolicy = msPolicy.getPolicy("measureSources")
        measureSources = measAlg.makeMeasureSources(self.exposure, msPolicy)
        
        self.sourceList = afwDetection.SourceSet()
        for i in range(len(objects)):
            source = afwDetection.Source()
            self.sourceList.append(source)

            source.setId(i)
            source.setFlagForDetection(source.getFlagForDetection() | measAlg.Flags.BINNED1);

            try:
                measureSources.apply(source, objects[i])
            except Exception, e:
                try:
                    print e
                except Exception, ee:
                    print ee
Esempio n. 17
0
class SourceDetectionTask(pipeBase.Task):
    """
    Detect positive and negative sources on an exposure and return a new SourceCatalog.
    """
    ConfigClass = SourceDetectionConfig
    _DefaultName = "sourceDetection"

    def __init__(self, schema=None, **kwds):
        """Create the detection task.  Most arguments are simply passed onto pipe_base.Task.

        If schema is not None, it will be used to register a 'flags.negative' flag field
        that will be set for negative detections.
        """
        pipeBase.Task.__init__(self, **kwds)
        if schema is not None:
            self.negativeFlagKey = schema.addField(
                "flags.negative",
                type="Flag",
                doc="set if source was detected as significantly negative")
        else:
            if self.config.thresholdPolarity == "both":
                self.log.log(self.log.WARN, "Detection polarity set to 'both', but no flag will be "\
                             "set to distinguish between positive and negative detections")
            self.negativeFlagKey = None

    @pipeBase.timeMethod
    def makeSourceCatalog(self,
                          table,
                          exposure,
                          doSmooth=True,
                          sigma=None,
                          clearMask=True):
        """Run source detection and create a SourceCatalog.

        To avoid dealing with sources and tables, use detectFootprints() to just get the FootprintSets.

        @param table    lsst.afw.table.SourceTable object that will be used to created the SourceCatalog.
        @param exposure Exposure to process; DETECTED mask plane will be set in-place.
        @param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
        @param sigma    sigma of PSF (pixels); used for smoothing and to grow detections;
            if None then measure the sigma of the PSF of the exposure
        @param clearMask Clear DETECTED{,_NEGATIVE} planes before running detection
        
        @return a Struct with:
          sources -- an lsst.afw.table.SourceCatalog object
          fpSets --- Struct returned by detectFootprints
        
        @raise pipe_base TaskError if sigma=None, doSmooth=True and the exposure has no PSF
        """
        if self.negativeFlagKey is not None and self.negativeFlagKey not in table.getSchema(
        ):
            raise ValueError("Table has incorrect Schema")
        fpSets = self.detectFootprints(exposure=exposure,
                                       doSmooth=doSmooth,
                                       sigma=sigma,
                                       clearMask=clearMask)
        sources = afwTable.SourceCatalog(table)
        table.preallocate(fpSets.numPos +
                          fpSets.numNeg)  # not required, but nice
        if fpSets.negative:
            fpSets.negative.makeSources(sources)
            if self.negativeFlagKey:
                for record in sources:
                    record.set(self.negativeFlagKey, True)
        if fpSets.positive:
            fpSets.positive.makeSources(sources)
        return pipeBase.Struct(sources=sources, fpSets=fpSets)

    @pipeBase.timeMethod
    def detectFootprints(self,
                         exposure,
                         doSmooth=True,
                         sigma=None,
                         clearMask=True):
        """Detect footprints.

        @param exposure Exposure to process; DETECTED{,_NEGATIVE} mask plane will be set in-place.
        @param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
        @param sigma    sigma of PSF (pixels); used for smoothing and to grow detections;
            if None then measure the sigma of the PSF of the exposure
        @param clearMask Clear both DETECTED and DETECTED_NEGATIVE planes before running detection

        @return a lsst.pipe.base.Struct with fields:
        - positive: lsst.afw.detection.FootprintSet with positive polarity footprints (may be None)
        - negative: lsst.afw.detection.FootprintSet with negative polarity footprints (may be None)
        - numPos: number of footprints in positive or 0 if detection polarity was negative
        - numNeg: number of footprints in negative or 0 if detection polarity was positive
        - background: re-estimated background.  None if reEstimateBackground==False
        
        @raise pipe_base TaskError if sigma=None and the exposure has no PSF
        """
        try:
            import lsstDebug
            display = lsstDebug.Info(__name__).display
        except ImportError, e:
            try:
                display
            except NameError:
                display = False

        if exposure is None:
            raise RuntimeException("No exposure for detection")

        maskedImage = exposure.getMaskedImage()
        region = maskedImage.getBBox(afwImage.PARENT)

        if clearMask:
            mask = maskedImage.getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED")
                      | mask.getPlaneBitMask("DETECTED_NEGATIVE"))
            del mask

        if sigma is None:
            psf = exposure.getPsf()
            if psf is None:
                raise pipeBase.TaskError(
                    "exposure has no PSF; must specify sigma")
            shape = psf.computeShape()
            sigma = shape.getDeterminantRadius()

        self.metadata.set("sigma", sigma)
        self.metadata.set("doSmooth", doSmooth)

        if not doSmooth:
            convolvedImage = maskedImage.Factory(maskedImage)
            middle = convolvedImage
        else:
            # smooth using a Gaussian (which is separate, hence fast) of width sigma
            # make a SingleGaussian (separable) kernel with the 'sigma'
            psf = exposure.getPsf()
            kWidth = (int(sigma * 7 + 0.5) / 2) * 2 + 1  # make sure it is odd
            self.metadata.set("smoothingKernelWidth", kWidth)
            gaussFunc = afwMath.GaussianFunction1D(sigma)
            gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc,
                                                  gaussFunc)

            convolvedImage = maskedImage.Factory(
                maskedImage.getBBox(afwImage.PARENT))

            afwMath.convolve(convolvedImage, maskedImage, gaussKernel,
                             afwMath.ConvolutionControl())
            #
            # Only search psf-smooth part of frame
            #
            goodBBox = gaussKernel.shrinkBBox(
                convolvedImage.getBBox(afwImage.PARENT))
            middle = convolvedImage.Factory(convolvedImage, goodBBox,
                                            afwImage.PARENT, False)
            #
            # Mark the parts of the image outside goodBBox as EDGE
            #
            self.setEdgeBits(maskedImage, goodBBox,
                             maskedImage.getMask().getPlaneBitMask("EDGE"))

        fpSets = pipeBase.Struct(positive=None, negative=None)

        if self.config.thresholdPolarity != "negative":
            fpSets.positive = self.thresholdImage(middle, "positive")
        if self.config.reEstimateBackground or self.config.thresholdPolarity != "positive":
            fpSets.negative = self.thresholdImage(middle, "negative")

        for polarity, maskName in (("positive", "DETECTED"),
                                   ("negative", "DETECTED_NEGATIVE")):
            fpSet = getattr(fpSets, polarity)
            if fpSet is None:
                continue
            fpSet.setRegion(region)
            if self.config.nSigmaToGrow > 0:
                nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
                self.metadata.set("nGrow", nGrow)
                fpSet = afwDet.FootprintSet(fpSet, nGrow, False)
            fpSet.setMask(maskedImage.getMask(), maskName)
            if not self.config.returnOriginalFootprints:
                setattr(fpSets, polarity, fpSet)

        fpSets.numPos = len(fpSets.positive.getFootprints()
                            ) if fpSets.positive is not None else 0
        fpSets.numNeg = len(fpSets.negative.getFootprints()
                            ) if fpSets.negative is not None else 0

        if self.config.thresholdPolarity != "negative":
            self.log.log(
                self.log.INFO, "Detected %d positive sources to %g sigma." %
                (fpSets.numPos, self.config.thresholdValue))

        fpSets.background = None
        if self.config.reEstimateBackground:
            mi = exposure.getMaskedImage()
            bkgd = getBackground(mi, self.config.background)

            if self.config.adjustBackground:
                self.log.log(
                    self.log.WARN, "Fiddling the background by %g" %
                    self.config.adjustBackground)

                bkgd += self.config.adjustBackground
            fpSets.background = bkgd
            self.log.log(
                self.log.INFO,
                "Resubtracting the background after object detection")
            mi -= bkgd.getImageF()
            del mi

        if self.config.thresholdPolarity == "positive":
            if self.config.reEstimateBackground:
                mask = maskedImage.getMask()
                mask &= ~mask.getPlaneBitMask("DETECTED_NEGATIVE")
                del mask
            fpSets.negative = None
        else:
            self.log.log(
                self.log.INFO, "Detected %d negative sources to %g %s" %
                (fpSets.numNeg, self.config.thresholdValue,
                 ("DN" if self.config.thresholdType == "value" else "sigma")))

        if display:
            ds9.mtv(exposure, frame=0, title="detection")

            if convolvedImage and display and display > 1:
                ds9.mtv(convolvedImage, frame=1, title="PSF smoothed")

            if middle and display and display > 1:
                ds9.mtv(middle, frame=2, title="middle")

        return fpSets
Esempio n. 18
0
    def convolveImage(self, maskedImage, psf, doSmooth=True):
        """Convolve the image with the PSF

        We convolve the image with a Gaussian approximation to the PSF,
        because this is separable and therefore fast. It's technically a
        correlation rather than a convolution, but since we use a symmetric
        Gaussian there's no difference.

        The convolution can be disabled with ``doSmooth=False``. If we do
        convolve, we mask the edges as ``EDGE`` and return the convolved image
        with the edges removed. This is because we can't convolve the edges
        because the kernel would extend off the image.

        Parameters
        ----------
        maskedImage : `lsst.afw.image.MaskedImage`
            Image to convolve.
        psf : `lsst.afw.detection.Psf`
            PSF to convolve with (actually with a Gaussian approximation
            to it).
        doSmooth : `bool`
            Actually do the convolution? Set to False when running on
            e.g. a pre-convolved image, or a mask plane.

        Return Struct contents
        ----------------------
        middle : `lsst.afw.image.MaskedImage`
            Convolved image, without the edges.
        sigma : `float`
            Gaussian sigma used for the convolution.
        """
        self.metadata.set("doSmooth", doSmooth)
        sigma = psf.computeShape().getDeterminantRadius()
        self.metadata.set("sigma", sigma)

        if not doSmooth:
            middle = maskedImage.Factory(maskedImage, deep=True)
            return pipeBase.Struct(middle=middle, sigma=sigma)

        # Smooth using a Gaussian (which is separable, hence fast) of width sigma
        # Make a SingleGaussian (separable) kernel with the 'sigma'
        kWidth = self.calculateKernelSize(sigma)
        self.metadata.set("smoothingKernelWidth", kWidth)
        gaussFunc = afwMath.GaussianFunction1D(sigma)
        gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc,
                                              gaussFunc)

        convolvedImage = maskedImage.Factory(maskedImage.getBBox())

        afwMath.convolve(convolvedImage, maskedImage, gaussKernel,
                         afwMath.ConvolutionControl())
        #
        # Only search psf-smoothed part of frame
        #
        goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
        middle = convolvedImage.Factory(convolvedImage, goodBBox,
                                        afwImage.PARENT, False)
        #
        # Mark the parts of the image outside goodBBox as EDGE
        #
        self.setEdgeBits(maskedImage, goodBBox,
                         maskedImage.getMask().getPlaneBitMask("EDGE"))

        return pipeBase.Struct(middle=middle, sigma=sigma)
    def testPeakLikelihoodFlux(self):
        """Test measurement with PeakLikelihoodFlux
        """
        # make mp: a flux measurer
        measControl = measAlg.PeakLikelihoodFluxControl()
        schema = afwTable.SourceTable.makeMinimalSchema()
        mp = measAlg.MeasureSourcesBuilder().addAlgorithm(measControl).build(
            schema)

        # make and measure a series of exposures containing just one star, approximately centered
        bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(100, 101))
        kernelWidth = 35
        var = 100
        fwhm = 3.0
        sigma = fwhm / FwhmPerSigma
        convolutionControl = afwMath.ConvolutionControl()
        psf = measAlg.SingleGaussianPsf(kernelWidth, kernelWidth, sigma)
        psfKernel = psf.getLocalKernel()
        psfImage = psf.computeKernelImage()
        sumPsfSq = numpy.sum(psfImage.getArray()**2)
        psfSqArr = psfImage.getArray()**2
        for flux in (1000, 10000):
            ctrInd = afwGeom.Point2I(50, 51)
            ctrPos = afwGeom.Point2D(ctrInd)

            kernelBBox = psfImage.getBBox(afwImage.PARENT)
            kernelBBox.shift(afwGeom.Extent2I(ctrInd))

            # compute predicted flux error
            unshMImage = makeFakeImage(bbox, [ctrPos], [flux], fwhm, var)

            # filter image by PSF
            unshFiltMImage = afwImage.MaskedImageF(
                unshMImage.getBBox(afwImage.PARENT))
            afwMath.convolve(unshFiltMImage, unshMImage, psfKernel,
                             convolutionControl)

            # compute predicted flux = value of image at peak / sum(PSF^2)
            # this is a sanity check of the algorithm, as much as anything
            predFlux = unshFiltMImage.getImage().get(ctrInd[0],
                                                     ctrInd[1]) / sumPsfSq
            self.assertLess(abs(flux - predFlux), flux * 0.01)

            # compute predicted flux error based on filtered pixels
            # = sqrt(value of filtered variance at peak / sum(PSF^2)^2)
            predFluxErr = math.sqrt(unshFiltMImage.getVariance().get(
                ctrInd[0], ctrInd[1])) / sumPsfSq

            # compute predicted flux error based on unfiltered pixels
            # = sqrt(sum(unfiltered variance * PSF^2)) / sum(PSF^2)
            # and compare to that derived from filtered pixels;
            # again, this is a test of the algorithm
            varView = afwImage.ImageF(unshMImage.getVariance(), kernelBBox)
            varArr = varView.getArray()
            unfiltPredFluxErr = math.sqrt(numpy.sum(
                varArr * psfSqArr)) / sumPsfSq
            self.assertLess(abs(unfiltPredFluxErr - predFluxErr),
                            predFluxErr * 0.01)

            for fracOffset in (afwGeom.Extent2D(0, 0),
                               afwGeom.Extent2D(0.2, -0.3)):
                adjCenter = ctrPos + fracOffset
                if fracOffset == (0, 0):
                    maskedImage = unshMImage
                    filteredImage = unshFiltMImage
                else:
                    maskedImage = makeFakeImage(bbox, [adjCenter], [flux],
                                                fwhm, var)
                    # filter image by PSF
                    filteredImage = afwImage.MaskedImageF(
                        maskedImage.getBBox(afwImage.PARENT))
                    afwMath.convolve(filteredImage, maskedImage, psfKernel,
                                     convolutionControl)

                exposure = afwImage.makeExposure(filteredImage)
                exposure.setPsf(psf)

                table = afwTable.SourceTable.make(schema)
                source = table.makeRecord()
                mp.apply(source, exposure, afwGeom.Point2D(*adjCenter))
                measFlux = source.get(measControl.name)
                measFluxErr = source.get(measControl.name + ".err")
                self.assertFalse(source.get(measControl.name + ".flags"))
                self.assertLess(abs(measFlux - flux), flux * 0.003)

                self.assertLess(abs(measFluxErr - predFluxErr),
                                predFluxErr * 0.2)

                # try nearby points and verify that the flux is smaller;
                # this checks that the sub-pixel shift is performed in the correct direction
                for dx in (-0.2, 0, 0.2):
                    for dy in (-0.2, 0, 0.2):
                        if dx == dy == 0:
                            continue
                        offsetCtr = afwGeom.Point2D(adjCenter[0] + dx,
                                                    adjCenter[1] + dy)
                        table = afwTable.SourceTable.make(schema)
                        source = table.makeRecord()
                        mp.apply(source, exposure, offsetCtr)
                        offsetFlux = source.get(measControl.name)
                        self.assertLess(offsetFlux, measFlux)

        # source so near edge of image that PSF does not overlap exposure should result in failure

        for edgePos in (
            (1, 50),
            (50, 1),
            (50, bbox.getHeight() - 1),
            (bbox.getWidth() - 1, 50),
        ):
            table = afwTable.SourceTable.make(schema)
            source = table.makeRecord()
            mp.apply(source, exposure, afwGeom.Point2D(*edgePos))
            self.assertTrue(source.get(measControl.name + ".flags"))

        # no PSF should result in failure: flags set
        noPsfExposure = afwImage.ExposureF(filteredImage)
        table = afwTable.SourceTable.make(schema)
        source = table.makeRecord()
        mp.apply(source, noPsfExposure, afwGeom.Point2D(*adjCenter))
        self.assertTrue(source.get(measControl.name + ".flags"))
Esempio n. 20
0
    def testDetection(self):
        """Test object detection"""
        #
        # Fix defects
        #
        # Mask known bad pixels
        #
        measAlgorithmsDir = lsst.utils.getPackageDir('meas_algorithms')
        badPixels = defects.policyToBadRegionList(os.path.join(measAlgorithmsDir,
                                                               "policy/BadPixels.paf"))
        # did someone lie about the origin of the maskedImage?  If so, adjust bad pixel list
        if self.XY0.getX() != self.mi.getX0() or self.XY0.getY() != self.mi.getY0():
            dx = self.XY0.getX() - self.mi.getX0()
            dy = self.XY0.getY() - self.mi.getY0()
            for bp in badPixels:
                bp.shift(-dx, -dy)

        algorithms.interpolateOverDefects(self.mi, self.psf, badPixels)
        #
        # Subtract background
        #
        bgGridSize = 64  # was 256 ... but that gives only one region and the spline breaks
        bctrl = afwMath.BackgroundControl(afwMath.Interpolate.NATURAL_SPLINE)
        bctrl.setNxSample(int(self.mi.getWidth()/bgGridSize) + 1)
        bctrl.setNySample(int(self.mi.getHeight()/bgGridSize) + 1)
        backobj = afwMath.makeBackground(self.mi.getImage(), bctrl)

        self.mi.getImage()[:] -= backobj.getImageF()
        #
        # Remove CRs
        #
        crConfig = algorithms.FindCosmicRaysConfig()
        algorithms.findCosmicRays(self.mi, self.psf, 0, pexConfig.makePolicy(crConfig))
        #
        # We do a pretty good job of interpolating, so don't propagagate the convolved CR/INTRP bits
        # (we'll keep them for the original CR/INTRP pixels)
        #
        savedMask = self.mi.getMask().Factory(self.mi.getMask(), True)
        saveBits = savedMask.getPlaneBitMask("CR") | \
            savedMask.getPlaneBitMask("BAD") | \
            savedMask.getPlaneBitMask("INTRP")  # Bits to not convolve
        savedMask &= saveBits

        msk = self.mi.getMask()
        msk &= ~saveBits  # Clear the saved bits
        del msk
        #
        # Smooth image
        #
        psf = algorithms.DoubleGaussianPsf(15, 15, self.FWHM/(2*math.sqrt(2*math.log(2))))

        cnvImage = self.mi.Factory(self.mi.getBBox())
        kernel = psf.getKernel()
        afwMath.convolve(cnvImage, self.mi, kernel, afwMath.ConvolutionControl())

        msk = cnvImage.getMask()
        msk |= savedMask  # restore the saved bits
        del msk

        threshold = afwDetection.Threshold(3, afwDetection.Threshold.STDEV)
        #
        # Only search the part of the frame that was PSF-smoothed
        #
        llc = lsst.geom.PointI(psf.getKernel().getWidth()//2, psf.getKernel().getHeight()//2)
        urc = lsst.geom.PointI(cnvImage.getWidth() - llc[0] - 1, cnvImage.getHeight() - llc[1] - 1)
        middle = cnvImage.Factory(cnvImage, lsst.geom.BoxI(llc, urc), afwImage.LOCAL)
        ds = afwDetection.FootprintSet(middle, threshold, "DETECTED")
        del middle
        #
        # Reinstate the saved (e.g. BAD) (and also the DETECTED | EDGE) bits in the unsmoothed image
        #
        savedMask[:] = cnvImage.getMask()
        msk = self.mi.getMask()
        msk |= savedMask
        del msk
        del savedMask

        if display:
            disp = afwDisplay.Display(frame=2)
            disp.mtv(self.mi, title=self._testMethodName + ": image")
            afwDisplay.Display(frame=3).mtv(cnvImage, title=self._testMethodName + ": cnvImage")

        #
        # Time to actually measure
        #
        schema = afwTable.SourceTable.makeMinimalSchema()
        sfm_config = measBase.SingleFrameMeasurementConfig()
        sfm_config.plugins = ["base_SdssCentroid", "base_CircularApertureFlux", "base_PsfFlux",
                              "base_SdssShape", "base_GaussianFlux",
                              "base_PixelFlags"]
        sfm_config.slots.centroid = "base_SdssCentroid"
        sfm_config.slots.shape = "base_SdssShape"
        sfm_config.slots.psfFlux = "base_PsfFlux"
        sfm_config.slots.gaussianFlux = None
        sfm_config.slots.apFlux = "base_CircularApertureFlux_3_0"
        sfm_config.slots.modelFlux = "base_GaussianFlux"
        sfm_config.slots.calibFlux = None
        sfm_config.plugins["base_SdssShape"].maxShift = 10.0
        sfm_config.plugins["base_CircularApertureFlux"].radii = [3.0]
        task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config)
        measCat = afwTable.SourceCatalog(schema)
        # detect the sources and run with the measurement task
        ds.makeSources(measCat)
        self.exposure.setPsf(self.psf)
        task.run(measCat, self.exposure)

        self.assertGreater(len(measCat), 0)
        for source in measCat:
            if source.get("base_PixelFlags_flag_edge"):
                continue

            if display:
                disp.dot("+", source.getX(), source.getY())
Esempio n. 21
0
def makeFakeKernelSet(sizeCell=128,
                      nCell=3,
                      deltaFunctionCounts=1.e4,
                      tGaussianWidth=1.0,
                      addNoise=True,
                      bgValue=100.,
                      display=False):
    """Generate test template and science images with sources.

    Parameters
    ----------
    sizeCell : `int`, optional
        Size of the square spatial cells in pixels.
    nCell : `int`, optional
        Number of adjacent spatial cells in both direction in both images.
    deltaFunctionCounts : `float`, optional
        Flux value for the template image sources.
    tGaussianWidth : `float`, optional
        Sigma of the generated Gaussian PSF sources in the template image.
    addNoise : `bool`, optional
        If `True`, Poisson noise is added to both the generated template
        and science images.
    bgValue : `float`, optional
        Background level to be added to the generated science image.
    display : `bool`, optional
        If `True` displays the generated template and science images by
        `lsst.afw.display.Display`.

    Notes
    -----
    - The generated images consist of adjacent ``nCell x nCell`` cells, each
      of pixel size ``sizeCell x sizeCell``.
    - The sources in the science image are generated by convolving the
      template by ``sKernel``. ``sKernel`` is a spatial `LinearCombinationKernel`
      of hard wired kernel bases functions. The linear combination has first
      order polynomial spatial dependence with polynomial parameters from ``fakeCoeffs()``.
    - The template image sources are generated in the center of each spatial
      cell from one pixel, set to `deltaFunctionCounts` counts, then convolved
      by a 2D Gaussian with sigma of `tGaussianWidth` along each axis.
    - The sources are also returned in ``kernelCellSet`` each source is "detected"
      exactly at the center of a cell.

    Returns
    -------
    tMi : `lsst.afw.image.MaskedImage`
        Generated template image.
    sMi : `lsst.afw.image.MaskedImage`
        Generated science image.
    sKernel : `lsst.afw.math.LinearCombinationKernel`
        The spatial kernel used to generate the sources in the science image.
    kernelCellSet : `lsst.afw.math.SpatialCellSet`
        Cell grid of `lsst.afw.math.SpatialCell` instances, containing
        `lsst.ip.diffim.KernelCandidate` instances around all the generated sources
        in the science image.
    configFake : `lsst.ip.diffim.ImagePsfMatchConfig`
        Config instance used in the image generation.
    """
    from . import imagePsfMatch
    configFake = imagePsfMatch.ImagePsfMatchConfig()
    configFake.kernel.name = "AL"
    subconfigFake = configFake.kernel.active
    subconfigFake.alardNGauss = 1
    subconfigFake.alardSigGauss = [
        2.5,
    ]
    subconfigFake.alardDegGauss = [
        2,
    ]
    subconfigFake.sizeCellX = sizeCell
    subconfigFake.sizeCellY = sizeCell
    subconfigFake.spatialKernelOrder = 1
    subconfigFake.spatialModelType = "polynomial"
    subconfigFake.singleKernelClipping = False  # variance is a hack
    subconfigFake.spatialKernelClipping = False  # variance is a hack
    if bgValue > 0.0:
        subconfigFake.fitForBackground = True

    psFake = pexConfig.makePropertySet(subconfigFake)

    basisList = makeKernelBasisList(subconfigFake)
    kSize = subconfigFake.kernelSize

    # This sets the final extent of each convolved delta function
    gaussKernelWidth = sizeCell // 2

    # This sets the scale over which pixels are correlated in the
    # spatial convolution; should be at least as big as the kernel you
    # are trying to fit for
    spatialKernelWidth = kSize

    # Number of bad pixels due to convolutions
    border = (gaussKernelWidth + spatialKernelWidth) // 2

    # Make a fake image with a matrix of delta functions
    totalSize = nCell * sizeCell + 2 * border
    tim = afwImage.ImageF(geom.Extent2I(totalSize, totalSize))
    for x in range(nCell):
        for y in range(nCell):
            tim[x * sizeCell + sizeCell // 2 + border - 1,
                y * sizeCell + sizeCell // 2 + border - 1,
                afwImage.LOCAL] = deltaFunctionCounts

    # Turn this into stars with a narrow width; conserve counts
    gaussFunction = afwMath.GaussianFunction2D(tGaussianWidth, tGaussianWidth)
    gaussKernel = afwMath.AnalyticKernel(gaussKernelWidth, gaussKernelWidth,
                                         gaussFunction)
    cim = afwImage.ImageF(tim.getDimensions())
    convolutionControl = afwMath.ConvolutionControl()
    convolutionControl.setDoNormalize(True)
    afwMath.convolve(cim, tim, gaussKernel, convolutionControl)
    tim = cim

    # Trim off border pixels
    bbox = gaussKernel.shrinkBBox(tim.getBBox(afwImage.LOCAL))
    tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)

    # Now make a science image which is this convolved with some
    # spatial function.  Use input basis list.
    polyFunc = afwMath.PolynomialFunction2D(1)
    kCoeffs = fakeCoeffs()
    nToUse = min(len(kCoeffs), len(basisList))

    # Make the full convolved science image
    sKernel = afwMath.LinearCombinationKernel(basisList[:nToUse], polyFunc)
    sKernel.setSpatialParameters(kCoeffs[:nToUse])
    sim = afwImage.ImageF(tim.getDimensions())
    convolutionControl = afwMath.ConvolutionControl()
    convolutionControl.setDoNormalize(True)
    afwMath.convolve(sim, tim, sKernel, convolutionControl)

    # Get the good subregion
    bbox = sKernel.shrinkBBox(sim.getBBox(afwImage.LOCAL))

    # Add background
    sim += bgValue

    # Watch out for negative values
    tim += 2 * np.abs(np.min(tim.getArray()))

    # Add noise?
    if addNoise:
        sim = makePoissonNoiseImage(sim)
        tim = makePoissonNoiseImage(tim)

    # And turn into MaskedImages
    sim = afwImage.ImageF(sim, bbox, afwImage.LOCAL)
    svar = afwImage.ImageF(sim, True)
    smask = afwImage.Mask(sim.getDimensions())
    smask.set(0x0)
    sMi = afwImage.MaskedImageF(sim, smask, svar)

    tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL)
    tvar = afwImage.ImageF(tim, True)
    tmask = afwImage.Mask(tim.getDimensions())
    tmask.set(0x0)
    tMi = afwImage.MaskedImageF(tim, tmask, tvar)

    if display:
        import lsst.afw.display as afwDisplay
        afwDisplay.Display(frame=1).mtv(tMi)
        afwDisplay.Display(frame=2).mtv(sMi)

    # Finally, make a kernelSet from these 2 images
    kernelCellSet = afwMath.SpatialCellSet(
        geom.Box2I(geom.Point2I(0, 0),
                   geom.Extent2I(sizeCell * nCell, sizeCell * nCell)),
        sizeCell, sizeCell)
    stampHalfWidth = 2 * kSize
    for x in range(nCell):
        for y in range(nCell):
            xCoord = x * sizeCell + sizeCell // 2
            yCoord = y * sizeCell + sizeCell // 2
            p0 = geom.Point2I(xCoord - stampHalfWidth, yCoord - stampHalfWidth)
            p1 = geom.Point2I(xCoord + stampHalfWidth, yCoord + stampHalfWidth)
            bbox = geom.Box2I(p0, p1)
            tsi = afwImage.MaskedImageF(tMi, bbox, origin=afwImage.LOCAL)
            ssi = afwImage.MaskedImageF(sMi, bbox, origin=afwImage.LOCAL)

            kc = diffimLib.makeKernelCandidate(xCoord, yCoord, tsi, ssi,
                                               psFake)
            kernelCellSet.insertCandidate(kc)

    tMi.setXY0(0, 0)
    sMi.setXY0(0, 0)
    return tMi, sMi, sKernel, kernelCellSet, configFake
Esempio n. 22
0
    def run(self, sensorRef, templateIdList=None):
        """Subtract an image from a template coadd and measure the result

        Steps include:
        - warp template coadd to match WCS of image
        - PSF match image to warped template
        - subtract image from PSF-matched, warped template
        - persist difference image
        - detect sources
        - measure sources

        @param sensorRef: sensor-level butler data reference, used for the following data products:
        Input only:
        - calexp
        - psf
        - ccdExposureId
        - ccdExposureId_bits
        - self.config.coaddName + "Coadd_skyMap"
        - self.config.coaddName + "Coadd"
        Input or output, depending on config:
        - self.config.coaddName + "Diff_subtractedExp"
        Output, depending on config:
        - self.config.coaddName + "Diff_matchedExp"
        - self.config.coaddName + "Diff_src"

        @return pipe_base Struct containing these fields:
        - subtractedExposure: exposure after subtracting template;
            the unpersisted version if subtraction not run but detection run
            None if neither subtraction nor detection run (i.e. nothing useful done)
        - subtractRes: results of subtraction task; None if subtraction not run
        - sources: detected and possibly measured sources; None if detection not run
        """
        self.log.info("Processing %s" % (sensorRef.dataId))

        # initialize outputs and some intermediate products
        subtractedExposure = None
        subtractRes = None
        selectSources = None
        kernelSources = None
        controlSources = None
        diaSources = None

        # We make one IdFactory that will be used by both icSrc and src datasets;
        # I don't know if this is the way we ultimately want to do things, but at least
        # this ensures the source IDs are fully unique.
        expBits = sensorRef.get("ccdExposureId_bits")
        expId = int(sensorRef.get("ccdExposureId"))
        idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)

        # Retrieve the science image we wish to analyze
        exposure = sensorRef.get("calexp", immediate=True)
        if self.config.doAddCalexpBackground:
            mi = exposure.getMaskedImage()
            mi += sensorRef.get("calexpBackground").getImage()
        if not exposure.hasPsf():
            raise pipeBase.TaskError("Exposure has no psf")
        sciencePsf = exposure.getPsf()

        subtractedExposureName = self.config.coaddName + "Diff_differenceExp"
        templateExposure = None  # Stitched coadd exposure
        templateSources = None  # Sources on the template image
        if self.config.doSubtract:
            template = self.getTemplate.run(exposure,
                                            sensorRef,
                                            templateIdList=templateIdList)
            templateExposure = template.exposure
            templateSources = template.sources

            # compute scienceSigmaOrig: sigma of PSF of science image before pre-convolution
            scienceSigmaOrig = sciencePsf.computeShape().getDeterminantRadius()

            # sigma of PSF of template image before warping
            templateSigma = templateExposure.getPsf().computeShape(
            ).getDeterminantRadius()

            # if requested, convolve the science exposure with its PSF
            # (properly, this should be a cross-correlation, but our code does not yet support that)
            # compute scienceSigmaPost: sigma of science exposure with pre-convolution, if done,
            # else sigma of original science exposure
            if self.config.doPreConvolve:
                convControl = afwMath.ConvolutionControl()
                # cannot convolve in place, so make a new MI to receive convolved image
                srcMI = exposure.getMaskedImage()
                destMI = srcMI.Factory(srcMI.getDimensions())
                srcPsf = sciencePsf
                if self.config.useGaussianForPreConvolution:
                    # convolve with a simplified PSF model: a double Gaussian
                    kWidth, kHeight = sciencePsf.getLocalKernel(
                    ).getDimensions()
                    preConvPsf = SingleGaussianPsf(kWidth, kHeight,
                                                   scienceSigmaOrig)
                else:
                    # convolve with science exposure's PSF model
                    preConvPsf = srcPsf
                afwMath.convolve(destMI, srcMI, preConvPsf.getLocalKernel(),
                                 convControl)
                exposure.setMaskedImage(destMI)
                scienceSigmaPost = scienceSigmaOrig * math.sqrt(2)
            else:
                scienceSigmaPost = scienceSigmaOrig

            # If requested, find sources in the image
            if self.config.doSelectSources:
                if not sensorRef.datasetExists("src"):
                    self.log.warn(
                        "Src product does not exist; running detection, measurement, selection"
                    )
                    # Run own detection and measurement; necessary in nightly processing
                    selectSources = self.subtract.getSelectSources(
                        exposure,
                        sigma=scienceSigmaPost,
                        doSmooth=not self.doPreConvolve,
                        idFactory=idFactory,
                    )
                else:
                    self.log.info("Source selection via src product")
                    # Sources already exist; for data release processing
                    selectSources = sensorRef.get("src")

                # Number of basis functions
                nparam = len(
                    makeKernelBasisList(
                        self.subtract.config.kernel.active,
                        referenceFwhmPix=scienceSigmaPost * FwhmPerSigma,
                        targetFwhmPix=templateSigma * FwhmPerSigma))

                if self.config.doAddMetrics:
                    # Modify the schema of all Sources
                    kcQa = KernelCandidateQa(nparam)
                    selectSources = kcQa.addToSchema(selectSources)

                if self.config.kernelSourcesFromRef:
                    # match exposure sources to reference catalog
                    astromRet = self.astrometer.loadAndMatch(
                        exposure=exposure, sourceCat=selectSources)
                    matches = astromRet.matches
                elif templateSources:
                    # match exposure sources to template sources
                    mc = afwTable.MatchControl()
                    mc.findOnlyClosest = False
                    matches = afwTable.matchRaDec(templateSources,
                                                  selectSources,
                                                  1.0 * afwGeom.arcseconds, mc)
                else:
                    raise RuntimeError(
                        "doSelectSources=True and kernelSourcesFromRef=False,"
                        +
                        "but template sources not available. Cannot match science "
                        +
                        "sources with template sources. Run process* on data from "
                        + "which templates are built.")

                kernelSources = self.sourceSelector.selectStars(
                    exposure, selectSources, matches=matches).starCat

                random.shuffle(kernelSources, random.random)
                controlSources = kernelSources[::self.config.controlStepSize]
                kernelSources = [
                    k for i, k in enumerate(kernelSources)
                    if i % self.config.controlStepSize
                ]

                if self.config.doSelectDcrCatalog:
                    redSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(
                            grMin=self.sourceSelector.config.grMax,
                            grMax=99.999))
                    redSources = redSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(redSources)

                    blueSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(
                            grMin=-99.999,
                            grMax=self.sourceSelector.config.grMin))
                    blueSources = blueSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(blueSources)

                if self.config.doSelectVariableCatalog:
                    varSelector = DiaCatalogSourceSelectorTask(
                        DiaCatalogSourceSelectorConfig(includeVariable=True))
                    varSources = varSelector.selectStars(
                        exposure, selectSources, matches=matches).starCat
                    controlSources.extend(varSources)

                self.log.info(
                    "Selected %d / %d sources for Psf matching (%d for control sample)"
                    % (len(kernelSources), len(selectSources),
                       len(controlSources)))
            allresids = {}
            if self.config.doUseRegister:
                self.log.info("Registering images")

                if templateSources is None:
                    # Run detection on the template, which is
                    # temporarily background-subtracted
                    templateSources = self.subtract.getSelectSources(
                        templateExposure,
                        sigma=templateSigma,
                        doSmooth=True,
                        idFactory=idFactory)

                # Third step: we need to fit the relative astrometry.
                #
                wcsResults = self.fitAstrometry(templateSources,
                                                templateExposure,
                                                selectSources)
                warpedExp = self.register.warpExposure(templateExposure,
                                                       wcsResults.wcs,
                                                       exposure.getWcs(),
                                                       exposure.getBBox())
                templateExposure = warpedExp

                # Create debugging outputs on the astrometric
                # residuals as a function of position.  Persistence
                # not yet implemented; expected on (I believe) #2636.
                if self.config.doDebugRegister:
                    # Grab matches to reference catalog
                    srcToMatch = {x.second.getId(): x.first for x in matches}

                    refCoordKey = wcsResults.matches[0].first.getTable(
                    ).getCoordKey()
                    inCentroidKey = wcsResults.matches[0].second.getTable(
                    ).getCentroidKey()
                    sids = [m.first.getId() for m in wcsResults.matches]
                    positions = [
                        m.first.get(refCoordKey) for m in wcsResults.matches
                    ]
                    residuals = [
                        m.first.get(refCoordKey).getOffsetFrom(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey)))
                        for m in wcsResults.matches
                    ]
                    allresids = dict(zip(sids, zip(positions, residuals)))

                    cresiduals = [
                        m.first.get(refCoordKey).getTangentPlaneOffset(
                            wcsResults.wcs.pixelToSky(
                                m.second.get(inCentroidKey)))
                        for m in wcsResults.matches
                    ]
                    colors = numpy.array([
                        -2.5 * numpy.log10(srcToMatch[x].get("g")) +
                        2.5 * numpy.log10(srcToMatch[x].get("r")) for x in sids
                        if x in srcToMatch.keys()
                    ])
                    dlong = numpy.array([
                        r[0].asArcseconds() for s, r in zip(sids, cresiduals)
                        if s in srcToMatch.keys()
                    ])
                    dlat = numpy.array([
                        r[1].asArcseconds() for s, r in zip(sids, cresiduals)
                        if s in srcToMatch.keys()
                    ])
                    idx1 = numpy.where(
                        colors < self.sourceSelector.config.grMin)
                    idx2 = numpy.where(
                        (colors >= self.sourceSelector.config.grMin)
                        & (colors <= self.sourceSelector.config.grMax))
                    idx3 = numpy.where(
                        colors > self.sourceSelector.config.grMax)
                    rms1Long = IqrToSigma * \
                        (numpy.percentile(dlong[idx1], 75)-numpy.percentile(dlong[idx1], 25))
                    rms1Lat = IqrToSigma * (numpy.percentile(dlat[idx1], 75) -
                                            numpy.percentile(dlat[idx1], 25))
                    rms2Long = IqrToSigma * \
                        (numpy.percentile(dlong[idx2], 75)-numpy.percentile(dlong[idx2], 25))
                    rms2Lat = IqrToSigma * (numpy.percentile(dlat[idx2], 75) -
                                            numpy.percentile(dlat[idx2], 25))
                    rms3Long = IqrToSigma * \
                        (numpy.percentile(dlong[idx3], 75)-numpy.percentile(dlong[idx3], 25))
                    rms3Lat = IqrToSigma * (numpy.percentile(dlat[idx3], 75) -
                                            numpy.percentile(dlat[idx3], 25))
                    self.log.info("Blue star offsets'': %.3f %.3f, %.3f %.3f" %
                                  (numpy.median(dlong[idx1]), rms1Long,
                                   numpy.median(dlat[idx1]), rms1Lat))
                    self.log.info(
                        "Green star offsets'': %.3f %.3f, %.3f %.3f" %
                        (numpy.median(dlong[idx2]), rms2Long,
                         numpy.median(dlat[idx2]), rms2Lat))
                    self.log.info("Red star offsets'': %.3f %.3f, %.3f %.3f" %
                                  (numpy.median(dlong[idx3]), rms3Long,
                                   numpy.median(dlat[idx3]), rms3Lat))

                    self.metadata.add("RegisterBlueLongOffsetMedian",
                                      numpy.median(dlong[idx1]))
                    self.metadata.add("RegisterGreenLongOffsetMedian",
                                      numpy.median(dlong[idx2]))
                    self.metadata.add("RegisterRedLongOffsetMedian",
                                      numpy.median(dlong[idx3]))
                    self.metadata.add("RegisterBlueLongOffsetStd", rms1Long)
                    self.metadata.add("RegisterGreenLongOffsetStd", rms2Long)
                    self.metadata.add("RegisterRedLongOffsetStd", rms3Long)

                    self.metadata.add("RegisterBlueLatOffsetMedian",
                                      numpy.median(dlat[idx1]))
                    self.metadata.add("RegisterGreenLatOffsetMedian",
                                      numpy.median(dlat[idx2]))
                    self.metadata.add("RegisterRedLatOffsetMedian",
                                      numpy.median(dlat[idx3]))
                    self.metadata.add("RegisterBlueLatOffsetStd", rms1Lat)
                    self.metadata.add("RegisterGreenLatOffsetStd", rms2Lat)
                    self.metadata.add("RegisterRedLatOffsetStd", rms3Lat)

            # warp template exposure to match exposure,
            # PSF match template exposure to exposure,
            # then return the difference

            # Return warped template...  Construct sourceKernelCand list after subtract
            self.log.info("Subtracting images")
            subtractRes = self.subtract.subtractExposures(
                templateExposure=templateExposure,
                scienceExposure=exposure,
                candidateList=kernelSources,
                convolveTemplate=self.config.convolveTemplate,
                doWarping=not self.config.doUseRegister)
            subtractedExposure = subtractRes.subtractedExposure

            if self.config.doWriteMatchedExp:
                sensorRef.put(subtractRes.matchedExposure,
                              self.config.coaddName + "Diff_matchedExp")

        if self.config.doDetection:
            self.log.info("Computing diffim PSF")
            if subtractedExposure is None:
                subtractedExposure = sensorRef.get(subtractedExposureName)

            # Get Psf from the appropriate input image if it doesn't exist
            if not subtractedExposure.hasPsf():
                if self.config.convolveTemplate:
                    subtractedExposure.setPsf(exposure.getPsf())
                else:
                    if templateExposure is None:
                        template = self.getTemplate.run(
                            exposure, sensorRef, templateIdList=templateIdList)
                    subtractedExposure.setPsf(template.exposure.getPsf())

        # If doSubtract is False, then subtractedExposure was fetched from disk (above), thus it may have
        # already been decorrelated. Thus, we do not do decorrelation if doSubtract is False.
        if self.config.doDecorrelation and self.config.doSubtract:
            decorrResult = self.decorrelate.run(exposure, templateExposure,
                                                subtractedExposure,
                                                subtractRes.psfMatchingKernel)
            subtractedExposure = decorrResult.correctedExposure

        if self.config.doDetection:
            self.log.info("Running diaSource detection")
            # Erase existing detection mask planes
            mask = subtractedExposure.getMaskedImage().getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED")
                      | mask.getPlaneBitMask("DETECTED_NEGATIVE"))

            table = afwTable.SourceTable.make(self.schema, idFactory)
            table.setMetadata(self.algMetadata)
            results = self.detection.makeSourceCatalog(
                table=table,
                exposure=subtractedExposure,
                doSmooth=not self.config.doPreConvolve)

            if self.config.doMerge:
                fpSet = results.fpSets.positive
                fpSet.merge(results.fpSets.negative, self.config.growFootprint,
                            self.config.growFootprint, False)
                diaSources = afwTable.SourceCatalog(table)
                fpSet.makeSources(diaSources)
                self.log.info("Merging detections into %d sources" %
                              (len(diaSources)))
            else:
                diaSources = results.sources

            if self.config.doMeasurement:
                self.log.info("Running diaSource measurement")
                if not self.config.doDipoleFitting:
                    self.measurement.run(diaSources, subtractedExposure)
                else:
                    if self.config.doSubtract:
                        self.measurement.run(diaSources, subtractedExposure,
                                             exposure,
                                             subtractRes.matchedExposure)
                    else:
                        self.measurement.run(diaSources, subtractedExposure,
                                             exposure)

            # Match with the calexp sources if possible
            if self.config.doMatchSources:
                if sensorRef.datasetExists("src"):
                    # Create key,val pair where key=diaSourceId and val=sourceId
                    matchRadAsec = self.config.diaSourceMatchRadius
                    matchRadPixel = matchRadAsec / exposure.getWcs(
                    ).pixelScale().asArcseconds()

                    srcMatches = afwTable.matchXy(sensorRef.get("src"),
                                                  diaSources, matchRadPixel)
                    srcMatchDict = dict([(srcMatch.second.getId(),
                                          srcMatch.first.getId())
                                         for srcMatch in srcMatches])
                    self.log.info("Matched %d / %d diaSources to sources" %
                                  (len(srcMatchDict), len(diaSources)))
                else:
                    self.log.warn(
                        "Src product does not exist; cannot match with diaSources"
                    )
                    srcMatchDict = {}

                # Create key,val pair where key=diaSourceId and val=refId
                refAstromConfig = AstrometryConfig()
                refAstromConfig.matcher.maxMatchDistArcSec = matchRadAsec
                refAstrometer = AstrometryTask(refAstromConfig)
                astromRet = refAstrometer.run(exposure=exposure,
                                              sourceCat=diaSources)
                refMatches = astromRet.matches
                if refMatches is None:
                    self.log.warn(
                        "No diaSource matches with reference catalog")
                    refMatchDict = {}
                else:
                    self.log.info(
                        "Matched %d / %d diaSources to reference catalog" %
                        (len(refMatches), len(diaSources)))
                    refMatchDict = dict([(refMatch.second.getId(),
                                          refMatch.first.getId())
                                         for refMatch in refMatches])

                # Assign source Ids
                for diaSource in diaSources:
                    sid = diaSource.getId()
                    if sid in srcMatchDict:
                        diaSource.set("srcMatchId", srcMatchDict[sid])
                    if sid in refMatchDict:
                        diaSource.set("refMatchId", refMatchDict[sid])

            if diaSources is not None and self.config.doWriteSources:
                sensorRef.put(diaSources,
                              self.config.coaddName + "Diff_diaSrc")

            if self.config.doAddMetrics and self.config.doSelectSources:
                self.log.info("Evaluating metrics and control sample")

                kernelCandList = []
                for cell in subtractRes.kernelCellSet.getCellList():
                    for cand in cell.begin(False):  # include bad candidates
                        kernelCandList.append(cand)

                # Get basis list to build control sample kernels
                basisList = kernelCandList[0].getKernel(
                    KernelCandidateF.ORIG).getKernelList()

                controlCandList = \
                    diffimTools.sourceTableToCandidateList(controlSources,
                                                           subtractRes.warpedExposure, exposure,
                                                           self.config.subtract.kernel.active,
                                                           self.config.subtract.kernel.active.detectionConfig,
                                                           self.log, doBuild=True, basisList=basisList)

                kcQa.apply(kernelCandList,
                           subtractRes.psfMatchingKernel,
                           subtractRes.backgroundModel,
                           dof=nparam)
                kcQa.apply(controlCandList, subtractRes.psfMatchingKernel,
                           subtractRes.backgroundModel)

                if self.config.doDetection:
                    kcQa.aggregate(selectSources, self.metadata, allresids,
                                   diaSources)
                else:
                    kcQa.aggregate(selectSources, self.metadata, allresids)

                sensorRef.put(selectSources,
                              self.config.coaddName + "Diff_kernelSrc")

        if self.config.doWriteSubtractedExp:
            sensorRef.put(subtractedExposure, subtractedExposureName)

        self.runDebug(exposure, subtractRes, selectSources, kernelSources,
                      diaSources)
        return pipeBase.Struct(
            subtractedExposure=subtractedExposure,
            subtractRes=subtractRes,
            sources=diaSources,
        )
Esempio n. 23
0
    def _testImages(self):
        """Check that the variance of the corrected diffim matches the theoretical value.
        """
        # Create the matching kernel. We used Gaussian PSFs for im1 and im2, so we can compute the "expected"
        # matching kernel sigma.
        psf1_sig = self.im1ex.getPsf().computeShape().getDeterminantRadius()
        psf2_sig = self.im2ex.getPsf().computeShape().getDeterminantRadius()
        sig_match = np.sqrt((psf1_sig**2. - psf2_sig**2.))
        # Sanity check - make sure PSFs are correct.
        self.assertClose(sig_match,
                         np.sqrt((self.psf1_sigma**2. - self.psf2_sigma**2.)),
                         rtol=1e-5)
        # mKernel = measAlg.SingleGaussianPsf(31, 31, sig_match)
        x0 = np.arange(-16, 16, 1)
        y0 = x0.copy()
        x0im, y0im = np.meshgrid(x0, y0)
        matchingKernel = singleGaussian2d(x0im,
                                          y0im,
                                          -1.,
                                          -1.,
                                          sigma_x=sig_match,
                                          sigma_y=sig_match)
        kernelImg = afwImage.ImageD(matchingKernel.shape[0],
                                    matchingKernel.shape[1])
        kernelImg.getArray()[:, :] = matchingKernel
        mKernel = afwMath.FixedKernel(kernelImg)

        # Create the matched template by convolving the template with the matchingKernel
        matched_im2ex = self.im2ex.clone()
        convCntrl = afwMath.ConvolutionControl(False, True, 0)
        afwMath.convolve(matched_im2ex.getMaskedImage(),
                         self.im2ex.getMaskedImage(), mKernel, convCntrl)

        # Expected (ideal) variance of difference image
        expected_var = self.svar + self.tvar
        print('Expected variance:', expected_var)

        # Uncorrected diffim exposure - variance plane is wrong (too low)
        tmp_diffExp = self.im1ex.getMaskedImage().clone()
        tmp_diffExp -= matched_im2ex.getMaskedImage()
        var = self._computeVarianceMean(tmp_diffExp)
        self.assertLess(var, expected_var)

        # Create the diffim (uncorrected)
        diffExp = self.im1ex.clone()
        tmp = diffExp.getMaskedImage()
        tmp -= matched_im2ex.getMaskedImage()
        # Uncorrected diffim exposure - variance is wrong (too low) - same as above but on pixels
        var = self._computePixelVariance(diffExp.getMaskedImage())
        self.assertLess(var, expected_var)

        # Uncorrected diffim exposure - variance plane is wrong (too low)
        mn = self._computeVarianceMean(diffExp.getMaskedImage())
        self.assertLess(mn, expected_var)
        print('UNCORRECTED VARIANCE:', var, mn)

        task = DecorrelateALKernelTask()
        decorrResult = task.run(self.im1ex, self.im2ex, diffExp, mKernel)
        corrected_diffExp = decorrResult.correctedExposure

        # Corrected diffim - variance should be close to expected.
        # We set the tolerance a bit higher here since the simulated images have many bright stars
        var = self._computePixelVariance(corrected_diffExp.getMaskedImage())
        self.assertClose(var, expected_var, rtol=0.05)

        # Check statistics of variance plane in corrected diffim
        mn = self._computeVarianceMean(corrected_diffExp.getMaskedImage())
        print('CORRECTED VARIANCE:', var, mn)
        self.assertClose(mn, expected_var, rtol=0.02)
        self.assertClose(var, mn, rtol=0.05)
Esempio n. 24
0
    def detectFootprints(self,
                         exposure,
                         doSmooth=True,
                         sigma=None,
                         clearMask=True):
        """!Detect footprints.

        \param exposure Exposure to process; DETECTED{,_NEGATIVE} mask plane will be set in-place.
        \param doSmooth if True, smooth the image before detection using a Gaussian of width sigma
        \param sigma    sigma of PSF (pixels); used for smoothing and to grow detections;
            if None then measure the sigma of the PSF of the exposure
        \param clearMask Clear both DETECTED and DETECTED_NEGATIVE planes before running detection

        \return a lsst.pipe.base.Struct with fields:
        - positive: lsst.afw.detection.FootprintSet with positive polarity footprints (may be None)
        - negative: lsst.afw.detection.FootprintSet with negative polarity footprints (may be None)
        - numPos: number of footprints in positive or 0 if detection polarity was negative
        - numNeg: number of footprints in negative or 0 if detection polarity was positive
        - background: re-estimated background.  None if reEstimateBackground==False

        \throws lsst.pipe.base.TaskError if sigma=None and the exposure has no PSF
        """
        try:
            import lsstDebug
            display = lsstDebug.Info(__name__).display
        except ImportError:
            try:
                display
            except NameError:
                display = False

        if exposure is None:
            raise RuntimeError("No exposure for detection")

        maskedImage = exposure.getMaskedImage()
        region = maskedImage.getBBox()

        if clearMask:
            mask = maskedImage.getMask()
            mask &= ~(mask.getPlaneBitMask("DETECTED")
                      | mask.getPlaneBitMask("DETECTED_NEGATIVE"))
            del mask

        if self.config.doTempLocalBackground:
            tempBgRes = self.tempLocalBackground.run(maskedImage)
            tempLocalBkgdImage = tempBgRes.background.getImage()

        if sigma is None:
            psf = exposure.getPsf()
            if psf is None:
                raise pipeBase.TaskError(
                    "exposure has no PSF; must specify sigma")
            shape = psf.computeShape()
            sigma = shape.getDeterminantRadius()

        self.metadata.set("sigma", sigma)
        self.metadata.set("doSmooth", doSmooth)

        if not doSmooth:
            convolvedImage = maskedImage.Factory(maskedImage)
            middle = convolvedImage
        else:
            # smooth using a Gaussian (which is separate, hence fast) of width sigma
            # make a SingleGaussian (separable) kernel with the 'sigma'
            psf = exposure.getPsf()
            kWidth = (int(sigma * 7 + 0.5) // 2) * 2 + 1  # make sure it is odd
            self.metadata.set("smoothingKernelWidth", kWidth)
            gaussFunc = afwMath.GaussianFunction1D(sigma)
            gaussKernel = afwMath.SeparableKernel(kWidth, kWidth, gaussFunc,
                                                  gaussFunc)

            convolvedImage = maskedImage.Factory(maskedImage.getBBox())

            afwMath.convolve(convolvedImage, maskedImage, gaussKernel,
                             afwMath.ConvolutionControl())
            #
            # Only search psf-smooth part of frame
            #
            goodBBox = gaussKernel.shrinkBBox(convolvedImage.getBBox())
            middle = convolvedImage.Factory(convolvedImage, goodBBox,
                                            afwImage.PARENT, False)
            #
            # Mark the parts of the image outside goodBBox as EDGE
            #
            self.setEdgeBits(maskedImage, goodBBox,
                             maskedImage.getMask().getPlaneBitMask("EDGE"))

        fpSets = pipeBase.Struct(positive=None, negative=None)

        if self.config.thresholdPolarity != "negative":
            fpSets.positive = self.thresholdImage(middle, "positive")
        if self.config.reEstimateBackground or self.config.thresholdPolarity != "positive":
            fpSets.negative = self.thresholdImage(middle, "negative")

        for polarity, maskName in (("positive", "DETECTED"),
                                   ("negative", "DETECTED_NEGATIVE")):
            fpSet = getattr(fpSets, polarity)
            if fpSet is None:
                continue
            fpSet.setRegion(region)
            if self.config.nSigmaToGrow > 0:
                nGrow = int((self.config.nSigmaToGrow * sigma) + 0.5)
                self.metadata.set("nGrow", nGrow)
                fpSet = afwDet.FootprintSet(fpSet, nGrow,
                                            self.config.isotropicGrow)
            fpSet.setMask(maskedImage.getMask(), maskName)
            if not self.config.returnOriginalFootprints:
                setattr(fpSets, polarity, fpSet)

        fpSets.numPos = len(fpSets.positive.getFootprints()
                            ) if fpSets.positive is not None else 0
        fpSets.numNeg = len(fpSets.negative.getFootprints()
                            ) if fpSets.negative is not None else 0

        if self.config.thresholdPolarity != "negative":
            self.log.log(
                self.log.INFO, "Detected %d positive sources to %g sigma." %
                (fpSets.numPos, self.config.thresholdValue *
                 self.config.includeThresholdMultiplier))

        if self.config.doTempLocalBackground:
            maskedImage += tempLocalBkgdImage

        fpSets.background = None
        if self.config.reEstimateBackground:
            mi = exposure.getMaskedImage()
            bkgd = self.background.fitBackground(mi)

            if self.config.adjustBackground:
                self.log.log(
                    self.log.WARN, "Fiddling the background by %g" %
                    self.config.adjustBackground)

                bkgd += self.config.adjustBackground
            fpSets.background = bkgd
            self.log.log(
                self.log.INFO,
                "Resubtracting the background after object detection")

            mi -= bkgd.getImageF()
            del mi

        if self.config.thresholdPolarity == "positive":
            if self.config.reEstimateBackground:
                mask = maskedImage.getMask()
                mask &= ~mask.getPlaneBitMask("DETECTED_NEGATIVE")
                del mask
            fpSets.negative = None
        else:
            self.log.log(
                self.log.INFO, "Detected %d negative sources to %g %s" %
                (fpSets.numNeg, self.config.thresholdValue,
                 ("DN" if self.config.thresholdType == "value" else "sigma")))

        if display:
            ds9.mtv(exposure, frame=0, title="detection")
            x0, y0 = exposure.getXY0()

            def plotPeaks(fps, ctype):
                if fps is None:
                    return
                with ds9.Buffering():
                    for fp in fps.getFootprints():
                        for pp in fp.getPeaks():
                            ds9.dot("+",
                                    pp.getFx() - x0,
                                    pp.getFy() - y0,
                                    ctype=ctype)

            plotPeaks(fpSets.positive, "yellow")
            plotPeaks(fpSets.negative, "red")

            if convolvedImage and display and display > 1:
                ds9.mtv(convolvedImage, frame=1, title="PSF smoothed")

        return fpSets
Esempio n. 25
0
def brighterFatterCorrection(exposure, kernel, maxIter, threshold, applyGain):
    """Apply brighter fatter correction in place for the image.

    Parameters
    ----------
    exposure : `lsst.afw.image.Exposure`
        Exposure to have brighter-fatter correction applied.  Modified
        by this method.
    kernel : `numpy.ndarray`
        Brighter-fatter kernel to apply.
    maxIter : scalar
        Number of correction iterations to run.
    threshold : scalar
        Convergence threshold in terms of the sum of absolute
        deviations between an iteration and the previous one.
    applyGain : `Bool`
        If True, then the exposure values are scaled by the gain prior
        to correction.

    Returns
    -------
    diff : `float`
        Final difference between iterations achieved in correction.
    iteration : `int`
        Number of iterations used to calculate correction.

    Notes
    -----
    This correction takes a kernel that has been derived from flat
    field images to redistribute the charge.  The gradient of the
    kernel is the deflection field due to the accumulated charge.

    Given the original image I(x) and the kernel K(x) we can compute
    the corrected image Ic(x) using the following equation:

    Ic(x) = I(x) + 0.5*d/dx(I(x)*d/dx(int( dy*K(x-y)*I(y))))

    To evaluate the derivative term we expand it as follows:

    0.5 * ( d/dx(I(x))*d/dx(int(dy*K(x-y)*I(y))) + I(x)*d^2/dx^2(int(dy* K(x-y)*I(y))) )

    Because we use the measured counts instead of the incident counts
    we apply the correction iteratively to reconstruct the original
    counts and the correction.  We stop iterating when the summed
    difference between the current corrected image and the one from
    the previous iteration is below the threshold.  We do not require
    convergence because the number of iterations is too large a
    computational cost.  How we define the threshold still needs to be
    evaluated, the current default was shown to work reasonably well
    on a small set of images.  For more information on the method see
    DocuShare Document-19407.

    The edges as defined by the kernel are not corrected because they
    have spurious values due to the convolution.
    """
    image = exposure.getMaskedImage().getImage()

    # The image needs to be units of electrons/holes
    with gainContext(exposure, image, applyGain):

        kLx = numpy.shape(kernel)[0]
        kLy = numpy.shape(kernel)[1]
        kernelImage = afwImage.ImageD(kLx, kLy)
        kernelImage.getArray()[:, :] = kernel
        tempImage = image.clone()

        nanIndex = numpy.isnan(tempImage.getArray())
        tempImage.getArray()[nanIndex] = 0.

        outImage = afwImage.ImageF(image.getDimensions())
        corr = numpy.zeros_like(image.getArray())
        prev_image = numpy.zeros_like(image.getArray())
        convCntrl = afwMath.ConvolutionControl(False, True, 1)
        fixedKernel = afwMath.FixedKernel(kernelImage)

        # Define boundary by convolution region.  The region that the correction will be
        # calculated for is one fewer in each dimension because of the second derivative terms.
        # NOTE: these need to use integer math, as we're using start:end as numpy index ranges.
        startX = kLx // 2
        endX = -kLx // 2
        startY = kLy // 2
        endY = -kLy // 2

        for iteration in range(maxIter):

            afwMath.convolve(outImage, tempImage, fixedKernel, convCntrl)
            tmpArray = tempImage.getArray()
            outArray = outImage.getArray()

            with numpy.errstate(invalid="ignore", over="ignore"):
                # First derivative term
                gradTmp = numpy.gradient(tmpArray[startY:endY, startX:endX])
                gradOut = numpy.gradient(outArray[startY:endY, startX:endX])
                first = (gradTmp[0] * gradOut[0] +
                         gradTmp[1] * gradOut[1])[1:-1, 1:-1]

                # Second derivative term
                diffOut20 = numpy.diff(outArray, 2, 0)[startY:endY,
                                                       startX + 1:endX - 1]
                diffOut21 = numpy.diff(outArray, 2, 1)[startY + 1:endY - 1,
                                                       startX:endX]
                second = tmpArray[startY + 1:endY - 1, startX + 1:endX -
                                  1] * (diffOut20 + diffOut21)

                corr[startY + 1:endY - 1,
                     startX + 1:endX - 1] = 0.5 * (first + second)

                tmpArray[:, :] = image.getArray()[:, :]
                tmpArray[nanIndex] = 0.
                tmpArray[startY:endY, startX:endX] += corr[startY:endY,
                                                           startX:endX]

            if iteration > 0:
                diff = numpy.sum(numpy.abs(prev_image - tmpArray))

                if diff < threshold:
                    break
                prev_image[:, :] = tmpArray[:, :]

        image.getArray()[startY + 1:endY - 1, startX + 1:endX - 1] += \
            corr[startY + 1:endY - 1, startX + 1:endX - 1]

    return diff, iteration