def testFromGrid(self): outOrder = 8 inOrder = 2 toInvert = makeRandomScaledPolynomialTransform(inOrder) bbox = lsst.afw.geom.Box2D(lsst.afw.geom.Point2D(432, -671), lsst.afw.geom.Point2D(527, -463)) fitter = ScaledPolynomialTransformFitter.fromGrid(outOrder, bbox, 50, 50, toInvert) fitter.fit(outOrder) fitter.updateModel() data = fitter.getData() result = fitter.getTransform() inputKey = lsst.afw.table.Point2DKey(data.schema["input"]) outputKey = lsst.afw.table.Point2DKey(data.schema["output"]) for record in data: self.assertFloatsAlmostEqual(np.array(record.get(inputKey)), np.array(toInvert(record.get(outputKey)))) self.assertFloatsAlmostEqual(np.array(result(record.get(inputKey))), np.array(record.get(outputKey)), rtol=1E-2) # even at much higher order, inverse can't be perfect.
def testFromGrid(self): outOrder = 8 inOrder = 2 toInvert = makeRandomScaledPolynomialTransform(inOrder) bbox = lsst.geom.Box2D(lsst.geom.Point2D(432, -671), lsst.geom.Point2D(527, -463)) fitter = ScaledPolynomialTransformFitter.fromGrid(outOrder, bbox, 50, 50, toInvert) fitter.fit(outOrder) fitter.updateModel() data = fitter.getData() result = fitter.getTransform() inputKey = lsst.afw.table.Point2DKey(data.schema["input"]) outputKey = lsst.afw.table.Point2DKey(data.schema["output"]) for record in data: self.assertFloatsAlmostEqual(np.array(record.get(inputKey)), np.array(toInvert(record.get(outputKey)))) self.assertFloatsAlmostEqual(np.array(result(record.get(inputKey))), np.array(record.get(outputKey)), rtol=1E-2) # even at much higher order, inverse can't be perfect.
def testFromMatches(self): # Setup artifical matches that correspond to a known (random) PolynomialTransform. order = 3 truePoly = makeRandomPolynomialTransform(order) crval = lsst.afw.coord.IcrsCoord(lsst.afw.geom.Point2D(35.0, 10.0), lsst.afw.geom.degrees) crpix = lsst.afw.geom.Point2D(50, 50) cd = lsst.afw.geom.LinearTransform.makeScaling( (0.2 * lsst.afw.geom.arcseconds).asDegrees()) initialWcs = lsst.afw.image.makeWcs(crval, crpix, cd[cd.XX], cd[cd.XY], cd[cd.YX], cd[cd.YY]) bbox = lsst.afw.geom.Box2D( (lsst.afw.geom.Point2D(crval.getPosition(lsst.afw.geom.arcseconds)) - lsst.afw.geom.Extent2D(20, 20)), (lsst.afw.geom.Point2D(crval.getPosition(lsst.afw.geom.arcseconds)) + lsst.afw.geom.Extent2D(20, 20))) srcSchema = lsst.afw.table.SourceTable.makeMinimalSchema() srcPosKey = lsst.afw.table.Point2DKey.addFields( srcSchema, "pos", "source position", "pix") srcErrKey = lsst.afw.table.CovarianceMatrix2fKey.addFields( srcSchema, "pos", ["x", "y"], ["pix", "pix"]) srcSchema.getAliasMap().set("slot_Centroid", "pos") nPoints = 10 trueSrc = lsst.afw.table.SourceCatalog(srcSchema) trueSrc.reserve(nPoints) measSrc = lsst.afw.table.SourceCatalog(srcSchema) measSrc.reserve(nPoints) ref = lsst.afw.table.SimpleCatalog( lsst.afw.table.SimpleTable.makeMinimalSchema()) ref.reserve(nPoints) refCoordKey = ref.getCoordKey() errScaling = 1E-14 matches = [] for i in range(nPoints): refRec = ref.addNew() skyPos = lsst.afw.geom.Point2D( np.random.uniform(low=bbox.getMinX(), high=bbox.getMaxX()), np.random.uniform(low=bbox.getMinY(), high=bbox.getMaxY())) skyCoord = lsst.afw.coord.IcrsCoord(skyPos, lsst.afw.geom.arcseconds) refRec.set(refCoordKey, skyCoord) trueRec = trueSrc.addNew() truePos = truePoly( initialWcs.skyToIntermediateWorldCoord(skyCoord)) trueRec.set(srcPosKey, truePos) measRec = measSrc.addNew() covSqrt = np.random.randn(3, 2) cov = (errScaling * (np.dot(covSqrt.transpose(), covSqrt) + np.diag([1.0, 1.0]))).astype(np.float32) # We don't actually perturb positions according to noise level, as # this makes it much harder to test that the result agrees with # what we put in. measPos = truePos measRec.set(srcPosKey, measPos) measRec.set(srcErrKey, cov) match = lsst.afw.table.ReferenceMatch( refRec, measRec, (measPos - truePos).computeNorm()) matches.append(match) # Construct a fitter, and verify that the internal catalog it constructs is what we expect. fitter = ScaledPolynomialTransformFitter.fromMatches( order, matches, initialWcs, 0.0) expected = lsst.meas.astrom.compose( fitter.getOutputScaling(), lsst.meas.astrom.compose(truePoly, fitter.getInputScaling().invert())) data = fitter.getData() dataOutKey = lsst.afw.table.Point2DKey(data.schema["src"]) dataInKey = lsst.afw.table.Point2DKey(data.schema["intermediate"]) dataErrKey = lsst.afw.table.CovarianceMatrix2fKey( data.schema["src"], ["x", "y"]) scaledInBBox = lsst.afw.geom.Box2D() scaledOutBBox = lsst.afw.geom.Box2D() vandermonde = np.zeros((nPoints, (order + 1) * (order + 2) // 2), dtype=float) for i, (match, dataRec, trueRec) in enumerate(zip(matches, data, trueSrc)): self.assertEqual(match.second.getX(), dataRec.get("src_x")) self.assertEqual(match.second.getY(), dataRec.get("src_y")) self.assertEqual(match.first.getId(), dataRec.get("ref_id")) self.assertEqual(match.second.getId(), dataRec.get("src_id")) refPos = initialWcs.skyToIntermediateWorldCoord( match.first.getCoord()) self.assertEqual(refPos.getX(), dataRec.get("intermediate_x")) self.assertEqual(refPos.getY(), dataRec.get("intermediate_y")) self.assertFloatsAlmostEqual(match.second.get(srcErrKey), dataRec.get(dataErrKey), rtol=1E-7) scaledIn = fitter.getInputScaling()(dataRec.get(dataInKey)) scaledOut = fitter.getOutputScaling()(dataRec.get(dataOutKey)) scaledInBBox.include(scaledIn) scaledOutBBox.include(scaledOut) self.assertFloatsAlmostEqual(np.array(expected(scaledIn)), np.array(scaledOut), rtol=1E-7) j = 0 for n in range(order + 1): for p in range(n + 1): q = n - p vandermonde[i, j] = scaledIn.getX()**p * scaledIn.getY()**q j += 1 # Verify that scaling transforms move inputs and outputs into [-1, 1] self.assertFloatsAlmostEqual(scaledInBBox.getMinX(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledInBBox.getMinY(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledInBBox.getMaxX(), 1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledInBBox.getMaxY(), 1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMinX(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMinY(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMaxX(), 1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMaxY(), 1.0, rtol=1E-12) # Run the fitter, and check that we get out approximately what we put in. fitter.fit(order) fitter.updateModel() # Check the transformed input points. self.assertFloatsAlmostEqual(data.get("model_x"), trueSrc.getX(), rtol=1E-15) self.assertFloatsAlmostEqual(data.get("model_y"), trueSrc.getY(), rtol=1E-15) # Check the actual transform's coefficients (after composing in the scaling, which is # a lot of the reason we lose a lot of precision here). fittedPoly = lsst.meas.astrom.PolynomialTransform.convert( fitter.getTransform()) self.assertFloatsAlmostEqual(fittedPoly.getXCoeffs(), truePoly.getXCoeffs(), rtol=1E-5, atol=1E-5) self.assertFloatsAlmostEqual(fittedPoly.getYCoeffs(), truePoly.getYCoeffs(), rtol=1E-5, atol=1E-5)
def testFromMatches(self): # Setup artifical matches that correspond to a known (random) PolynomialTransform. order = 3 truePoly = makeRandomPolynomialTransform(order) crval = lsst.afw.geom.SpherePoint(35.0, 10.0, lsst.afw.geom.degrees) crpix = lsst.afw.geom.Point2D(50, 50) cd = lsst.afw.geom.LinearTransform.makeScaling((0.2*lsst.afw.geom.arcseconds).asDegrees()).getMatrix() initialWcs = lsst.afw.geom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cd) bbox = lsst.afw.geom.Box2D( crval.getPosition(lsst.afw.geom.arcseconds) - lsst.afw.geom.Extent2D(20, 20), crval.getPosition(lsst.afw.geom.arcseconds) + lsst.afw.geom.Extent2D(20, 20), ) srcSchema = lsst.afw.table.SourceTable.makeMinimalSchema() srcPosKey = lsst.afw.table.Point2DKey.addFields(srcSchema, "pos", "source position", "pix") srcErrKey = lsst.afw.table.CovarianceMatrix2fKey.addFields(srcSchema, "pos", ["x", "y"], ["pix", "pix"]) srcSchema.getAliasMap().set("slot_Centroid", "pos") nPoints = 10 trueSrc = lsst.afw.table.SourceCatalog(srcSchema) trueSrc.reserve(nPoints) measSrc = lsst.afw.table.SourceCatalog(srcSchema) measSrc.reserve(nPoints) ref = lsst.afw.table.SimpleCatalog(lsst.afw.table.SimpleTable.makeMinimalSchema()) ref.reserve(nPoints) refCoordKey = ref.getCoordKey() errScaling = 1E-14 matches = [] initialIwcToSky = lsst.afw.geom.getIntermediateWorldCoordsToSky(initialWcs) for i in range(nPoints): refRec = ref.addNew() raDeg, decDeg = ( np.random.uniform(low=bbox.getMinX(), high=bbox.getMaxX()), np.random.uniform(low=bbox.getMinY(), high=bbox.getMaxY()), ) skyCoord = lsst.afw.geom.SpherePoint(raDeg, decDeg, lsst.afw.geom.arcseconds) refRec.set(refCoordKey, skyCoord) trueRec = trueSrc.addNew() truePos = truePoly(initialIwcToSky.applyInverse(skyCoord)) trueRec.set(srcPosKey, truePos) measRec = measSrc.addNew() covSqrt = np.random.randn(3, 2) cov = (errScaling*(np.dot(covSqrt.transpose(), covSqrt) + np.diag([1.0, 1.0]))).astype(np.float32) # We don't actually perturb positions according to noise level, as # this makes it much harder to test that the result agrees with # what we put in. measPos = truePos measRec.set(srcPosKey, measPos) measRec.set(srcErrKey, cov) match = lsst.afw.table.ReferenceMatch(refRec, measRec, (measPos - truePos).computeNorm()) matches.append(match) # Construct a fitter, and verify that the internal catalog it constructs is what we expect. fitter = ScaledPolynomialTransformFitter.fromMatches(order, matches, initialWcs, 0.0) expected = lsst.meas.astrom.compose( fitter.getOutputScaling(), lsst.meas.astrom.compose(truePoly, fitter.getInputScaling().inverted()) ) data = fitter.getData() dataOutKey = lsst.afw.table.Point2DKey(data.schema["src"]) dataInKey = lsst.afw.table.Point2DKey(data.schema["intermediate"]) dataErrKey = lsst.afw.table.CovarianceMatrix2fKey(data.schema["src"], ["x", "y"]) scaledInBBox = lsst.afw.geom.Box2D() scaledOutBBox = lsst.afw.geom.Box2D() vandermonde = np.zeros((nPoints, (order + 1)*(order + 2)//2), dtype=float) for i, (match, dataRec, trueRec) in enumerate(zip(matches, data, trueSrc)): self.assertEqual(match.second.getX(), dataRec.get("src_x")) self.assertEqual(match.second.getY(), dataRec.get("src_y")) self.assertEqual(match.first.getId(), dataRec.get("ref_id")) self.assertEqual(match.second.getId(), dataRec.get("src_id")) refPos = initialIwcToSky.applyInverse(match.first.getCoord()) self.assertEqual(refPos.getX(), dataRec.get("intermediate_x")) self.assertEqual(refPos.getY(), dataRec.get("intermediate_y")) self.assertFloatsAlmostEqual(match.second.get(srcErrKey), dataRec.get(dataErrKey), rtol=1E-7) scaledIn = fitter.getInputScaling()(dataRec.get(dataInKey)) scaledOut = fitter.getOutputScaling()(dataRec.get(dataOutKey)) scaledInBBox.include(scaledIn) scaledOutBBox.include(scaledOut) self.assertFloatsAlmostEqual(np.array(expected(scaledIn)), np.array(scaledOut), rtol=1E-7) j = 0 for n in range(order + 1): for p in range(n + 1): q = n - p vandermonde[i, j] = scaledIn.getX()**p * scaledIn.getY()**q j += 1 # Verify that scaling transforms move inputs and outputs into [-1, 1] self.assertFloatsAlmostEqual(scaledInBBox.getMinX(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledInBBox.getMinY(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledInBBox.getMaxX(), 1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledInBBox.getMaxY(), 1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMinX(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMinY(), -1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMaxX(), 1.0, rtol=1E-12) self.assertFloatsAlmostEqual(scaledOutBBox.getMaxY(), 1.0, rtol=1E-12) # Run the fitter, and check that we get out approximately what we put in. fitter.fit(order) fitter.updateModel() # Check the transformed input points. self.assertFloatsAlmostEqual(data.get("model_x"), trueSrc.getX(), rtol=1E-15) self.assertFloatsAlmostEqual(data.get("model_y"), trueSrc.getY(), rtol=1E-15) # Check the actual transform's coefficients (after composing in the scaling, which is # a lot of the reason we lose a lot of precision here). fittedPoly = lsst.meas.astrom.PolynomialTransform.convert(fitter.getTransform()) self.assertFloatsAlmostEqual(fittedPoly.getXCoeffs(), truePoly.getXCoeffs(), rtol=1E-5, atol=1E-5) self.assertFloatsAlmostEqual(fittedPoly.getYCoeffs(), truePoly.getYCoeffs(), rtol=1E-5, atol=1E-5)