def insert(self, source):
        """Insert source into the histogram."""

        ixx, iyy, ixy = source.getIxx(), source.getIyy(), source.getIxy()
        if self.detector:
            distorter = self.detector.getDistortion()
            if distorter:
                p = afwGeom.Point2D(source.getX() + self.xy0.getX(),
                                    source.getY() + self.xy0.getY())
                m = distorter.undistort(p, geomEllip.Quadrupole(ixx, iyy, ixy),
                                        self.detector)
                ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy()

        try:
            pixel = self.momentsToPixel(ixx, iyy)
            i = int(pixel[0])
            j = int(pixel[1])
        except:
            return 0

        if i in range(0, self._xSize) and j in range(0, self._ySize):
            if i != 0 or j != 0:
                self._psfImage.set(i, j, self._psfImage.get(i, j) + 1)
                self._num += 1
                return 1  # success

        return 0  # failure
Example #2
0
    def testHsmSourceMomentsRound(self):
        for (i, imageid) in enumerate(file_indices):
            source = self.runMeasurement("ext_shapeHSM_HsmSourceMomentsRound",
                                         imageid, x_centroid[i], y_centroid[i],
                                         sky_var[i])
            x = source.get("ext_shapeHSM_HsmSourceMomentsRound_x")
            y = source.get("ext_shapeHSM_HsmSourceMomentsRound_y")
            xx = source.get("ext_shapeHSM_HsmSourceMomentsRound_xx")
            yy = source.get("ext_shapeHSM_HsmSourceMomentsRound_yy")
            xy = source.get("ext_shapeHSM_HsmSourceMomentsRound_xy")
            flux = source.get("ext_shapeHSM_HsmSourceMomentsRound_Flux")

            # Centroids from GalSim use the FITS lower-left corner of 1,1
            offset = self.xy0 + self.offset
            self.assertAlmostEqual(x - offset.getX(),
                                   round_moments_expected[i][4] - 1, 3)
            self.assertAlmostEqual(y - offset.getY(),
                                   round_moments_expected[i][5] - 1, 3)

            expected = afwEll.Quadrupole(
                afwEll.SeparableDistortionDeterminantRadius(
                    round_moments_expected[i][1], round_moments_expected[i][2],
                    round_moments_expected[i][0]))
            self.assertAlmostEqual(xx, expected.getIxx(), SHAPE_DECIMALS)
            self.assertAlmostEqual(xy, expected.getIxy(), SHAPE_DECIMALS)
            self.assertAlmostEqual(yy, expected.getIyy(), SHAPE_DECIMALS)

            self.assertAlmostEqual(flux, round_moments_expected[i][3],
                                   SHAPE_DECIMALS)
Example #3
0
    def testHsmPsfMoments(self):
        for width in (2.0, 3.0, 4.0):
            psf = afwDetection.GaussianPsf(35, 35, width)
            exposure = afwImage.ExposureF(45, 56)
            exposure.getMaskedImage().set(1.0, 0, 1.0)
            exposure.setPsf(psf)

            # perform the shape measurement
            msConfig = base.SingleFrameMeasurementConfig()
            msConfig.algorithms.names = ["ext_shapeHSM_HsmPsfMoments"]
            plugin, cat = makePluginAndCat(lsst.meas.extensions.shapeHSM.HsmPsfMomentsAlgorithm,
                                           "ext_shapeHSM_HsmPsfMoments", centroid="centroid",
                                           control=lsst.meas.extensions.shapeHSM.HsmPsfMomentsControl())
            source = cat.addNew()
            source.set("centroid_x", 23)
            source.set("centroid_y", 34)
            offset = afwGeom.Point2I(23, 34)
            tmpSpans = afwGeom.SpanSet.fromShape(int(width), offset=offset)
            source.setFootprint(afwDetection.Footprint(tmpSpans))
            plugin.measure(source, exposure)
            x = source.get("ext_shapeHSM_HsmPsfMoments_x")
            y = source.get("ext_shapeHSM_HsmPsfMoments_y")
            xx = source.get("ext_shapeHSM_HsmPsfMoments_xx")
            yy = source.get("ext_shapeHSM_HsmPsfMoments_yy")
            xy = source.get("ext_shapeHSM_HsmPsfMoments_xy")

            self.assertAlmostEqual(x, 0.0, 3)
            self.assertAlmostEqual(y, 0.0, 3)

            expected = afwEll.Quadrupole(afwEll.Axes(width, width, 0.0))

            self.assertAlmostEqual(xx, expected.getIxx(), SHAPE_DECIMALS)
            self.assertAlmostEqual(xy, expected.getIxy(), SHAPE_DECIMALS)
            self.assertAlmostEqual(yy, expected.getIyy(), SHAPE_DECIMALS)
Example #4
0
 def testCorrectWeightedMoments(self):
     '''
     Test that the measured moments can be corrected for the fact that the measurement
     contains information on the moments of the weight function used to make the
     measurement. The results should only contain the objects shape and not the moments
     used to make the measurement. This is a subset of the functionality in testNoNoiseGaussians.
     Because the other test tests a broader scope, it is useful to have this test happen under
     more restrictive conditions.
     '''
     for dEllipseCore in self.ellipseCores:
         for dCenter in self.centers:
             dEllipse = el.Ellipse(dEllipseCore, dCenter)
             dArray = self.evaluateGaussian(dEllipse)
             for wEllipseCore in self.ellipseCores:
                 for wCenter in self.centers:
                     wEllipse = el.Ellipse(wEllipseCore, wCenter)
                     wArray = self.evaluateGaussian(wEllipse)
                     product = dArray * wArray
                     i0 = np.sum(product)
                     ix = np.sum(product * self.xg) / i0
                     iy = np.sum(product * self.yg) / i0
                     ixx = np.sum(product * (self.xg - ix)**2) / i0
                     iyy = np.sum(product * (self.yg - iy)**2) / i0
                     ixy = np.sum(product * (self.xg - ix) *
                                  (self.yg - iy)) / i0
                     mEllipseCore = el.Quadrupole(ixx, iyy, ixy)
                     mCenter = lsst.afw.geom.Point2D(ix, iy)
                     SimpleShape.correctWeightedMoments(
                         wEllipseCore, mEllipseCore, mCenter)
                     self.assertFloatsAlmostEqual(
                         mEllipseCore.getParameterVector(),
                         dEllipseCore.getParameterVector(),
                         rtol=1E-8,
                         atol=1E-11)
Example #5
0
def main():
    x = numpy.linspace(-5, 5, 101)
    y = numpy.linspace(-5, 5, 101)
    ellipses.Quadrupole(ellipses.Axes(1.2, 0.8, 0.3))
    hermiteBasis = lsst.shapelet.BasisEvaluator(4, lsst.shapelet.HERMITE)
    laguerreBasis = lsst.shapelet.BasisEvaluator(4, lsst.shapelet.LAGUERRE)
    processBasis(hermiteBasis, x, y)
    processBasis(laguerreBasis, x, y)
    pyplot.show()
Example #6
0
 def setUp(self):
     self.ellipseCores = [
         el.Quadrupole(25.0, 25.0, 0.0),
         el.Quadrupole(27.0, 22.0, -5.0),
         el.Quadrupole(23.0, 28.0, 2.0),
     ]
     self.centers = [
         lsst.afw.geom.Point2D(0.0, 0.0),
         lsst.afw.geom.Point2D(2.0, 3.0),
         lsst.afw.geom.Point2D(-1.0, 2.5),
     ]
     for ellipseCore in self.ellipseCores:
         ellipseCore.scale(2)
     self.bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(-500, -500),
                                     lsst.afw.geom.Point2I(50, 50))
     self.xg, self.yg = np.meshgrid(
         np.arange(self.bbox.getBeginX(), self.bbox.getEndX(), dtype=float),
         np.arange(self.bbox.getBeginY(), self.bbox.getEndY(), dtype=float))
Example #7
0
    def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
        """
        @param[in] exposure: exposure containing the psf candidates (lsst.afw.image.Exposure)
        @param[in] psfCandidateList: a sequence of PSF candidates (each an lsst.meas.algorithms.PsfCandidate);
            typically obtained by detecting sources and then running them through a star selector
        @param[in,out] metadata  a home for interesting tidbits of information
        @param[in] flagKey: schema key used to mark sources actually used in PSF determination
    
        @return psf
        """

        import lsstDebug
        display = lsstDebug.Info(__name__).display 
        displayExposure = display and \
            lsstDebug.Info(__name__).displayExposure     # display the Exposure + spatialCells 
        displayPsfCandidates = display and \
            lsstDebug.Info(__name__).displayPsfCandidates # show the viable candidates 
        displayPsfComponents = display and \
            lsstDebug.Info(__name__).displayPsfComponents # show the basis functions
        showBadCandidates = display and \
            lsstDebug.Info(__name__).showBadCandidates # Include bad candidates (meaningless, methinks)
        displayResiduals = display and \
            lsstDebug.Info(__name__).displayResiduals         # show residuals
        displayPsfMosaic = display and \
            lsstDebug.Info(__name__).displayPsfMosaic   # show mosaic of reconstructed PSF(x,y)
        matchKernelAmplitudes = lsstDebug.Info(__name__).matchKernelAmplitudes # match Kernel amplitudes for spatial plots
        normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals # Normalise residuals by object amplitude 

        mi = exposure.getMaskedImage()
        
        nCand = len(psfCandidateList)
        if nCand == 0:
            raise RuntimeError("No PSF candidates supplied.")

        #
        # How big should our PSF models be?
        #
        if display:                     # only needed for debug plots
            # construct and populate a spatial cell set
            bbox = mi.getBBox(afwImage.PARENT)
            psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY)
        else:
            psfCellSet = None
        
        sizes = np.empty(nCand)
        for i, psfCandidate in enumerate(psfCandidateList):
            try:
                if psfCellSet:
                    psfCellSet.insertCandidate(psfCandidate)
            except Exception, e:
                self.debugLog.debug(2, "Skipping PSF candidate %d of %d: %s" % (i, len(psfCandidateList), e))
                continue

            source = psfCandidate.getSource()
            quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
            rmsSize = quad.getTraceRadius()
            sizes[i] = rmsSize
Example #8
0
def compareMoments(basis, x, y, z):
    e = basis.evaluate()
    monopole = z.sum()
    dipole = geom.Point2D((x * z).sum() / monopole, (y * z).sum() / monopole)
    dx = x - dipole.getX()
    dy = y - dipole.getY()
    quadrupole = ellipses.Quadrupole((dx**2 * z).sum() / monopole,
                                     (dy**1 * z).sum() / monopole,
                                     (dx * dy * z).sum() / monopole)
    print(ellipses.Ellipse(quadrupole, monopole))
    print(e.computeMoments())
Example #9
0
    def testNullDistortionDefaultCcd(self):
        """Test that we can use a NullDistortion even if the Detector is default-constructed"""
        ccd = cameraGeom.Ccd(cameraGeom.Id("dummy"))
        distorter = cameraGeom.NullDistortion()

        pin = afwGeom.PointD(0, 0)
        pout = distorter.undistort(pin, ccd)

        self.assertEqual(pin, pout)

        ein = geomEllip.Quadrupole(1, 0, 1)
        eout = distorter.distort(pin, ein, ccd)

        self.assertEqual(ein, eout)
Example #10
0
    def testHsmPsfMoments(self, width, useSourceCentroidOffset, varyBBox,
                          wrongBBox, center):
        psf = PyGaussianPsf(35,
                            35,
                            width,
                            varyBBox=varyBBox,
                            wrongBBox=wrongBBox)
        exposure = afwImage.ExposureF(45, 56)
        exposure.getMaskedImage().set(1.0, 0, 1.0)
        exposure.setPsf(psf)

        # perform the shape measurement
        msConfig = base.SingleFrameMeasurementConfig()
        msConfig.algorithms.names = ["ext_shapeHSM_HsmPsfMoments"]
        control = lsst.meas.extensions.shapeHSM.HsmPsfMomentsControl()
        self.assertFalse(control.useSourceCentroidOffset)
        control.useSourceCentroidOffset = useSourceCentroidOffset
        plugin, cat = makePluginAndCat(
            lsst.meas.extensions.shapeHSM.HsmPsfMomentsAlgorithm,
            "ext_shapeHSM_HsmPsfMoments",
            centroid="centroid",
            control=control)
        source = cat.addNew()
        source.set("centroid_x", center[0])
        source.set("centroid_y", center[1])
        offset = geom.Point2I(*center)
        tmpSpans = afwGeom.SpanSet.fromShape(int(width), offset=offset)
        source.setFootprint(afwDetection.Footprint(tmpSpans))
        plugin.measure(source, exposure)
        x = source.get("ext_shapeHSM_HsmPsfMoments_x")
        y = source.get("ext_shapeHSM_HsmPsfMoments_y")
        xx = source.get("ext_shapeHSM_HsmPsfMoments_xx")
        yy = source.get("ext_shapeHSM_HsmPsfMoments_yy")
        xy = source.get("ext_shapeHSM_HsmPsfMoments_xy")
        self.assertFalse(source.get("ext_shapeHSM_HsmPsfMoments_flag"))
        self.assertFalse(
            source.get("ext_shapeHSM_HsmPsfMoments_flag_no_pixels"))
        self.assertFalse(
            source.get("ext_shapeHSM_HsmPsfMoments_flag_not_contained"))
        self.assertFalse(
            source.get("ext_shapeHSM_HsmPsfMoments_flag_parent_source"))

        self.assertAlmostEqual(x, 0.0, 3)
        self.assertAlmostEqual(y, 0.0, 3)

        expected = afwEll.Quadrupole(afwEll.Axes(width, width, 0.0))
        self.assertAlmostEqual(xx, expected.getIxx(), SHAPE_DECIMALS)
        self.assertAlmostEqual(xy, expected.getIxy(), SHAPE_DECIMALS)
        self.assertAlmostEqual(yy, expected.getIyy(), SHAPE_DECIMALS)
