Exemple #1
0
 def testGaussianFunction2D(self):
     """Note: Assumes GaussianFunction1D is correct (tested elsewhere)."""
     errMsg = "{} = {} != {} for pos1={}, pos2={}, x={}, y={}, sigma1={}, sigma2={}, angle={}"
     areaMsg = "%s area = %s != 1.0 for sigma1=%s, sigma2=%s"
     f = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
     f1 = afwMath.GaussianFunction1D(1.0)
     f2 = afwMath.GaussianFunction1D(1.0)
     for sigma1 in (0.1, 1.0, 3.0):
         for sigma2 in (0.1, 1.0, 3.0):
             for angle in (0.0, 0.4, 1.1):
                 sinNegAngle = math.sin(-angle)
                 cosNegAngle = math.cos(-angle)
                 f.setParameters((sigma1, sigma2, angle))
                 g = f.clone()
                 f1.setParameters((sigma1, ))
                 f2.setParameters((sigma2, ))
                 fSum = 0.0
                 delta1 = sigma1 / 5.0
                 delta2 = sigma2 / 5.0
                 for pos1 in np.arange(-sigma1 * 5, sigma1 * 5.01, delta1):
                     for pos2 in np.arange(-sigma2 * 5.0, sigma2 * 5.01,
                                           delta2):
                         x = (cosNegAngle * pos1) + (sinNegAngle * pos2)
                         y = (-sinNegAngle * pos1) + (cosNegAngle * pos2)
                         predVal = f1(pos1) * f2(pos2)
                         fSum += predVal
                         msg = errMsg.format(
                             type(f).__name__, f(x, y), predVal, pos1, pos2,
                             x, y, sigma1, sigma2, angle)
                         self.assertFloatsAlmostEqual(f(x, y),
                                                      predVal,
                                                      msg=msg,
                                                      atol=self.atol,
                                                      rtol=None)
                         msg = errMsg.format(
                             type(g).__name__, g(x, y), predVal, pos1, pos2,
                             x, y, sigma1, sigma2, angle) + "; clone"
                         self.assertFloatsAlmostEqual(g(x, y),
                                                      predVal,
                                                      msg=msg,
                                                      atol=self.atol,
                                                      rtol=None)
                 approxArea = fSum * delta1 * delta2
                 msg = areaMsg % (type(f).__name__, approxArea, sigma1,
                                  sigma2)
                 # approxArea is very approximate, so we need a high
                 # tolerance threshold.
                 self.assertFloatsAlmostEqual(approxArea,
                                              1.0,
                                              msg=msg,
                                              atol=1e-6,
                                              rtol=None)
Exemple #2
0
    def testZeroSizeKernel(self):
        """Creating a kernel with width or height < 1 should raise an exception.

        Note: this ignores the default constructors, which produce kernels with height = width = 0.
        The default constructors are only intended to support persistence, not to produce useful kernels.
        """
        gaussFunc2D = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        gaussFunc1D = afwMath.GaussianFunction1D(1.0)
        zeroPoint = lsst.geom.Point2I(0, 0)
        for kWidth in (-1, 0, 1):
            for kHeight in (-1, 0, 1):
                if (kHeight > 0) and (kWidth > 0):
                    continue
                if (kHeight >= 0) and (kWidth >= 0):
                    # don't try to create an image with negative dimensions
                    blankImage = afwImage.ImageF(
                        lsst.geom.Extent2I(kWidth, kHeight))
                    self.assertRaises(
                        Exception, afwMath.FixedKernel, blankImage)
                self.assertRaises(
                    Exception, afwMath.AnalyticKernel, kWidth, kHeight, gaussFunc2D)
                self.assertRaises(Exception, afwMath.SeparableKernel,
                                  kWidth, kHeight, gaussFunc1D, gaussFunc1D)
                self.assertRaises(
                    Exception, afwMath.DeltaFunctionKernel, kWidth, kHeight, zeroPoint)
Exemple #3
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["doSmooth"] = doSmooth
        sigma = psf.computeShape(psf.getAveragePosition()).getDeterminantRadius()
        self.metadata["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["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)
