Coverage for tests/test_fitAffineWcsTask.py: 24%
136 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-24 10:05 +0000
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-24 10:05 +0000
1# This file is part of meas_astrom.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22import math
23import unittest
25import numpy as np
27import lsst.pipe.base
28import lsst.utils.tests
29import lsst.geom
30import lsst.afw.geom as afwGeom
31import lsst.afw.table as afwTable
32from lsst.meas.algorithms import LoadReferenceObjectsTask
33from lsst.meas.base import SingleFrameMeasurementTask
34from lsst.meas.astrom import FitAffineWcsTask, TransformedSkyWcsMaker
37class BaseTestCase:
39 """A test case for FitAffineWcsTask.
41 Use involves setting one class attribute:
42 * MatchClass: match class, e.g. ReferenceMatch or SourceMatch
43 """
44 MatchClass = None
46 def setUp(self):
47 crval = lsst.geom.SpherePoint(44, 45, lsst.geom.degrees)
48 crpix = lsst.geom.Point2D(15000, 4000)
50 scale = 1 * lsst.geom.arcseconds
51 cdMatrix = afwGeom.makeCdMatrix(scale=scale, flipX=True)
52 self.tanWcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix)
53 self.loadData()
55 def loadData(self, rangePix=3000, numPoints=25):
56 """Load catalogs and make the match list
58 This is a separate function so data can be reloaded if fitting more than once
59 (each time a WCS is fit it may update the source catalog, reference catalog and match list)
60 """
61 refSchema = LoadReferenceObjectsTask.makeMinimalSchema(
62 filterNameList=["r"], addIsPhotometric=True, addCentroid=True)
63 self.refCat = afwTable.SimpleCatalog(refSchema)
64 srcSchema = afwTable.SourceTable.makeMinimalSchema()
65 SingleFrameMeasurementTask(schema=srcSchema)
66 self.srcCoordKey = afwTable.CoordKey(srcSchema["coord"])
67 self.srcCentroidKey = afwTable.Point2DKey(srcSchema["slot_Centroid"])
68 self.srcCentroidKey_xErr = srcSchema["slot_Centroid_xErr"].asKey()
69 self.srcCentroidKey_yErr = srcSchema["slot_Centroid_yErr"].asKey()
70 self.sourceCat = afwTable.SourceCatalog(srcSchema)
72 self.matches = []
74 for i in np.linspace(0., rangePix, numPoints):
75 for j in np.linspace(0., rangePix, numPoints):
76 src = self.sourceCat.addNew()
77 refObj = self.refCat.addNew()
79 src.set(self.srcCentroidKey, lsst.geom.Point2D(i, j))
80 src.set(self.srcCentroidKey_xErr, 0.1)
81 src.set(self.srcCentroidKey_yErr, 0.1)
83 c = self.tanWcs.pixelToSky(i, j)
84 refObj.setCoord(c)
86 self.matches.append(self.MatchClass(refObj, src, 0.0))
88 def tearDown(self):
89 del self.refCat
90 del self.sourceCat
91 del self.matches
92 del self.tanWcs
94 def checkResults(self, fitRes, catsUpdated):
95 """Check results.
96 """
97 self.assertLess(fitRes.scatterOnSky.asArcseconds(), 0.001)
98 affineWcs = fitRes.wcs
99 maxAngSep = 0*lsst.geom.radians
100 maxPixSep = 0
101 refCoordKey = afwTable.CoordKey(self.refCat.schema["coord"])
102 if catsUpdated:
103 refCentroidKey = afwTable.Point2DKey(self.refCat.schema["centroid"])
104 maxDistErr = 0*lsst.geom.radians
105 for refObj, src, distRad in self.matches:
106 srcPixPos = src.get(self.srcCentroidKey)
107 refCoord = refObj.get(refCoordKey)
108 if catsUpdated:
109 refPixPos = refObj.get(refCentroidKey)
110 srcCoord = src.get(self.srcCoordKey)
111 else:
112 refPixPos = affineWcs.skyToPixel(refCoord)
113 srcCoord = affineWcs.pixelToSky(srcPixPos)
115 angSep = refCoord.separation(srcCoord)
116 dist = distRad*lsst.geom.radians
117 distErr = abs(dist - angSep)
118 maxDistErr = max(maxDistErr, distErr)
119 maxAngSep = max(maxAngSep, angSep)
121 pixSep = math.hypot(*(srcPixPos - refPixPos))
122 maxPixSep = max(maxPixSep, pixSep)
124 print("max angular separation = %0.4f arcsec" % (maxAngSep.asArcseconds(),))
125 print("max pixel separation = %0.3f" % (maxPixSep,))
126 self.assertLess(maxAngSep.asArcseconds(), 0.001)
127 self.assertLess(maxPixSep, 0.005)
128 if catsUpdated:
129 allowedDistErr = 1e-7
130 else:
131 allowedDistErr = 0.001
132 self.assertLess(maxDistErr.asArcseconds(), allowedDistErr,
133 "Computed distance in match list is off by %s arcsec" % (maxDistErr.asArcseconds(),))
135 def doTest(self, name, func):
136 """Apply func(x, y) to each source in self.sourceCat, then fit and
137 check the resulting WCS.
138 """
139 bbox = lsst.geom.Box2I()
140 for refObj, src, d in self.matches:
141 origPos = src.get(self.srcCentroidKey)
142 x, y = func(*origPos)
143 distortedPos = lsst.geom.Point2D(x, y)
144 src.set(self.srcCentroidKey, distortedPos)
145 bbox.include(lsst.geom.Point2I(lsst.geom.Point2I(distortedPos)))
147 fitter = FitAffineWcsTask()
148 fitRes = fitter.fitWcs(
149 matches=self.matches,
150 initWcs=self.tanWcs,
151 bbox=bbox,
152 refCat=self.refCat,
153 sourceCat=self.sourceCat,
154 )
156 self.checkResults(fitRes, catsUpdated=True)
158 def doTestAffine(self, name, offset, matrix):
159 """Apply func(x, y) to each source in self.sourceCat, then fit and
160 check the resulting WCS.
161 """
162 wcsMaker = TransformedSkyWcsMaker(self.tanWcs)
164 newWcs = wcsMaker.makeWcs(offset, matrix)
165 bbox = lsst.geom.Box2I()
166 for refObj, src, d in self.matches:
167 origPos = src.get(self.srcCentroidKey)
168 newCoord = newWcs.pixelToSky(origPos)
169 src.setCoord(newCoord)
170 bbox.include(lsst.geom.Point2I(lsst.geom.Point2I(origPos)))
172 fitter = FitAffineWcsTask()
173 fitRes = fitter.fitWcs(
174 matches=self.matches,
175 initWcs=newWcs,
176 bbox=bbox,
177 refCat=self.refCat,
178 sourceCat=self.sourceCat,
179 )
181 self.checkResults(fitRes, catsUpdated=True)
183 def doTestAffineReverse(self, name, offset, matrix):
184 """Apply func(x, y) to each source in self.sourceCat, then fit and
185 check the resulting WCS.
186 """
187 wcsMaker = TransformedSkyWcsMaker(self.tanWcs)
189 newWcs = wcsMaker.makeWcs(offset, matrix)
190 bbox = lsst.geom.Box2I()
191 for refObj, src, d in self.matches:
192 origCoord = src.get(self.srcCoordKey)
193 newCentroid = newWcs.skyToPixel(origCoord)
194 src.set(self.srcCentroidKey, newCentroid)
195 bbox.include(lsst.geom.Point2I(lsst.geom.Point2I(newCentroid)))
197 fitter = FitAffineWcsTask()
198 fitRes = fitter.fitWcs(
199 matches=self.matches,
200 initWcs=self.tanWcs,
201 bbox=bbox,
202 refCat=self.refCat,
203 sourceCat=self.sourceCat,
204 )
206 self.checkResults(fitRes, catsUpdated=True)
209class SideLoadTestCases:
211 """Base class implementations of testing methods.
213 Explicitly does not inherit from unittest.TestCase"""
215 def testTrivial(self):
216 """Add no distortion"""
217 self.doTest("testTrivial", lambda x, y: (x, y))
219 def testOffset(self):
220 """Add an offset"""
221 self.doTest("testOffset", lambda x, y: (x + 5, y + 7))
223 def testSkyOffset(self):
224 """Add an on sky offset"""
225 self.doTestAffine("testSkyOffset",
226 [77, -200],
227 np.array([[1.0, 0.0], [0.0, 1.0]]))
228 self.doTestAffineReverse("testSkyOffsetRev",
229 [77, -200],
230 np.array([[1.0, 0.0], [0.0, 1.0]]))
232 def testAffine(self):
233 """Add an Affine (shear + scale + rot) distortion"""
234 self.doTestAffine("testAffine",
235 [0, 0],
236 np.array([[0.4, 0.1], [-0.21, 2.0]]))
237 self.doTestAffineReverse("testAffineRev",
238 [0, 0],
239 np.array([[0.4, 0.1], [-0.21, 2.0]]))
241 def testAffineAndOffset(self):
242 """Add a transform and offset"""
243 self.doTestAffine("testAffineAndOffset",
244 [30, 100],
245 np.array([[0.5, 0.01], [-0.2, 0.3]]))
246 self.doTestAffineReverse("testAffineAndOffsetRev",
247 [30, 100],
248 np.array([[0.5, 0.01], [-0.2, 0.3]]))
251# The test classes inherit from two base classes and differ in the match
252# class being used.
254class FitAffineWcsTaskTestCaseReferenceMatch(BaseTestCase,
255 SideLoadTestCases,
256 lsst.utils.tests.TestCase):
257 MatchClass = afwTable.ReferenceMatch
260class MemoryTester(lsst.utils.tests.MemoryTestCase):
261 pass
264def setup_module(module):
265 lsst.utils.tests.init()
268if __name__ == "__main__": 268 ↛ 269line 268 didn't jump to line 269, because the condition on line 268 was never true
269 lsst.utils.tests.init()
270 unittest.main()