def makeSourceCat(self, distortedWcs): """Make a source catalog by reading the position reference stars and distorting the positions """ loadRes = self.refObjLoader.loadPixelBox(bbox=self.bbox, wcs=distortedWcs, filterName="r") refCat = loadRes.refCat refCentroidKey = afwTable.Point2DKey(refCat.schema["centroid"]) refFluxRKey = refCat.schema["r_flux"].asKey() sourceSchema = afwTable.SourceTable.makeMinimalSchema() measBase.SingleFrameMeasurementTask( schema=sourceSchema) # expand the schema sourceCat = afwTable.SourceCatalog(sourceSchema) sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) sourceInstFluxKey = sourceSchema["slot_ApFlux_instFlux"].asKey() sourceInstFluxErrKey = sourceSchema["slot_ApFlux_instFluxErr"].asKey() sourceCat.reserve(len(refCat)) for refObj in refCat: src = sourceCat.addNew() src.set(sourceCentroidKey, refObj.get(refCentroidKey)) src.set(sourceInstFluxKey, refObj.get(refFluxRKey)) src.set(sourceInstFluxErrKey, refObj.get(refFluxRKey) / 100) return sourceCat
def setUp(self): # make a nominal match list where the distances are 0; test can then modify # source centroid, reference coord or distance field for each match, as desired self.wcs = afwGeom.makeSkyWcs(crpix=lsst.geom.Point2D(1500, 1500), crval=lsst.geom.SpherePoint(215.5, 53.0, lsst.geom.degrees), cdMatrix=afwGeom.makeCdMatrix(scale=5.1e-5*lsst.geom.degrees)) self.bboxD = lsst.geom.Box2D(lsst.geom.Point2D(10, 100), lsst.geom.Extent2D(1000, 1500)) self.numMatches = 25 sourceSchema = afwTable.SourceTable.makeMinimalSchema() # add centroid (and many other unwanted fields) to sourceSchema SingleFrameMeasurementTask(schema=sourceSchema) self.sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) self.sourceCat = afwTable.SourceCatalog(sourceSchema) refSchema = afwTable.SourceTable.makeMinimalSchema() self.refCoordKey = afwTable.CoordKey(refSchema["coord"]) self.refCat = afwTable.SourceCatalog(refSchema) self.matchList = [] np.random.seed(5) pixPointList = [lsst.geom.Point2D(pos) for pos in np.random.random_sample([self.numMatches, 2])*self.bboxD.getDimensions() + self.bboxD.getMin()] for pixPoint in pixPointList: src = self.sourceCat.addNew() src.set(self.sourceCentroidKey, pixPoint) ref = self.refCat.addNew() ref.set(self.refCoordKey, self.wcs.pixelToSky(pixPoint)) match = afwTable.ReferenceMatch(ref, src, 0) self.matchList.append(match)
def loadData(self, rangePix=3000, numPoints=25): """Load catalogs and make the match list This is a separate function so data can be reloaded if fitting more than once (each time a WCS is fit it may update the source catalog, reference catalog and match list) """ refSchema = LoadReferenceObjectsTask.makeMinimalSchema( filterNameList=["r"], addIsPhotometric=True, addCentroid=True) self.refCat = afwTable.SimpleCatalog(refSchema) srcSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=srcSchema) self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"]) self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"]) self.srcCentroidKey_xErr = srcSchema["slot_Centroid_xErr"].asKey() self.srcCentroidKey_yErr = srcSchema["slot_Centroid_yErr"].asKey() self.sourceCat = afwTable.SourceCatalog(srcSchema) self.matches = [] for i in np.linspace(0., rangePix, numPoints): for j in np.linspace(0., rangePix, numPoints): src = self.sourceCat.addNew() refObj = self.refCat.addNew() src.set(self.srcCentroidKey, lsst.geom.Point2D(i, j)) src.set(self.srcCentroidKey_xErr, 0.1) src.set(self.srcCentroidKey_yErr, 0.1) c = self.tanWcs.pixelToSky(i, j) refObj.setCoord(c) self.matches.append(self.MatchClass(refObj, src, 0.0))
def makeCatalog(self, apCorrScale=1.0, numSources=5): sourceCat = afwTable.SourceCatalog(self.schema) fluxName = self.name + "_flux" flagName = self.name + "_flag" fluxSigmaName = self.name + "_fluxSigma" apFluxName = self.apname + "_flux" apFlagName = self.apname + "_flag" apFluxSigmaName = self.apname + "_fluxSigma" fluxKey = self.schema.find(fluxName).key flagKey = self.schema.find(flagName).key fluxSigmaKey = self.schema.find(fluxSigmaName).key apFluxKey = self.schema.find(apFluxName).key apFlagKey = self.schema.find(apFlagName).key apFluxSigmaKey = self.schema.find(apFluxSigmaName).key centroidKey = afwTable.Point2DKey(self.schema["slot_Centroid"]) inputFilterFlagKey = self.schema.find(self.meas_apCorr_task.config.sourceSelector.active.field).key x = np.random.rand(numSources)*self.exposure.getWidth() + self.exposure.getX0() y = np.random.rand(numSources)*self.exposure.getHeight() + self.exposure.getY0() for _i in range(numSources): source_test_flux = 5.1 source_test_centroid = afwGeom.Point2D(x[_i], y[_i]) source = sourceCat.addNew() source.set(fluxKey, source_test_flux) source.set(apFluxKey, source_test_flux * apCorrScale) source.set(centroidKey, source_test_centroid) source.set(fluxSigmaKey, 0.) source.set(apFluxSigmaKey, 0.) source.set(flagKey, False) source.set(apFlagKey, False) source.set(inputFilterFlagKey, True) return(sourceCat)
def testUsedFlag(self): """Test that the solver will record number of sources used to table if it is passed a schema on initialization. """ distortedWcs = afwImage.DistortedTanWcs(self.tanWcs, afwGeom.IdentityXYTransform()) self.exposure.setWcs(distortedWcs) loadRes = self.refObjLoader.loadPixelBox(bbox=self.bbox, wcs=distortedWcs, filterName="r") refCat = loadRes.refCat refCentroidKey = afwTable.Point2DKey(refCat.schema["centroid"]) refFluxRKey = refCat.schema["r_flux"].asKey() sourceSchema = afwTable.SourceTable.makeMinimalSchema() measBase.SingleFrameMeasurementTask( schema=sourceSchema) # expand the schema config = AstrometryTask.ConfigClass() config.wcsFitter.order = 2 config.wcsFitter.numRejIter = 0 # schema must be passed to the solver task constructor solver = AstrometryTask(config=config, refObjLoader=self.refObjLoader, schema=sourceSchema) sourceCat = afwTable.SourceCatalog(sourceSchema) sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) sourceFluxKey = sourceSchema["slot_ApFlux_flux"].asKey() sourceFluxSigmaKey = sourceSchema["slot_ApFlux_fluxSigma"].asKey() for refObj in refCat: src = sourceCat.addNew() src.set(sourceCentroidKey, refObj.get(refCentroidKey)) src.set(sourceFluxKey, refObj.get(refFluxRKey)) src.set(sourceFluxSigmaKey, refObj.get(refFluxRKey) / 100) results = solver.run( sourceCat=sourceCat, exposure=self.exposure, ) # check that the used flag is set the right number of times count = 0 for source in sourceCat: if source.get('calib_astrometryUsed'): count += 1 self.assertEqual(count, len(results.matches))
def doTest(self, pixelsToTanPixels, order=3): """Test using pixelsToTanPixels to distort the source positions """ distortedWcs = afwGeom.makeModifiedWcs(pixelTransform=pixelsToTanPixels, wcs=self.tanWcs, modifyActualPixels=False) self.exposure.setWcs(distortedWcs) sourceCat = self.makeSourceCat(distortedWcs) config = AstrometryTask.ConfigClass() config.wcsFitter.order = order config.wcsFitter.numRejIter = 0 solver = AstrometryTask(config=config, refObjLoader=self.refObjLoader) results = solver.run( sourceCat=sourceCat, exposure=self.exposure, ) fitWcs = self.exposure.getWcs() self.assertRaises(Exception, self.assertWcsAlmostEqualOverBBox, fitWcs, distortedWcs) self.assertWcsAlmostEqualOverBBox(distortedWcs, fitWcs, self.bbox, maxDiffSky=0.01*afwGeom.arcseconds, maxDiffPix=0.02) srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"]) refCoordKey = afwTable.CoordKey(results.refCat.schema["coord"]) refCentroidKey = afwTable.Point2DKey(results.refCat.schema["centroid"]) maxAngSep = afwGeom.Angle(0) maxPixSep = 0 for refObj, src, d in results.matches: refCoord = refObj.get(refCoordKey) refPixPos = refObj.get(refCentroidKey) srcCoord = src.get(srcCoordKey) srcPixPos = src.getCentroid() angSep = refCoord.separation(srcCoord) maxAngSep = max(maxAngSep, angSep) pixSep = math.hypot(*(srcPixPos-refPixPos)) maxPixSep = max(maxPixSep, pixSep) print("max angular separation = %0.4f arcsec" % (maxAngSep.asArcseconds(),)) print("max pixel separation = %0.3f" % (maxPixSep,)) self.assertLess(maxAngSep.asArcseconds(), 0.0026) self.assertLess(maxPixSep, 0.015) # try again, but without fitting the WCS config.forceKnownWcs = True solverNoFit = AstrometryTask(config=config, refObjLoader=self.refObjLoader) self.exposure.setWcs(distortedWcs) resultsNoFit = solverNoFit.run( sourceCat=sourceCat, exposure=self.exposure, ) self.assertIsNone(resultsNoFit.scatterOnSky) # fitting should result in matches that are at least as good # (strictly speaking fitting might result in a larger match list with # some outliers, but in practice this test passes) meanFitDist = np.mean([match.distance for match in results.matches]) meanNoFitDist = np.mean([match.distance for match in resultsNoFit.matches]) self.assertLessEqual(meanFitDist, meanNoFitDist)
def __init__(self, config, name, schemaMapper, metadata): ForcedPlugin.__init__(self, config, name, schemaMapper, metadata) schema = schemaMapper.editOutputSchema() # Allocate x and y fields, join these into a single FunctorKey for ease-of-use. xKey = schema.addField(name + "_x", type="D", doc="transformed reference centroid column", units="pixel") yKey = schema.addField(name + "_y", type="D", doc="transformed reference centroid row", units="pixel") self.centroidKey = afwTable.Point2DKey(xKey, yKey)
def initializeSourceCatalog(schema=None, name=None, flux=None, sigma=None, centroid=None): fluxName = name + "_flux" fluxSigmaName = name + "_fluxSigma" fluxKey = schema.find(fluxName).key centroidKey = afwTable.Point2DKey(schema["slot_Centroid"]) sourceCat = afwTable.SourceCatalog(schema) source = sourceCat.addNew() source.set(fluxKey, flux) source.set(fluxSigmaName, sigma) source.set(centroidKey, centroid) return(sourceCat)
def initializeSourceCatalog(schema=None, name=None, instFlux=None, sigma=None, centroid=None): instFluxName = name + "_instFlux" instFluxErrName = name + "_instFluxErr" instFluxKey = schema.find(instFluxName).key centroidKey = afwTable.Point2DKey(schema["slot_Centroid"]) sourceCat = afwTable.SourceCatalog(schema) source = sourceCat.addNew() source.set(instFluxKey, instFlux) source.set(instFluxErrName, sigma) source.set(centroidKey, centroid) return(sourceCat)
def setUp(self): # make a nominal match list where the distances are 0; test can then modify # source centroid, reference coord or distance field for each match, as desired ctrPix = afwGeom.Point2I(1500, 1500) metadata = PropertySet() metadata.set("RADECSYS", "FK5") metadata.set("EQUINOX", 2000.0) metadata.set("CTYPE1", "RA---TAN") metadata.set("CTYPE2", "DEC--TAN") metadata.set("CUNIT1", "deg") metadata.set("CUNIT2", "deg") metadata.set("CRVAL1", 215.5) metadata.set("CRVAL2", 53.0) metadata.set("CRPIX1", ctrPix[0] + 1) metadata.set("CRPIX2", ctrPix[1] + 1) metadata.set("CD1_1", 5.1e-05) metadata.set("CD1_2", 0.0) metadata.set("CD2_2", -5.1e-05) metadata.set("CD2_1", 0.0) self.wcs = afwImage.makeWcs(metadata) self.bboxD = afwGeom.Box2D(afwGeom.Point2D(10, 100), afwGeom.Extent2D(1000, 1500)) self.numMatches = 25 sourceSchema = afwTable.SourceTable.makeMinimalSchema() # add centroid (and many other unwanted fields) to sourceSchema SingleFrameMeasurementTask(schema=sourceSchema) self.sourceCentroidKey = afwTable.Point2DKey( sourceSchema["slot_Centroid"]) self.sourceCat = afwTable.SourceCatalog(sourceSchema) refSchema = afwTable.SourceTable.makeMinimalSchema() self.refCoordKey = afwTable.CoordKey(refSchema["coord"]) self.refCat = afwTable.SourceCatalog(refSchema) self.matchList = [] np.random.seed(5) pixPointList = [ afwGeom.Point2D(pos) for pos in np.random.random_sample([self.numMatches, 2]) * self.bboxD.getDimensions() + self.bboxD.getMin() ] for pixPoint in pixPointList: src = self.sourceCat.addNew() src.set(self.sourceCentroidKey, pixPoint) ref = self.refCat.addNew() ref.set(self.refCoordKey, self.wcs.pixelToSky(pixPoint)) match = afwTable.ReferenceMatch(ref, src, 0) self.matchList.append(match)
def testSourceNotUsed(self): """ Check that a source outside the bounding box is flagged as not used (False).""" fluxName = self.name + "_flux" apCorrFlagKey = self.schema.find("apcorr_" + self.name + "_used").key sourceCat = self.makeCatalog() source = sourceCat.addNew() source_test_flux = 5.1 source_test_centroid = afwGeom.Point2D(15, 7.1) fluxKey = self.schema.find(fluxName).key centroidKey = afwTable.Point2DKey(self.schema["slot_Centroid"]) source.set(fluxKey, source_test_flux) source.set(centroidKey, source_test_centroid) self.meas_apCorr_task.run(catalog=sourceCat, exposure=self.exposure) self.assertFalse(sourceCat[apCorrFlagKey][-1])
def checkResults(self, fitRes, catsUpdated): """Check results @param[in] fitRes a object with two fields: - wcs fit TAN-SIP WCS, an lsst.afw.geom.SkyWcs - scatterOnSky median on-sky scatter, an lsst.afw.geom.Angle @param[in] catsUpdated if True then coord field of self.sourceCat and centroid fields of self.refCat have been updated """ self.assertLess(fitRes.scatterOnSky.asArcseconds(), 0.001) tanSipWcs = fitRes.wcs maxAngSep = 0 * lsst.geom.radians maxPixSep = 0 refCoordKey = afwTable.CoordKey(self.refCat.schema["coord"]) if catsUpdated: refCentroidKey = afwTable.Point2DKey( self.refCat.schema["centroid"]) maxDistErr = 0 * lsst.geom.radians for refObj, src, distRad in self.matches: srcPixPos = src.get(self.srcCentroidKey) refCoord = refObj.get(refCoordKey) if catsUpdated: refPixPos = refObj.get(refCentroidKey) srcCoord = src.get(self.srcCoordKey) else: refPixPos = tanSipWcs.skyToPixel(refCoord) srcCoord = tanSipWcs.pixelToSky(srcPixPos) angSep = refCoord.separation(srcCoord) dist = distRad * lsst.geom.radians distErr = abs(dist - angSep) maxDistErr = max(maxDistErr, distErr) maxAngSep = max(maxAngSep, angSep) pixSep = math.hypot(*(srcPixPos - refPixPos)) maxPixSep = max(maxPixSep, pixSep) print("max angular separation = %0.4f arcsec" % (maxAngSep.asArcseconds(), )) print("max pixel separation = %0.3f" % (maxPixSep, )) self.assertLess(maxAngSep.asArcseconds(), 0.001) self.assertLess(maxPixSep, 0.005) if catsUpdated: allowedDistErr = 1e-7 else: allowedDistErr = 0.001 self.assertLess( maxDistErr.asArcseconds(), allowedDistErr, "Computed distance in match list is off by %s arcsec" % (maxDistErr.asArcseconds(), ))
def setUp(self): crval = IcrsCoord(afwGeom.PointD(44., 45.)) crpix = afwGeom.PointD(0, 0) arcsecPerPixel = 1 / 3600.0 CD11 = arcsecPerPixel CD12 = 0 CD21 = 0 CD22 = arcsecPerPixel self.tanWcs = makeWcs(crval, crpix, CD11, CD12, CD21, CD22) S = 300 N = 5 if self.MatchClass == afwTable.ReferenceMatch: refSchema = LoadReferenceObjectsTask.makeMinimalSchema( filterNameList=["r"], addFluxSigma=True, addIsPhotometric=True) self.refCat = afwTable.SimpleCatalog(refSchema) elif self.MatchClass == afwTable.SourceMatch: refSchema = afwTable.SourceTable.makeMinimalSchema() self.refCat = afwTable.SourceCatalog(refSchema) else: raise RuntimeError("Unsupported MatchClass=%r" % (self.MatchClass, )) srcSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=srcSchema) self.refCoordKey = afwTable.CoordKey(refSchema["coord"]) self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"]) self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"]) self.sourceCat = afwTable.SourceCatalog(srcSchema) self.origSourceCat = afwTable.SourceCatalog( srcSchema) # undistorted copy self.matches = [] for i in np.linspace(0., S, N): for j in np.linspace(0., S, N): src = self.sourceCat.addNew() refObj = self.refCat.addNew() src.set(self.srcCentroidKey, afwGeom.Point2D(i, j)) c = self.tanWcs.pixelToSky(afwGeom.Point2D(i, j)) refObj.setCoord(c) self.matches.append(self.MatchClass(refObj, src, 0.0))
def loadData(self, rangePix=3000, numPoints=25): """Load catalogs and make the match list This is a separate function so data can be reloaded if fitting more than once (each time a WCS is fit it may update the source catalog, reference catalog and match list) """ if self.MatchClass == afwTable.ReferenceMatch: refSchema = LoadReferenceObjectsTask.makeMinimalSchema( filterNameList=["r"], addIsPhotometric=True, addCentroid=True) self.refCat = afwTable.SimpleCatalog(refSchema) elif self.MatchClass == afwTable.SourceMatch: refSchema = afwTable.SourceTable.makeMinimalSchema() self.refCat = afwTable.SourceCatalog(refSchema) else: raise RuntimeError("Unsupported MatchClass=%r" % (self.MatchClass, )) srcSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=srcSchema) self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"]) self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"]) self.srcCentroidKey_xErr = srcSchema["slot_Centroid_xErr"].asKey() self.srcCentroidKey_yErr = srcSchema["slot_Centroid_yErr"].asKey() self.sourceCat = afwTable.SourceCatalog(srcSchema) self.matches = [] for i in np.linspace(0., rangePix, numPoints): for j in np.linspace(0., rangePix, numPoints): src = self.sourceCat.addNew() refObj = self.refCat.addNew() src.set(self.srcCentroidKey, lsst.geom.Point2D(i, j)) src.set(self.srcCentroidKey_xErr, 0.1) src.set(self.srcCentroidKey_yErr, 0.1) c = self.tanWcs.pixelToSky(i, j) refObj.setCoord(c) if False: print( "x,y = (%.1f, %.1f) pixels -- RA,Dec = (%.3f, %.3f) deg" % (i, j, c.toFk5().getRa().asDegrees(), c.toFk5().getDec().asDegrees())) self.matches.append(self.MatchClass(refObj, src, 0.0))
def checkResults(self, fitRes, catsUpdated): """Check results. """ self.assertLess(fitRes.scatterOnSky.asArcseconds(), 0.001) affineWcs = fitRes.wcs maxAngSep = 0 * lsst.geom.radians maxPixSep = 0 refCoordKey = afwTable.CoordKey(self.refCat.schema["coord"]) if catsUpdated: refCentroidKey = afwTable.Point2DKey( self.refCat.schema["centroid"]) maxDistErr = 0 * lsst.geom.radians for refObj, src, distRad in self.matches: srcPixPos = src.get(self.srcCentroidKey) refCoord = refObj.get(refCoordKey) if catsUpdated: refPixPos = refObj.get(refCentroidKey) srcCoord = src.get(self.srcCoordKey) else: refPixPos = affineWcs.skyToPixel(refCoord) srcCoord = affineWcs.pixelToSky(srcPixPos) angSep = refCoord.separation(srcCoord) dist = distRad * lsst.geom.radians distErr = abs(dist - angSep) maxDistErr = max(maxDistErr, distErr) maxAngSep = max(maxAngSep, angSep) pixSep = math.hypot(*(srcPixPos - refPixPos)) maxPixSep = max(maxPixSep, pixSep) print("max angular separation = %0.4f arcsec" % (maxAngSep.asArcseconds(), )) print("max pixel separation = %0.3f" % (maxPixSep, )) self.assertLess(maxAngSep.asArcseconds(), 0.001) self.assertLess(maxPixSep, 0.005) if catsUpdated: allowedDistErr = 1e-7 else: allowedDistErr = 0.001 self.assertLess( maxDistErr.asArcseconds(), allowedDistErr, "Computed distance in match list is off by %s arcsec" % (maxDistErr.asArcseconds(), ))
def setUp(self): crval = afwGeom.SpherePoint(44, 45, afwGeom.degrees) crpix = afwGeom.PointD(0, 0) scale = 1 * afwGeom.arcseconds self.tanWcs = afwGeom.makeSkyWcs( crpix=crpix, crval=crval, cdMatrix=afwGeom.makeCdMatrix(scale=scale)) S = 300 N = 5 if self.MatchClass == afwTable.ReferenceMatch: refSchema = LoadReferenceObjectsTask.makeMinimalSchema( filterNameList=["r"], addFluxSigma=True, addIsPhotometric=True) self.refCat = afwTable.SimpleCatalog(refSchema) elif self.MatchClass == afwTable.SourceMatch: refSchema = afwTable.SourceTable.makeMinimalSchema() self.refCat = afwTable.SourceCatalog(refSchema) else: raise RuntimeError("Unsupported MatchClass=%r" % (self.MatchClass, )) srcSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=srcSchema) self.refCoordKey = afwTable.CoordKey(refSchema["coord"]) self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"]) self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"]) self.sourceCat = afwTable.SourceCatalog(srcSchema) self.origSourceCat = afwTable.SourceCatalog( srcSchema) # undistorted copy self.matches = [] for i in np.linspace(0., S, N): for j in np.linspace(0., S, N): src = self.sourceCat.addNew() refObj = self.refCat.addNew() src.set(self.srcCentroidKey, afwGeom.Point2D(i, j)) c = self.tanWcs.pixelToSky(afwGeom.Point2D(i, j)) refObj.setCoord(c) self.matches.append(self.MatchClass(refObj, src, 0.0))
def _trimToBBox(refCat, bbox, wcs): """!Remove objects outside a given pixel-based bbox and set centroid and hasCentroid fields @param[in,out] refCat a catalog of objects (an lsst.afw.table.SimpleCatalog, or other table type that has fields "coord", "centroid" and "hasCentroid"). The "coord" field is read. The "centroid" and "hasCentroid" fields are set. @param[in] bbox pixel region (an afwImage.Box2D) @param[in] wcs WCS used to convert sky position to pixel position (an lsst.afw.math.WCS) @return a catalog of reference objects in bbox, with centroid and hasCentroid fields set """ afwTable.updateRefCentroids(wcs, refCat) centroidKey = afwTable.Point2DKey(refCat.schema["centroid"]) retStarCat = type(refCat)(refCat.table) for star in refCat: point = star.get(centroidKey) if bbox.contains(point): retStarCat.append(star) return retStarCat
def _trimToBBox(refCat, bbox, wcs): """!Remove objects outside a given pixel-based bbox and set centroid and hasCentroid fields @param[in] refCat a catalog of objects (an lsst.afw.table.SimpleCatalog, or other table type that supports getCoord() on records) @param[in] bbox pixel region (an afwImage.Box2D) @param[in] wcs WCS used to convert sky position to pixel position (an lsst.afw.math.WCS) @return a catalog of reference objects in bbox, with centroid and hasCentroid fields set """ centroidKey = afwTable.Point2DKey(refCat.schema["centroid"]) hasCentroidKey = refCat.schema["hasCentroid"].asKey() retStarCat = type(refCat)(refCat.table) for star in refCat: point = wcs.skyToPixel(star.getCoord()) if bbox.contains(point): star.set(centroidKey, point) star.set(hasCentroidKey, True) retStarCat.append(star) return retStarCat
def testRejectBlends(self): """Test the PcaPsfDeterminerTask blend removal.""" """ We give it a single blended source, asking it to remove blends, and check that it barfs in the expected way. """ psfDeterminerClass = measAlg.psfDeterminerRegistry["pca"] config = psfDeterminerClass.ConfigClass() config.doRejectBlends = True psfDeterminer = psfDeterminerClass(config=config) schema = afwTable.SourceTable.makeMinimalSchema() # Use The single frame measurement task to populate the schema with standard keys measBase.SingleFrameMeasurementTask(schema) catalog = afwTable.SourceCatalog(schema) source = catalog.addNew() # Make the source blended, with necessary information to calculate pca spanShift = afwGeom.Point2I(54, 123) spans = afwGeom.SpanSet.fromShape(6, offset=spanShift) foot = afwDetection.Footprint(spans, self.exposure.getBBox()) foot.addPeak(45, 123, 6) foot.addPeak(47, 126, 5) source.setFootprint(foot) centerKey = afwTable.Point2DKey(source.schema['slot_Centroid']) shapeKey = afwTable.QuadrupoleKey(schema['slot_Shape']) source.set(centerKey, afwGeom.Point2D(46, 124)) source.set(shapeKey, afwGeom.Quadrupole(1.1, 2.2, 1)) candidates = [measAlg.makePsfCandidate(source, self.exposure)] metadata = dafBase.PropertyList() with self.assertRaises(RuntimeError) as cm: psfDeterminer.determinePsf(self.exposure, candidates, metadata) self.assertEqual(str(cm.exception), "All PSF candidates removed as blends")
def approximateWcs(wcs, bbox, order=3, nx=20, ny=20, iterations=3, skyTolerance=0.001 * afwGeom.arcseconds, pixelTolerance=0.02, useTanWcs=False): """Approximate an existing WCS as a TAN-SIP WCS The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box. @param[in] wcs wcs to approximate @param[in] bbox the region over which the WCS will be fit @param[in] order order of SIP fit @param[in] nx number of grid points along x @param[in] ny number of grid points along y @param[in] iterations number of times to iterate over fitting @param[in] skyTolerance maximum allowed difference in world coordinates between input wcs and approximate wcs (default is 0.001 arcsec) @param[in] pixelTolerance maximum allowed difference in pixel coordinates between input wcs and approximate wcs (default is 0.02 pixels) @param[in] useTanWcs send a TAN version of wcs to the fitter? It is documented to require that, but I don't think the fitter actually cares @return the fit TAN-SIP WCS """ if useTanWcs: crCoord = wcs.getSkyOrigin() crPix = wcs.getPixelOrigin() cdMat = wcs.getCDMatrix() tanWcs = afwImage.makeWcs(crCoord, crPix, cdMat[0, 0], cdMat[0, 1], cdMat[1, 0], cdMat[1, 1]) else: tanWcs = wcs # create a matchList consisting of a grid of points covering the bbox refSchema = afwTable.SimpleTable.makeMinimalSchema() refCoordKey = afwTable.CoordKey(refSchema["coord"]) refCat = afwTable.SimpleCatalog(refSchema) sourceSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) sourceCat = afwTable.SourceCatalog(sourceSchema) matchList = [] bboxd = afwGeom.Box2D(bbox) for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx): for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny): pixelPos = afwGeom.Point2D(x, y) skyCoord = wcs.pixelToSky(pixelPos) refObj = refCat.addNew() refObj.set(refCoordKey, skyCoord) source = sourceCat.addNew() source.set(sourceCentroidKey, pixelPos) matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0)) # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge for indx in range(iterations): sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox) tanWcs = sipObject.getNewWcs() fitWcs = sipObject.getNewWcs() mockTest = _MockTestCase() assertWcsAlmostEqualOverBBox(mockTest, wcs, fitWcs, bbox, maxDiffSky=skyTolerance, maxDiffPix=pixelTolerance) return fitWcs
def approximateWcs(wcs, camera_wrapper=None, detector_name=None, obs_metadata=None, order=3, nx=20, ny=20, iterations=3, skyTolerance=0.001 * afwGeom.arcseconds, pixelTolerance=0.02): """Approximate an existing WCS as a TAN-SIP WCS The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box. @param[in] wcs wcs to approximate @param[in] camera_wrapper is an instantiation of GalSimCameraWrapper @param[in] detector_name is the name of the detector @param[in] obs_metadata is an ObservationMetaData characterizing the telescope pointing @param[in] order order of SIP fit @param[in] nx number of grid points along x @param[in] ny number of grid points along y @param[in] iterations number of times to iterate over fitting @param[in] skyTolerance maximum allowed difference in world coordinates between input wcs and approximate wcs (default is 0.001 arcsec) @param[in] pixelTolerance maximum allowed difference in pixel coordinates between input wcs and approximate wcs (default is 0.02 pixels) @return the fit TAN-SIP WCS """ tanWcs = wcs # create a matchList consisting of a grid of points covering the bbox refSchema = afwTable.SimpleTable.makeMinimalSchema() refCoordKey = afwTable.CoordKey(refSchema["coord"]) refCat = afwTable.SimpleCatalog(refSchema) sourceSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) sourceCat = afwTable.SourceCatalog(sourceSchema) # 20 March 2017 # the 'try' block is how it works in swig; # the 'except' block is how it works in pybind11 try: matchList = afwTable.ReferenceMatchVector() except AttributeError: matchList = [] bbox = camera_wrapper.getBBox(detector_name) bboxd = afwGeom.Box2D(bbox) for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx): for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny): pixelPos = afwGeom.Point2D(x, y) ra, dec = camera_wrapper.raDecFromPixelCoords( np.array([x]), np.array([y]), detector_name, obs_metadata=obs_metadata, epoch=2000.0, includeDistortion=True) skyCoord = afwGeom.SpherePoint(ra[0], dec[0], LsstGeom.degrees) refObj = refCat.addNew() refObj.set(refCoordKey, skyCoord) source = sourceCat.addNew() source.set(sourceCentroidKey, pixelPos) matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0)) # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge for indx in range(iterations): sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox) tanWcs = sipObject.getNewWcs() fitWcs = sipObject.getNewWcs() return fitWcs
def approximateWcs(wcs, bbox, camera=None, detector=None, obs_metadata=None, order=3, nx=20, ny=20, iterations=3, skyTolerance=0.001 * afwGeom.arcseconds, pixelTolerance=0.02, useTanWcs=False): """Approximate an existing WCS as a TAN-SIP WCS The fit is performed by evaluating the WCS at a uniform grid of points within a bounding box. @param[in] wcs wcs to approximate @param[in] bbox the region over which the WCS will be fit @param[in] camera is an instantiation of afw.cameraGeom.camera @param[in] detector is a detector from camera @param[in] obs_metadata is an ObservationMetaData characterizing the telescope pointing @param[in] order order of SIP fit @param[in] nx number of grid points along x @param[in] ny number of grid points along y @param[in] iterations number of times to iterate over fitting @param[in] skyTolerance maximum allowed difference in world coordinates between input wcs and approximate wcs (default is 0.001 arcsec) @param[in] pixelTolerance maximum allowed difference in pixel coordinates between input wcs and approximate wcs (default is 0.02 pixels) @param[in] useTanWcs send a TAN version of wcs to the fitter? It is documented to require that, but I don't think the fitter actually cares @return the fit TAN-SIP WCS """ if useTanWcs: crCoord = wcs.getSkyOrigin() crPix = wcs.getPixelOrigin() cdMat = wcs.getCDMatrix() tanWcs = afwImage.makeWcs(crCoord, crPix, cdMat[0, 0], cdMat[0, 1], cdMat[1, 0], cdMat[1, 1]) else: tanWcs = wcs # create a matchList consisting of a grid of points covering the bbox refSchema = afwTable.SimpleTable.makeMinimalSchema() refCoordKey = afwTable.CoordKey(refSchema["coord"]) refCat = afwTable.SimpleCatalog(refSchema) sourceSchema = afwTable.SourceTable.makeMinimalSchema() SingleFrameMeasurementTask(schema=sourceSchema) # expand the schema sourceCentroidKey = afwTable.Point2DKey(sourceSchema["slot_Centroid"]) sourceCat = afwTable.SourceCatalog(sourceSchema) # 20 March 2017 # the 'try' block is how it works in swig; # the 'except' block is how it works in pybind11 try: matchList = afwTable.ReferenceMatchVector() except AttributeError: matchList = [] bboxd = afwGeom.Box2D(bbox) for x in np.linspace(bboxd.getMinX(), bboxd.getMaxX(), nx): for y in np.linspace(bboxd.getMinY(), bboxd.getMaxY(), ny): pixelPos = afwGeom.Point2D(x, y) ra, dec = raDecFromPixelCoords(np.array([x]), np.array([y]), [detector.getName()], camera=camera, obs_metadata=obs_metadata, epoch=2000.0, includeDistortion=True) skyCoord = afwCoord.Coord(afwGeom.Point2D(ra[0], dec[0])) refObj = refCat.addNew() refObj.set(refCoordKey, skyCoord) source = sourceCat.addNew() source.set(sourceCentroidKey, pixelPos) matchList.append(afwTable.ReferenceMatch(refObj, source, 0.0)) # The TAN-SIP fitter is fitting x and y separately, so we have to iterate to make it converge for indx in range(iterations): sipObject = makeCreateWcsWithSip(matchList, tanWcs, order, bbox) tanWcs = sipObject.getNewWcs() fitWcs = sipObject.getNewWcs() return fitWcs
def singleTestInstance(self, filename, distortFunc, doPlot=False): sourceCat = self.loadSourceCatalog(self.filename) refCat = self.computePosRefCatalog(sourceCat) distortedCat = distort.distortList(sourceCat, distortFunc) if doPlot: import matplotlib.pyplot as plt undistorted = [ self.wcs.skyToPixel( self.distortedWcs.pixelToSky(ss.getCentroid())) for ss in distortedCat ] refs = [self.wcs.skyToPixel(ss.getCoord()) for ss in refCat] def plot(catalog, symbol): plt.plot([ss.getX() for ss in catalog], [ss.getY() for ss in catalog], symbol) plot(distortedCat, 'b+') # Distorted positions: blue + plot(undistorted, 'g+') # Undistorted positions: green + plot(refs, 'rx') # Reference catalog: red x # The green + should overlap with the red x, because that's how # MatchPessimisticB does it. plt.show() sourceCat = distortedCat matchRes = self.MatchPessimisticB.matchObjectsToSources( refCat=refCat, sourceCat=sourceCat, wcs=self.distortedWcs, refFluxField="r_flux", ) matches = matchRes.matches if doPlot: measAstrom.plotAstrometry(matches=matches, refCat=refCat, sourceCat=sourceCat) self.assertEqual(len(matches), 183) refCoordKey = afwTable.CoordKey(refCat.schema["coord"]) srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"]) refCentroidKey = afwTable.Point2DKey(refCat.getSchema()["centroid"]) maxDistErr = afwGeom.Angle(0) for refObj, source, distRad in matches: sourceCoord = source.get(srcCoordKey) refCoord = refObj.get(refCoordKey) predDist = sourceCoord.angularSeparation(refCoord) distErr = abs(predDist - distRad * afwGeom.radians) maxDistErr = max(distErr, maxDistErr) if refObj.getId() != source.getId(): refCentroid = refObj.get(refCentroidKey) sourceCentroid = source.getCentroid() radius = math.hypot(*(refCentroid - sourceCentroid)) self.fail( "ID mismatch: %s at %s != %s at %s; error = %0.1f pix" % (refObj.getId(), refCentroid, source.getId(), sourceCentroid, radius)) self.assertLess(maxDistErr.asArcseconds(), 1e-7)
def singleTestInstance(self, filename, distortFunc, doPlot=False): sourceCat = self.loadSourceCatalog(self.filename) refCat = self.computePosRefCatalog(sourceCat) # Apply source selector to sourceCat, using the astrometry config defaults tempConfig = measAstrom.AstrometryTask.ConfigClass() tempConfig.matcher.retarget(measAstrom.MatchOptimisticBTask) tempConfig.sourceSelector["matcher"].excludePixelFlags = False tempSolver = measAstrom.AstrometryTask(config=tempConfig, refObjLoader=None) sourceSelection = tempSolver.sourceSelector.run(sourceCat) distortedCat = distort.distortList(sourceSelection.sourceCat, distortFunc) if doPlot: import matplotlib.pyplot as plt undistorted = [ self.wcs.skyToPixel( self.distortedWcs.pixelToSky(ss.getCentroid())) for ss in distortedCat ] refs = [self.wcs.skyToPixel(ss.getCoord()) for ss in refCat] def plot(catalog, symbol): plt.plot([ss.getX() for ss in catalog], [ss.getY() for ss in catalog], symbol) # plot(sourceCat, 'k+') # Original positions: black + plot(distortedCat, 'b+') # Distorted positions: blue + plot(undistorted, 'g+') # Undistorted positions: green + plot(refs, 'rx') # Reference catalog: red x # The green + should overlap with the red x, because that's how matchOptimisticB does it. # The black + happens to overlap with those also, but that's beside the point. plt.show() sourceCat = distortedCat matchRes = self.matchOptimisticB.matchObjectsToSources( refCat=refCat, sourceCat=sourceCat, wcs=self.distortedWcs, sourceFluxField='slot_ApFlux_instFlux', refFluxField="r_flux", ) matches = matchRes.matches if doPlot: measAstrom.plotAstrometry(matches=matches, refCat=refCat, sourceCat=sourceCat) self.assertEqual(len(matches), 183) refCoordKey = afwTable.CoordKey(refCat.schema["coord"]) srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"]) refCentroidKey = afwTable.Point2DKey(refCat.getSchema()["centroid"]) maxDistErr = 0 * lsst.geom.radians for refObj, source, distRad in matches: sourceCoord = source.get(srcCoordKey) refCoord = refObj.get(refCoordKey) predDist = sourceCoord.separation(refCoord) distErr = abs(predDist - distRad * lsst.geom.radians) maxDistErr = max(distErr, maxDistErr) if refObj.getId() != source.getId(): refCentroid = refObj.get(refCentroidKey) sourceCentroid = source.getCentroid() radius = math.hypot(*(refCentroid - sourceCentroid)) self.fail( "ID mismatch: %s at %s != %s at %s; error = %0.1f pix" % (refObj.getId(), refCentroid, source.getId(), sourceCentroid, radius)) self.assertLess(maxDistErr.asArcseconds(), 1e-7)