Exemple #4
0
def smooth_gauss(masked_image, sigma, nsigma=7.0):
    """
    Smooth image with a Gaussian kernel. 

    Parameters
    ----------
    masked_image : lsst.afw.image.imageLib.MaskedImageF
        Masked image object to be smoothed
    sigma : float
        Standard deviation of Gaussian
    nsigma : float, optional
        Number of sigma for kernel width

    Returns
    -------
    convolved_image : lsst.afw.image.imageLib.MaskedImageF
        The convolved masked image
    """
    width = (int(sigma*nsigma + 0.5) // 2)*2 + 1 # make sure it is odd
    gauss_func = afwMath.GaussianFunction1D(sigma)
    gauss_kern = afwMath.SeparableKernel(width, width, gauss_func, gauss_func)
    convolved_image = masked_image.Factory(masked_image.getBBox())
    afwMath.convolve(convolved_image, masked_image, gauss_kern, 
                     afwMath.ConvolutionControl())
    return convolved_image
Exemple #5
0
    def testGaussianFunction1D(self):
        def basicGaussian(x, sigma):
            return (1.0 / (sigma * math.sqrt(2 * math.pi))) * math.exp(
                -x**2 / (2.0 * sigma**2))

        f = afwMath.GaussianFunction1D(1.0)
        for xsigma in (0.1, 1.0, 3.0):
            f.setParameters((xsigma, ))
            g = f.clone()
            xdelta = xsigma / 10.0
            fSum = 0.0
            for x in np.arange(-xsigma * 20, xsigma * 20.01, xdelta):
                predVal = basicGaussian(x, xsigma)
                fSum += predVal
                msg = f"{type(f).__name__} = {f(x)} != {predVal} for x={x}, xsigma={xsigma}"
                self.assertFloatsAlmostEqual(f(x),
                                             predVal,
                                             msg=msg,
                                             atol=self.atol,
                                             rtol=None)
                msg += "; clone"
                self.assertFloatsAlmostEqual(g(x),
                                             predVal,
                                             msg=msg,
                                             atol=self.atol,
                                             rtol=None)
            approxArea = fSum * xdelta
            msg = f"{type(f).__name__} area = {approxArea} != 1.0 for xsigma={xsigma}"
            self.assertFloatsAlmostEqual(approxArea,
                                         1.0,
                                         msg=msg,
                                         atol=self.atol,
                                         rtol=None)
Exemple #6
0
    def testGaussianFunction1D(self):
        """A test for GaussianFunction1D"""
        def basicGaussian(x, sigma):
            return (1.0 / (sigma * math.sqrt(2 * math.pi))) * math.exp(
                -x**2 / (2.0 * sigma**2))

        f = afwMath.GaussianFunction1D(1.0)
        for xsigma in (0.1, 1.0, 3.0):
            f.setParameters((xsigma, ))
            g = f.clone()
            xdelta = xsigma / 10.0
            fSum = 0.0
            for x in numpy.arange(-xsigma * 20, xsigma * 20.01, xdelta):
                predVal = basicGaussian(x, xsigma)
                fSum += predVal
                if not numpy.allclose(predVal, f(x)):
                    self.fail("%s = %s != %s for x=%s, xsigma=%s" % \
                        (type(f).__name__, f(x), predVal, x, xsigma))
                if not numpy.allclose(predVal, g(x)):
                    self.fail("%s = %s != %s for x=%s, xsigma=%s; clone" % \
                        (type(f).__name__, f(x), predVal, x, xsigma))
            approxArea = fSum * xdelta
            if not numpy.allclose(approxArea, 1.0):
                self.fail("%s area = %s != 1.0 for xsigma=%s" % \
                    (type(f).__name__, approxArea, xsigma))
Exemple #7
0
    def testSpatiallyVaryingSeparableConvolve(self):
        """Test convolution with a spatially varying SeparableKernel
        """
        kWidth = 7
        kHeight = 6

        # create spatial model
        sFunc = afwMath.PolynomialFunction2D(1)

        minSigma = 0.1
        maxSigma = 3.0

        # spatial parameters are a list of entries, one per kernel parameter;
        # each entry is a list of spatial parameters
        sParams = (
            (minSigma, (maxSigma - minSigma) / self.width, 0.0),
            (minSigma, 0.0, (maxSigma - minSigma) / self.height),
            (0.0, 0.0, 0.0),
        )

        gaussFunc1 = afwMath.GaussianFunction1D(1.0)
        gaussFunc2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        separableKernel = afwMath.SeparableKernel(kWidth, kHeight, gaussFunc1,
                                                  gaussFunc1, sFunc)
        analyticKernel = afwMath.AnalyticKernel(kWidth, kHeight, gaussFunc2,
                                                sFunc)
        separableKernel.setSpatialParameters(sParams[0:2])
        analyticKernel.setSpatialParameters(sParams)

        self.runStdTest(
            separableKernel,
            refKernel=analyticKernel,
            kernelDescr="Spatially Varying Gaussian Separable Kernel")
Exemple #8
0
    def convolveImage(self, maskedImage, psf, doSmooth=True):
        """Convolve the image with the PSF

            This differs from the standard SourceDetectionTask convolveImage
            in that it allows the user to specify a scaling to the sigma. For GOTO coadds, we found that convolving by the sigma blurred the image too much, meaning that some peaks were missed. By reducing the width of the Gaussian kernel, the smoothing is reduced.

            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?

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

        # This is the only thing that differs from SourceDetectionTask:
        sigma *= self.config.scaleSigma
        self.metadata.set("sigma", sigma)

        if not doSmooth:
            middle = maskedImage.Factory(maskedImage)
            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)
Exemple #9
0
    def testMakeBadKernels(self):
        """Attempt to make various invalid kernels; make sure the constructor shows an exception
        """
        kWidth = 4
        kHeight = 3

        gaussFunc1 = afwMath.GaussianFunction1D(1.0)
        gaussFunc2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        spFunc = afwMath.PolynomialFunction2D(1)
        kernelList = []
        kernelList.append(afwMath.FixedKernel(
            afwImage.ImageD(lsst.geom.Extent2I(kWidth, kHeight), 0.1)))
        kernelList.append(afwMath.FixedKernel(
            afwImage.ImageD(lsst.geom.Extent2I(kWidth, kHeight), 0.2)))

        for numKernelParams in (2, 4):
            spFuncList = []
            for ii in range(numKernelParams):
                spFuncList.append(spFunc.clone())
            try:
                afwMath.AnalyticKernel(kWidth, kHeight, gaussFunc2, spFuncList)
                self.fail("Should have failed with wrong # of spatial functions")
            except pexExcept.Exception:
                pass

        for numKernelParams in (1, 3):
            spFuncList = []
            for ii in range(numKernelParams):
                spFuncList.append(spFunc.clone())
            try:
                afwMath.LinearCombinationKernel(kernelList, spFuncList)
                self.fail("Should have failed with wrong # of spatial functions")
            except pexExcept.Exception:
                pass
            kParamList = [0.2]*numKernelParams
            try:
                afwMath.LinearCombinationKernel(kernelList, kParamList)
                self.fail("Should have failed with wrong # of kernel parameters")
            except pexExcept.Exception:
                pass
            try:
                afwMath.SeparableKernel(
                    kWidth, kHeight, gaussFunc1, gaussFunc1, spFuncList)
                self.fail("Should have failed with wrong # of spatial functions")
            except pexExcept.Exception:
                pass

        for pointX in range(-1, kWidth+2):
            for pointY in range(-1, kHeight+2):
                if (0 <= pointX < kWidth) and (0 <= pointY < kHeight):
                    continue
                try:
                    afwMath.DeltaFunctionKernel(
                        kWidth, kHeight, lsst.geom.Point2I(pointX, pointY))
                    self.fail("Should have failed with point not on kernel")
                except pexExcept.Exception:
                    pass
Exemple #10
0
    def psf(self, exposure, sources):
        """Measure the PSF

        @param exposure Exposure to process
        @param sources Measured sources on exposure
        """
        assert exposure, "No exposure provided"
        assert sources, "No sources provided"
        psfPolicy = self.config['psf']
        selName = psfPolicy['selectName']
        selPolicy = psfPolicy['select'].getPolicy()
        algName = psfPolicy['algorithmName']
        algPolicy = psfPolicy['algorithm'].getPolicy()
        self.log.log(self.log.INFO, "Measuring PSF")

        #
        # Run an extra detection step to mask out faint stars
        #
        if False:
            print "RHL is cleaning faint sources"

            import lsst.afw.math as afwMath

            sigma = 1.0
            gaussFunc = afwMath.GaussianFunction1D(sigma)
            gaussKernel = afwMath.SeparableKernel(15, 15, gaussFunc, gaussFunc)

            im = exposure.getMaskedImage().getImage()
            convolvedImage = im.Factory(im.getDimensions())
            afwMath.convolve(convolvedImage, im, gaussKernel)
            del im

            fs = afwDet.makeFootprintSet(convolvedImage,
                                         afwDet.createThreshold(4, "stdev"))
            fs = afwDet.makeFootprintSet(fs, 3, True)
            fs.setMask(exposure.getMaskedImage().getMask(), "DETECTED")

        starSelector = measAlg.makeStarSelector(selName, selPolicy)
        psfCandidateList = starSelector.selectStars(exposure, sources)

        psfDeterminer = measAlg.makePsfDeterminer(algName, algPolicy)
        psf, cellSet = psfDeterminer.determinePsf(exposure, psfCandidateList)

        # The PSF candidates contain a copy of the source, and so we need to explicitly propagate new flags
        for cand in psfCandidateList:
            cand = measAlg.cast_PsfCandidateF(cand)
            src = cand.getSource()
            if src.getFlagForDetection() & measAlg.Flags.PSFSTAR:
                ident = src.getId()
                src = sources[ident]
                assert src.getId() == ident
                src.setFlagForDetection(src.getFlagForDetection()
                                        | measAlg.Flags.PSFSTAR)

        exposure.setPsf(psf)
        return psf, cellSet
Exemple #11
0
    def testSeparableKernel(self):
        """Test SeparableKernel using a Gaussian function
        """
        kWidth = 5
        kHeight = 8

        pol = pexPolicy.Policy()
        additionalData = dafBase.PropertySet()
        loc = dafPersist.LogicalLocation(
            os.path.join(testPath, "data", "kernel4.boost"))
        persistence = dafPersist.Persistence.getPersistence(pol)

        gaussFunc1 = afwMath.GaussianFunction1D(1.0)
        k = afwMath.SeparableKernel(kWidth, kHeight, gaussFunc1, gaussFunc1)
        fArr = np.zeros(shape=[k.getWidth(), k.getHeight()], dtype=float)
        np.zeros(shape=[k.getWidth(), k.getHeight()], dtype=float)
        gaussFunc = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        for xsigma in (0.1, 1.0, 3.0):
            gaussFunc1.setParameters((xsigma, ))
            for ysigma in (0.1, 1.0, 3.0):
                gaussFunc.setParameters((xsigma, ysigma, 0.0))
                # compute array of function values and normalize
                for row in range(k.getHeight()):
                    y = row - k.getCtrY()
                    for col in range(k.getWidth()):
                        x = col - k.getCtrX()
                        fArr[col, row] = gaussFunc(x, y)
                fArr /= fArr.sum()

                k.setKernelParameters((xsigma, ysigma))

                storageList = dafPersist.StorageList()
                storage = persistence.getPersistStorage("XmlStorage", loc)
                storageList.append(storage)
                persistence.persist(k, storageList, additionalData)

                storageList2 = dafPersist.StorageList()
                storage2 = persistence.getRetrieveStorage("XmlStorage", loc)
                storageList2.append(storage2)
                x = persistence.unsafeRetrieve("SeparableKernel", storageList2,
                                               additionalData)
                k2 = afwMath.SeparableKernel.swigConvert(x)

                self.kernelCheck(k, k2)

                kImage = afwImage.ImageD(k2.getDimensions())
                k2.computeImage(kImage, True)
                kArr = kImage.getArray().transpose()
                if not np.allclose(fArr, kArr):
                    self.fail(
                        "%s = %s != %s for xsigma=%s, ysigma=%s" %
                        (k2.__class__.__name__, kArr, fArr, xsigma, ysigma))
Exemple #12
0
    def testGaussianFunction2D(self):
        """A test for GaussianFunction2D
        Assumes GaussianFunction1D is correct (tested elsewhere)
        """
        f = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        f1 = afwMath.GaussianFunction1D(1.0)
        f2 = afwMath.GaussianFunction1D(1.0)
        for sigma1 in (0.1, 1.0, 3.0):
            for sigma2 in (0.1, 1.0, 3.0):
                for angle in (0.0, 0.4, 1.1):
                    sinNegAngle = math.sin(-angle)
                    cosNegAngle = math.cos(-angle)
                    f.setParameters((sigma1, sigma2, angle))
                    g = f.clone()
                    f1.setParameters((sigma1, ))
                    f2.setParameters((sigma2, ))
                    fSum = 0.0
                    delta1 = sigma1 / 5.0
                    delta2 = sigma2 / 5.0
                    for pos1 in numpy.arange(-sigma1 * 5, sigma1 * 5.01,
                                             delta1):
                        for pos2 in numpy.arange(-sigma2 * 5.0, sigma2 * 5.01,
                                                 delta2):
                            x = (cosNegAngle * pos1) + (sinNegAngle * pos2)
                            y = (-sinNegAngle * pos1) + (cosNegAngle * pos2)
                            predVal = f1(pos1) * f2(pos2)
                            fSum += predVal
                            if not numpy.allclose(predVal, f(x, y)):
                                self.fail(
                                    "%s = %s != %s for pos1=%s, pos2=%s, x=%s, y=%s, sigma1=%s, sigma2=%s, angle=%s" % \
(type(f).__name__, f(x, y), predVal, pos1, pos2, x, y, sigma1, sigma2, angle))
                            if not numpy.allclose(predVal, g(x, y)):
                                self.fail(
                                    "%s = %s != %s for pos1=%s, pos2=%s, x=%s, y=%s, sigma1=%s, sigma2=%s, angle=%s; clone" % \
(type(g).__name__, g(x, y), predVal, pos1, pos2, x, y, sigma1, sigma2, angle))
                    approxArea = fSum * delta1 * delta2
                    if not numpy.allclose(approxArea, 1.0):
                        self.fail("%s area = %s != 1.0 for sigma1=%s, sigma2=%s" % \
                            (type(f).__name__, approxArea, sigma1, sigma2))
Exemple #13
0
def findBrightestStarRHL(exp, minArea=100, maxRadialOffset=1500):
    import lsst.afw.detection as afwDetect
    import lsst.afw.image as afwImage
    import lsst.afw.math as afwMath

    cexp = exp.clone()

    sigma = 4

    ksize = 1 + 4*int(sigma + 1)
    gauss1d = afwMath.GaussianFunction1D(sigma)
    kernel = afwMath.SeparableKernel(ksize, ksize, gauss1d, gauss1d)

    convolvedImage = cexp.maskedImage.Factory(cexp.maskedImage.getBBox())
    afwMath.convolve(convolvedImage, cexp.maskedImage, kernel)
    bbox = geom.BoxI(geom.PointI(ksize//2, ksize//2), geom.PointI(cexp.getWidth() - ksize//2 - 1, cexp.getHeight() - ksize//2 - 1))
    tmp = cexp.maskedImage[bbox]
    cexp.image *= 0
    tmp[bbox, afwImage.PARENT] = convolvedImage[bbox]

    del convolvedImage; del tmp

    if False:
        defectBBoxes = [
            geom.BoxI(geom.PointI(548, 3562), geom.PointI(642, 3999)),
            geom.BoxI(geom.PointI(474, 3959), geom.PointI(1056, 3999)),
            geom.BoxI(geom.PointI(500, 0),    geom.PointI(680, 40)),
        ]
        for bbox in defectBBoxes:
            cexp.maskedImage[bbox] = 0

    threshold = 1000
    feet = afwDetect.FootprintSet(cexp.maskedImage, afwDetect.Threshold(threshold), "DETECTED").getFootprints()

    maxVal = None
    centroid = None
    for foot in feet:
        peak = foot.peaks[0]
        
        if minArea > 0 and foot.getArea() < minArea:
            continue
            
        x, y = peak.getCentroid()
        if np.hypot(x - 2036, y - 2000) > maxRadialOffset:
            continue

        if maxVal is None or peak.getPeakValue() > maxVal:
            maxVal = peak.getPeakValue()
            centroid = peak.getCentroid()
            
    return centroid
Exemple #14
0
    def testSeparableKernel(self):
        """Test SeparableKernel using a Gaussian function
        """
        kWidth = 5
        kHeight = 8

        gaussFunc1 = afwMath.GaussianFunction1D(1.0)
        kernel = afwMath.SeparableKernel(kWidth, kHeight, gaussFunc1,
                                         gaussFunc1)
        self.basicTests(kernel, 2)

        kernelResized = self.verifyResized(kernel)
        self.basicTests(kernelResized, 2)

        fArr = np.zeros(shape=[kernel.getWidth(),
                               kernel.getHeight()],
                        dtype=float)
        gaussFunc = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        for xsigma in (0.1, 1.0, 3.0):
            gaussFunc1.setParameters((xsigma, ))
            for ysigma in (0.1, 1.0, 3.0):
                gaussFunc.setParameters((xsigma, ysigma, 0.0))
                # compute array of function values and normalize
                for row in range(kernel.getHeight()):
                    y = row - kernel.getCtrY()
                    for col in range(kernel.getWidth()):
                        x = col - kernel.getCtrX()
                        fArr[col, row] = gaussFunc(x, y)
                fArr /= fArr.sum()

                kernel.setKernelParameters((xsigma, ysigma))
                kImage = afwImage.ImageD(kernel.getDimensions())
                kernel.computeImage(kImage, True)
                kArr = kImage.getArray().transpose()
                if not np.allclose(fArr, kArr):
                    self.fail("%s = %s != %s for xsigma=%s, ysigma=%s" %
                              (kernel.__class__.__name__, kArr, fArr, xsigma,
                               ysigma))
        kernelClone = kernel.clone()
        errStr = self.compareKernels(kernel, kernelClone)
        if errStr:
            self.fail(errStr)

        kernel.setKernelParameters((1.2, 0.6))
        errStr = self.compareKernels(kernel, kernelClone)
        if not errStr:
            self.fail(
                "Clone was modified by changing original's kernel parameters")

        self.verifyCache(kernel, hasCache=True)
Exemple #15
0
    def testSeparableConvolve(self):
        """Test convolve of a separable kernel with a spatially invariant Gaussian function
        """
        kWidth = 7
        kHeight = 6

        gaussFunc1 = afwMath.GaussianFunction1D(1.0)
        gaussFunc2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        separableKernel = afwMath.SeparableKernel(kWidth, kHeight, gaussFunc1, gaussFunc1)
        analyticKernel = afwMath.AnalyticKernel(kWidth, kHeight, gaussFunc2)
        
        self.runStdTest(
            separableKernel,
            refKernel = analyticKernel,
            kernelDescr = "Gaussian Separable Kernel (compared to AnalyticKernel equivalent)")
Exemple #16
0
def smoothExp(exp, fwhm, display=False):
    import lsst.afw.math as afwMath

    smoothedScienceExposure = exp.clone()
    gauss = afwMath.GaussianFunction1D(fwhm)
    kSize = int(2 * fwhm + 0.5) + 1
    kernel = afwMath.SeparableKernel(kSize, kSize, gauss, gauss)
    afwMath.convolve(smoothedScienceExposure.getMaskedImage(),
                     exp.getMaskedImage(), kernel)

    if display:
        import lsst.afw.display.ds9 as ds9
        ds9.mtv(exp, title="science", frame=1)
        ds9.mtv(smoothedScienceExposure, title="smoothed", frame=2)

    return smoothedScienceExposure
Exemple #17
0
def smooth_gauss(masked_image, sigma=2.0, nsigma=7.0, 
                 inplace=False, use_scipy=False, **kwargs):
    """
    Smooth image with a Gaussian kernel. 

    Parameters
    ----------
    masked_image : lsst.afw.image.imageLib.MaskedImageF
        Masked image object to be smoothed
    sigma : float
        Standard deviation of Gaussian
    nsigma : float, optional
        Number of sigma for kernel width
    inplace : bool, optional
        If True, smooth the image inplace. Else, return a clone 
        of the masked image. 
    use_scipy : bool, optional
        If True, perform smoothing using scipy.ndimage.

    Returns
    -------
    convolved_image : lsst.afw.image.imageLib.MaskedImageF
        The convolved masked image
    """
    if use_scipy:
        img_arr = get_image_ndarray(masked_image)
        img_arr_smooth = ndi.gaussian_filter(
            img_arr, sigma, truncate=nsigma, **kwargs)
        if inplace:
            convolved_image = masked_image
        else:
            convolved_image = masked_image.Factory(masked_image.getBBox())
        convolved_image.getImage().getArray()[:] = img_arr_smooth
    else:
        width = (int(sigma*nsigma + 0.5) // 2)*2 + 1 # make sure it is odd
        gauss_func = afwMath.GaussianFunction1D(sigma)
        gauss_kern = afwMath.SeparableKernel(
            width, width, gauss_func, gauss_func)
        convolved_image = masked_image.Factory(masked_image.getBBox())
        afwMath.convolve(convolved_image, masked_image, gauss_kern,
                         afwMath.ConvolutionControl())
        if inplace:
            img_arr_smooth = convolved_image.getImage().getArray()
            masked_image.getImage().getArray()[:] = img_arr_smooth
    return convolved_image
def getSeparableKernel(kSize, imSize, spOrder):
    """Return spatially varying separable kernel: a pair of 1-d Gaussians
    
    @param kSize: kernel size (scalar; height = width)
    @param x, y imSize: image size
    """
    gaussFunc = afwMath.GaussianFunction1D(1)
    polyFunc = afwMath.PolynomialFunction2D(spOrder)
    kernel = afwMath.SeparableKernel(kSize, kSize, gaussFunc, gaussFunc, polyFunc)
    
    minSigma = 0.1
    maxSigma = 3.0

    spParams = getSpatialParameters(2, polyFunc)
    spParams[0][0:3] = [minSigma, (maxSigma - minSigma) / float(imSize[0]), 0.0]
    spParams[1][0:3] = [minSigma, 0.0, (maxSigma - minSigma) / float(imSize[0])]
    kernel.setSpatialParameters(spParams);
    return kernel
Exemple #19
0
 def testCast(self):
     for instance in (afwMath.Chebyshev1Function1F(2),
                      afwMath.GaussianFunction1F(1.0),
                      afwMath.LanczosFunction1F(3),
                      afwMath.NullFunction1F(),
                      afwMath.PolynomialFunction1F(2)):
         Class = type(instance)
         base = instance.clone()
         self.assertEqual(type(base), afwMath.Function1F)
         derived = Class.cast(base)
         self.assertEqual(type(derived), Class)
     for instance in (afwMath.Chebyshev1Function1D(2),
                      afwMath.GaussianFunction1D(1.0),
                      afwMath.LanczosFunction1D(3),
                      afwMath.NullFunction1D(),
                      afwMath.PolynomialFunction1D(2)):
         Class = type(instance)
         base = instance.clone()
         self.assertEqual(type(base), afwMath.Function1D)
         derived = Class.cast(base)
         self.assertEqual(type(derived), Class)
     for instance in (afwMath.Chebyshev1Function2F(2),
                      afwMath.GaussianFunction2F(1.0, 1.0),
                      afwMath.DoubleGaussianFunction2F(1.0),
                      afwMath.LanczosFunction2F(3),
                      afwMath.NullFunction2F(),
                      afwMath.PolynomialFunction2F(2)):
         Class = type(instance)
         base = instance.clone()
         self.assertEqual(type(base), afwMath.Function2F)
         derived = Class.cast(base)
         self.assertEqual(type(derived), Class)
     for instance in (afwMath.Chebyshev1Function2D(2),
                      afwMath.GaussianFunction2D(1.0, 1.0),
                      afwMath.DoubleGaussianFunction2D(1.0),
                      afwMath.LanczosFunction2D(3),
                      afwMath.NullFunction2D(),
                      afwMath.PolynomialFunction2D(2)):
         Class = type(instance)
         base = instance.clone()
         self.assertEqual(type(base), afwMath.Function2D)
         derived = Class.cast(base)
         self.assertEqual(type(derived), Class)
Exemple #20
0
    def testSeparableKernel(self):
        """Test SeparableKernel using a Gaussian function
        """
        kWidth = 5
        kHeight = 8

        gaussFunc1 = afwMath.GaussianFunction1D(1.0)
        k = afwMath.SeparableKernel(kWidth, kHeight, gaussFunc1, gaussFunc1)
        center = k.getCtr()
        fArr = np.zeros(shape=[k.getWidth(), k.getHeight()], dtype=float)
        np.zeros(shape=[k.getWidth(), k.getHeight()], dtype=float)
        gaussFunc = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
        for xsigma in (0.1, 1.0, 3.0):
            gaussFunc1.setParameters((xsigma, ))
            for ysigma in (0.1, 1.0, 3.0):
                gaussFunc.setParameters((xsigma, ysigma, 0.0))
                # compute array of function values and normalize
                for row in range(k.getHeight()):
                    y = row - center.getY()
                    for col in range(k.getWidth()):
                        x = col - center.getX()
                        fArr[col, row] = gaussFunc(x, y)
                fArr /= fArr.sum()

                k.setKernelParameters((xsigma, ysigma))

                with lsst.utils.tests.getTempFilePath(".fits") as filename:
                    k.writeFits(filename)
                    k2 = afwMath.SeparableKernel.readFits(filename)

                self.kernelCheck(k, k2)

                kImage = afwImage.ImageD(k2.getDimensions())
                k2.computeImage(kImage, True)
                kArr = kImage.getArray().transpose()
                if not np.allclose(fArr, kArr):
                    self.fail(f"{k2.__class__.__name__} = {kArr} != {fArr} "
                              f"for xsigma={xsigma}, ysigma={ysigma}")
Exemple #21
0
    def testSVSeparableKernel(self):
        """Test spatially varying SeparableKernel using a Gaussian function

        Just tests cloning.
        """
        kWidth = 5
        kHeight = 8

        # spatial model
        spFunc = afwMath.PolynomialFunction2D(1)

        # spatial parameters are a list of entries, one per kernel parameter;
        # each entry is a list of spatial parameters
        sParams = (
            (1.0, 1.0, 0.0),
            (1.0, 0.0, 1.0),
        )

        gaussFunc = afwMath.GaussianFunction1D(1.0)
        kernel = afwMath.SeparableKernel(
            kWidth, kHeight, gaussFunc, gaussFunc, spFunc)
        kernel.setSpatialParameters(sParams)

        kernelClone = kernel.clone()
        errStr = self.compareKernels(kernel, kernelClone)
        if errStr:
            self.fail(errStr)

        newSParams = (
            (0.1, 0.2, 0.5),
            (0.1, 0.5, 0.2),
        )
        kernel.setSpatialParameters(newSParams)
        errStr = self.compareKernels(kernel, kernelClone)
        if not errStr:
            self.fail(
                "Clone was modified by changing original's spatial parameters")
Exemple #22
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
Exemple #23
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