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