Example #11
0
    def testDerivative3(self):
        amplitude = 3.2
        radius = 2.7
        psfEllipse = ellipses.Quadrupole(2.3, 1.8, 0.6)
        psfAmplitude = 5.3
        builder = ms.GaussianModelBuilder(self.x, self.y, amplitude, radius,
                                          psfEllipse, psfAmplitude)
        a = numpy.zeros((3, builder.getSize()), dtype=float).transpose()
        builder.update(self.ellipse)
        builder.computeDerivative(a)

        def makeEllipse(p):
            return ellipses.Axes(*p)

        n = self.buildNumericalDerivative(builder,
                                          self.ellipse.getParameterVector(),
                                          makeEllipse)
        # no hard requirement for tolerances here, but I've dialed them to the max to avoid regressions
        self.assertClose(a, n, rtol=1E-15, atol=1E-9)
    def determineKernelSize(self, psfCandidateList):
        """Sets self.kernelSize (logic in this routine is cut-and-paste from pcaPsfDeterminer)"""

        sizes = np.zeros(len(psfCandidateList))

        for i, psfCandidate in enumerate(psfCandidateList):
            source = psfCandidate.getSource()
            quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(),
                                     source.getIxy())
            axes = afwEll.Axes(quad)
            sizes[i] = axes.getA()

        if self.config.kernelSize >= 15:
            print >> sys.stderr, "WARNING: NOT scaling kernelSize by stellar quadrupole moment, but using absolute value"
            self.kernelSize = int(self.config.kernelSize)
        else:
            self.kernelSize = 2 * int(self.config.kernelSize *
                                      np.sqrt(np.median(sizes)) + 0.5) + 1
            self.kernelSize = max(self.kernelSize, self.config.kernelSizeMin)
            self.kernelSize = min(self.kernelSize, self.config.kernelSizeMax)

        print >> sys.stderr, 'setting kernelSize =', self.kernelSize
Example #13
0
 def testModel3(self):
     amplitude = 3.2
     radius = 2.7
     psfEllipse = ellipses.Quadrupole(2.3, 1.8, 0.6)
     psfAmplitude = 5.3
     mgc = ms.GaussianComponent(amplitude, radius)
     shapelet = mgc.makeShapelet(ellipses.Ellipse(self.ellipse))
     psf = ms.GaussianComponent(psfAmplitude, 1.0).makeShapelet(
         ellipses.Ellipse(psfEllipse))
     shapelet = shapelet.convolve(psf)
     builder = ms.GaussianModelBuilder(self.x, self.y, amplitude, radius,
                                       psfEllipse, psfAmplitude)
     builder.update(self.ellipse)
     self.ellipse.scale(radius)
     ellipse = self.ellipse.convolve(psfEllipse)
     self.assertClose(builder.getModel(),
                      amplitude * psfAmplitude * self.buildModel(ellipse))
     z0 = builder.getModel()
     z1 = psfAmplitude * amplitude * self.buildModel(ellipse)
     z2 = self.evalShapelets(shapelet)
     self.assertClose(z0, z1)
     self.assertClose(z0, z2)
Example #14
0
    def testzAxisCases(self):

        r = 1000.0
        iqq = geomEllip.Quadrupole(1.0, 1.0, 0.0)

        px = afwGeom.Point2D(r, 0.0)
        py = afwGeom.Point2D(0.0, r)
        pxy = afwGeom.Point2D(r / numpy.sqrt(2.0), r / numpy.sqrt(2.0))

        nDist = cameraGeom.NullDistortion()
        rDist = cameraGeom.RadialPolyDistortion(self.coeffs)

        rcoeffs = self.coeffs[:]
        rcoeffs.reverse()

        r2Known = numpy.polyval(rcoeffs, r)
        epsilon = 1.0e-6
        drKnown = -(r2Known - numpy.polyval(rcoeffs, r + epsilon)) / epsilon

        points = [px, py, pxy]
        scalings = [drKnown**2, drKnown**2, 0.5 * (drKnown**2 - 1.0)]
        for i in range(3):
            p = points[i]
            scale = scalings[i]

            # check the point
            p2 = rDist.distort(p, self.det)
            x, y = p2.getX(), p2.getY()
            r2Calc = numpy.hypot(x, y)
            if self.prynt:
                print "r2known,r2Calc: ", r2Known, r2Calc
            self.assertAlmostEqual(r2Known, r2Calc)

            # check the moment
            iqq2 = rDist.distort(p, iqq, self.det)
            iqqList = [iqq2.getIxx(), iqq2.getIyy(), iqq2.getIxy()]
            if self.prynt:
                print "scale: ", scale, iqqList[i]
            self.assertAlmostEqual(scale, iqqList[i])
    def setUp(self):
        self.x0, self.y0 = 0, 0
        self.nx, self.ny = 512, 512  #2048, 4096
        self.sky = 100.0
        self.nObj = 100

        # make a distorter
        # This is a lot of distortion ... from circle r=1, to ellipse with a=1.3 (ie. 30%)
        # For suprimecam, we expect only about 5%
        self.distCoeffs = [0.0, 1.0, 2.0e-04, 3.0e-8]
        lanczosOrder = 3
        coefficientsDistort = True
        self.distorter = cameraGeom.RadialPolyDistortion(
            self.distCoeffs, coefficientsDistort, lanczosOrder)

        # make a detector
        self.detector = cameraUtils.makeDefaultCcd(
            afwGeom.Box2I(afwGeom.Point2I(0, 0),
                          afwGeom.Extent2I(self.nx, self.ny)))
        self.detector.setDistortion(self.distorter)
        self.detector.setCenter(cameraGeom.FpPoint(
            255.5, 255.5))  # move boresight from center to 0,0

        if False:
            for x, y in [(0, 0), (0, 511), (511, 0), (511, 511)]:
                p = afwGeom.Point2D(x, y)
                iqq = self.distorter.distort(p, geomEllip.Quadrupole(),
                                             self.detector)
                print x, y, geomEllip.Axes(iqq)
                print self.detector.getPositionFromPixel(p).getMm()

        print "Max distortion on this detector: ", self.distorter.computeMaxShear(
            self.detector)

        # detection policies
        self.detConfig = measAlg.SourceDetectionConfig()

        # measurement policies
        self.measSrcConfig = measAlg.SourceMeasurementConfig()

        # psf star selector
        starSelectorFactory = measAlg.starSelectorRegistry["secondMoment"]
        starSelectorConfig = starSelectorFactory.ConfigClass()
        starSelectorConfig.fluxLim = 5000.0
        starSelectorConfig.histSize = 32
        starSelectorConfig.clumpNSigma = 1.0
        starSelectorConfig.badFlags = []
        self.starSelector = starSelectorFactory(starSelectorConfig)

        # psf determiner
        psfDeterminerFactory = measAlg.psfDeterminerRegistry["pca"]
        psfDeterminerConfig = psfDeterminerFactory.ConfigClass()
        width, height = self.nx, self.ny
        nEigenComponents = 3
        psfDeterminerConfig.sizeCellX = width // 3
        psfDeterminerConfig.sizeCellY = height // 3
        psfDeterminerConfig.nEigenComponents = nEigenComponents
        psfDeterminerConfig.spatialOrder = 1
        psfDeterminerConfig.kernelSizeMin = 31
        psfDeterminerConfig.nStarPerCell = 0
        psfDeterminerConfig.nStarPerCellSpatialFit = 0  # unlimited
        self.psfDeterminer = psfDeterminerFactory(psfDeterminerConfig)
