def setUp(self): self.crval = afwCoord.IcrsCoord(afwGeom.PointD(44., 45.)) self.crpix = afwGeom.Point2D(15000, 4000) arcsecPerPixel = 1 / 3600.0 CD11 = arcsecPerPixel CD12 = 0 CD21 = 0 CD22 = arcsecPerPixel self.wcs = afwImage.makeWcs(self.crval, self.crpix, CD11, CD12, CD21, CD22) refSchema = afwTable.SimpleTable.makeMinimalSchema() self.refCentroidKey = afwTable.Point2DKey.addFields( refSchema, "centroid", "centroid", "pixels") self.refCoordKey = afwTable.CoordKey(refSchema["coord"]) self.refCat = afwTable.SimpleCatalog(refSchema) # an alias is required to make src.getCentroid() work; # simply defining a field named "slot_Centroid" doesn't suffice srcSchema = afwTable.SourceTable.makeMinimalSchema() self.srcCentroidKey = afwTable.Point2DKey.addFields( srcSchema, "base_SdssCentroid", "centroid", "pixels") srcAliases = srcSchema.getAliasMap() srcAliases.set("slot_Centroid", "base_SdssCentroid") self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"]) self.sourceCat = afwTable.SourceCatalog(srcSchema)
def setUp(self): self.crval = afwGeom.SpherePoint(44.0, 45.0, afwGeom.degrees) self.crpix = afwGeom.Point2D(15000, 4000) arcsecPerPixel = 1 / 3600.0 cdMatrix = afwGeom.makeCdMatrix(arcsecPerPixel * afwGeom.arcseconds) self.wcs = afwGeom.makeSkyWcs(crval=self.crval, crpix=self.crpix, cdMatrix=cdMatrix) refSchema = afwTable.SimpleTable.makeMinimalSchema() self.refCentroidKey = afwTable.Point2DKey.addFields( refSchema, "centroid", "centroid", "pixels") self.refCoordKey = afwTable.CoordKey(refSchema["coord"]) self.refHasCentroidKey = refSchema.addField("hasCentroid", type="Flag") self.refCat = afwTable.SimpleCatalog(refSchema) # an alias is required to make src.getCentroid() work; # simply defining a field named "slot_Centroid" doesn't suffice srcSchema = afwTable.SourceTable.makeMinimalSchema() self.srcCentroidKey = afwTable.Point2DKey.addFields( srcSchema, "base_SdssCentroid", "centroid", "pixels") srcAliases = srcSchema.getAliasMap() srcAliases.set("slot_Centroid", "base_SdssCentroid") self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"]) self.sourceCat = afwTable.SourceCatalog(srcSchema)
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 doTest(self, pixelsToTanPixels): """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) print("number of stars =", len(sourceCat)) config = ANetAstrometryTask.ConfigClass() solver = ANetAstrometryTask(config=config, refObjLoader=self.refObjLoader, schema=self.makeSourceSchema()) 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*lsst.geom.arcseconds, maxDiffPix=0.02) srcCoordKey = afwTable.CoordKey(sourceCat.schema["coord"]) refCoordKey = afwTable.CoordKey(results.refCat.schema["coord"]) maxAngSep = 0*lsst.geom.radians maxPixSep = 0 for refObj, src, d in results.matches: refCoord = refObj.get(refCoordKey) refPixPos = fitWcs.skyToPixel(refCoord) 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.005) self.assertLess(maxPixSep, 0.03) # try again, but without fitting the WCS config.forceKnownWcs = True solverNoFit = ANetAstrometryTask(config=config, refObjLoader=self.refObjLoader, schema=self.makeSourceSchema()) self.exposure.setWcs(distortedWcs) noFitResults = solverNoFit.run( sourceCat=sourceCat, exposure=self.exposure, ) self.assertGreater(len(noFitResults.refCat), 300)
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 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 setMatchDistance(matches): """Set the distance field of the matches in a match list to the distance in radians on the sky @warning the coord field of the source in each match must be correct @param[in,out] matches a list of matches, an instance of lsst.afw.table.ReferenceMatch reads the coord field of the source and reference object of each match writes the distance field of each match """ if len(matches) < 1: return sourceCoordKey = afwTable.CoordKey(matches[0].first.schema["coord"]) refObjCoordKey = afwTable.CoordKey(matches[0].second.schema["coord"]) for match in matches: sourceCoord = match.first.get(sourceCoordKey) refObjCoord = match.second.get(refObjCoordKey) match.distance = refObjCoord.separation(sourceCoord).asRadians()
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 testInterface(self): obsTestDir = lsst.utils.getPackageDir('obs_test') inputDir = os.path.join(obsTestDir, "data", "input") # Configure a ProcessCcd task such that it will return a minimal # number of measurements plus our test plugin. cfg = ProcessCcdConfig() cfg.calibrate.measurement.plugins.names = ["base_SdssCentroid", "base_SkyCoord", PLUGIN_NAME] cfg.calibrate.measurement.slots.shape = None cfg.calibrate.measurement.slots.psfFlux = None cfg.calibrate.measurement.slots.apFlux = None cfg.calibrate.measurement.slots.gaussianFlux = None cfg.calibrate.measurement.slots.modelFlux = None cfg.calibrate.measurement.slots.calibFlux = None # no reference catalog, so... cfg.calibrate.doAstrometry = False cfg.calibrate.doPhotoCal = False # disable aperture correction because we aren't measuring aperture flux cfg.calibrate.doApCorr = False # Extendedness requires modelFlux, disabled above. cfg.calibrate.catalogCalculation.plugins.names.discard("base_ClassificationExtendedness") # Process the test data with ProcessCcd then perform a transform. with tempDirectory() as tempDir: measResult = ProcessCcdTask.parseAndRun(args=[inputDir, "--output", tempDir, "--id", "visit=1"], config=cfg, doReturnResults=True) trArgs = [tempDir, "--output", tempDir, "--id", "visit=1", "-c", "inputConfigType=processCcd_config"] trResult = SrcTransformTask.parseAndRun(args=trArgs, doReturnResults=True) # It should be possible to reprocess the data through a new transform task with exactly # the same configuration without throwing. This check is useful since we are # constructing the task on the fly, which could conceivably cause problems with # configuration/metadata persistence. trResult = SrcTransformTask.parseAndRun(args=trArgs, doReturnResults=True) measSrcs = measResult.resultList[0].result.calibRes.sourceCat trSrcs = trResult.resultList[0].result # The length of the measured and transformed catalogs should be the same. self.assertEqual(len(measSrcs), len(trSrcs)) # Each source should have been measured & transformed appropriately. for measSrc, trSrc in zip(measSrcs, trSrcs): # The TrivialMeasurement should be transformed as defined above. self.assertEqual(trSrc[PLUGIN_NAME], measSrc[PLUGIN_NAME]) self.assertEqual(trSrc[PLUGIN_NAME + "_transform"], -1.0 * measSrc[PLUGIN_NAME]) # The SdssCentroid should be transformed to celestial coordinates. # Checking that the full transformation has been done correctly is # out of scope for this test case; we just ensure that there's # plausible position in the transformed record. trCoord = afwTable.CoordKey(trSrcs.schema["base_SdssCentroid"]).get(trSrc) self.assertAlmostEqual(measSrc.getCoord().getLongitude(), trCoord.getLongitude()) self.assertAlmostEqual(measSrc.getCoord().getLatitude(), trCoord.getLatitude())
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 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 setMatchDistance(matches): """Set the distance field of the matches in a match list to the distance in radians on the sky. Parameters ---------- matches : `list` of `lsst.afw.table.ReferenceMatch` a list of matches, reads the coord field of the source and reference object of each match writes the distance field of each match Notes ----- .. warning:: the coord field of the source in each match must be correct """ if len(matches) < 1: return sourceCoordKey = afwTable.CoordKey(matches[0].first.schema["coord"]) refObjCoordKey = afwTable.CoordKey(matches[0].second.schema["coord"]) for match in matches: sourceCoord = match.first.get(sourceCoordKey) refObjCoord = match.second.get(refObjCoordKey) match.distance = refObjCoord.separation(sourceCoord).asRadians()
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 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 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)
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 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