Coverage for tests/test_astrometryTransform.py: 24%
121 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-11 12:20 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-11 12:20 +0000
1# This file is part of jointcal.
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/>.
22"""Tests of the AstrometryTransform objects and its helpers."""
23import numpy as np
25import unittest
26import lsst.utils.tests
28import lsst.geom
29import lsst.log
30import lsst.jointcal
31from lsst.jointcal.astrometryTransform import (AstrometryTransformLinear,
32 AstrometryTransformPolynomial, inversePolyTransform)
35class AstrometryTransformPolynomialBase:
36 def setUp(self):
37 self.longMessage = True
38 np.random.seed(100)
40 # TRACE level so we can see all the inverse iteration steps.
41 lsst.log.setLevel('', lsst.log.TRACE)
43 # default initialized gtansfoPoly should be the identity
44 self.polyIdentity = AstrometryTransformPolynomial(9)
46 self.poly2 = AstrometryTransformPolynomial(2)
47 # A "reasonable" polynomial from fitting some data.
48 poly2Str = "1\norder 2\n-0.167892516757 -5.19165870491e-05 2.0273225968e-07 2.75150063324e-11 -1.41377300894e-11 7.74131250823e-12 -0.355492046755 3.64496426108e-07 5.18028912201e-05 -1.38429955144e-11 -2.23437887691e-11 2.23415522405e-11 \n" # noqa: E501
49 self.poly2.read(poly2Str)
51 # NOTE: taken from the "visit" component of a model, so needs to be
52 # evaluated on a different bbox.
53 self.poly3 = AstrometryTransformPolynomial(3)
54 poly3Str = "1\norder 3\n-0.110057982995 -0.00384939195727 2.22344576374e-05 1.01023479109e-09 -5.2469672592e-09 1.50668618207e-09 1.46346365118e-09 -1.63797654003e-10 1.27721649626e-09 -2.05428888876e-12 -0.115317494814 2.21413899258e-05 0.00385039699775 -5.72570678996e-09 -9.59649028554e-09 1.41401351683e-08 -1.97886824954e-10 -1.30841763583e-09 -1.95789465504e-11 -1.26379172569e-09 " # noqa: E501
55 self.poly3.read(poly3Str)
57 self.poly9 = AstrometryTransformPolynomial(9)
58 # A "reasonable" polynomial from fitting some data.
59 poly9Str = "1\norder 9\n-0.16793632503 -5.19113500529e-05 4.6484637807e-07 1.41209689574e-09 -8.13063885535e-10 -5.52862561337e-10 -7.20917258572e-12 1.44268609502e-12 1.01158414497e-12 5.81014911789e-13 1.72417265293e-14 -1.39482997394e-15 -1.34837756422e-15 -5.5315543147e-16 -3.26263945732e-16 -2.27835189163e-17 1.54838068036e-19 1.36520025882e-18 4.39218993176e-19 1.09711054987e-19 1.07909937222e-19 1.75422369904e-20 9.46438635874e-22 -7.47653768123e-22 -4.21000295132e-22 -1.63261283932e-23 2.18164463104e-23 -2.29125892915e-23 -7.79605618485e-24 -8.96899627417e-25 1.8328850322e-25 2.429872115e-25 1.57383988209e-26 -1.25289729437e-26 -1.60798488421e-26 3.50802217715e-27 1.84635563103e-27 3.27562810981e-28 4.22037315014e-30 -5.54664361872e-29 -2.8639429519e-29 1.1383384424e-29 1.04059046024e-30 3.18765755999e-30 -3.9564850064e-31 -1.8028544084e-31 -3.82041218173e-32 -1.70229355716e-32 1.32310477371e-32 9.28363194124e-35 1.5990001923e-33 -1.31379494936e-33 6.88708291597e-35 -2.23831688762e-34 2.33466866348e-35 -0.355489287839 1.33735110546e-06 5.17833395824e-05 -6.95722643803e-09 -1.04122329759e-09 6.40573984039e-12 2.20559605231e-11 5.41569680032e-12 4.5540873349e-13 1.26533773481e-13 -3.78843184326e-14 -1.40950401053e-14 -1.79770054411e-15 -8.32768571948e-18 -2.33287234831e-16 3.78385302886e-17 2.09977398841e-17 2.56668227438e-18 6.94279232614e-19 -1.62606627953e-19 2.04795207418e-19 -2.24588539576e-20 -1.79824251677e-20 -2.83870287858e-21 -3.26896753863e-22 -2.59328106552e-22 1.11975449899e-22 -9.70654488549e-23 7.70881954106e-24 8.70008772668e-24 2.08267357514e-24 -3.206329176e-26 1.25458989726e-25 5.34275512133e-26 -3.49011241433e-26 2.55423596751e-26 -1.37582742645e-27 -2.21887269529e-27 -7.87749245556e-28 4.74161700401e-29 -2.40765221773e-29 -1.32783315748e-29 -7.20736097924e-30 5.46646062406e-30 -3.51762994693e-30 9.24376571391e-32 2.36641447777e-31 1.05246663621e-31 7.65324655394e-33 -1.07479816221e-32 6.75168688604e-33 -7.73208895042e-34 6.25567958067e-34 -3.54400370636e-34 1.97908370676e-34 \n" # noqa: E501
60 self.poly9.read(poly9Str)
62 # make a grid of points to evaluate on
63 self._makePoints(0, 1000, 0, 1000, 200)
65 def _makePoints(self, minX, maxX, minY, maxY, num):
66 """Sets self.points to a 2d grid of num points from min->max."""
67 self.frame = lsst.jointcal.frame.Frame(lsst.jointcal.star.Point(minX, minY),
68 lsst.jointcal.star.Point(maxX, maxY))
69 num = 200
70 xx = np.linspace(minX, maxX, num)
71 yy = np.linspace(minY, maxY, num)
72 self.points = []
73 for x in xx:
74 for y in yy:
75 self.points.append(lsst.geom.Point2D(x, y))
78class LinearTransformTestCase(AstrometryTransformPolynomialBase, lsst.utils.tests.TestCase):
79 def test_str(self):
80 """Check that the string representations of a linear transform is reasonable.
81 """
82 # an identity polynomial
83 linear = AstrometryTransformLinear()
84 expect = "1 0 + 0\n0 1 + 0"
85 self.assertIn(expect, str(linear))
87 # make the zeroth-order newy term non-zero
88 linear.setCoefficient(0, 0, 1, 2.0)
89 # make the first-order x term of newx zero
90 linear.setCoefficient(1, 0, 0, 0)
91 expect = "0 0 + 0\n0 1 + 2"
92 self.assertIn(expect, str(linear))
95class InversePolyTransformTestCase(AstrometryTransformPolynomialBase, lsst.utils.tests.TestCase):
96 def checkInverse(self, poly, inverse, maxDiff):
97 """Test that ``astrometryTransformPolynomial(inverse(point))==point`` to within maxDiff."""
98 results = []
99 for point in self.points:
100 # TODO: Fix these "Point"s once DM-4044 is done.
101 tempPoint = lsst.jointcal.star.Point(point[0], point[1])
102 result = inverse.apply(poly.apply(tempPoint))
103 results.append(lsst.geom.Point2D(result.x, result.y))
105 self.assertPairListsAlmostEqual(results, self.points, maxDiff=maxDiff)
107 def testInversePolyIdentity(self):
108 precision = 1e-8
109 inverse = inversePolyTransform(self.polyIdentity, self.frame, precision)
110 self.checkInverse(self.polyIdentity, inverse, precision)
112 def testInversePoly2(self):
113 precision = 1e-6
114 inverse = inversePolyTransform(self.poly2, self.frame, precision)
115 self.checkInverse(self.poly2, inverse, 1e-7)
117 def testInversePoly3(self):
118 # Different bbox for this one, because it is a focal plane to tangent plane transform.
119 minX = 14.6927
120 maxX = 42.38
121 minY = -62.5486
122 maxY = -0.323848
123 self._makePoints(minX, maxX, minY, maxY, 200)
125 precision = 1e-7
126 inverse = inversePolyTransform(self.poly3, self.frame, precision, maxOrder=5)
127 self.checkInverse(self.poly3, inverse, 3e-8)
129 def testInversePoly9(self):
130 precision = 1e-6
131 inverse = inversePolyTransform(self.poly9, self.frame, precision,
132 maxOrder=11, nSteps=100)
133 self.checkInverse(self.poly9, inverse, 4e-5)
135 def testNotEnoughPoints(self):
136 with self.assertRaises(RuntimeError):
137 inversePolyTransform(self.poly2, self.frame, 1e-4, nSteps=2)
140class AstrometryTransformPolynomialTestCase(AstrometryTransformPolynomialBase, lsst.utils.tests.TestCase):
141 def checkToAstMap(self, poly, inverseMaxDiff=1e-6):
142 """Test that AstrometryTransformPolynomial.toAstMap() gives accurate results.
144 Parameters
145 ----------
146 poly : `lsst.jointcal.astrometryTransform.AstrometryTransformPolynomial`
147 The polynomial to be tested.
148 inverseMaxDiff : `float`
149 Required accuracy on inverse polynomial.
150 See `lsst.afw.geom.utils.assertPairsAlmostEqual`.
151 """
152 # maxDiff should be small, because this should be a near-exact conversion,
153 # modulo implementation details.
154 maxDiff = 1e-10
156 astMap = poly.toAstMap(self.frame)
157 expects = []
158 forwards = []
159 inverses = []
160 for point in self.points:
161 # TODO: Fix these "Point"s once DM-4044 is done.
162 tempPoint = lsst.jointcal.star.Point(point[0], point[1])
163 expect = poly.apply(tempPoint)
164 expects.append(lsst.geom.Point2D(expect.x, expect.y))
165 result = astMap.applyForward(point)
166 forwards.append(result)
167 inverses.append(astMap.applyInverse(result))
169 self.assertPairListsAlmostEqual(forwards, expects, maxDiff=maxDiff)
170 self.assertPairListsAlmostEqual(inverses, self.points, maxDiff=inverseMaxDiff)
172 def testToAstMapIdentity(self):
173 self.checkToAstMap(self.polyIdentity)
175 def testToAstMapOrder2(self):
176 self.checkToAstMap(self.poly2)
178 def testToAstMapOrder3(self):
179 # Different bbox for this one, because it was fit on a focal plane.
180 minX = 14.6927
181 maxX = 42.38
182 minY = -62.5486
183 maxY = -0.323848
184 self._makePoints(minX, maxX, minY, maxY, 200)
186 self.checkToAstMap(self.poly3, inverseMaxDiff=5e-6)
188 def testToAstMapOrder9(self):
189 # looser tolerance: 9th order polynomials are harder to get a good inverse for.
190 self.checkToAstMap(self.poly9, inverseMaxDiff=5e-5)
192 def test_str(self):
193 """Check that the string representations of polynomials are reasonable.
194 """
195 # an identity polynomial
196 poly2 = AstrometryTransformPolynomial(2)
197 expect = "newx = 1*x\nnewy = 1*y"
198 self.assertIn(expect, str(poly2))
200 # make the zeroth-order newy term non-zero
201 poly2.setCoefficient(0, 0, 1, 2.0)
202 # make the first-order x term of newx zero
203 poly2.setCoefficient(1, 0, 0, 0)
204 expect = "newx = 0\nnewy = 2 + 1*y"
205 self.assertIn(expect, str(poly2))
207 # make the second-order y term of newx non-zero
208 poly2.setCoefficient(0, 2, 0, 2.0)
209 expect = "newx = 2*y^2\nnewy = 2 + 1*y"
210 self.assertIn(expect, str(poly2))
213class MemoryTester(lsst.utils.tests.MemoryTestCase):
214 pass
217def setup_module(module):
218 lsst.utils.tests.init()
221if __name__ == "__main__": 221 ↛ 222line 221 didn't jump to line 222, because the condition on line 221 was never true
222 lsst.utils.tests.init()
223 unittest.main()