Example #16
0
def main(dataDir,
         visit,
         title="",
         outputTxtFileName=None,
         showFwhm=False,
         minFwhm=None,
         maxFwhm=None,
         correctDistortion=False,
         showEllipticity=False,
         ellipticityDirection=False,
         showNdataFwhm=False,
         showNdataEll=False,
         minNdata=None,
         maxNdata=None,
         gridPoints=30,
         verbose=False):

    butler = dafPersist.ButlerFactory(mapper=hscSim.HscSimMapper(
        root=dataDir)).create()
    camera = butler.get("camera")

    if not (showFwhm or showEllipticity or showNdataFwhm or showNdataEll
            or outputTxtFileName):
        showFwhm = True
    #
    # Get a dict of cameraGeom::Ccd indexed by serial number
    #
    ccds = {}
    for raft in camera:
        for ccd in raft:
            ccd.setTrimmed(True)
            ccds[ccd.getId().getSerial()] = ccd
    #
    # Read all the tableSeeingMap files, converting their (x, y) to focal plane coordinates
    #
    xArr = []
    yArr = []
    ellArr = []
    fwhmArr = []
    paArr = []
    aArr = []
    bArr = []
    e1Arr = []
    e2Arr = []
    elle1e2Arr = []
    for tab in butler.subset("tableSeeingMap", visit=visit):
        # we could use tab.datasetExists() but it prints a rude message
        fileName = butler.get("tableSeeingMap_filename", **tab.dataId)[0]
        if not os.path.exists(fileName):
            continue

        with open(fileName) as fd:
            ccd = None
            for line in fd.readlines():
                if re.search(r"^\s*#", line):
                    continue
                fields = [float(_) for _ in line.split()]

                if ccd is None:
                    ccd = ccds[int(fields[0])]

                x, y, fwhm, ell, pa, a, b = fields[1:8]
                x, y = ccd.getPositionFromPixel(afwGeom.PointD(x, y)).getMm()
                xArr.append(x)
                yArr.append(y)
                ellArr.append(ell)
                fwhmArr.append(fwhm)
                paArr.append(pa)
                aArr.append(a)
                bArr.append(b)
                if len(fields) == 11:
                    e1 = fields[8]
                    e2 = fields[9]
                    elle1e2 = fields[10]
                else:
                    e1 = -9999.
                    e2 = -9999.
                    elle1e2 = -9999.
                e1Arr.append(e1)
                e2Arr.append(e2)
                elle1e2Arr.append(elle1e2)

    xArr = np.array(xArr)
    yArr = np.array(yArr)
    ellArr = np.array(ellArr)
    fwhmArr = np.array(fwhmArr) * 0.168  # arcseconds
    paArr = np.radians(np.array(paArr))
    aArr = np.array(aArr)
    bArr = np.array(bArr)

    e1Arr = np.array(e1Arr)
    e2Arr = np.array(e2Arr)
    elle1e2Arr = np.array(elle1e2Arr)

    if correctDistortion:
        import lsst.afw.geom.ellipses as afwEllipses

        dist = camera.getDistortion()
        for i in range(len(aArr)):
            axes = afwEllipses.Axes(aArr[i], bArr[i], paArr[i])
            if False:  # testing only!
                axes = afwEllipses.Axes(1.0, 1.0, np.arctan2(yArr[i], xArr[i]))
            quad = afwEllipses.Quadrupole(axes)
            quad = quad.transform(
                dist.computeQuadrupoleTransform(
                    afwGeom.PointD(xArr[i], yArr[i]), False))
            axes = afwEllipses.Axes(quad)
            aArr[i], bArr[i], paArr[i] = axes.getA(), axes.getB(
            ), axes.getTheta()

        ellArr = 1 - bArr / aArr

    if len(xArr) == 0:
        gridPoints = 0
        xs, ys = [], []
    else:
        N = gridPoints * 1j
        extent = [min(xArr), max(xArr), min(yArr), max(yArr)]
        xs, ys = np.mgrid[extent[0]:extent[1]:N, extent[2]:extent[3]:N]

    title = [
        title,
    ]

    title.append("\n#")

    if outputTxtFileName:
        f = open(outputTxtFileName, 'w')
        f.write("# %s visit %s\n" % (" ".join(title), visit))
        for x, y, ell, fwhm, pa, a, b, e1, e2, elle1e2 in zip(
                xArr, yArr, ellArr, fwhmArr, paArr, aArr, bArr, e1Arr, e2Arr,
                elle1e2Arr):
            f.write('%f %f %f %f %f %f %f %f %f %f\n' %
                    (x, y, ell, fwhm, pa, a, b, e1, e2, elle1e2))

    if showFwhm:
        title.append("FWHM (arcsec)")
        if len(xs) > 0:
            fwhmResampled = griddata(xArr, yArr, fwhmArr, xs, ys)
            plt.imshow(fwhmResampled.T,
                       extent=extent,
                       vmin=minFwhm,
                       vmax=maxFwhm,
                       origin='lower')
            plt.colorbar()

        if outputTxtFileName:

            ndataGrids = getNumDataGrids(xArr, yArr, fwhmArr, xs, ys)

            f = open(outputTxtFileName + '-fwhm-grid.txt', 'w')
            f.write("# %s visit %s\n" % (" ".join(title), visit))
            for xline, yline, fwhmline, ndataline in zip(
                    xs.tolist(), ys.tolist(), fwhmResampled.tolist(),
                    ndataGrids):
                for xx, yy, fwhm, ndata in zip(xline, yline, fwhmline,
                                               ndataline):
                    if fwhm is None:
                        fwhm = -9999
                    f.write('%f %f %f %d\n' % (xx, yy, fwhm, ndata))

    elif showEllipticity:
        title.append("Ellipticity")
        scale = 4

        if ellipticityDirection:  # we don't care about the magnitude
            ellArr = 0.1

        u = -ellArr * np.cos(paArr)
        v = -ellArr * np.sin(paArr)
        if gridPoints > 0:
            u = griddata(xArr, yArr, u, xs, ys)
            v = griddata(xArr, yArr, v, xs, ys)
            x, y = xs, ys
        else:
            x, y = xArr, yArr

        Q = plt.quiver(
            x,
            y,
            u,
            v,
            scale=scale,
            pivot="middle",
            headwidth=0,
            headlength=0,
            headaxislength=0,
        )
        keyLen = 0.10
        if not ellipticityDirection:  # we care about the magnitude
            plt.quiverkey(Q, 0.20, 0.95, keyLen, "e=%g" % keyLen, labelpos='W')

        if outputTxtFileName:
            ndataGrids = getNumDataGrids(xArr, yArr, ellArr, xs, ys)

            f = open(outputTxtFileName + '-ell-grid.txt', 'w')
            f.write("# %s visit %s\n" % (" ".join(title), visit))
            #f.write('# %f %f %f %f %f %f %f\n' % (x, y, ell, fwhm, pa, a, b))
            for xline, yline, uline, vline, ndataline in zip(
                    x.tolist(), y.tolist(), u.tolist(), v.tolist(),
                    ndataGrids):
                for xx, yy, uu, vv, ndata in zip(xline, yline, uline, vline,
                                                 ndataline):
                    if uu is None:
                        uu = -9999
                    if vv is None:
                        vv = -9999
                    f.write('%f %f %f %f %d\n' % (xx, yy, uu, vv, ndata))

    elif showNdataFwhm:
        title.append("N per fwhm grid")
        if len(xs) > 0:
            ndataGrids = getNumDataGrids(xArr, yArr, fwhmArr, xs, ys)
            plt.imshow(ndataGrids,
                       interpolation='nearest',
                       extent=extent,
                       vmin=minNdata,
                       vmax=maxNdata,
                       origin='lower')
            plt.colorbar()
        else:
            pass

    elif showNdataEll:
        title.append("N per ell grid")
        if len(xs) > 0:
            ndataGrids = getNumDataGrids(xArr, yArr, ellArr, xs, ys)
            plt.imshow(ndataGrids,
                       interpolation='nearest',
                       extent=extent,
                       vmin=minNdata,
                       vmax=maxNdata,
                       origin='lower')
            plt.colorbar()
        else:
            pass

    #plt.plot(xArr, yArr, "r.")
    #plt.plot(xs, ys, "b.")
    plt.axes().set_aspect('equal')
    plt.axis([-20000, 20000, -20000, 20000])

    def frameInfoFrom(filepath):
        import pyfits
        with pyfits.open(filepath) as hdul:
            h = hdul[0].header
            'object=ABELL2163 filter=HSC-I exptime=360.0 alt=62.11143274 azm=202.32265181 hst=(23:40:08.363-23:40:48.546)'
            return 'object=%s filter=%s exptime=%.1f azm=%.2f hst=%s' % (
                h['OBJECT'], h['FILTER01'], h['EXPTIME'], h['AZIMUTH'],
                h['HST'])

    title.insert(
        0,
        frameInfoFrom(
            butler.get('raw_filename', {
                'visit': visit,
                'ccd': 0
            })[0]))
    title.append(r'$\langle$FWHM$\rangle %4.2f$"' % np.median(fwhmArr))
    plt.title("%s visit=%s" % (" ".join(title), visit), fontsize=9)

    return plt
    def selectStars(self, exposure, catalog, matches=None):
        """Return a list of PSF candidates that represent likely stars
        
        A list of PSF candidates may be used by a PSF fitter to construct a PSF.
        
        @param[in] exposure: the exposure containing the sources
        @param[in] catalog: a SourceCatalog containing sources that may be stars
        @param[in] matches: astrometric matches; ignored by this star selector
        
        @return psfCandidateList: a list of PSF candidates.
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayExposure = lsstDebug.Info(
            __name__).displayExposure  # display the Exposure + spatialCells
        plotMagSize = lsstDebug.Info(
            __name__).plotMagSize  # display the magnitude-size relation
        dumpData = lsstDebug.Info(
            __name__).dumpData  # dump data to pickle file?

        # create a log for my application
        logger = pexLogging.Log(pexLogging.getDefaultLog(),
                                "meas.algorithms.objectSizeStarSelector")

        detector = exposure.getDetector()
        distorter = None
        xy0 = afwGeom.Point2D(0, 0)
        if not detector is None:
            cPix = detector.getCenterPixel()
            detSize = detector.getSize()
            xy0.setX(cPix.getX() - int(0.5 * detSize.getMm()[0]))
            xy0.setY(cPix.getY() - int(0.5 * detSize.getMm()[1]))
            distorter = detector.getDistortion()
        #
        # Look at the distribution of stars in the magnitude-size plane
        #
        flux = catalog.get(self._sourceFluxField)

        xx = numpy.empty(len(catalog))
        xy = numpy.empty_like(xx)
        yy = numpy.empty_like(xx)
        for i, source in enumerate(catalog):
            Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
            if distorter:
                xpix, ypix = source.getX() + xy0.getX(), source.getY(
                ) + xy0.getY()
                p = afwGeom.Point2D(xpix, ypix)
                m = distorter.undistort(p, geomEllip.Quadrupole(Ixx, Iyy, Ixy),
                                        detector)
                Ixx, Ixy, Iyy = m.getIxx(), m.getIxy(), m.getIyy()

            xx[i], xy[i], yy[i] = Ixx, Ixy, Iyy

        width = numpy.sqrt(xx + yy)

        bad = reduce(lambda x, y: numpy.logical_or(x, catalog.get(y)),
                     self._badFlags, False)
        bad = numpy.logical_or(bad, flux < self._fluxMin)
        bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(width)))
        bad = numpy.logical_or(bad, numpy.logical_not(numpy.isfinite(flux)))
        bad = numpy.logical_or(bad, width < self._widthMin)
        bad = numpy.logical_or(bad, width > self._widthMax)
        if self._fluxMax > 0:
            bad = numpy.logical_or(bad, flux > self._fluxMax)
        good = numpy.logical_not(bad)

        if not numpy.any(good):
            raise RuntimeError(
                "No objects passed our cuts for consideration as psf stars")

        mag = -2.5 * numpy.log10(flux[good])
        width = width[good]
        #
        # Look for the maximum in the size histogram, then search upwards for the minimum that separates
        # the initial peak (of, we presume, stars) from the galaxies
        #
        if dumpData:
            import os, cPickle as pickle
            _ii = 0
            while True:
                pickleFile = os.path.expanduser(
                    os.path.join("~", "widths-%d.pkl" % _ii))
                if not os.path.exists(pickleFile):
                    break
                _ii += 1

            with open(pickleFile, "wb") as fd:
                pickle.dump(mag, fd, -1)
                pickle.dump(width, fd, -1)

        centers, clusterId = _kcenters(width, nCluster=4, useMedian=True)

        if display and plotMagSize and pyplot:
            fig = plot(mag,
                       width,
                       centers,
                       clusterId,
                       marker="+",
                       markersize=3,
                       markeredgewidth=None,
                       ltype=':',
                       clear=True)
        else:
            fig = None

        clusterId = _improveCluster(width, centers, clusterId)

        if display and plotMagSize and pyplot:
            plot(mag,
                 width,
                 centers,
                 clusterId,
                 marker="x",
                 markersize=3,
                 markeredgewidth=None)

        stellar = (clusterId == 0)
        #
        # We know enough to plot, if so requested
        #
        frame = 0

        if fig:
            if display and displayExposure:
                ds9.mtv(exposure.getMaskedImage(),
                        frame=frame,
                        title="PSF candidates")

                global eventHandler
                eventHandler = EventHandler(fig.get_axes()[0],
                                            mag,
                                            width,
                                            catalog.getX()[good],
                                            catalog.getY()[good],
                                            frames=[frame])

            fig.show()

            #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

            while True:
                try:
                    reply = raw_input(
                        "continue? [c h(elp) q(uit) p(db)] ").strip()
                except EOFError:
                    reply = "y"

                if reply:
                    if reply[0] == "h":
                        print """\
    We cluster the points; red are the stellar candidates and the other colours are other clusters.
    Points labelled + are rejects from the cluster (only for cluster 0).

    At this prompt, you can continue with almost any key; 'p' enters pdb, and 'h' prints this text

    If displayExposure is true, you can put the cursor on a point and hit 'p' to see it in ds9.
    """
                    elif reply[0] == "p":
                        import pdb
                        pdb.set_trace()
                    elif reply[0] == 'q':
                        sys.exit(1)
                    else:
                        break

        if display and displayExposure:
            mi = exposure.getMaskedImage()

            with ds9.Buffering():
                for i, source in enumerate(catalog):
                    if good[i]:
                        ctype = ds9.GREEN  # star candidate
                    else:
                        ctype = ds9.RED  # not star

                    ds9.dot("+",
                            source.getX() - mi.getX0(),
                            source.getY() - mi.getY0(),
                            frame=frame,
                            ctype=ctype)
        #
        # Time to use that stellar classification to generate psfCandidateList
        #
        with ds9.Buffering():
            psfCandidateList = []
            for isStellar, source in zip(
                    stellar, [s for g, s in zip(good, catalog) if g]):
                if not isStellar:
                    continue

                try:
                    psfCandidate = algorithmsLib.makePsfCandidate(
                        source, exposure)
                    # The setXXX methods are class static, but it's convenient to call them on
                    # an instance as we don't know Exposure's pixel type
                    # (and hence psfCandidate's exact type)
                    if psfCandidate.getWidth() == 0:
                        psfCandidate.setBorderWidth(self._borderWidth)
                        psfCandidate.setWidth(self._kernelSize +
                                              2 * self._borderWidth)
                        psfCandidate.setHeight(self._kernelSize +
                                               2 * self._borderWidth)

                    im = psfCandidate.getMaskedImage().getImage()
                    vmax = afwMath.makeStatistics(im, afwMath.MAX).getValue()
                    if not numpy.isfinite(vmax):
                        continue
                    psfCandidateList.append(psfCandidate)

                    if display and displayExposure:
                        ds9.dot("o",
                                source.getX() - mi.getX0(),
                                source.getY() - mi.getY0(),
                                size=4,
                                frame=frame,
                                ctype=ds9.CYAN)
                except Exception as err:
                    logger.log(
                        pexLogging.Log.INFO,
                        "Failed to make a psfCandidate from source %d: %s" %
                        (source.getId(), err))

        return psfCandidateList
Example #18
0
 def setUp(self):
     ixx, iyy, ixy = 1.0, 1.0, 0.0
     self.data = geomEllip.Quadrupole(ixx, iyy, ixy)
    def determinePsf(self, exposure, psfCandidateList, metadata=None, flagKey=None):
        """Determine a PSFEX PSF model for an exposure given a list of PSF
        candidates.

        Parameters
        ----------
        exposure: `lsst.afw.image.Exposure`
            Exposure containing the PSF candidates.
        psfCandidateList: iterable of `lsst.meas.algorithms.PsfCandidate`
            Sequence of PSF candidates typically obtained by detecting sources
            and then running them through a star selector.
        metadata: metadata, optional
            A home for interesting tidbits of information.
        flagKey: `lsst.afw.table.Key`, optional
            Schema key used to mark sources actually used in PSF determination.

        Returns
        -------
        psf: `lsst.meas.extensions.psfex.PsfexPsf`
            The determined PSF.
        """

        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayExposure = display and \
            lsstDebug.Info(__name__).displayExposure      # display the Exposure + spatialCells
        displayPsfComponents = display and \
            lsstDebug.Info(__name__).displayPsfComponents  # show the basis functions
        showBadCandidates = display and \
            lsstDebug.Info(__name__).showBadCandidates    # Include bad candidates (meaningless, methinks)
        displayResiduals = display and \
            lsstDebug.Info(__name__).displayResiduals     # show residuals
        displayPsfMosaic = display and \
            lsstDebug.Info(__name__).displayPsfMosaic     # show mosaic of reconstructed PSF(x,y)
        normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals
        afwDisplay.setDefaultMaskTransparency(75)
        # Normalise residuals by object amplitude

        mi = exposure.getMaskedImage()

        nCand = len(psfCandidateList)
        if nCand == 0:
            raise RuntimeError("No PSF candidates supplied.")
        #
        # How big should our PSF models be?
        #
        if display:                     # only needed for debug plots
            # construct and populate a spatial cell set
            bbox = mi.getBBox(afwImage.PARENT)
            psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY)
        else:
            psfCellSet = None

        sizes = np.empty(nCand)
        for i, psfCandidate in enumerate(psfCandidateList):
            try:
                if psfCellSet:
                    psfCellSet.insertCandidate(psfCandidate)
            except Exception as e:
                self.log.debug("Skipping PSF candidate %d of %d: %s", i, len(psfCandidateList), e)
                continue

            source = psfCandidate.getSource()
            quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(), source.getIxy())
            rmsSize = quad.getTraceRadius()
            sizes[i] = rmsSize

        if self.config.kernelSize >= 15:
            self.log.warn("NOT scaling kernelSize by stellar quadrupole moment, but using absolute value")
            actualKernelSize = int(self.config.kernelSize)
        else:
            actualKernelSize = 2 * int(self.config.kernelSize * np.sqrt(np.median(sizes)) + 0.5) + 1
            if actualKernelSize < self.config.kernelSizeMin:
                actualKernelSize = self.config.kernelSizeMin
            if actualKernelSize > self.config.kernelSizeMax:
                actualKernelSize = self.config.kernelSizeMax
            if display:
                rms = np.median(sizes)
                print("Median PSF RMS size=%.2f pixels (\"FWHM\"=%.2f)" % (rms, 2*np.sqrt(2*np.log(2))*rms))

        # If we manually set the resolution then we need the size in pixel
        # units
        pixKernelSize = actualKernelSize
        if self.config.samplingSize > 0:
            pixKernelSize = int(actualKernelSize*self.config.samplingSize)
            if pixKernelSize % 2 == 0:
                pixKernelSize += 1
        self.log.trace("Psfex Kernel size=%.2f, Image Kernel Size=%.2f", actualKernelSize, pixKernelSize)
        psfCandidateList[0].setHeight(pixKernelSize)
        psfCandidateList[0].setWidth(pixKernelSize)

        # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- BEGIN PSFEX
        #
        # Insert the good candidates into the set
        #
        defaultsFile = os.path.join(os.environ["MEAS_EXTENSIONS_PSFEX_DIR"], "config", "default-lsst.psfex")
        args_md = dafBase.PropertySet()
        args_md.set("BASIS_TYPE", str(self.config.psfexBasis))
        args_md.set("PSFVAR_DEGREES", str(self.config.spatialOrder))
        args_md.set("PSF_SIZE", str(actualKernelSize))
        args_md.set("PSF_SAMPLING", str(self.config.samplingSize))
        prefs = psfex.Prefs(defaultsFile, args_md)
        prefs.setCommandLine([])
        prefs.addCatalog("psfexPsfDeterminer")

        prefs.use()
        principalComponentExclusionFlag = bool(bool(psfex.Context.REMOVEHIDDEN)
                                               if False else psfex.Context.KEEPHIDDEN)
        context = psfex.Context(prefs.getContextName(), prefs.getContextGroup(),
                                prefs.getGroupDeg(), principalComponentExclusionFlag)
        set = psfex.Set(context)
        set.setVigSize(pixKernelSize, pixKernelSize)
        set.setFwhm(2*np.sqrt(2*np.log(2))*np.median(sizes))
        set.setRecentroid(self.config.recentroid)

        catindex, ext = 0, 0
        backnoise2 = afwMath.makeStatistics(mi.getImage(), afwMath.VARIANCECLIP).getValue()
        ccd = exposure.getDetector()
        if ccd:
            gain = np.mean(np.array([a.getGain() for a in ccd]))
        else:
            gain = 1.0
            self.log.warn("Setting gain to %g" % (gain,))

        pc = 0
        contextvalp = []
        for i, key in enumerate(context.getName()):
            if context.getPcflag(i):
                raise RuntimeError("Principal Components can not be accessed")
                contextvalp.append(pcval[pc])  # noqa: F821
                pc += 1
            elif key[0] == ':':
                try:
                    contextvalp.append(exposure.getMetadata().getScalar(key[1:]))
                except KeyError:
                    raise RuntimeError("*Error*: %s parameter not found in the header of %s" %
                                       (key[1:], prefs.getContextName()))
            else:
                try:
                    contextvalp.append(np.array([psfCandidateList[_].getSource().get(key)
                                                 for _ in range(nCand)]))
                except KeyError:
                    raise RuntimeError("*Error*: %s parameter not found" % (key,))
                set.setContextname(i, key)

        if display:
            frame = 0
            if displayExposure:
                disp = afwDisplay.Display(frame=frame)
                disp.mtv(exposure, title="psf determination")

        badBits = mi.getMask().getPlaneBitMask(self.config.badMaskBits)
        fluxName = prefs.getPhotfluxRkey()
        fluxFlagName = "base_" + fluxName + "_flag"

        xpos, ypos = [], []
        for i, psfCandidate in enumerate(psfCandidateList):
            source = psfCandidate.getSource()
            xc, yc = source.getX(), source.getY()
            try:
                int(xc), int(yc)
            except ValueError:
                continue

            try:
                pstamp = psfCandidate.getMaskedImage().clone()
            except Exception:
                continue

            if fluxFlagName in source.schema and source.get(fluxFlagName):
                continue

            flux = source.get(fluxName)
            if flux < 0 or np.isnan(flux):
                continue

            # From this point, we're configuring the "sample" (PSFEx's version
            # of a PSF candidate).
            # Having created the sample, we must proceed to configure it, and
            # then fini (finalize), or it will be malformed.
            try:
                sample = set.newSample()
                sample.setCatindex(catindex)
                sample.setExtindex(ext)
                sample.setObjindex(i)

                imArray = pstamp.getImage().getArray()
                imArray[np.where(np.bitwise_and(pstamp.getMask().getArray(), badBits))] = \
                    -2*psfex.BIG
                sample.setVig(imArray)

                sample.setNorm(flux)
                sample.setBacknoise2(backnoise2)
                sample.setGain(gain)
                sample.setX(xc)
                sample.setY(yc)
                sample.setFluxrad(sizes[i])

                for j in range(set.getNcontext()):
                    sample.setContext(j, float(contextvalp[j][i]))
            except Exception as e:
                self.log.debug("Exception when processing sample at (%f,%f): %s", xc, yc, e)
                continue
            else:
                set.finiSample(sample)

            xpos.append(xc)  # for QA
            ypos.append(yc)

        if displayExposure:
            with disp.Buffering():
                disp.dot("o", xc, yc, ctype=afwDisplay.CYAN, size=4)

        if set.getNsample() == 0:
            raise RuntimeError("No good PSF candidates to pass to PSFEx")

        # ---- Update min and max and then the scaling
        for i in range(set.getNcontext()):
            cmin = contextvalp[i].min()
            cmax = contextvalp[i].max()
            set.setContextScale(i, cmax - cmin)
            set.setContextOffset(i, (cmin + cmax)/2.0)

        # Don't waste memory!
        set.trimMemory()

        # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- END PSFEX
        #
        # Do a PSFEX decomposition of those PSF candidates
        #
        fields = []
        field = psfex.Field("Unknown")
        field.addExt(exposure.getWcs(), exposure.getWidth(), exposure.getHeight(), set.getNsample())
        field.finalize()

        fields.append(field)

        sets = []
        sets.append(set)

        psfex.makeit(fields, sets)
        psfs = field.getPsfs()

        # Flag which objects were actually used in psfex by
        good_indices = []
        for i in range(sets[0].getNsample()):
            index = sets[0].getSample(i).getObjindex()
            if index > -1:
                good_indices.append(index)

        if flagKey is not None:
            for i, psfCandidate in enumerate(psfCandidateList):
                source = psfCandidate.getSource()
                if i in good_indices:
                    source.set(flagKey, True)

        xpos = np.array(xpos)
        ypos = np.array(ypos)
        numGoodStars = len(good_indices)
        avgX, avgY = np.mean(xpos), np.mean(ypos)

        psf = psfex.PsfexPsf(psfs[0], geom.Point2D(avgX, avgY))

        if False and (displayResiduals or displayPsfMosaic):
            ext = 0
            frame = 1
            diagnostics = True
            catDir = "."
            title = "psfexPsfDeterminer"
            psfex.psfex.showPsf(psfs, set, ext,
                                [(exposure.getWcs(), exposure.getWidth(), exposure.getHeight())],
                                nspot=3, trim=5, frame=frame, diagnostics=diagnostics, outDir=catDir,
                                title=title)
        #
        # Display code for debugging
        #
        if display:
            assert psfCellSet is not None

            if displayExposure:
                maUtils.showPsfSpatialCells(exposure, psfCellSet, showChi2=True,
                                            symb="o", ctype=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED,
                                            size=8, display=disp)
            if displayResiduals:
                disp4 = afwDisplay.Display(frame=4)
                maUtils.showPsfCandidates(exposure, psfCellSet, psf=psf, display=disp4,
                                          normalize=normalizeResiduals,
                                          showBadCandidates=showBadCandidates)
            if displayPsfComponents:
                disp6 = afwDisplay.Display(frame=6)
                maUtils.showPsf(psf, display=disp6)
            if displayPsfMosaic:
                disp7 = afwDisplay.Display(frame=7)
                maUtils.showPsfMosaic(exposure, psf, display=disp7, showFwhm=True)
                disp.scale('linear', 0, 1)
        #
        # Generate some QA information
        #
        # Count PSF stars
        #
        if metadata is not None:
            metadata.set("spatialFitChi2", np.nan)
            metadata.set("numAvailStars", nCand)
            metadata.set("numGoodStars", numGoodStars)
            metadata.set("avgX", avgX)
            metadata.set("avgY", avgY)

        return psf, psfCellSet
    def testShape(self):
        """Tests computation of shape means.
        """
        sources = afwTable.SourceCatalog(self.sourceTable)
        clusters = apCluster.SourceClusterCatalog(self.clusterTable)
        centroidKey = self.sourceTable.getCentroidKey()
        shapeKey = self.sourceTable.getShapeKey()
        shapeErrKey = self.sourceTable.getShapeErrKey()
        badshapeKey = self.sourceTable.getSchema().find("flags.badshape").key
        for i in xrange(6):
            s = sources.addNew()
            s["exposure.id"] = 0
            s["exposure.filter.id"] = 0
            s.setRa(0.0 * afwGeom.radians)
            s.setDec(0.0 * afwGeom.radians)
            s.set(centroidKey, afwGeom.Point2D(1799.5, 1799.5))
            s.set(shapeKey, afwGeomEllipses.Quadrupole(i, 2.0*i, 0.5*i))
            s.set(shapeErrKey, numpy.identity(3, dtype=numpy.float32))
            if i < 1 or i > 3:
                s.set(badshapeKey, True)
        cluster = clusters.addNew()
        seVec = apCluster.computeBasicAttributes(
            cluster, sources, self.exposures, self.spConfig.exposurePrefix)
        fkv = afwTable.FlagKeyVector()
        fkv.push_back(badshapeKey)
        apCluster.computeShapeMean(cluster, seVec, "shape", fkv)
        scale2 = math.radians(1.0/3600.0)**2
        self.assertEqual(cluster.getNumSources(), 6)
        self.assertEqual(cluster.getNumSources("u"), 6)
        self.assertEqual(cluster.getShapeCount("u"), 3)
        shape = cluster.getShape("u")
        self.assertAlmostEqual(shape.getIxx(), scale2 * 2.0)
        self.assertAlmostEqual(shape.getIxx(), scale2 * 4.0)
        self.assertAlmostEqual(shape.getIxx(), scale2 * 1.0)
        shapeErr = cluster.getShapeErr("u")
        self.assertAlmostEqual(shapeErr[0,1], 0.0)
        self.assertAlmostEqual(shapeErr[0,2], 0.0)
        self.assertAlmostEqual(shapeErr[1,2], 0.0)
        self.assertEqual(shapeErr[0,1], shapeErr[1,0])
        self.assertEqual(shapeErr[0,2], shapeErr[2,0])
        self.assertEqual(shapeErr[1,2], shapeErr[2,1])
        self.assertAlmostEqual(shapeErr[0,0], scale2**2/3)
        self.assertAlmostEqual(shapeErr[1,1], scale2**2/3)
        self.assertAlmostEqual(shapeErr[2,2], scale2**2/3)

        # Test with a single source
        s = sources[2]
        sources = afwTable.SourceCatalog(self.sourceTable)
        sources.append(s)
        cluster = clusters.addNew()
        seVec = apCluster.computeBasicAttributes(
            cluster, sources, self.exposures, self.spConfig.exposurePrefix)
        apCluster.computeShapeMean(cluster, seVec, "shape", fkv)
        self.assertEqual(cluster.getNumSources(), 1)
        self.assertEqual(cluster.getNumSources("u"), 1)
        self.assertEqual(cluster.getShapeCount("u"), 1)
        shape = cluster.getShape("u")
        self.assertAlmostEqual(shape.getIxx(), scale2 * 2.0)
        self.assertAlmostEqual(shape.getIxx(), scale2 * 4.0)
        self.assertAlmostEqual(shape.getIxx(), scale2 * 1.0)
        shapeErr = cluster.getShapeErr("u")
        self.assertAlmostEqual(shapeErr[0,1], 0.0)
        self.assertAlmostEqual(shapeErr[0,2], 0.0)
        self.assertAlmostEqual(shapeErr[1,2], 0.0)
        self.assertEqual(shapeErr[0,1], shapeErr[1,0])
        self.assertEqual(shapeErr[0,2], shapeErr[2,0])
        self.assertEqual(shapeErr[1,2], shapeErr[2,1])
        self.assertAlmostEqual(shapeErr[0,0], scale2**2)
        self.assertAlmostEqual(shapeErr[1,1], scale2**2)
        self.assertAlmostEqual(shapeErr[2,2], scale2**2)
Example #21
0
    def testHsmPsfMomentsDebiasedEdge(self, width, useSourceCentroidOffset,
                                      center):
        # As we reduce the flux, our deviation from the expected value
        # increases, so decrease tolerance.
        var = 1.2
        for flux, decimals in [
            (1e6, 3),
            (1e4, 2),
            (1e3, 1),
        ]:
            psf = PyGaussianPsf(35, 35, width)
            exposure = afwImage.ExposureF(45, 56)
            exposure.getMaskedImage().set(1.0, 0, 2 * var + 1.1)
            exposure.setPsf(psf)

            # perform the shape measurement
            control = lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedControl(
            )
            control.useSourceCentroidOffset = useSourceCentroidOffset
            self.assertEqual(control.noiseSource, "variance")
            plugin, cat = makePluginAndCat(
                lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedAlgorithm,
                "ext_shapeHSM_HsmPsfMomentsDebiased",
                centroid="centroid",
                psfflux="base_PsfFlux",
                control=control)
            source = cat.addNew()
            source.set("centroid_x", center[0])
            source.set("centroid_y", center[1])
            offset = geom.Point2I(*center)
            source.set("base_PsfFlux_instFlux", flux)
            tmpSpans = afwGeom.SpanSet.fromShape(int(width), offset=offset)
            source.setFootprint(afwDetection.Footprint(tmpSpans))

            # Edge fails when setting noise from var plane
            with self.assertRaises(base.MeasurementError):
                plugin.measure(source, exposure)

            # Succeeds when noise is from meta
            exposure.getMetadata().set("BGMEAN", var)
            control.noiseSource = "meta"
            plugin, cat = makePluginAndCat(
                lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedAlgorithm,
                "ext_shapeHSM_HsmPsfMomentsDebiased",
                centroid="centroid",
                psfflux="base_PsfFlux",
                control=control)
            source = cat.addNew()
            source.set("centroid_x", center[0])
            source.set("centroid_y", center[1])
            offset = geom.Point2I(*center)
            source.set("base_PsfFlux_instFlux", flux)
            tmpSpans = afwGeom.SpanSet.fromShape(int(width), offset=offset)
            source.setFootprint(afwDetection.Footprint(tmpSpans))
            plugin.measure(source, exposure)

            x = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_x")
            y = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_y")
            xx = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_xx")
            yy = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_yy")
            xy = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_xy")
            self.assertFalse(
                source.get("ext_shapeHSM_HsmPsfMomentsDebiased_flag"))
            self.assertFalse(
                source.get(
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_no_pixels"))
            self.assertFalse(
                source.get(
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_not_contained"))
            self.assertFalse(
                source.get(
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_parent_source"))
            # but _does_ set EDGE flag in this case
            self.assertTrue(
                source.get("ext_shapeHSM_HsmPsfMomentsDebiased_flag_edge"))

            expected = afwEll.Quadrupole(afwEll.Axes(width, width, 0.0))

            self.assertAlmostEqual(x, 0.0, decimals)
            self.assertAlmostEqual(y, 0.0, decimals)

            T = expected.getIxx() + expected.getIyy()
            self.assertAlmostEqual((xx - expected.getIxx()) / T, 0.0, decimals)
            self.assertAlmostEqual((xy - expected.getIxy()) / T, 0.0, decimals)
            self.assertAlmostEqual((yy - expected.getIyy()) / T, 0.0, decimals)

            # But fails hard if meta doesn't contain BGMEAN
            exposure.getMetadata().remove("BGMEAN")
            plugin, cat = makePluginAndCat(
                lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedAlgorithm,
                "ext_shapeHSM_HsmPsfMomentsDebiased",
                centroid="centroid",
                psfflux="base_PsfFlux",
                control=control)
            source = cat.addNew()
            source.set("centroid_x", center[0])
            source.set("centroid_y", center[1])
            offset = geom.Point2I(*center)
            source.set("base_PsfFlux_instFlux", flux)
            tmpSpans = afwGeom.SpanSet.fromShape(int(width), offset=offset)
            source.setFootprint(afwDetection.Footprint(tmpSpans))
            with self.assertRaises(base.FatalAlgorithmError):
                plugin.measure(source, exposure)
Example #22
0
    def check(self, psfFwhm=0.5, flux=1000.0, forced=False):
        """Check that we can measure convolved fluxes

        We create an image with a Gaussian PSF and a single point source.
        Measurements of the point source should match expectations for a
        Gaussian of the known sigma and known aperture radius.

        Parameters
        ----------
        psfFwhm : `float`
            PSF FWHM (arcsec)
        flux : `float`
            Source flux (ADU)
        forced : `bool`
            Forced measurement?
        """
        bbox = afwGeom.Box2I(afwGeom.Point2I(12345, 6789),
                             afwGeom.Extent2I(200, 300))

        # We'll only achieve the target accuracy if the pixel scale is rather smaller than Gaussians
        # involved. Otherwise it's important to consider the convolution with the pixel grid, and we're
        # not doing that here.
        scale = 0.1 * afwGeom.arcseconds

        TaskClass = measBase.ForcedMeasurementTask if forced else measBase.SingleFrameMeasurementTask

        exposure, center = makeExposure(bbox, scale, psfFwhm, flux)
        measConfig = TaskClass.ConfigClass()
        algName = "ext_convolved_ConvolvedFlux"
        measConfig.plugins.names.add(algName)
        if not forced:
            measConfig.plugins.names.add("ext_photometryKron_KronFlux")
        else:
            measConfig.copyColumns = {
                "id": "objectId",
                "parent": "parentObjectId"
            }
        values = [ii / scale.asArcseconds() for ii in (0.6, 0.8, 1.0, 1.2)]
        algConfig = measConfig.plugins[algName]
        algConfig.seeing = values
        algConfig.aperture.radii = values
        algConfig.aperture.maxSincRadius = max(
            values) + 1  # Get as exact as we can

        if forced:
            offset = lsst.afw.geom.Extent2D(-12.3, 45.6)
            kronRadiusName = "my_Kron_Radius"
            kronRadius = 12.345
            refWcs = exposure.getWcs().clone()
            refWcs.shiftReferencePixel(offset)
            measConfig.plugins[algName].kronRadiusName = kronRadiusName
            refSchema = afwTable.SourceTable.makeMinimalSchema()
            centroidKey = afwTable.Point2DKey.addFields(refSchema,
                                                        "my_centroid",
                                                        doc="centroid",
                                                        unit="pixel")
            shapeKey = afwTable.QuadrupoleKey.addFields(
                refSchema, "my_shape", "shape")
            refSchema.getAliasMap().set("slot_Centroid", "my_centroid")
            refSchema.getAliasMap().set("slot_Shape", "my_shape")
            refSchema.addField("my_centroid_flag",
                               type="Flag",
                               doc="centroid flag")
            refSchema.addField("my_shape_flag", type="Flag", doc="shape flag")
            refSchema.addField(kronRadiusName,
                               type=float,
                               doc="my custom kron radius",
                               units="pixel")
            refCat = afwTable.SourceCatalog(refSchema)
            refSource = refCat.addNew()
            refSource.set(centroidKey, center + offset)
            refSource.set(
                shapeKey,
                afwEll.Quadrupole(afwEll.Axes(kronRadius, kronRadius, 0)))
            refSource.set(kronRadiusName, kronRadius)
            refSource.setCoord(refWcs.pixelToSky(refSource.get(centroidKey)))
            taskInitArgs = (refSchema, )
            taskRunArgs = (refCat, refWcs)
        else:
            taskInitArgs = (afwTable.SourceTable.makeMinimalSchema(), )
            taskRunArgs = ()

        algMetadata = dafBase.PropertyList()
        task = TaskClass(*taskInitArgs,
                         config=measConfig,
                         algMetadata=algMetadata)

        schema = task.schema
        measCat = afwTable.SourceCatalog(schema)
        source = measCat.addNew()
        source.getTable().setMetadata(algMetadata)
        ss = afwDetection.FootprintSet(exposure.getMaskedImage(),
                                       afwDetection.Threshold(0.1))
        fp = ss.getFootprints()[0]
        source.setFootprint(fp)

        task.run(measCat, exposure, *taskRunArgs)

        disp = afwDisplay.Display(frame)
        disp.mtv(exposure)
        disp.dot("x",
                 *center,
                 origin=afwImage.PARENT,
                 title="psfFwhm=%f" % (psfFwhm, ))

        self.checkSchema(schema, algConfig.getAllApertureResultNames())
        self.checkSchema(schema, algConfig.getAllKronResultNames())
        self.checkSchema(schema, algConfig.getAllResultNames())

        if not forced:
            kronRadius = source.get("ext_photometryKron_KronFlux_radius")

        self.assertFalse(source.get(algName + "_flag"))  # algorithm succeeded
        originalSeeing = psfFwhm / scale.asArcseconds()
        for ii, targetSeeing in enumerate(algConfig.seeing):
            deconvolve = targetSeeing < originalSeeing
            self.assertEqual(source.get(algName + "_%d_deconv" % ii),
                             deconvolve)
            seeing = originalSeeing if deconvolve else targetSeeing

            def expected(radius, sigma=seeing / SIGMA_TO_FWHM):
                """Return expected flux for 2D Gaussian with nominated sigma"""
                return flux * (1.0 - math.exp(-0.5 * (radius / sigma)**2))

            # Kron succeeded and match expectation
            if not forced:
                kronName = algConfig.getKronResultName(targetSeeing)
                kronApRadius = algConfig.kronRadiusForFlux * kronRadius
                self.assertFloatsAlmostEqual(source.get(kronName + "_flux"),
                                             expected(kronApRadius),
                                             rtol=1.0e-3)
                self.assertGreater(source.get(kronName + "_fluxSigma"), 0)
                self.assertFalse(source.get(kronName + "_flag"))

            # Aperture measurements suceeded and match expectation
            for jj, radius in enumerate(
                    measConfig.algorithms[algName].aperture.radii):
                name = algConfig.getApertureResultName(targetSeeing, radius)
                self.assertFloatsAlmostEqual(source.get(name + "_flux"),
                                             expected(radius),
                                             rtol=1.0e-3)
                self.assertFalse(source.get(name + "_flag"))
                self.assertGreater(source.get(name + "_fluxSigma"), 0)
    def selectStars(self, exposure, catalog, matches=None):
        """Return a list of PSF candidates that represent likely stars
        
        A list of PSF candidates may be used by a PSF fitter to construct a PSF.
        
        @param[in] exposure: the exposure containing the sources
        @param[in] catalog: a SourceCatalog containing sources that may be stars
        @param[in] matches: astrometric matches; ignored by this star selector
        
        @return psfCandidateList: a list of PSF candidates.
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayExposure = lsstDebug.Info(
            __name__).displayExposure  # display the Exposure + spatialCells

        isGoodSource = CheckSource(catalog.getTable(), self.config.badFlags,
                                   self.config.fluxLim, self.config.fluxMax)

        detector = exposure.getDetector()
        distorter = None
        xy0 = afwGeom.Point2D(0, 0)
        if not detector is None:
            cPix = detector.getCenterPixel()
            detSize = detector.getSize()
            xy0.setX(cPix.getX() - int(0.5 * detSize.getMm()[0]))
            xy0.setY(cPix.getY() - int(0.5 * detSize.getMm()[1]))
            distorter = detector.getDistortion()

        mi = exposure.getMaskedImage()
        #
        # Create an Image of Ixx v. Iyy, i.e. a 2-D histogram
        #

        # Use stats on our Ixx/yy values to determine the xMax/yMax range for clump image
        iqqList = []
        for s in catalog:
            ixx, iyy = s.getIxx(), s.getIyy()
            # ignore NaN and unrealistically large values
            if (ixx == ixx and ixx < self.config.histMomentMax and iyy == iyy
                    and iyy < self.config.histMomentMax and isGoodSource(s)):
                iqqList.append(s.getIxx())
                iqqList.append(s.getIyy())
        stat = afwMath.makeStatistics(
            iqqList, afwMath.MEANCLIP | afwMath.STDEVCLIP | afwMath.MAX)
        iqqMean = stat.getValue(afwMath.MEANCLIP)
        iqqStd = stat.getValue(afwMath.STDEVCLIP)
        iqqMax = stat.getValue(afwMath.MAX)

        iqqLimit = max(iqqMean + self.config.histMomentClip * iqqStd,
                       self.config.histMomentMaxMultiplier * iqqMean)
        # if the max value is smaller than our range, use max as the limit, but don't go below N*mean
        if iqqLimit > iqqMax:
            iqqLimit = max(self.config.histMomentMinMultiplier * iqqMean,
                           iqqMax)

        psfHist = _PsfShapeHistogram(detector=detector,
                                     xSize=self.config.histSize,
                                     ySize=self.config.histSize,
                                     ixxMax=iqqLimit,
                                     iyyMax=iqqLimit,
                                     xy0=xy0)

        if display and displayExposure:
            frame = 0
            ds9.mtv(mi, frame=frame, title="PSF candidates")

        with ds9.Buffering():
            for source in catalog:
                if isGoodSource(source):
                    if psfHist.insert(
                            source
                    ):  # n.b. this call has the side effect of inserting
                        ctype = ds9.GREEN  # good
                    else:
                        ctype = ds9.MAGENTA  # rejected
                else:
                    ctype = ds9.RED  # bad

                if display and displayExposure:
                    ds9.dot("o",
                            source.getX() - mi.getX0(),
                            source.getY() - mi.getY0(),
                            frame=frame,
                            ctype=ctype)

        clumps = psfHist.getClumps(display=display)

        #
        # Go through and find all the PSF-like objects
        #
        # We'll split the image into a number of cells, each of which contributes only
        # one PSF candidate star
        #
        psfCandidateList = []

        # psf candidate shapes must lie within this many RMS of the average shape
        # N.b. if Ixx == Iyy, Ixy = 0 the criterion is
        # dx^2 + dy^2 < self.config.clumpNSigma*(Ixx + Iyy) == 2*self.config.clumpNSigma*Ixx
        for source in catalog:
            Ixx, Ixy, Iyy = source.getIxx(), source.getIxy(), source.getIyy()
            if distorter:
                xpix, ypix = source.getX() + xy0.getX(), source.getY(
                ) + xy0.getY()
                p = afwGeom.Point2D(xpix, ypix)
                m = distorter.undistort(p, geomEllip.Quadrupole(Ixx, Iyy, Ixy),
                                        detector)
                Ixx, Iyy, Ixy = m.getIxx(), m.getIyy(), m.getIxy()

            x, y = psfHist.momentsToPixel(Ixx, Iyy)
            for clump in clumps:
                dx, dy = (x - clump.x), (y - clump.y)

                if math.sqrt(clump.a * dx * dx + 2 * clump.b * dx * dy +
                             clump.c * dy * dy) < 2 * self.config.clumpNSigma:
                    # A test for > would be confused by NaN
                    if not isGoodSource(source):
                        continue
                    try:
                        psfCandidate = algorithmsLib.makePsfCandidate(
                            source, exposure)

                        # The setXXX methods are class static, but it's convenient to call them on
                        # an instance as we don't know Exposure's pixel type
                        # (and hence psfCandidate's exact type)
                        if psfCandidate.getWidth() == 0:
                            psfCandidate.setBorderWidth(
                                self.config.borderWidth)
                            psfCandidate.setWidth(self.config.kernelSize +
                                                  2 * self.config.borderWidth)
                            psfCandidate.setHeight(self.config.kernelSize +
                                                   2 * self.config.borderWidth)

                        im = psfCandidate.getMaskedImage().getImage()
                        if not numpy.isfinite(
                                afwMath.makeStatistics(
                                    im, afwMath.MAX).getValue()):
                            continue
                        psfCandidateList.append(psfCandidate)

                        if display and displayExposure:
                            ds9.dot("o",
                                    source.getX() - mi.getX0(),
                                    source.getY() - mi.getY0(),
                                    size=4,
                                    frame=frame,
                                    ctype=ds9.CYAN)
                    except Exception as err:
                        pass  # FIXME: should log this!
                    break

        return psfCandidateList
