def setUp(self): config = SingleFrameMeasurementTask.ConfigClass() config.slots.apFlux = 'base_CircularApertureFlux_12_0' self.schema = afwTable.SourceTable.makeMinimalSchema() self.measureSources = SingleFrameMeasurementTask(self.schema, config=config) bbox = afwGeom.BoxI(afwGeom.PointI(0, 0), afwGeom.ExtentI(self.width, self.height)) self.cellSet = afwMath.SpatialCellSet(bbox, 100) self.footprintSet = afwDetection.FootprintSet( self.mi, afwDetection.Threshold(self.detectThresh), "DETECTED") self.catalog = self.measure(self.footprintSet, self.exposure) for source in self.catalog: try: cand = measAlg.makePsfCandidate(source, self.exposure) self.cellSet.insertCandidate(cand) except Exception as e: print(e) continue
def testVisit(self, nCell=3): bskv = ipDiffim.BuildSingleKernelVisitorF(self.kList, self.policy) sizeCellX = self.policy.get("sizeCellX") sizeCellY = self.policy.get("sizeCellY") kernelCellSet = afwMath.SpatialCellSet(afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(sizeCellX * nCell, sizeCellY * nCell)), sizeCellX, sizeCellY) nTot = 0 for candX in range(nCell): for candY in range(nCell): if candX == nCell // 2 and candY == nCell // 2: kc = self.makeCandidate(100.0, candX * sizeCellX + sizeCellX // 2, candY * sizeCellY + sizeCellY // 2) else: kc = self.makeCandidate(1.0, candX * sizeCellX + sizeCellX // 2, candY * sizeCellY + sizeCellY // 2) kernelCellSet.insertCandidate(kc) nTot += 1 kernelCellSet.visitCandidates(bskv, 1) self.assertEqual(bskv.getNProcessed(), nTot) self.assertEqual(bskv.getNRejected(), 0) for cell in kernelCellSet.getCellList(): for cand in cell.begin(False): self.assertEqual(cand.getStatus(), afwMath.SpatialCellCandidate.GOOD)
def testVisit(self, nCell=3): ksv = ipDiffim.makeKernelSumVisitor(self.ps) sizeCellX = self.ps["sizeCellX"] sizeCellY = self.ps["sizeCellY"] kernelCellSet = afwMath.SpatialCellSet( geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(sizeCellX * nCell, sizeCellY * nCell)), sizeCellX, sizeCellY) for candX in range(nCell): for candY in range(nCell): if candX == nCell // 2 and candY == nCell // 2: kc = self.makeCandidate(100.0, candX * sizeCellX + sizeCellX // 2, candY * sizeCellY + sizeCellY // 2) else: kc = self.makeCandidate(1.0, candX * sizeCellX + sizeCellX // 2, candY * sizeCellY + sizeCellY // 2) kc.build(self.kList) kernelCellSet.insertCandidate(kc) ksv.setMode(ipDiffim.KernelSumVisitorF.AGGREGATE) kernelCellSet.visitCandidates(ksv, 1) ksv.processKsumDistribution() ksv.setMode(ipDiffim.KernelSumVisitorF.REJECT) kernelCellSet.visitCandidates(ksv, 1) self.assertEqual(ksv.getNRejected(), 1)
def selectPsfSources(exposure, matches, psfPolicy): """Get a list of suitable stars to construct a PSF.""" import lsstDebug display = lsstDebug.Info(__name__).display displayExposure = lsstDebug.Info(__name__).displayExposure # display the Exposure + spatialCells # # Unpack policy # kernelSize = psfPolicy.get("kernelSize") borderWidth = psfPolicy.get("borderWidth") sizePsfCellX = psfPolicy.get("sizeCellX") sizePsfCellY = psfPolicy.get("sizeCellY") # mi = exposure.getMaskedImage() if display and displayExposure: disp = afwDisplay.Display(frame=0) disp.mtv(mi, title="PSF candidates") psfCellSet = afwMath.SpatialCellSet(mi.getBBox(), sizePsfCellX, sizePsfCellY) psfStars = [] for val in matches: ref, source = val[0:2] if not (ref.getFlagForDetection() & measAlg.Flags.STAR) or \ (source.getFlagForDetection() & measAlg.Flags.BAD): continue try: cand = measAlg.makePsfCandidate(source, mi) # # 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 cand's exact type) if cand.getWidth() == 0: cand.setBorderWidth(borderWidth) cand.setWidth(kernelSize + 2*borderWidth) cand.setHeight(kernelSize + 2*borderWidth) im = cand.getMaskedImage().getImage() max = afwMath.makeStatistics(im, afwMath.MAX).getValue() if not np.isfinite(max): continue psfCellSet.insertCandidate(cand) if display and displayExposure: disp.dot("+", source.getXAstrom() - mi.getX0(), source.getYAstrom() - mi.getY0(), size=4, ctype=afwDisplay.CYAN) disp.dot("o", source.getXAstrom() - mi.getX0(), source.getYAstrom() - mi.getY0(), size=4, ctype=afwDisplay.CYAN) except Exception: continue source.setFlagForDetection(source.getFlagForDetection() | measAlg.Flags.STAR) psfStars += [source] return psfStars, psfCellSet
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 _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList): """Build a SpatialCellSet for use with the solve method. Parameters ---------- templateMaskedImage : `lsst.afw.image.MaskedImage` MaskedImage to PSF-matched to scienceMaskedImage scienceMaskedImage : `lsst.afw.image.MaskedImage` Reference MaskedImage candidateList : `list` A list of footprints/maskedImages for kernel candidates; - Currently supported: list of Footprints or measAlg.PsfCandidateF Returns ------- kernelCellSet : `lsst.afw.math.SpatialCellSet` a SpatialCellSet for use with self._solve """ if not candidateList: raise RuntimeError( "Candidate list must be populated by makeCandidateList") sizeCellX, sizeCellY = self._adaptCellSize(candidateList) # Object to store the KernelCandidates for spatial modeling kernelCellSet = afwMath.SpatialCellSet(templateMaskedImage.getBBox(), sizeCellX, sizeCellY) ps = pexConfig.makePropertySet(self.kConfig) # Place candidates within the spatial grid for cand in candidateList: if isinstance(cand, afwDetect.Footprint): bbox = cand.getBBox() else: bbox = cand['footprint'].getBBox() tmi = afwImage.MaskedImageF(templateMaskedImage, bbox) smi = afwImage.MaskedImageF(scienceMaskedImage, bbox) if not isinstance(cand, afwDetect.Footprint): if 'source' in cand: cand = cand['source'] xPos = cand.getCentroid()[0] yPos = cand.getCentroid()[1] cand = diffimLib.makeKernelCandidate(xPos, yPos, tmi, smi, ps) self.log.debug("Candidate %d at %f, %f", cand.getId(), cand.getXCenter(), cand.getYCenter()) kernelCellSet.insertCandidate(cand) return kernelCellSet
def testInsert(self): mi = afwImage.MaskedImageF(geom.Extent2I(10, 10)) kc = ipDiffim.makeKernelCandidate(0., 0., mi, mi, self.ps) kc.setStatus(afwMath.SpatialCellCandidate.GOOD) sizeCellX = self.ps["sizeCellX"] sizeCellY = self.ps["sizeCellY"] kernelCellSet = afwMath.SpatialCellSet(geom.Box2I(geom.Point2I(0, 0), geom.Extent2I(1, 1)), sizeCellX, sizeCellY) kernelCellSet.insertCandidate(kc) nSeen = 0 for cell in kernelCellSet.getCellList(): for cand in cell.begin(True): self.assertEqual(cand.getStatus(), afwMath.SpatialCellCandidate.GOOD) nSeen += 1 self.assertEqual(nSeen, 1)
def testSpatialCell(self): dx, dy, sx, sy = 100, 100, 50, 50 for x0, y0 in [(0, 0), (100, 100)]: # only works for tests where dx,dx is some multiple of sx,sy assert(dx//sx == float(dx)/float(sx)) assert(dy//sy == float(dy)/float(sy)) bbox = afwGeom.Box2I(afwGeom.Point2I(x0, y0), afwGeom.Extent2I(dx, dy)) cset = afwMath.SpatialCellSet(bbox, sx, sy) for cell in cset.getCellList(): label = cell.getLabel() nx, ny = [int(z) for z in label.split()[1].split('x')] cbbox = cell.getBBox() self.assertEqual(cbbox.getMinX(), nx*sx + x0) self.assertEqual(cbbox.getMinY(), ny*sy + y0) self.assertEqual(cbbox.getMaxX(), (nx+1)*sx + x0 - 1) self.assertEqual(cbbox.getMaxY(), (ny+1)*sy + y0 - 1)
def makePsfCellSet(self, exposure, psfCandidateList): """Construct and populate a spatial cell set (based on meas_algorithms/pcaPsf.py)""" mi = exposure.getMaskedImage() # construct and populate a spatial cell set bbox = mi.getBBox(afwImage.PARENT) psfCellSet = afwMath.SpatialCellSet(bbox, self.config.sizeCellX, self.config.sizeCellY) # FIXME: understand under which circumstances the try..except fails for (i, psfCandidate) in enumerate(psfCandidateList): try: psfCellSet.insertCandidate(psfCandidate) except Exception, e: self.debugLog.debug( 2, "Skipping PSF candidate %d of %d: %s" % (i, len(psfCandidateList), e)) continue
def stamps(self, exp1, exp2): """Find suitable stamps @param[in] exp1 First exposure of interest @param[in] exp2 Second exposure of interest @output Cell set """ policy = self.config['diff'].getPolicy() # XXX The following was cut from lsst.ip.diffim.createPsfMatchingKernel, since that does the stamp # identification and kernel solution within the same function, while one might imagine overriding one # of these with some other method. # Object to store the KernelCandidates for spatial modeling kernelCellSet = afwMath.SpatialCellSet(exp1.getBBox(afwImage.PARENT), policy.getInt("sizeCellX"), policy.getInt("sizeCellY")) # Candidate source footprints to use for Psf matching footprints = diffim.getCollectionOfFootprintsForPsfMatching( exp2, exp1, policy) # Place candidate footprints within the spatial grid for fp in footprints: bbox = fp.getBBox() # Grab the centers in the parent's coordinate system xC = 0.5 * (bbox.getMinX() + bbox.getMaxX()) yC = 0.5 * (bbox.getMinY() + bbox.getMaxY()) # Since the footprint is in the parent's coordinate system, # while the BBox uses the child's coordinate system. bbox.shift(-afwGeom.Extent2I(exp2.getXY0())) tmi = afwImage.MaskedImageF(exp2, bbox) smi = afwImage.MaskedImageF(exp1, bbox) cand = diffim.makeKernelCandidate(xC, yC, tmi, smi) kernelCellSet.insertCandidate(cand) return kernelCellSet
def testVisit(self, nCell = 3): imagePca = ipDiffim.KernelPcaD() kpv = ipDiffim.makeKernelPcaVisitor(imagePca) sizeCellX = self.policy.get("sizeCellX") sizeCellY = self.policy.get("sizeCellY") kernelCellSet = afwMath.SpatialCellSet(afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(sizeCellX * nCell, sizeCellY * nCell)), sizeCellX, sizeCellY) for candX in range(nCell): for candY in range(nCell): if candX == nCell // 2 and candY == nCell // 2: kc = self.makeCandidate(100.0, candX * sizeCellX + sizeCellX // 2, candY * sizeCellY + sizeCellY // 2) else: kc = self.makeCandidate(1.0, candX * sizeCellX + sizeCellX // 2, candY * sizeCellY + sizeCellY // 2) kc.build(self.kList) kernelCellSet.insertCandidate(kc) kernelCellSet.visitCandidates(kpv, 1) imagePca.analyze() eigenImages = imagePca.getEigenImages() eigenValues = imagePca.getEigenValues() # took in 3 images self.assertEqual(len(eigenImages), nCell * nCell) self.assertEqual(len(eigenValues), nCell * nCell) # all the same shape, only 1 eigenvalue self.assertAlmostEqual(eigenValues[0], 1.0) self.assertAlmostEqual(eigenValues[1], 0.0) self.assertAlmostEqual(eigenValues[2], 0.0)
def _buildCellSet(self, templateMaskedImage, scienceMaskedImage, candidateList): """!Build a SpatialCellSet for use with the solve method @param templateMaskedImage: MaskedImage to PSF-matched to scienceMaskedImage @param scienceMaskedImage: reference MaskedImage @param candidateList: a list of footprints/maskedImages for kernel candidates; if None then source detection is run. - Currently supported: list of Footprints or measAlg.PsfCandidateF @return kernelCellSet: a SpatialCellSet for use with self._solve """ if not candidateList: raise RuntimeError( "Candidate list must be populated by makeCandidateList") sizeCellX, sizeCellY = self._adaptCellSize(candidateList) # Object to store the KernelCandidates for spatial modeling kernelCellSet = afwMath.SpatialCellSet(templateMaskedImage.getBBox(), sizeCellX, sizeCellY) policy = pexConfig.makePolicy(self.kConfig) # Place candidates within the spatial grid for cand in candidateList: bbox = cand['footprint'].getBBox() tmi = afwImage.MaskedImageF(templateMaskedImage, bbox) smi = afwImage.MaskedImageF(scienceMaskedImage, bbox) cand = diffimLib.makeKernelCandidate(cand['source'], tmi, smi, policy) self.log.logdebug( "Candidate %d at %f, %f" % (cand.getId(), cand.getXCenter(), cand.getYCenter())) kernelCellSet.insertCandidate(cand) return kernelCellSet
def run(self, exposure, exposureIdInfo=None, background=None): """Produce characterization outputs with no processing. Parameters ---------- exposure : `lsst.afw.image.Exposure` Exposure to characterize. exposureIdInfo : `lsst.obs.base.ExposureIdInfo` ID info for exposure. background : `lsst.afw.math.BackgroundList` Initial model of background already subtracted from exposure. Returns ------- result : `lsst.pipe.base.Struct` Struct containing these fields: ``characterized`` Characterized exposure (`lsst.afw.image.Exposure`). ``sourceCat`` Detected sources (`lsst.afw.table.SourceCatalog`). ``backgroundModel`` Model of background subtracted from exposure (`lsst.afw.math.BackgroundList`) ``psfCellSet`` Spatial cells of PSF candidates (`lsst.afw.math.SpatialCellSet`) """ # Can't persist empty BackgroundList; DM-33714 bg = afwMath.BackgroundMI( geom.Box2I(geom.Point2I(0, 0), geom.Point2I(16, 16)), afwImage.MaskedImageF(16, 16)) return Struct( characterized=exposure, sourceCat=afwTable.SourceCatalog(), backgroundModel=afwMath.BackgroundList(bg), psfCellSet=afwMath.SpatialCellSet(exposure.getBBox(), 10), )
def readSrc(self, dataRef): """Read source catalog etc for input dataRef The following are returned: Source catalog, matched list, and wcs will be read from 'src', 'srcMatch', and 'calexp_md', respectively. NOTE: If the detector has nQuarter%4 != 0 (i.e. it is rotated w.r.t the focal plane coordinate system), the (x, y) pixel values of the centroid slot for the source catalogs are rotated such that pixel (0, 0) is the LLC (i.e. the coordinate system expected by meas_mosaic). If color transformation information is given, it will be applied to the reference flux of the matched list. The source catalog and matched list will be converted to measMosaic's Source and SourceMatch and returned. The number of 'Source's in each cell defined by config.cellSize will be limited to brightest config.nStarPerCell. """ self.log = Log.getDefaultLogger() dataId = dataRef.dataId try: if not dataRef.datasetExists("src"): raise RuntimeError("no data for src %s" % (dataId)) if not dataRef.datasetExists("calexp_md"): raise RuntimeError("no data for calexp_md %s" % (dataId)) calexp_md = dataRef.get("calexp_md", immediate=True) detector = dataRef.get("camera")[dataRef.dataId[ "ccd"]] # OK for HSC; maybe not for other cameras wcs = afwGeom.makeSkyWcs(calexp_md) nQuarter = detector.getOrientation().getNQuarter() sources = dataRef.get("src", immediate=True, flags=afwTable.SOURCE_IO_NO_FOOTPRINTS) # Check if we are looking at HSC stack outputs: if so, no pixel rotation of sources is # required, but alias mapping must be set to associate HSC's schema with that of LSST. hscRun = mosaicUtils.checkHscStack(calexp_md) if hscRun is None: if nQuarter % 4 != 0: dims = afwImage.bboxFromMetadata(calexp_md).getDimensions() sources = mosaicUtils.rotatePixelCoords( sources, dims.getX(), dims.getY(), nQuarter) # Set the aliap map for the source catalog if self.config.srcSchemaMap is not None and hscRun is not None: aliasMap = sources.schema.getAliasMap() for lsstName, otherName in self.config.srcSchemaMap.items(): aliasMap.set(lsstName, otherName) refObjLoader = self.config.loadAstrom.apply( butler=dataRef.getButler()) srcMatch = dataRef.get("srcMatch", immediate=True) if hscRun is not None: # The reference object loader grows the bbox by the config parameter pixelMargin. This # is set to 50 by default but is not reflected by the radius parameter set in the # metadata, so some matches may reside outside the circle searched within this radius # Thus, increase the radius set in the metadata fed into joinMatchListWithCatalog() to # accommodate. matchmeta = srcMatch.table.getMetadata() rad = matchmeta.getDouble("RADIUS") matchmeta.setDouble( "RADIUS", rad * 1.05, "field radius in degrees, approximate, padded") matches = refObjLoader.joinMatchListWithCatalog(srcMatch, sources) # Set the aliap map for the matched sources (i.e. the [1] attribute schema for each match) if self.config.srcSchemaMap is not None and hscRun is not None: for mm in matches: aliasMap = mm[1].schema.getAliasMap() for lsstName, otherName in self.config.srcSchemaMap.items( ): aliasMap.set(lsstName, otherName) if hscRun is not None: for slot in ("PsfFlux", "ModelFlux", "ApFlux", "InstFlux", "Centroid", "Shape"): getattr(matches[0][1].getTable(), "define" + slot)(getattr( sources, "get" + slot + "Definition")()) # For some reason, the CalibFlux slot in sources is coming up as centroid_sdss, so # set it to flux_naive explicitly for slot in ("CalibFlux", ): getattr(matches[0][1].getTable(), "define" + slot)("flux_naive") matches = [m for m in matches if m[0] is not None] refSchema = matches[0][0].schema if matches else None if self.cterm is not None and len(matches) != 0: # Add a "flux" field to the input schema of the first element # of the match and populate it with a colorterm correct flux. mapper = afwTable.SchemaMapper(refSchema) for key, field in refSchema: mapper.addMapping(key) fluxKey = mapper.editOutputSchema().addField( "flux", type=float, doc="Reference flux") fluxSigmaKey = mapper.editOutputSchema().addField( "fluxSigma", type=float, doc="Reference flux uncertainty") table = afwTable.SimpleTable.make(mapper.getOutputSchema()) table.preallocate(len(matches)) for match in matches: newMatch = table.makeRecord() newMatch.assign(match[0], mapper) match[0] = newMatch primaryFluxKey = refSchema.find( refSchema.join(self.cterm.primary, "flux")).key secondaryFluxKey = refSchema.find( refSchema.join(self.cterm.secondary, "flux")).key primaryFluxSigmaKey = refSchema.find( refSchema.join(self.cterm.primary, "fluxSigma")).key secondaryFluxSigmaKey = refSchema.find( refSchema.join(self.cterm.secondary, "fluxSigma")).key refFlux1 = numpy.array( [m[0].get(primaryFluxKey) for m in matches]) refFlux2 = numpy.array( [m[0].get(secondaryFluxKey) for m in matches]) refFluxSigma1 = numpy.array( [m[0].get(primaryFluxSigmaKey) for m in matches]) refFluxSigma2 = numpy.array( [m[0].get(secondaryFluxSigmaKey) for m in matches]) refMag1 = -2.5 * numpy.log10(refFlux1) refMag2 = -2.5 * numpy.log10(refFlux2) refMag = self.cterm.transformMags(refMag1, refMag2) refFlux = numpy.power(10.0, -0.4 * refMag) refFluxSigma = self.cterm.propagateFluxErrors( refFluxSigma1, refFluxSigma2) matches = [ self.setCatFlux(m, flux, fluxKey, fluxSigma, fluxSigmaKey) for m, flux, fluxSigma in zip(matches, refFlux, refFluxSigma) if flux == flux ] else: filterName = afwImage.Filter(calexp_md).getName() refFluxField = measAlg.getRefFluxField(refSchema, filterName) refSchema.getAliasMap().set("flux", refFluxField) # LSST reads in a_net catalogs with flux in "janskys", so must convert back to DN. matches = mosaicUtils.matchJanskyToDn(matches) selSources = self.selectStars(sources, self.config.includeSaturated) selMatches = self.selectStars(matches, self.config.includeSaturated) retSrc = list() retMatch = list() if len(selMatches) > self.config.minNumMatch: naxis1, naxis2 = afwImage.bboxFromMetadata( calexp_md).getDimensions() if hscRun is None: if nQuarter % 2 != 0: naxis1, naxis2 = naxis2, naxis1 bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(naxis1, naxis2)) cellSet = afwMath.SpatialCellSet(bbox, self.config.cellSize, self.config.cellSize) for s in selSources: if numpy.isfinite(s.getRa().asDegrees()): # get rid of NaN src = measMosaic.Source(s) src.setExp(dataId["visit"]) src.setChip(dataId["ccd"]) try: tmp = measMosaic.SpatialCellSource(src) cellSet.insertCandidate(tmp) except: self.log.info( "FAILED TO INSERT CANDIDATE: visit=%d ccd=%d x=%f y=%f" % (dataRef.dataId["visit"], dataRef. dataId["ccd"], src.getX(), src.getY()) + " bbox=" + str(bbox)) for cell in cellSet.getCellList(): cell.sortCandidates() for i, cand in enumerate(cell): src = cand.getSource() retSrc.append(src) if i == self.config.nStarPerCell - 1: break for m in selMatches: if m[0] is not None and m[1] is not None: match = (measMosaic.Source(m[0], wcs), measMosaic.Source(m[1])) match[1].setExp(dataId["visit"]) match[1].setChip(dataId["ccd"]) retMatch.append(match) else: self.log.info( "%8d %3d : %d/%d matches Suspicious to wrong match. Ignore this CCD" % (dataRef.dataId["visit"], dataRef.dataId["ccd"], len(selMatches), len(matches))) except Exception as e: self.log.warn("Failed to read %s: %s" % (dataId, e)) return dataId, [None, None, None] return dataId, [retSrc, retMatch, wcs]
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())
def setUp(self): width, height = 100, 300 self.mi = afwImage.MaskedImageF(afwGeom.ExtentI(width, height)) self.mi.set(0) self.mi.getVariance().set(10) self.mi.getMask().addMaskPlane("DETECTED") self.FWHM = 5 self.ksize = 25 # size of desired kernel self.exposure = afwImage.makeExposure(self.mi) psf = roundTripPsf( 2, algorithms.DoubleGaussianPsf( self.ksize, self.ksize, self.FWHM / (2 * math.sqrt(2 * math.log(2))), 1, 0.1)) self.exposure.setPsf(psf) for x, y in [ (20, 20), #(30, 35), (50, 50), (60, 20), (60, 210), (20, 210) ]: flux = 10000 - 0 * x - 10 * y sigma = 3 + 0.01 * (y - self.mi.getHeight() / 2) psf = roundTripPsf( 3, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, sigma, 1, 0.1)) im = psf.computeImage().convertF() im *= flux x0y0 = afwGeom.PointI(x - self.ksize // 2, y - self.ksize // 2) smi = self.mi.getImage().Factory( self.mi.getImage(), afwGeom.BoxI(x0y0, afwGeom.ExtentI(self.ksize)), afwImage.LOCAL) if False: # Test subtraction with non-centered psfs im = afwMath.offsetImage(im, 0.5, 0.5) smi += im del psf del im del smi roundTripPsf( 4, algorithms.DoubleGaussianPsf( self.ksize, self.ksize, self.FWHM / (2 * math.sqrt(2 * math.log(2))), 1, 0.1)) self.cellSet = afwMath.SpatialCellSet( afwGeom.BoxI(afwGeom.PointI(0, 0), afwGeom.ExtentI(width, height)), 100) ds = afwDetection.FootprintSet(self.mi, afwDetection.Threshold(10), "DETECTED") # # Prepare to measure # schema = afwTable.SourceTable.makeMinimalSchema() sfm_config = measBase.SingleFrameMeasurementConfig() sfm_config.plugins = [ "base_SdssCentroid", "base_CircularApertureFlux", "base_PsfFlux", "base_SdssShape", "base_GaussianFlux", "base_PixelFlags" ] sfm_config.slots.centroid = "base_SdssCentroid" sfm_config.slots.shape = "base_SdssShape" sfm_config.slots.psfFlux = "base_PsfFlux" sfm_config.slots.instFlux = None sfm_config.slots.apFlux = "base_CircularApertureFlux_3_0" sfm_config.slots.modelFlux = "base_GaussianFlux" sfm_config.slots.calibFlux = None sfm_config.plugins["base_SdssShape"].maxShift = 10.0 sfm_config.plugins["base_CircularApertureFlux"].radii = [3.0] task = measBase.SingleFrameMeasurementTask(schema, config=sfm_config) measCat = afwTable.SourceCatalog(schema) # detect the sources and run with the measurement task ds.makeSources(measCat) task.run(measCat, self.exposure) for source in measCat: self.cellSet.insertCandidate( algorithms.makePsfCandidate(source, self.exposure))
def checkMatches(srcMatchSet, exposure, log=None): if not exposure: return {} if log is None: log = Log.getLogger("meas.astrom.verifyWcs.checkMatches") im = exposure.getMaskedImage().getImage() width, height = im.getWidth(), im.getHeight() nx, ny = 3, 3 w, h = width // nx, height // ny if w == 0: w = 1 while nx * w < width: w += 1 if h == 0: h = 1 while ny * h < height: h += 1 cellSet = afwMath.SpatialCellSet( afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(width, height)), w, h) # # Populate cellSet # i = -1 for m in srcMatchSet: i += 1 src = m.second csrc = afwDetection.Source() csrc.setId(i) csrc.setXAstrom(src.getXAstrom()) csrc.setYAstrom(src.getYAstrom()) try: cellSet.insertCandidate( measAlg.PsfCandidateF(csrc, exposure.getMaskedImage())) except Exception as e: log.warn(str(e)) ncell = len(cellSet.getCellList()) nobj = np.ndarray(ncell, dtype='i') for i in range(ncell): cell = cellSet.getCellList()[i] nobj[i] = cell.size() dx = np.ndarray(cell.size()) dy = np.ndarray(cell.size()) j = 0 for cand in cell: # # Swig doesn't know that we're a SpatialCellImageCandidate; all it knows is that we have # a SpatialCellCandidate so we need an explicit (dynamic) cast # mid = cand.getSource().getId() dx[j] = srcMatchSet[mid].first.getXAstrom( ) - srcMatchSet[mid].second.getXAstrom() dy[j] = srcMatchSet[mid].first.getYAstrom( ) - srcMatchSet[mid].second.getYAstrom() j += 1 log.debug("%s %-30s %8s dx,dy = %5.2f,%5.2f rms_x,y = %5.2f,%5.2f", cell.getLabel(), cell.getBBox(), ("nobj=%d" % cell.size()), dx.mean(), dy.mean(), dx.std(), dy.std()) nobj.sort() values = {} values["minObjectsPerCell"] = int( nobj[0]) # otherwise it's a numpy integral type values["maxObjectsPerCell"] = int(nobj[-1]) values["meanObjectsPerCell"] = nobj.mean() values["stdObjectsPerCell"] = nobj.std() return values
def tst(): afwMath.SpatialCellSet( lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(500, 500)), 0, 3)
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 setUp(self): self.cellSet = afwMath.SpatialCellSet( afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(501, 501)), 2, 3)
def setUp(self): config = SingleFrameMeasurementTask.ConfigClass() config.slots.apFlux = 'base_CircularApertureFlux_12_0' self.schema = afwTable.SourceTable.makeMinimalSchema() self.measureSources = SingleFrameMeasurementTask(self.schema, config=config) width, height = 110, 301 self.mi = afwImage.MaskedImageF(geom.ExtentI(width, height)) self.mi.set(0) sd = 3 # standard deviation of image self.mi.getVariance().set(sd * sd) self.mi.getMask().addMaskPlane("DETECTED") self.ksize = 31 # size of desired kernel sigma1 = 1.75 sigma2 = 2 * sigma1 self.exposure = afwImage.makeExposure(self.mi) self.exposure.setPsf( measAlg.DoubleGaussianPsf(self.ksize, self.ksize, 1.5 * sigma1, 1, 0.1)) cdMatrix = np.array([1.0, 0.0, 0.0, 1.0]) cdMatrix.shape = (2, 2) wcs = afwGeom.makeSkyWcs(crpix=geom.PointD(0, 0), crval=geom.SpherePoint( 0.0, 0.0, geom.degrees), cdMatrix=cdMatrix) self.exposure.setWcs(wcs) # # Make a kernel with the exactly correct basis functions. # Useful for debugging # basisKernelList = [] for sigma in (sigma1, sigma2): basisKernel = afwMath.AnalyticKernel( self.ksize, self.ksize, afwMath.GaussianFunction2D(sigma, sigma)) basisImage = afwImage.ImageD(basisKernel.getDimensions()) basisKernel.computeImage(basisImage, True) basisImage /= np.sum(basisImage.getArray()) if sigma == sigma1: basisImage0 = basisImage else: basisImage -= basisImage0 basisKernelList.append(afwMath.FixedKernel(basisImage)) order = 1 # 1 => up to linear spFunc = afwMath.PolynomialFunction2D(order) exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc) exactKernel.setSpatialParameters([[1.0, 0, 0], [0.0, 0.5 * 1e-2, 0.2e-2]]) rand = afwMath.Random() # make these tests repeatable by setting seed addNoise = True if addNoise: im = self.mi.getImage() afwMath.randomGaussianImage(im, rand) # N(0, 1) im *= sd # N(0, sd^2) del im xarr, yarr = [], [] for x, y in [ (20, 20), (60, 20), (30, 35), (50, 50), (20, 90), (70, 160), (25, 265), (75, 275), (85, 30), (50, 120), (70, 80), (60, 210), (20, 210), ]: xarr.append(x) yarr.append(y) for x, y in zip(xarr, yarr): dx = rand.uniform() - 0.5 # random (centered) offsets dy = rand.uniform() - 0.5 k = exactKernel.getSpatialFunction(1)( x, y) # functional variation of Kernel ... b = (k * sigma1**2 / ((1 - k) * sigma2**2) ) # ... converted double Gaussian's "b" # flux = 80000 - 20*x - 10*(y/float(height))**2 flux = 80000 * (1 + 0.1 * (rand.uniform() - 0.5)) I0 = flux * (1 + b) / (2 * np.pi * (sigma1**2 + b * sigma2**2)) for iy in range(y - self.ksize // 2, y + self.ksize // 2 + 1): if iy < 0 or iy >= self.mi.getHeight(): continue for ix in range(x - self.ksize // 2, x + self.ksize // 2 + 1): if ix < 0 or ix >= self.mi.getWidth(): continue II = I0 * psfVal(ix, iy, x + dx, y + dy, sigma1, sigma2, b) Isample = rand.poisson(II) if addNoise else II self.mi.image[ix, iy, afwImage.LOCAL] += Isample self.mi.variance[ix, iy, afwImage.LOCAL] += II bbox = geom.BoxI(geom.PointI(0, 0), geom.ExtentI(width, height)) self.cellSet = afwMath.SpatialCellSet(bbox, 100) self.footprintSet = afwDetection.FootprintSet( self.mi, afwDetection.Threshold(100), "DETECTED") self.catalog = self.measure(self.footprintSet, self.exposure) for source in self.catalog: try: cand = measAlg.makePsfCandidate(source, self.exposure) self.cellSet.insertCandidate(cand) except Exception as e: print(e) continue
def _buildCellSet(self, exposure, referencePsfModel): """Build a SpatialCellSet for use with the solve method Parameters ---------- exposure : `lsst.afw.image.Exposure` The science exposure that will be convolved; must contain a Psf referencePsfModel : `lsst.afw.detection.Psf` Psf model to match to Returns ------- result : `struct` - ``kernelCellSet`` : a SpatialCellSet to be used by self._solve - ``referencePsfModel`` : Validated and/or modified reference model used to populate the SpatialCellSet Notes ----- If the reference Psf model and science Psf model have different dimensions, adjust the referencePsfModel (the model to which the exposure PSF will be matched) to match that of the science Psf. If the science Psf dimensions vary across the image, as is common with a WarpedPsf, either pad or clip (depending on config.padPsf) the dimensions to be constant. """ sizeCellX = self.kConfig.sizeCellX sizeCellY = self.kConfig.sizeCellY scienceBBox = exposure.getBBox() # Extend for proper spatial matching kernel all the way to edge, especially for narrow strips scienceBBox.grow(geom.Extent2I(sizeCellX, sizeCellY)) sciencePsfModel = exposure.getPsf() dimenR = referencePsfModel.getLocalKernel(scienceBBox.getCenter()).getDimensions() regionSizeX, regionSizeY = scienceBBox.getDimensions() scienceX0, scienceY0 = scienceBBox.getMin() kernelCellSet = afwMath.SpatialCellSet(geom.Box2I(scienceBBox), sizeCellX, sizeCellY) nCellX = regionSizeX//sizeCellX nCellY = regionSizeY//sizeCellY if nCellX == 0 or nCellY == 0: raise ValueError("Exposure dimensions=%s and sizeCell=(%s, %s). Insufficient area to match" % (scienceBBox.getDimensions(), sizeCellX, sizeCellY)) # Survey the PSF dimensions of the Spatial Cell Set # to identify the minimum enclosed or maximum bounding square BBox. widthList = [] heightList = [] for row in range(nCellY): posY = sizeCellY*row + sizeCellY//2 + scienceY0 for col in range(nCellX): posX = sizeCellX*col + sizeCellX//2 + scienceX0 widthS, heightS = sciencePsfModel.computeBBox(geom.Point2D(posX, posY)).getDimensions() widthList.append(widthS) heightList.append(heightS) psfSize = max(max(heightList), max(widthList)) if self.config.doAutoPadPsf: minPsfSize = nextOddInteger(self.kConfig.kernelSize*self.config.autoPadPsfTo) paddingPix = max(0, minPsfSize - psfSize) else: if self.config.padPsfBy % 2 != 0: raise ValueError("Config padPsfBy (%i pixels) must be even number." % self.config.padPsfBy) paddingPix = self.config.padPsfBy if paddingPix > 0: self.log.debug("Padding Science PSF from (%d, %d) to (%d, %d) pixels", psfSize, psfSize, paddingPix + psfSize, paddingPix + psfSize) psfSize += paddingPix # Check that PSF is larger than the matching kernel maxKernelSize = psfSize - 1 if maxKernelSize % 2 == 0: maxKernelSize -= 1 if self.kConfig.kernelSize > maxKernelSize: message = """ Kernel size (%d) too big to match Psfs of size %d. Please reconfigure by setting one of the following: 1) kernel size to <= %d 2) doAutoPadPsf=True 3) padPsfBy to >= %s """ % (self.kConfig.kernelSize, psfSize, maxKernelSize, self.kConfig.kernelSize - maxKernelSize) raise ValueError(message) dimenS = geom.Extent2I(psfSize, psfSize) if (dimenR != dimenS): try: referencePsfModel = referencePsfModel.resized(psfSize, psfSize) self.log.info("Adjusted dimensions of reference PSF model from %s to %s", dimenR, dimenS) except Exception as e: self.log.warning("Zero padding or clipping the reference PSF model of type %s and dimensions" " %s to the science Psf dimensions %s because: %s", referencePsfModel.__class__.__name__, dimenR, dimenS, e) dimenR = dimenS ps = pexConfig.makePropertySet(self.kConfig) for row in range(nCellY): # place at center of cell posY = sizeCellY*row + sizeCellY//2 + scienceY0 for col in range(nCellX): # place at center of cell posX = sizeCellX*col + sizeCellX//2 + scienceX0 getTraceLogger(self.log, 4).debug("Creating Psf candidate at %.1f %.1f", posX, posY) # reference kernel image, at location of science subimage referenceMI = self._makePsfMaskedImage(referencePsfModel, posX, posY, dimensions=dimenR) # kernel image we are going to convolve scienceMI = self._makePsfMaskedImage(sciencePsfModel, posX, posY, dimensions=dimenR) # The image to convolve is the science image, to the reference Psf. kc = diffimLib.makeKernelCandidate(posX, posY, scienceMI, referenceMI, ps) kernelCellSet.insertCandidate(kc) import lsstDebug display = lsstDebug.Info(__name__).display displaySpatialCells = lsstDebug.Info(__name__).displaySpatialCells maskTransparency = lsstDebug.Info(__name__).maskTransparency if not maskTransparency: maskTransparency = 0 if display: afwDisplay.setDefaultMaskTransparency(maskTransparency) if display and displaySpatialCells: dituils.showKernelSpatialCells(exposure.getMaskedImage(), kernelCellSet, symb="o", ctype=afwDisplay.CYAN, ctypeUnused=afwDisplay.YELLOW, ctypeBad=afwDisplay.RED, size=4, frame=lsstDebug.frame, title="Image to be convolved") lsstDebug.frame += 1 return pipeBase.Struct(kernelCellSet=kernelCellSet, referencePsfModel=referencePsfModel, )
def setUp(self): width, height = 100, 300 self.mi = afwImage.MaskedImageF(afwGeom.ExtentI(width, height)) self.mi.set(0) self.mi.getVariance().set(10) self.mi.getMask().addMaskPlane("DETECTED") self.FWHM = 5 self.ksize = 25 # size of desired kernel self.exposure = afwImage.makeExposure(self.mi) psf = roundTripPsf( 2, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, self.FWHM / (2 * sqrt(2 * log(2))), 1, 0.1)) self.exposure.setPsf(psf) for x, y in [ (20, 20), #(30, 35), (50, 50), (60, 20), (60, 210), (20, 210) ]: flux = 10000 - 0 * x - 10 * y sigma = 3 + 0.01 * (y - self.mi.getHeight() / 2) psf = roundTripPsf( 3, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, sigma, 1, 0.1)) im = psf.computeImage().convertF() im *= flux smi = self.mi.getImage().Factory( self.mi.getImage(), afwGeom.BoxI( afwGeom.PointI(x - self.ksize / 2, y - self.ksize / 2), afwGeom.ExtentI(self.ksize)), afwImage.LOCAL) if False: # Test subtraction with non-centered psfs im = afwMath.offsetImage(im, 0.5, 0.5) smi += im del psf del im del smi psf = roundTripPsf( 4, algorithms.DoubleGaussianPsf(self.ksize, self.ksize, self.FWHM / (2 * sqrt(2 * log(2))), 1, 0.1)) self.cellSet = afwMath.SpatialCellSet( afwGeom.BoxI(afwGeom.PointI(0, 0), afwGeom.ExtentI(width, height)), 100) ds = afwDetection.FootprintSet(self.mi, afwDetection.Threshold(10), "DETECTED") # # Prepare to measure # msConfig = algorithms.SourceMeasurementConfig() msConfig.load("tests/config/MeasureSources.py") schema = afwTable.SourceTable.makeMinimalSchema() measureSources = msConfig.makeMeasureSources(schema) catalog = afwTable.SourceCatalog(schema) msConfig.slots.calibFlux = None msConfig.slots.setupTable(catalog.table) ds.makeSources(catalog) for i, source in enumerate(catalog): measureSources.applyWithPeak(source, self.exposure) self.cellSet.insertCandidate( algorithms.makePsfCandidate(source, self.exposure))
def setUp(self): width, height = 110, 301 self.mi = afwImage.MaskedImageF(afwGeom.ExtentI(width, height)) self.mi.set(0) sd = 3 # standard deviation of image self.mi.getVariance().set(sd*sd) self.mi.getMask().addMaskPlane("DETECTED") self.FWHM = 5 self.ksize = 31 # size of desired kernel sigma1 = 1.75 sigma2 = 2*sigma1 self.exposure = afwImage.makeExposure(self.mi) self.exposure.setPsf(measAlg.DoubleGaussianPsf(self.ksize, self.ksize, 1.5*sigma1, 1, 0.1)) crval = afwCoord.makeCoord(afwCoord.ICRS, 0.0*afwGeom.degrees, 0.0*afwGeom.degrees) wcs = afwImage.makeWcs(crval, afwGeom.PointD(0, 0), 1.0, 0, 0, 1.0) self.exposure.setWcs(wcs) ccd = cameraGeom.Ccd(cameraGeom.Id(1)) ccd.addAmp(cameraGeom.Amp(cameraGeom.Id(0), afwGeom.BoxI(afwGeom.PointI(0,0), self.exposure.getDimensions()), afwGeom.BoxI(afwGeom.PointI(0,0), afwGeom.ExtentI(0,0)), afwGeom.BoxI(afwGeom.PointI(0,0), self.exposure.getDimensions()), cameraGeom.ElectronicParams(1.0, 100.0, 65535))) self.exposure.setDetector(ccd) self.exposure.getDetector().setDistortion(None) # # Make a kernel with the exactly correct basis functions. Useful for debugging # basisKernelList = afwMath.KernelList() for sigma in (sigma1, sigma2): basisKernel = afwMath.AnalyticKernel(self.ksize, self.ksize, afwMath.GaussianFunction2D(sigma, sigma)) basisImage = afwImage.ImageD(basisKernel.getDimensions()) basisKernel.computeImage(basisImage, True) basisImage /= np.sum(basisImage.getArray()) if sigma == sigma1: basisImage0 = basisImage else: basisImage -= basisImage0 basisKernelList.append(afwMath.FixedKernel(basisImage)) order = 1 # 1 => up to linear spFunc = afwMath.PolynomialFunction2D(order) exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc) exactKernel.setSpatialParameters([[1.0, 0, 0], [0.0, 0.5*1e-2, 0.2e-2]]) rand = afwMath.Random() # make these tests repeatable by setting seed addNoise = True if addNoise: im = self.mi.getImage() afwMath.randomGaussianImage(im, rand) # N(0, 1) im *= sd # N(0, sd^2) del im xarr, yarr = [], [] for x, y in [(20, 20), (60, 20), (30, 35), (50, 50), (20, 90), (70, 160), (25, 265), (75, 275), (85, 30), (50, 120), (70, 80), (60, 210), (20, 210), ]: xarr.append(x) yarr.append(y) for x, y in zip(xarr, yarr): dx = rand.uniform() - 0.5 # random (centered) offsets dy = rand.uniform() - 0.5 k = exactKernel.getSpatialFunction(1)(x, y) # functional variation of Kernel ... b = (k*sigma1**2/((1 - k)*sigma2**2)) # ... converted double Gaussian's "b" #flux = 80000 - 20*x - 10*(y/float(height))**2 flux = 80000*(1 + 0.1*(rand.uniform() - 0.5)) I0 = flux*(1 + b)/(2*np.pi*(sigma1**2 + b*sigma2**2)) for iy in range(y - self.ksize//2, y + self.ksize//2 + 1): if iy < 0 or iy >= self.mi.getHeight(): continue for ix in range(x - self.ksize//2, x + self.ksize//2 + 1): if ix < 0 or ix >= self.mi.getWidth(): continue I = I0*psfVal(ix, iy, x + dx, y + dy, sigma1, sigma2, b) Isample = rand.poisson(I) if addNoise else I self.mi.getImage().set(ix, iy, self.mi.getImage().get(ix, iy) + Isample) self.mi.getVariance().set(ix, iy, self.mi.getVariance().get(ix, iy) + I) # bbox = afwGeom.BoxI(afwGeom.PointI(0,0), afwGeom.ExtentI(width, height)) self.cellSet = afwMath.SpatialCellSet(bbox, 100) self.footprintSet = afwDetection.FootprintSet(self.mi, afwDetection.Threshold(100), "DETECTED") self.catalog = SpatialModelPsfTestCase.measure(self.footprintSet, self.exposure) for source in self.catalog: try: cand = measAlg.makePsfCandidate(source, self.exposure) self.cellSet.insertCandidate(cand) except Exception, e: print e continue
def tst(): afwMath.SpatialCellSet( afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(500, 500)), 0, 3)
def checkMatches(srcMatchSet, exposure, log=None): """Check astrometric matches and assess Wcs quality by computing statics over spacial cells in the image. Parameters ---------- srcMatchSet : `list` of `lsst.afw.table.ReferenceMatch` List of matched sources to a reference catalog. exposure : `lsst.afw.image.Exposure` Image the sources in srcMatchSet were detected/measured in. log : `lsst.log.Log` Logger object. Returns ------- values : `dict` Result dictionary with fields: - ``minObjectsPerCell`` : (`int`) - ``maxObjectsPerCell`` : (`int`) - ``meanObjectsPerCell`` : (`float`) - ``stdObjectsPerCell`` : (`float`) """ if not exposure: return {} if log is None: log = Log.getLogger("meas.astrom.verifyWcs.checkMatches") im = exposure.getMaskedImage().getImage() width, height = im.getWidth(), im.getHeight() nx, ny = 3, 3 w, h = width // nx, height // ny if w == 0: w = 1 while nx * w < width: w += 1 if h == 0: h = 1 while ny * h < height: h += 1 cellSet = afwMath.SpatialCellSet( lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(width, height)), w, h) # # Populate cellSet # i = -1 for m in srcMatchSet: i += 1 src = m.second csrc = afwDetection.Source() csrc.setId(i) csrc.setXAstrom(src.getXAstrom()) csrc.setYAstrom(src.getYAstrom()) try: cellSet.insertCandidate( measAlg.PsfCandidateF(csrc, exposure.getMaskedImage())) except Exception as e: log.warn(str(e)) ncell = len(cellSet.getCellList()) nobj = np.ndarray(ncell, dtype='i') for i in range(ncell): cell = cellSet.getCellList()[i] nobj[i] = cell.size() dx = np.ndarray(cell.size()) dy = np.ndarray(cell.size()) j = 0 for cand in cell: # # Swig doesn't know that we're a SpatialCellImageCandidate; all it knows is that we have # a SpatialCellCandidate so we need an explicit (dynamic) cast # mid = cand.getSource().getId() dx[j] = srcMatchSet[mid].first.getXAstrom( ) - srcMatchSet[mid].second.getXAstrom() dy[j] = srcMatchSet[mid].first.getYAstrom( ) - srcMatchSet[mid].second.getYAstrom() j += 1 log.debug("%s %-30s %8s dx,dy = %5.2f,%5.2f rms_x,y = %5.2f,%5.2f", cell.getLabel(), cell.getBBox(), ("nobj=%d" % cell.size()), dx.mean(), dy.mean(), dx.std(), dy.std()) nobj.sort() values = {} values["minObjectsPerCell"] = int( nobj[0]) # otherwise it's a numpy integral type values["maxObjectsPerCell"] = int(nobj[-1]) values["meanObjectsPerCell"] = nobj.mean() values["stdObjectsPerCell"] = nobj.std() return values
def setUp(self): self.schema = afwTable.SourceTable.makeMinimalSchema() config = measBase.SingleFrameMeasurementConfig() config.algorithms.names = [ "base_PixelFlags", "base_SdssCentroid", "base_GaussianFlux", "base_SdssShape", "base_CircularApertureFlux", "base_PsfFlux", ] config.algorithms["base_CircularApertureFlux"].radii = [3.0] config.slots.centroid = "base_SdssCentroid" config.slots.psfFlux = "base_PsfFlux" config.slots.apFlux = "base_CircularApertureFlux_3_0" config.slots.modelFlux = None config.slots.instFlux = None config.slots.calibFlux = None config.slots.shape = "base_SdssShape" self.measureTask = measBase.SingleFrameMeasurementTask(self.schema, config=config) width, height = 110, 301 self.mi = afwImage.MaskedImageF(afwGeom.ExtentI(width, height)) self.mi.set(0) sd = 3 # standard deviation of image self.mi.getVariance().set(sd * sd) self.mi.getMask().addMaskPlane("DETECTED") self.FWHM = 5 self.ksize = 31 # size of desired kernel sigma1 = 1.75 sigma2 = 2 * sigma1 self.exposure = afwImage.makeExposure(self.mi) self.exposure.setPsf( measAlg.DoubleGaussianPsf(self.ksize, self.ksize, 1.5 * sigma1, 1, 0.1)) self.exposure.setDetector(DetectorWrapper().detector) # # Make a kernel with the exactly correct basis functions. Useful for debugging # basisKernelList = [] for sigma in (sigma1, sigma2): basisKernel = afwMath.AnalyticKernel( self.ksize, self.ksize, afwMath.GaussianFunction2D(sigma, sigma)) basisImage = afwImage.ImageD(basisKernel.getDimensions()) basisKernel.computeImage(basisImage, True) basisImage /= np.sum(basisImage.getArray()) if sigma == sigma1: basisImage0 = basisImage else: basisImage -= basisImage0 basisKernelList.append(afwMath.FixedKernel(basisImage)) order = 1 # 1 => up to linear spFunc = afwMath.PolynomialFunction2D(order) exactKernel = afwMath.LinearCombinationKernel(basisKernelList, spFunc) exactKernel.setSpatialParameters([[1.0, 0, 0], [0.0, 0.5 * 1e-2, 0.2e-2]]) self.exactPsf = measAlg.PcaPsf(exactKernel) rand = afwMath.Random() # make these tests repeatable by setting seed addNoise = True if addNoise: im = self.mi.getImage() afwMath.randomGaussianImage(im, rand) # N(0, 1) im *= sd # N(0, sd^2) del im xarr, yarr = [], [] for x, y in [ (20, 20), (60, 20), (30, 35), (50, 50), (20, 90), (70, 160), (25, 265), (75, 275), (85, 30), (50, 120), (70, 80), (60, 210), (20, 210), ]: xarr.append(x) yarr.append(y) for x, y in zip(xarr, yarr): dx = rand.uniform() - 0.5 # random (centered) offsets dy = rand.uniform() - 0.5 k = exactKernel.getSpatialFunction(1)( x, y) # functional variation of Kernel ... b = (k * sigma1**2 / ((1 - k) * sigma2**2) ) # ... converted double Gaussian's "b" # flux = 80000 - 20*x - 10*(y/float(height))**2 flux = 80000 * (1 + 0.1 * (rand.uniform() - 0.5)) I0 = flux * (1 + b) / (2 * np.pi * (sigma1**2 + b * sigma2**2)) for iy in range(y - self.ksize // 2, y + self.ksize // 2 + 1): if iy < 0 or iy >= self.mi.getHeight(): continue for ix in range(x - self.ksize // 2, x + self.ksize // 2 + 1): if ix < 0 or ix >= self.mi.getWidth(): continue intensity = I0 * psfVal(ix, iy, x + dx, y + dy, sigma1, sigma2, b) Isample = rand.poisson( intensity) if addNoise else intensity self.mi.getImage().set( ix, iy, self.mi.getImage().get(ix, iy) + Isample) self.mi.getVariance().set( ix, iy, self.mi.getVariance().get(ix, iy) + intensity) # bbox = afwGeom.BoxI(afwGeom.PointI(0, 0), afwGeom.ExtentI(width, height)) self.cellSet = afwMath.SpatialCellSet(bbox, 100) self.footprintSet = afwDetection.FootprintSet( self.mi, afwDetection.Threshold(100), "DETECTED") self.catalog = self.measure(self.footprintSet, self.exposure) for source in self.catalog: try: cand = measAlg.makePsfCandidate(source, self.exposure) self.cellSet.insertCandidate(cand) except Exception as e: print(e) continue
def setUp(self): self.cellSet = afwMath.SpatialCellSet( lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(501, 501)), 260, 200)
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 setUp(self): self.config = ipDiffim.ImagePsfMatchTask.ConfigClass() self.config.kernel.name = "AL" self.subconfig = self.config.kernel.active # Test was put together before the min size went to 21 self.subconfig.kernelSize = 19 self.subconfig.scaleByFwhm = False self.subconfig.fitForBackground = True self.subconfig.spatialModelType = "polynomial" self.ps = pexConfig.makePropertySet(self.subconfig) self.smi = afwImage.MaskedImageF( 'tests/compareToHotpants/scienceMI.fits') self.tmi = afwImage.MaskedImageF( 'tests/compareToHotpants/templateMI.fits') self.smi.setXY0(0, 0) self.tmi.setXY0(0, 0) # Run detection # detConfig = self.subconfig.detectionConfig # Note here regarding detConfig: # # If I set detThresholdType = "pixel_stdev", I get slightly # different centroids than if I use "stdev". These different # centroids screw up the testing since hotpants was hardcoded to # use the "stdev" centroids. For completeness these are: # # 32 32 # 96 32 # 160 32 # 96 95 # 31 96 # 160 96 # 96 160 # 160 160 # 32 160 # As of Winter2013, KernelCandidateDetectionF does not return # these exact centroids anymore, so I need to hardcode them # in. self.footprints = [] for xc, yc in [(32, 32), (96, 32), (160, 32), (96, 95), (31, 96), (160, 96), (96, 160), (160, 160), (32, 160)]: self.footprints.append( afwDet.Footprint( afwGeom.SpanSet( geom.Box2I(geom.Point2I(xc, yc), geom.Extent2I(1, 1))))) # Make a basis list that hotpants has been run with nGauss = 1 sGauss = [3.] dGauss = [3] self.subconfig.alardNGauss = nGauss self.subconfig.alardSigGauss = sGauss self.subconfig.alardDegGauss = dGauss basisList0 = ipDiffim.makeKernelBasisList(self.subconfig) # HP does things in a different order, and with different normalization, so reorder list order = [0, 2, 5, 9, 1, 4, 8, 3, 7, 6] scaling = [ 1.000000e+00, 8.866037e-02, 1.218095e+01, 5.099318e-03, 8.866037e-02, 4.179772e-02, 1.138120e-02, 1.218095e+01, 1.138120e-02, 5.099318e-03 ] self.basisList = [] for i in range(len(order)): im = afwImage.ImageD(basisList0[order[i]].getDimensions()) basisList0[order[i]].computeImage(im, False) im /= scaling[i] # im.writeFits('k%d.fits' % (i)) k = afwMath.FixedKernel(im) self.basisList.append(k) # And a place to put candidates self.kernelCellSet = afwMath.SpatialCellSet( geom.Box2I( geom.Point2I(0, 0), geom.Extent2I(self.smi.getWidth(), self.smi.getHeight())), self.ps["sizeCellX"], self.ps["sizeCellY"]) # There are some -1 factors that come from differences in how # convolution is done. Some resulting convovled images end up # being a factor of -1 different, therefore the coefficients # need to be a factor of -1 different as well. self.parity = [1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1]