Example #24
0
    def testHsmPsfMomentsDebiased(self, width, useSourceCentroidOffset,
                                  varyBBox, wrongBBox, center):
        # As a note, it's really hard to actually unit test whether we've
        # succesfully "debiased" these measurements.  That would require a
        # many-object comparison of moments with and without noise.  So we just
        # test similar to the biased moments above.
        var = 1.2
        # As we reduce the flux, our deviation from the expected value
        # increases, so decrease tolerance.
        for flux, decimals in [
            (1e6, 3),
            (1e4, 1),
            (1e3, 0),
        ]:
            psf = PyGaussianPsf(35,
                                35,
                                width,
                                varyBBox=varyBBox,
                                wrongBBox=wrongBBox)
            exposure = afwImage.ExposureF(45, 56)
            exposure.getMaskedImage().set(1.0, 0, var)
            exposure.setPsf(psf)

            # perform the shape measurement
            control = lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedControl(
            )
            self.assertTrue(control.useSourceCentroidOffset)
            self.assertEqual(control.noiseSource, "variance")
            control.useSourceCentroidOffset = useSourceCentroidOffset
            plugin, cat = makePluginAndCat(
                lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedAlgorithm,
                "ext_shapeHSM_HsmPsfMomentsDebiased",
                centroid="centroid",
                psfflux="base_PsfFlux",
                control=control)
            source = cat.addNew()
            source.set("centroid_x", center[0])
            source.set("centroid_y", center[1])
            offset = geom.Point2I(*center)
            source.set("base_PsfFlux_instFlux", flux)
            tmpSpans = afwGeom.SpanSet.fromShape(int(width), offset=offset)
            source.setFootprint(afwDetection.Footprint(tmpSpans))

            plugin.measure(source, exposure)
            x = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_x")
            y = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_y")
            xx = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_xx")
            yy = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_yy")
            xy = source.get("ext_shapeHSM_HsmPsfMomentsDebiased_xy")
            for flag in [
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_no_pixels",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_not_contained",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_parent_source",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_edge"
            ]:
                self.assertFalse(source.get(flag))

            expected = afwEll.Quadrupole(afwEll.Axes(width, width, 0.0))

            self.assertAlmostEqual(x, 0.0, decimals)
            self.assertAlmostEqual(y, 0.0, decimals)

            T = expected.getIxx() + expected.getIyy()
            self.assertAlmostEqual((xx - expected.getIxx()) / T, 0.0, decimals)
            self.assertAlmostEqual((xy - expected.getIxy()) / T, 0.0, decimals)
            self.assertAlmostEqual((yy - expected.getIyy()) / T, 0.0, decimals)

            # Repeat using noiseSource='meta'.  Should get nearly the same
            # results if BGMEAN is set to `var` above.
            exposure2 = afwImage.ExposureF(45, 56)
            # set the variance plane to something else to ensure we're
            # ignoring it
            exposure2.getMaskedImage().set(1.0, 0, 2 * var + 1.1)
            exposure2.setPsf(psf)
            exposure2.getMetadata().set("BGMEAN", var)

            control2 = lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedControl(
            )
            control2.noiseSource = "meta"
            control2.useSourceCentroidOffset = useSourceCentroidOffset
            plugin2, cat2 = makePluginAndCat(
                lsst.meas.extensions.shapeHSM.HsmPsfMomentsDebiasedAlgorithm,
                "ext_shapeHSM_HsmPsfMomentsDebiased",
                centroid="centroid",
                psfflux="base_PsfFlux",
                control=control2)
            source2 = cat2.addNew()
            source2.set("centroid_x", center[0])
            source2.set("centroid_y", center[1])
            offset2 = geom.Point2I(*center)
            source2.set("base_PsfFlux_instFlux", flux)
            tmpSpans2 = afwGeom.SpanSet.fromShape(int(width), offset=offset2)
            source2.setFootprint(afwDetection.Footprint(tmpSpans2))

            plugin2.measure(source2, exposure2)
            x2 = source2.get("ext_shapeHSM_HsmPsfMomentsDebiased_x")
            y2 = source2.get("ext_shapeHSM_HsmPsfMomentsDebiased_y")
            xx2 = source2.get("ext_shapeHSM_HsmPsfMomentsDebiased_xx")
            yy2 = source2.get("ext_shapeHSM_HsmPsfMomentsDebiased_yy")
            xy2 = source2.get("ext_shapeHSM_HsmPsfMomentsDebiased_xy")
            for flag in [
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_no_pixels",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_not_contained",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_parent_source",
                    "ext_shapeHSM_HsmPsfMomentsDebiased_flag_edge"
            ]:
                self.assertFalse(source.get(flag))

            # Would be identically equal, but variance input via "BGMEAN" is
            # consumed in c++ as a double, where variance from the variance
            # plane is a c++ float.
            self.assertAlmostEqual(x, x2, 8)
            self.assertAlmostEqual(y, y2, 8)
            self.assertAlmostEqual(xx, xx2, 5)
            self.assertAlmostEqual(xy, xy2, 5)
            self.assertAlmostEqual(yy, yy2, 5)
Example #25
0
def plantSources(x0, y0, nx, ny, sky, nObj, wid, detector, useRandom=False):

    tanSys = detector.makeCameraSys(cameraGeom.TAN_PIXELS)
    pixToTanXYTransform = detector.getTransformMap()[tanSys]

    img0 = afwImage.ImageF(afwGeom.ExtentI(nx, ny))
    img = afwImage.ImageF(afwGeom.ExtentI(nx, ny))

    ixx0, iyy0, ixy0 = wid*wid, wid*wid, 0.0

    edgeBuffer = 40.0*wid

    flux = 1.0e4
    nkx, nky = int(10*wid) + 1, int(10*wid) + 1
    xhwid,yhwid = nkx/2, nky/2

    nRow = int(math.sqrt(nObj))
    xstep = (nx - 1 - 0.0*edgeBuffer)/(nRow+1)
    ystep = (ny - 1 - 0.0*edgeBuffer)/(nRow+1)

    if useRandom:
        nObj = nRow*nRow

    goodAdded0 = []
    goodAdded = []

    for i in range(nObj):

        # get our position
        if useRandom:
            xcen0, ycen0 = numpy.random.uniform(nx), numpy.random.uniform(ny)
        else:
            xcen0, ycen0 = xstep*((i%nRow) + 1), ystep*(int(i/nRow) + 1)
        ixcen0, iycen0 = int(xcen0), int(ycen0)

        # distort position and shape
        pTan = afwGeom.Point2D(xcen0, ycen0)
        linTransform = pixToTanXYTransform.linearizeReverseTransform(pTan).getLinear()
        m = geomEllip.Quadrupole(ixx0, iyy0, ixy0)
        m.transform(linTransform)

        p = pixToTanXYTransform.reverseTransform(pTan)
        xcen, ycen = xcen0, ycen0 #p.getX(), p.getY()
        if (xcen < 1.0*edgeBuffer or (nx - xcen) < 1.0*edgeBuffer or
            ycen < 1.0*edgeBuffer or (ny - ycen) < 1.0*edgeBuffer):
            continue
        ixcen, iycen = int(xcen), int(ycen)
        ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy()

        # plant the object
        tmp = 0.25*(ixx-iyy)**2 + ixy**2
        a2 = 0.5*(ixx+iyy) + numpy.sqrt(tmp)
        b2 = 0.5*(ixx+iyy) - numpy.sqrt(tmp)
        #ellip = 1.0 - numpy.sqrt(b2/a2)
        theta = 0.5*numpy.arctan2(2.0*ixy, ixx-iyy)
        a = numpy.sqrt(a2)
        b = numpy.sqrt(b2)

        c, s = math.cos(theta), math.sin(theta)
        good0, good = True, True
        for y in range(nky):
            iy = iycen + y - yhwid
            iy0 = iycen0 + y - yhwid

            for x in range(nkx):
                ix = ixcen + x - xhwid
                ix0 = ixcen0 + x - xhwid

                if ix >= 0 and ix < nx and iy >= 0 and iy < ny:
                    dx, dy = ix - xcen, iy - ycen
                    u =  c*dx + s*dy
                    v = -s*dx + c*dy
                    I0 = flux/(2*math.pi*a*b)
                    val = I0*math.exp(-0.5*((u/a)**2 + (v/b)**2))
                    if val < 0:
                        val = 0
                    prevVal = img.get(ix, iy)
                    img.set(ix, iy, val+prevVal)
                else:
                    good = False

                if ix0 >=0 and ix0 < nx and iy0 >= 0 and iy0 < ny:
                    dx, dy = ix - xcen, iy - ycen
                    I0 = flux/(2*math.pi*wid*wid)
                    val = I0*math.exp(-0.5*((dx/wid)**2 + (dy/wid)**2))
                    if val < 0:
                        val = 0
                    prevVal = img0.get(ix0, iy0)
                    img0.set(ix0, iy0, val+prevVal)
                else:
                    good0 = False

        if good0:
            goodAdded0.append([xcen,ycen])
        if good:
            goodAdded.append([xcen,ycen])

    # add sky and noise
    img += sky
    img0 += sky
    noise = afwImage.ImageF(afwGeom.ExtentI(nx, ny))
    noise0 = afwImage.ImageF(afwGeom.ExtentI(nx, ny))
    for i in range(nx):
        for j in range(ny):
            noise.set(i, j, numpy.random.poisson(img.get(i,j) ))
            noise0.set(i, j, numpy.random.poisson(img0.get(i,j) ))


    edgeWidth = int(0.5*edgeBuffer)
    mask = afwImage.MaskU(afwGeom.ExtentI(nx, ny))
    left   = afwGeom.Box2I(afwGeom.Point2I(0,0), afwGeom.ExtentI(edgeWidth, ny))
    right  = afwGeom.Box2I(afwGeom.Point2I(nx - edgeWidth,0), afwGeom.ExtentI(edgeWidth, ny))
    top    = afwGeom.Box2I(afwGeom.Point2I(0,ny - edgeWidth), afwGeom.ExtentI(nx, edgeWidth))
    bottom = afwGeom.Box2I(afwGeom.Point2I(0,0), afwGeom.ExtentI(nx, edgeWidth))

    for pos in [left, right, top, bottom]:
        msk = afwImage.MaskU(mask, pos, False)
        msk.set(msk.getPlaneBitMask('EDGE'))

    expos = afwImage.makeExposure(afwImage.makeMaskedImage(noise, mask, afwImage.ImageF(noise, True)))
    expos0 = afwImage.makeExposure(afwImage.makeMaskedImage(noise0, mask, afwImage.ImageF(noise0, True)))

    im = expos.getMaskedImage().getImage()
    im0 = expos0.getMaskedImage().getImage()
    im -= sky
    im0 -= sky


    return expos, goodAdded, expos0, goodAdded0
Example #26
0
    def roundTrip(self, dist, *args):

        if len(args) == 2:
            x, y = args
            p = afwGeom.Point2D(x, y)
            pDist = dist.distort(p, self.det)
            pp = dist.undistort(pDist, self.det)

            if self.prynt:
                print "p:     %.12f %.12f" % (p.getX(), p.getY())
                print "pDist: %.12f %.12f" % (pDist.getX(), pDist.getY())
                print "pp:    %.12f %.12f" % (pp.getX(), pp.getY())

            self.assertAlmostEqual(pp.getX(), p.getX())
            self.assertAlmostEqual(pp.getY(), p.getY())

        if len(args) == 3:
            x, y, m = args
            ixx, iyy, ixy = m

            p = afwGeom.Point2D(x, y)
            pDist = dist.distort(p, self.det)
            m = geomEllip.Quadrupole(ixx, iyy, ixy)
            mDist = dist.distort(p, m, self.det)
            mm = dist.undistort(pDist, mDist, self.det)
            r0 = math.sqrt(x * x + y * y)

            theta = math.atan2(y, x)
            #dx = 0.001*math.cos(theta)
            #dy = 0.001*math.sin(theta)
            #dr = math.sqrt(dx*dx+dy*dy)
            scale = 1.0000001
            p2 = afwGeom.Point2D(scale * x, scale * y)
            p2Dist = dist.distort(p2, self.det)

            r1 = math.sqrt(pDist.getX() * pDist.getX() +
                           pDist.getY() * pDist.getY())
            r2 = math.sqrt(p2Dist.getX() * p2Dist.getX() +
                           p2Dist.getY() * p2Dist.getY())
            if r0 > 0:
                drTest = (r2 - r1) / ((scale - 1.0) * r0)
            else:
                drTest = 0.0

            if self.prynt:
                print "m:     %.12f %.12f %.12f" % (m.getIxx(), m.getIyy(),
                                                    m.getIxy())
                print "mDist: %.12f %.12f %.12f" % (
                    mDist.getIxx(), mDist.getIyy(), mDist.getIxy())
                print "mp:    %.12f %.12f %.12f" % (mm.getIxx(), mm.getIyy(),
                                                    mm.getIxy())

                ixyTmp = m.getIxy() if m.getIxy() != 0.0 else 1.0
                print "err:   %.12f %.12f %.12f" % (
                    (m.getIxx() - mm.getIxx()) / m.getIxx(),
                    (m.getIyy() - mm.getIyy()) / m.getIyy(),
                    (m.getIxy() - mm.getIxy()) / ixyTmp,
                )

            self.assertAlmostEqual(mm.getIxx(), m.getIxx())
            self.assertAlmostEqual(mm.getIyy(), m.getIyy())
            self.assertAlmostEqual(mm.getIxy(), m.getIxy())
Example #27
0
    def determinePsf(self,
                     exposure,
                     psfCandidateList,
                     metadata=None,
                     flagKey=None):
        """!Determine a PCA PSF model for an exposure given a list of PSF candidates

        \param[in] exposure exposure containing the psf candidates (lsst.afw.image.Exposure)
        \param[in] psfCandidateList a sequence of PSF candidates (each an lsst.meas.algorithms.PsfCandidate);
            typically obtained by detecting sources and then running them through a star selector
        \param[in,out] metadata  a home for interesting tidbits of information
        \param[in] flagKey schema key used to mark sources actually used in PSF determination

        \return a list of
         - psf: the measured PSF, an lsst.meas.algorithms.PcaPsf
         - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayExposure = lsstDebug.Info(
            __name__).displayExposure  # display the Exposure + spatialCells
        displayPsfCandidates = lsstDebug.Info(
            __name__).displayPsfCandidates  # show the viable candidates
        displayIterations = lsstDebug.Info(
            __name__).displayIterations  # display on each PSF iteration
        displayPsfComponents = lsstDebug.Info(
            __name__).displayPsfComponents  # show the PCA components
        displayResiduals = lsstDebug.Info(
            __name__).displayResiduals  # show residuals
        displayPsfMosaic = lsstDebug.Info(
            __name__).displayPsfMosaic  # show mosaic of reconstructed PSF(x,y)
        # match Kernel amplitudes for spatial plots
        matchKernelAmplitudes = lsstDebug.Info(__name__).matchKernelAmplitudes
        # Keep matplotlib alive post mortem
        keepMatplotlibPlots = lsstDebug.Info(__name__).keepMatplotlibPlots
        displayPsfSpatialModel = lsstDebug.Info(
            __name__).displayPsfSpatialModel  # Plot spatial model?
        showBadCandidates = lsstDebug.Info(
            __name__).showBadCandidates  # Include bad candidates
        # Normalize residuals by object amplitude
        normalizeResiduals = lsstDebug.Info(__name__).normalizeResiduals
        pause = lsstDebug.Info(
            __name__).pause  # Prompt user after each iteration?

        if display > 1:
            pause = True

        mi = exposure.getMaskedImage()

        if len(psfCandidateList) == 0:
            raise RuntimeError("No PSF candidates supplied.")

        # construct and populate a spatial cell set
        bbox = mi.getBBox()
        psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX,
                                            self.config.sizeCellY)
        sizes = []
        for i, psfCandidate in enumerate(psfCandidateList):
            if psfCandidate.getSource().getPsfFluxFlag():  # bad measurement
                continue

            try:
                psfCellSet.insertCandidate(psfCandidate)
            except Exception as e:
                self.log.debug("Skipping PSF candidate %d of %d: %s", i,
                               len(psfCandidateList), e)
                continue
            source = psfCandidate.getSource()

            quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(),
                                     source.getIxy())
            axes = afwEll.Axes(quad)
            sizes.append(axes.getA())
        if len(sizes) == 0:
            raise RuntimeError("No usable PSF candidates supplied")
        nEigenComponents = self.config.nEigenComponents  # initial version

        if self.config.kernelSize >= 15:
            self.log.warn(
                "WARNING: NOT scaling kernelSize by stellar quadrupole moment "
                +
                "because config.kernelSize=%s >= 15; using config.kernelSize as as the width, instead",
                self.config.kernelSize)
            actualKernelSize = int(self.config.kernelSize)
        else:
            medSize = numpy.median(sizes)
            actualKernelSize = 2 * int(self.config.kernelSize *
                                       math.sqrt(medSize) + 0.5) + 1
            if actualKernelSize < self.config.kernelSizeMin:
                actualKernelSize = self.config.kernelSizeMin
            if actualKernelSize > self.config.kernelSizeMax:
                actualKernelSize = self.config.kernelSizeMax

            if display:
                print("Median size=%s" % (medSize, ))
        self.log.trace("Kernel size=%s", actualKernelSize)

        # Set size of image returned around candidate
        psfCandidateList[0].setHeight(actualKernelSize)
        psfCandidateList[0].setWidth(actualKernelSize)

        if self.config.doRejectBlends:
            # Remove blended candidates completely
            blendedCandidates = [
            ]  # Candidates to remove; can't do it while iterating
            for cell, cand in candidatesIter(psfCellSet, False):
                if len(cand.getSource().getFootprint().getPeaks()) > 1:
                    blendedCandidates.append((cell, cand))
                    continue
            if display:
                print("Removing %d blended Psf candidates" %
                      len(blendedCandidates))
            for cell, cand in blendedCandidates:
                cell.removeCandidate(cand)
            if sum(1 for cand in candidatesIter(psfCellSet, False)) == 0:
                raise RuntimeError("All PSF candidates removed as blends")

        if display:
            frame = 0
            if displayExposure:
                ds9.mtv(exposure, frame=frame, title="psf determination")
                maUtils.showPsfSpatialCells(exposure,
                                            psfCellSet,
                                            self.config.nStarPerCell,
                                            symb="o",
                                            ctype=ds9.CYAN,
                                            ctypeUnused=ds9.YELLOW,
                                            size=4,
                                            frame=frame)

        #
        # Do a PCA decomposition of those PSF candidates
        #
        reply = "y"  # used in interactive mode
        for iterNum in range(self.config.nIterForPsf):
            if display and displayPsfCandidates:  # Show a mosaic of usable PSF candidates
                #
                import lsst.afw.display.utils as displayUtils

                stamps = []
                for cell in psfCellSet.getCellList():
                    for cand in cell.begin(not showBadCandidates
                                           ):  # maybe include bad candidates
                        try:
                            im = cand.getMaskedImage()

                            chi2 = cand.getChi2()
                            if chi2 > 1e100:
                                chi2 = numpy.nan

                            stamps.append(
                                (im, "%d%s" %
                                 (maUtils.splitId(cand.getSource().getId(),
                                                  True)["objId"], chi2),
                                 cand.getStatus()))
                        except Exception as e:
                            continue

                if len(stamps) == 0:
                    print(
                        "WARNING: No PSF candidates to show; try setting showBadCandidates=True"
                    )
                else:
                    mos = displayUtils.Mosaic()
                    for im, label, status in stamps:
                        im = type(im)(im, True)
                        try:
                            im /= afwMath.makeStatistics(
                                im, afwMath.MAX).getValue()
                        except NotImplementedError:
                            pass

                        mos.append(
                            im, label, ds9.GREEN
                            if status == afwMath.SpatialCellCandidate.GOOD else
                            ds9.YELLOW if status ==
                            afwMath.SpatialCellCandidate.UNKNOWN else ds9.RED)

                    mos.makeMosaic(frame=8, title="Psf Candidates")

            # Re-fit until we don't have any candidates with naughty chi^2 values influencing the fit
            cleanChi2 = False  # Any naughty (negative/NAN) chi^2 values?
            while not cleanChi2:
                cleanChi2 = True
                #
                # First, estimate the PSF
                #
                psf, eigenValues, nEigenComponents, fitChi2 = \
                    self._fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)
                #
                # In clipping, allow all candidates to be innocent until proven guilty on this iteration.
                # Throw out any prima facie guilty candidates (naughty chi^2 values)
                #
                for cell in psfCellSet.getCellList():
                    awfulCandidates = []
                    for cand in cell.begin(False):  # include bad candidates
                        cand.setStatus(afwMath.SpatialCellCandidate.UNKNOWN
                                       )  # until proven guilty
                        rchi2 = cand.getChi2()
                        if not numpy.isfinite(rchi2) or rchi2 <= 0:
                            # Guilty prima facie
                            awfulCandidates.append(cand)
                            cleanChi2 = False
                            self.log.debug("chi^2=%s; id=%s", cand.getChi2(),
                                           cand.getSource().getId())
                    for cand in awfulCandidates:
                        if display:
                            print("Removing bad candidate: id=%d, chi^2=%f" % \
                                  (cand.getSource().getId(), cand.getChi2()))
                        cell.removeCandidate(cand)

            #
            # Clip out bad fits based on reduced chi^2
            #
            badCandidates = list()
            for cell in psfCellSet.getCellList():
                for cand in cell.begin(False):  # include bad candidates
                    rchi2 = cand.getChi2(
                    )  # reduced chi^2 when fitting PSF to candidate
                    assert rchi2 > 0
                    if rchi2 > self.config.reducedChi2ForPsfCandidates:
                        badCandidates.append(cand)

            badCandidates.sort(key=lambda x: x.getChi2(), reverse=True)
            numBad = numCandidatesToReject(len(badCandidates), iterNum,
                                           self.config.nIterForPsf)
            for i, c in zip(range(numBad), badCandidates):
                if display:
                    chi2 = c.getChi2()
                    if chi2 > 1e100:
                        chi2 = numpy.nan

                    print("Chi^2 clipping %-4d  %.2g" %
                          (c.getSource().getId(), chi2))
                c.setStatus(afwMath.SpatialCellCandidate.BAD)

            #
            # Clip out bad fits based on spatial fitting.
            #
            # This appears to be better at getting rid of sources that have a single dominant kernel component
            # (other than the zeroth; e.g., a nearby contaminant) because the surrounding sources (which help
            # set the spatial model) don't contain that kernel component, and so the spatial modeling
            # downweights the component.
            #

            residuals = list()
            candidates = list()
            kernel = psf.getKernel()
            noSpatialKernel = psf.getKernel()
            for cell in psfCellSet.getCellList():
                for cand in cell.begin(False):
                    candCenter = afwGeom.PointD(cand.getXCenter(),
                                                cand.getYCenter())
                    try:
                        im = cand.getMaskedImage(kernel.getWidth(),
                                                 kernel.getHeight())
                    except Exception as e:
                        continue

                    fit = fitKernelParamsToImage(noSpatialKernel, im,
                                                 candCenter)
                    params = fit[0]
                    kernels = fit[1]
                    amp = 0.0
                    for p, k in zip(params, kernels):
                        amp += p * k.getSum()

                    predict = [
                        kernel.getSpatialFunction(k)(candCenter.getX(),
                                                     candCenter.getY())
                        for k in range(kernel.getNKernelParameters())
                    ]

                    #print cand.getSource().getId(), [a / amp for a in params], predict

                    residuals.append(
                        [a / amp - p for a, p in zip(params, predict)])
                    candidates.append(cand)

            residuals = numpy.array(residuals)

            for k in range(kernel.getNKernelParameters()):
                if False:
                    # Straight standard deviation
                    mean = residuals[:, k].mean()
                    rms = residuals[:, k].std()
                elif False:
                    # Using interquartile range
                    sr = numpy.sort(residuals[:, k])
                    mean = sr[int(0.5*len(sr))] if len(sr) % 2 else \
                        0.5 * (sr[int(0.5*len(sr))] + sr[int(0.5*len(sr))+1])
                    rms = 0.74 * (sr[int(0.75 * len(sr))] -
                                  sr[int(0.25 * len(sr))])
                else:
                    stats = afwMath.makeStatistics(
                        residuals[:, k], afwMath.MEANCLIP | afwMath.STDEVCLIP)
                    mean = stats.getValue(afwMath.MEANCLIP)
                    rms = stats.getValue(afwMath.STDEVCLIP)

                rms = max(
                    1.0e-4,
                    rms)  # Don't trust RMS below this due to numerical issues

                if display:
                    print("Mean for component %d is %f" % (k, mean))
                    print("RMS for component %d is %f" % (k, rms))
                badCandidates = list()
                for i, cand in enumerate(candidates):
                    if numpy.fabs(residuals[i, k] -
                                  mean) > self.config.spatialReject * rms:
                        badCandidates.append(i)

                badCandidates.sort(
                    key=lambda x: numpy.fabs(residuals[x, k] - mean),
                    reverse=True)

                numBad = numCandidatesToReject(len(badCandidates), iterNum,
                                               self.config.nIterForPsf)

                for i, c in zip(range(min(len(badCandidates), numBad)),
                                badCandidates):
                    cand = candidates[c]
                    if display:
                        print("Spatial clipping %d (%f,%f) based on %d: %f vs %f" % \
                              (cand.getSource().getId(), cand.getXCenter(), cand.getYCenter(), k,
                               residuals[badCandidates[i], k], self.config.spatialReject * rms))
                    cand.setStatus(afwMath.SpatialCellCandidate.BAD)

            #
            # Display results
            #
            if display and displayIterations:
                if displayExposure:
                    if iterNum > 0:
                        ds9.erase(frame=frame)
                    maUtils.showPsfSpatialCells(exposure,
                                                psfCellSet,
                                                self.config.nStarPerCell,
                                                showChi2=True,
                                                symb="o",
                                                size=8,
                                                frame=frame,
                                                ctype=ds9.YELLOW,
                                                ctypeBad=ds9.RED,
                                                ctypeUnused=ds9.MAGENTA)
                    if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
                        maUtils.showPsfSpatialCells(
                            exposure,
                            psfCellSet,
                            self.config.nStarPerCellSpatialFit,
                            symb="o",
                            size=10,
                            frame=frame,
                            ctype=ds9.YELLOW,
                            ctypeBad=ds9.RED)
                if displayResiduals:
                    while True:
                        try:
                            maUtils.showPsfCandidates(
                                exposure,
                                psfCellSet,
                                psf=psf,
                                frame=4,
                                normalize=normalizeResiduals,
                                showBadCandidates=showBadCandidates)
                            maUtils.showPsfCandidates(
                                exposure,
                                psfCellSet,
                                psf=psf,
                                frame=5,
                                normalize=normalizeResiduals,
                                showBadCandidates=showBadCandidates,
                                variance=True)
                        except:
                            if not showBadCandidates:
                                showBadCandidates = True
                                continue
                        break

                if displayPsfComponents:
                    maUtils.showPsf(psf, eigenValues, frame=6)
                if displayPsfMosaic:
                    maUtils.showPsfMosaic(exposure,
                                          psf,
                                          frame=7,
                                          showFwhm=True)
                    ds9.scale('linear', 0, 1, frame=7)
                if displayPsfSpatialModel:
                    maUtils.plotPsfSpatialModel(
                        exposure,
                        psf,
                        psfCellSet,
                        showBadCandidates=True,
                        matchKernelAmplitudes=matchKernelAmplitudes,
                        keepPlots=keepMatplotlibPlots)

                if pause:
                    while True:
                        try:
                            reply = input(
                                "Next iteration? [ynchpqQs] ").strip()
                        except EOFError:
                            reply = "n"

                        reply = reply.split()
                        if reply:
                            reply, args = reply[0], reply[1:]
                        else:
                            reply = ""

                        if reply in ("", "c", "h", "n", "p", "q", "Q", "s",
                                     "y"):
                            if reply == "c":
                                pause = False
                            elif reply == "h":
                                print("c[ontinue without prompting] h[elp] n[o] p[db] q[uit displaying] " \
                                      "s[ave fileName] y[es]")
                                continue
                            elif reply == "p":
                                import pdb
                                pdb.set_trace()
                            elif reply == "q":
                                display = False
                            elif reply == "Q":
                                sys.exit(1)
                            elif reply == "s":
                                fileName = args.pop(0)
                                if not fileName:
                                    print("Please provide a filename")
                                    continue

                                print("Saving to %s" % fileName)
                                maUtils.saveSpatialCellSet(psfCellSet,
                                                           fileName=fileName)
                                continue
                            break
                        else:
                            print("Unrecognised response: %s" % reply,
                                  file=sys.stderr)

                    if reply == "n":
                        break

        # One last time, to take advantage of the last iteration
        psf, eigenValues, nEigenComponents, fitChi2 = \
            self._fitPsf(exposure, psfCellSet, actualKernelSize, nEigenComponents)

        #
        # Display code for debugging
        #
        if display and reply != "n":
            if displayExposure:
                maUtils.showPsfSpatialCells(exposure,
                                            psfCellSet,
                                            self.config.nStarPerCell,
                                            showChi2=True,
                                            symb="o",
                                            ctype=ds9.YELLOW,
                                            ctypeBad=ds9.RED,
                                            size=8,
                                            frame=frame)
                if self.config.nStarPerCellSpatialFit != self.config.nStarPerCell:
                    maUtils.showPsfSpatialCells(
                        exposure,
                        psfCellSet,
                        self.config.nStarPerCellSpatialFit,
                        symb="o",
                        ctype=ds9.YELLOW,
                        ctypeBad=ds9.RED,
                        size=10,
                        frame=frame)
                if displayResiduals:
                    maUtils.showPsfCandidates(
                        exposure,
                        psfCellSet,
                        psf=psf,
                        frame=4,
                        normalize=normalizeResiduals,
                        showBadCandidates=showBadCandidates)

            if displayPsfComponents:
                maUtils.showPsf(psf, eigenValues, frame=6)

            if displayPsfMosaic:
                maUtils.showPsfMosaic(exposure, psf, frame=7, showFwhm=True)
                ds9.scale("linear", 0, 1, frame=7)
            if displayPsfSpatialModel:
                maUtils.plotPsfSpatialModel(
                    exposure,
                    psf,
                    psfCellSet,
                    showBadCandidates=True,
                    matchKernelAmplitudes=matchKernelAmplitudes,
                    keepPlots=keepMatplotlibPlots)
        #
        # Generate some QA information
        #
        # Count PSF stars
        #
        numGoodStars = 0
        numAvailStars = 0

        avgX = 0.0
        avgY = 0.0

        for cell in psfCellSet.getCellList():
            for cand in cell.begin(False):  # don't ignore BAD stars
                numAvailStars += 1

            for cand in cell.begin(True):  # do ignore BAD stars
                src = cand.getSource()
                if flagKey is not None:
                    src.set(flagKey, True)
                avgX += src.getX()
                avgY += src.getY()
                numGoodStars += 1

        avgX /= numGoodStars
        avgY /= numGoodStars

        if metadata is not None:
            metadata.set("spatialFitChi2", fitChi2)
            metadata.set("numGoodStars", numGoodStars)
            metadata.set("numAvailStars", numAvailStars)
            metadata.set("avgX", avgX)
            metadata.set("avgY", avgY)

        psf = PcaPsf(psf.getKernel(), afwGeom.Point2D(avgX, avgY))

        return psf, psfCellSet
    def determinePsf(self,
                     exposure,
                     psfCandidateList,
                     metadata=None,
                     flagKey=None):
        """!Determine a PCA PSF model for an exposure given a list of PSF candidates

        \param[in] exposure exposure containing the psf candidates (lsst.afw.image.Exposure)
        \param[in] psfCandidateList a sequence of PSF candidates (each an lsst.meas.algorithms.PsfCandidate);
            typically obtained by detecting sources and then running them through a star selector
        \param[in,out] metadata  a home for interesting tidbits of information
        \param[in] flagKey schema key used to mark sources actually used in PSF determination

        \return a list of
         - psf: the measured PSF, an lsst.meas.algorithms.PcaPsf
         - cellSet: an lsst.afw.math.SpatialCellSet containing the PSF candidates
        """
        import lsstDebug
        display = lsstDebug.Info(__name__).display
        displayExposure = lsstDebug.Info(
            __name__).displayExposure  # display the Exposure + spatialCells
        displayPsfCandidates = lsstDebug.Info(
            __name__).displayPsfCandidates  # show the viable candidates
        displayIterations = lsstDebug.Info(
            __name__).displayIterations  # display on each PSF iteration
        displayPsfComponents = lsstDebug.Info(
            __name__).displayPsfComponents  # show the PCA components
        displayResiduals = lsstDebug.Info(
            __name__).displayResiduals  # show residuals
        displayPsfMosaic = lsstDebug.Info(
            __name__).displayPsfMosaic  # show mosaic of reconstructed PSF(x,y)
        matchKernelAmplitudes = lsstDebug.Info(
            __name__).matchKernelAmplitudes  # match Kernel amplitudes
        # for spatial plots
        keepMatplotlibPlots = lsstDebug.Info(
            __name__).keepMatplotlibPlots  # Keep matplotlib alive
        # post mortem
        displayPsfSpatialModel = lsstDebug.Info(
            __name__).displayPsfSpatialModel  # Plot spatial model?
        showBadCandidates = lsstDebug.Info(
            __name__).showBadCandidates  # Include bad candidates
        normalizeResiduals = lsstDebug.Info(
            __name__).normalizeResiduals  # Normalise residuals by
        # object amplitude
        pause = lsstDebug.Info(
            __name__).pause  # Prompt user after each iteration?

        if display > 1:
            pause = True

        mi = exposure.getMaskedImage()

        if len(psfCandidateList) == 0:
            raise RuntimeError("No PSF candidates supplied.")

        # construct and populate a spatial cell set
        bbox = mi.getBBox()
        psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX,
                                            self.config.sizeCellY)
        sizes = []
        for i, psfCandidate in enumerate(psfCandidateList):
            if psfCandidate.getSource().getPsfFluxFlag():  # bad measurement
                continue

            try:
                psfCellSet.insertCandidate(psfCandidate)
            except Exception, e:
                self.debugLog.debug(
                    2, "Skipping PSF candidate %d of %d: %s" %
                    (i, len(psfCandidateList), e))
                continue
            source = psfCandidate.getSource()

            quad = afwEll.Quadrupole(source.getIxx(), source.getIyy(),
                                     source.getIxy())
            axes = afwEll.Axes(quad)
            sizes.append(axes.getA())