Coverage for tests/test_sipApproximation.py: 14%
135 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-27 02:52 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-27 02:52 -0700
1#
2# Developed for the LSST Data Management System.
3# This product includes software developed by the LSST Project
4# (https://www.lsst.org).
5# See the COPYRIGHT file at the top-level directory of this distribution
6# for details of code ownership.
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation, either version 3 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program. If not, see <https://www.gnu.org/licenses/>.
20#
22import os
23import unittest
24import numpy as np
25from numpy.testing import assert_allclose
26import lsst.utils.tests
27from lsst.daf.base import PropertyList
28from lsst.geom import Point2D, Point2I, Extent2I, Box2D, Box2I, SpherePoint, radians
29from lsst.afw.geom import (SipApproximation, makeSkyWcs, getPixelToIntermediateWorldCoords, SkyWcs,
30 calculateSipWcsHeader)
33def makePropertyListFromDict(md):
34 result = PropertyList()
35 for k, v in md.items():
36 result.add(k, v)
37 return result
40def extractCtorArgs(md):
41 wcs = makeSkyWcs(makePropertyListFromDict(md))
42 kwds = {
43 "pixelToIwc": getPixelToIntermediateWorldCoords(wcs),
44 "bbox": Box2D(Box2I(Point2I(0, 0), Extent2I(md["NAXES1"], md["NAXES2"]))),
45 "crpix": Point2D(md["CRPIX1"] - 1.0, md["CRPIX2"] - 1.0), # -1 for LSST vs. FITS conventions
46 "cd": np.array([[md["CD1_1"], md["CD1_2"]],
47 [md["CD2_1"], md["CD2_2"]]]),
48 }
49 return kwds
52def packedRange(maxOrder, minOrder=0):
53 for n in range(minOrder, maxOrder + 1):
54 for p in range(n + 1):
55 yield p, n - p
58class SipApproximationTestCases(lsst.utils.tests.TestCase):
60 def setUp(self):
61 self.random = np.random.RandomState(1)
62 # A TAN projection declared as SIP whose PIXELS<->IWC transform should
63 # be the identity map.
64 self.identity = {
65 'CD1_1': 1.0,
66 'CD1_2': 0.0,
67 'CD2_1': 0.0,
68 'CD2_2': 1.0,
69 'CRPIX1': 1.0,
70 'CRPIX2': 1.0,
71 'CRVAL1': 60.0,
72 'CRVAL2': 40.0,
73 'CTYPE1': 'RA---TAN-SIP',
74 'CTYPE2': 'DEC--TAN-SIP',
75 'CUNIT1': 'deg',
76 'CUNIT2': 'deg',
77 'EQUINOX': 2000.0,
78 'NAXES1': 2048,
79 'NAXES2': 4176,
80 'NAXIS': 2,
81 'RADESYS': 'FK5',
82 'A_ORDER': 0,
83 'B_ORDER': 0,
84 'AP_ORDER': 0,
85 'BP_ORDER': 0,
86 }
87 # A very simple TAN-SIP projection with only first-order terms (meaning
88 # that it could have been rewritten as TAN-only with different CD values).
89 self.linear = {
90 'CD1_1': 1.0,
91 'CD1_2': 0.0,
92 'CD2_1': 0.0,
93 'CD2_2': 1.0,
94 'CRPIX1': 5.0,
95 'CRPIX2': 6.0,
96 'CRVAL1': 60.0,
97 'CRVAL2': 40.0,
98 'CTYPE1': 'RA---TAN-SIP',
99 'CTYPE2': 'DEC--TAN-SIP',
100 'CUNIT1': 'deg',
101 'CUNIT2': 'deg',
102 'EQUINOX': 2000.0,
103 'NAXES1': 2048,
104 'NAXES2': 4176,
105 'NAXIS': 2,
106 'RADESYS': 'FK5',
107 'A_ORDER': 1,
108 'B_ORDER': 1,
109 'AP_ORDER': 1,
110 'BP_ORDER': 1,
111 'A_1_0': 1.0,
112 'A_0_1': 2.0,
113 'B_1_0': 2.0,
114 'B_0_1': 3.0,
115 'AP_1_0': 0.0,
116 'AP_0_1': -0.5,
117 'BP_1_0': -0.5,
118 'BP_0_1': -0.5,
119 }
120 # 'calexp03' data are from the calexp of HSC visit=1228 ccd=3,
121 # from rerun RC/w_2017_50/DM-12929. It has realistic distortion
122 # with CRPIX within the CCD image.
123 self.calexp03 = {
124 'AP_0_0': -0.00042766492723573385,
125 'AP_0_1': -1.421294634690333e-06,
126 'AP_0_2': -2.249070237131596e-06,
127 'AP_0_3': 7.614693711846379e-11,
128 'AP_0_4': -2.1350939718171946e-15,
129 'AP_1_0': -2.9070491393671955e-06,
130 'AP_1_1': -2.600191883647906e-06,
131 'AP_1_2': 3.209251813253422e-10,
132 'AP_1_3': -6.377163911922792e-15,
133 'AP_2_0': -6.917400901963954e-06,
134 'AP_2_1': 1.441645068817284e-10,
135 'AP_2_2': -1.4384914067292817e-14,
136 'AP_3_0': 5.179922357600516e-10,
137 'AP_3_1': -9.618846794061896e-15,
138 'AP_4_0': -1.6455748303909694e-14,
139 'AP_ORDER': 4,
140 'A_0_2': 2.2496092550091135e-06,
141 'A_0_3': -5.7226624826215967e-11,
142 'A_1_1': 2.601332947490435e-06,
143 'A_1_2': -2.548161981578503e-10,
144 'A_2_0': 6.918453783853945e-06,
145 'A_2_1': -7.070288496574992e-11,
146 'A_3_0': -4.1515075181628575e-10,
147 'A_ORDER': 3,
148 'BP_0_0': -0.00027519363188290014,
149 'BP_0_1': -1.339922915231262e-06,
150 'BP_0_2': -2.7508000129989704e-06,
151 'BP_0_3': 1.6166435243727447e-10,
152 'BP_0_4': -2.6159444529413283e-15,
153 'BP_1_0': -7.170533032300998e-07,
154 'BP_1_1': -4.157202213924163e-06,
155 'BP_1_2': 1.0655925530147004e-10,
156 'BP_1_3': -5.8777807071581165e-15,
157 'BP_2_0': -1.4037210918092256e-06,
158 'BP_2_1': 1.9156184107700897e-10,
159 'BP_2_2': -5.251709685663605e-15,
160 'BP_3_0': -7.09409582069615e-11,
161 'BP_3_1': -5.02267443301453e-15,
162 'BP_4_0': 5.642051133209865e-16,
163 'BP_ORDER': 4,
164 'B_0_2': 2.751296696053813e-06,
165 'B_0_3': -1.363848403847289e-10,
166 'B_1_1': 4.157792860966118e-06,
167 'B_1_2': -5.4037152183616375e-11,
168 'B_2_0': 1.4039800636228212e-06,
169 'B_2_1': -1.2897941316430668e-10,
170 'B_3_0': 9.63138959016586e-11,
171 'B_ORDER': 3,
172 'CD1_1': -1.178015203669291e-06,
173 'CD1_2': 4.498766772429112e-05,
174 'CD2_1': 4.29304691824093e-05,
175 'CD2_2': -1.0762546100726416e-06,
176 'CRPIX1': 1096.8935760454308,
177 'CRPIX2': 2262.9403834197587,
178 'CRVAL1': 149.81129315622917,
179 'CRVAL2': 1.527518593302043,
180 'CTYPE1': 'RA---TAN-SIP',
181 'CTYPE2': 'DEC--TAN-SIP',
182 'CUNIT1': 'deg',
183 'CUNIT2': 'deg',
184 'EQUINOX': 2000.0,
185 'NAXES1': 2048,
186 'NAXES2': 4176,
187 'NAXIS': 2,
188 'RADESYS': 'FK5'
189 }
190 # 'wcs22' data are from the [meas_mosaic] wcs of HSC visit=1228 ccd=22 tract=9813,
191 # from rerun RC/w_2017_50/DM-12929. This has realistic distortion with CRPIX
192 # near the center of the focal plane (off the edge of the CCD).
193 self.wcs22 = {
194 'AP_0_1': -1.4877747209973921e-05,
195 'AP_0_2': 2.595831425638402e-09,
196 'AP_0_3': -3.538473304544541e-13,
197 'AP_0_4': -1.7364255070987416e-17,
198 'AP_0_5': 4.158354613624071e-21,
199 'AP_0_6': 6.031504872540217e-26,
200 'AP_0_7': -1.8843528197634618e-29,
201 'AP_0_8': -4.411702853182809e-35,
202 'AP_0_9': 2.998016558839638e-38,
203 'AP_1_0': 2.1597763415259763e-05,
204 'AP_1_1': -1.7037264968756363e-08,
205 'AP_1_2': 1.0563437512655601e-10,
206 'AP_1_3': 5.513447366302908e-17,
207 'AP_1_4': 4.1396908622107236e-20,
208 'AP_1_5': -5.300207420437093e-25,
209 'AP_1_6': 1.1459136839582329e-28,
210 'AP_1_7': 1.0114481756477905e-33,
211 'AP_1_8': 5.688355826686257e-38,
212 'AP_2_0': 2.9292904845913305e-09,
213 'AP_2_1': -2.31720223718549e-13,
214 'AP_2_2': -7.483299584011452e-17,
215 'AP_2_3': -1.0888257447773327e-21,
216 'AP_2_4': 5.387989056591254e-25,
217 'AP_2_5': 1.644007231402398e-29,
218 'AP_2_6': -1.0830432046814581e-33,
219 'AP_2_7': -3.804272720395786e-38,
220 'AP_3_0': 1.0448304714804088e-10,
221 'AP_3_1': 2.6347274534827444e-17,
222 'AP_3_2': 9.623865277012687e-20,
223 'AP_3_3': -2.8543130699430308e-25,
224 'AP_3_4': 3.0929382240412716e-28,
225 'AP_3_5': 8.14501749342599e-34,
226 'AP_3_6': 2.2629352809700625e-37,
227 'AP_4_0': -1.2664081335530406e-17,
228 'AP_4_1': 8.338516492121341e-22,
229 'AP_4_2': 4.683815141227516e-25,
230 'AP_4_3': 3.8297278086295593e-29,
231 'AP_4_4': -1.0848140289090295e-33,
232 'AP_4_5': -1.6055236700259204e-37,
233 'AP_5_0': 5.923796465250516e-20,
234 'AP_5_1': -3.0109528249623673e-25,
235 'AP_5_2': 2.07651550777521e-28,
236 'AP_5_3': -3.3488672921196585e-34,
237 'AP_5_4': 5.371228109055884e-37,
238 'AP_6_0': 3.0606077273393017e-26,
239 'AP_6_1': -7.470520624005137e-30,
240 'AP_6_2': -7.924054929029662e-34,
241 'AP_6_3': -8.993856894381261e-38,
242 'AP_7_0': 2.8965736955513715e-29,
243 'AP_7_1': 6.805599512576653e-34,
244 'AP_7_2': 5.5526197352423534e-37,
245 'AP_8_0': 4.496329614076484e-35,
246 'AP_8_1': 2.915048166495553e-38,
247 'AP_9_0': 1.9036813587133663e-37,
248 'AP_ORDER': 9,
249 'A_0_2': -1.6811953691767574e-09,
250 'A_0_3': 1.0433743662634e-12,
251 'A_0_4': -6.8711691147178074e-18,
252 'A_0_5': -1.2339761900405834e-20,
253 'A_0_6': 9.361497711139168e-26,
254 'A_0_7': 5.447712238518265e-29,
255 'A_0_8': -2.283455665486541e-34,
256 'A_0_9': -8.148729256556932e-38,
257 'A_1_1': 1.3292850389050882e-09,
258 'A_1_2': -1.0511955221299713e-10,
259 'A_1_3': 2.0198006554663002e-16,
260 'A_1_4': -2.2187859071190358e-20,
261 'A_1_5': -9.096711215990621e-25,
262 'A_1_6': -1.0779518715344257e-29,
263 'A_1_7': 1.3704000006676665e-33,
264 'A_1_8': -7.427824514345201e-38,
265 'A_2_0': 6.368561442632054e-09,
266 'A_2_1': -2.4600357643424362e-12,
267 'A_2_2': -2.435654620248234e-17,
268 'A_2_3': 3.547267533946869e-20,
269 'A_2_4': -6.487365679719972e-26,
270 'A_2_5': -1.521351230094737e-28,
271 'A_2_6': 2.1227383264900538e-34,
272 'A_2_7': 2.0108583869008413e-37,
273 'A_3_0': -1.043925630542753e-10,
274 'A_3_1': 1.75976184042504e-16,
275 'A_3_2': -3.2500931028178183e-20,
276 'A_3_3': -1.921035718735496e-24,
277 'A_3_4': -1.6197076806807766e-28,
278 'A_3_5': 4.657442220122837e-33,
279 'A_3_6': -4.1544398563283404e-38,
280 'A_4_0': -1.572172681976455e-16,
281 'A_4_1': 4.101331132363016e-20,
282 'A_4_2': 6.1547414425568145e-25,
283 'A_4_3': -3.9664912422773684e-28,
284 'A_4_4': -9.219688587239831e-34,
285 'A_4_5': 8.961182174237393e-37,
286 'A_5_0': -3.1263190379608905e-20,
287 'A_5_1': -6.744938189499249e-25,
288 'A_5_2': -1.6066918861635898e-28,
289 'A_5_3': 4.500410209244365e-33,
290 'A_5_4': 1.6023740299725829e-37,
291 'A_6_0': 9.442291591849256e-25,
292 'A_6_1': -1.9578930866414858e-28,
293 'A_6_2': -1.9703616513681726e-33,
294 'A_6_3': 9.254059979828214e-37,
295 'A_7_0': 2.857311310929313e-29,
296 'A_7_1': 8.203568899883087e-34,
297 'A_7_2': 8.581372417066432e-39,
298 'A_8_0': -1.8039031898052798e-33,
299 'A_8_1': 2.898481891491671e-37,
300 'A_9_0': -1.4089135319319912e-37,
301 'A_ORDER': 9,
302 'BP_0_1': 1.3269406378579873e-05,
303 'BP_0_2': -2.4020130376277656e-08,
304 'BP_0_3': 1.0578604850716356e-10,
305 'BP_0_4': 1.2705099582590507e-17,
306 'BP_0_5': 4.374702590256722e-20,
307 'BP_0_6': -1.1904361273665637e-25,
308 'BP_0_7': 9.365376813229858e-29,
309 'BP_0_8': 1.0249348542687937e-35,
310 'BP_0_9': 9.913174239204098e-38,
311 'BP_1_0': 5.3154970941211545e-06,
312 'BP_1_1': 1.338585221404198e-09,
313 'BP_1_2': -4.403323203838703e-13,
314 'BP_1_3': -4.927675412191317e-17,
315 'BP_1_4': 4.2056599337502155e-21,
316 'BP_1_5': 4.347494937639946e-25,
317 'BP_1_6': -1.7016369393589545e-29,
318 'BP_1_7': -9.250712710300761e-34,
319 'BP_1_8': 2.903862291589102e-38,
320 'BP_2_0': -8.361037592783e-09,
321 'BP_2_1': 1.0557933487998814e-10,
322 'BP_2_2': 6.851578459014938e-17,
323 'BP_2_3': 9.166056602172931e-20,
324 'BP_2_4': -5.588794654793661e-25,
325 'BP_2_5': 2.799146126748001e-28,
326 'BP_2_6': 4.736465967290488e-34,
327 'BP_2_7': 3.479972936276621e-37,
328 'BP_3_0': 1.9579873417948577e-13,
329 'BP_3_1': -3.4635677527711934e-18,
330 'BP_3_2': 7.39087279804992e-21,
331 'BP_3_3': 1.7492745475725066e-25,
332 'BP_3_4': -4.035333126173646e-29,
333 'BP_3_5': -1.3361684693427232e-33,
334 'BP_3_6': 4.955950912353302e-38,
335 'BP_4_0': 8.209637950392852e-18,
336 'BP_4_1': 5.1024702870142e-20,
337 'BP_4_2': -6.8032763060891895e-25,
338 'BP_4_3': 2.1044072099103315e-28,
339 'BP_4_4': 1.6036540498846812e-33,
340 'BP_4_5': 7.0605046394838895e-37,
341 'BP_5_0': -1.2718164521958228e-21,
342 'BP_5_1': 3.861294906642736e-26,
343 'BP_5_2': -3.728708833310905e-29,
344 'BP_5_3': 7.585835353752152e-34,
345 'BP_5_4': 1.0442735673821763e-37,
346 'BP_6_0': 1.315728787073803e-26,
347 'BP_6_1': 5.121275456285683e-29,
348 'BP_6_2': 1.3292445159690183e-33,
349 'BP_6_3': 5.9934345037665075e-37,
350 'BP_7_0': -2.0388122227546495e-30,
351 'BP_7_1': -2.75093255434579e-34,
352 'BP_7_2': 6.416444202667438e-38,
353 'BP_8_0': -1.4895913066402677e-34,
354 'BP_8_1': 1.6803705066965438e-37,
355 'BP_9_0': 1.8813475916412184e-38,
356 'BP_ORDER': 9,
357 'B_0_2': 2.42424716719325e-08,
358 'B_0_3': -1.0625210421541499e-10,
359 'B_0_4': -3.734011267534742e-17,
360 'B_0_5': -4.4320952447253e-21,
361 'B_0_6': 2.136396320364695e-25,
362 'B_0_7': -1.0261671476558508e-28,
363 'B_0_8': -2.8687265716879407e-34,
364 'B_0_9': 7.34451334399625e-38,
365 'B_1_1': -2.4662102612928633e-09,
366 'B_1_2': 4.6033704144968226e-14,
367 'B_1_3': 6.575057893720466e-17,
368 'B_1_4': 5.132957383456082e-22,
369 'B_1_5': -4.354806940180001e-25,
370 'B_1_6': -9.86162124079778e-30,
371 'B_1_7': 7.692996166964473e-34,
372 'B_1_8': 2.4662028685683337e-38,
373 'B_2_0': 9.397728194428527e-09,
374 'B_2_1': -1.0671833625668622e-10,
375 'B_2_2': -5.398605316354907e-17,
376 'B_2_3': -1.862909181494854e-21,
377 'B_2_4': 4.0889634152600245e-25,
378 'B_2_5': -3.6402639280175727e-28,
379 'B_2_6': -6.9963627184091545e-34,
380 'B_2_7': 4.295158728944756e-37,
381 'B_3_0': -1.8786163554348966e-13,
382 'B_3_1': 1.972512581046491e-17,
383 'B_3_2': 3.6729496252874392e-22,
384 'B_3_3': -3.3840481271540886e-25,
385 'B_3_4': -6.797729495720485e-30,
386 'B_3_5': 1.4493402427155075e-33,
387 'B_3_6': 3.998788271456866e-38,
388 'B_4_0': -3.7050392480384804e-17,
389 'B_4_1': -3.1322715658144286e-22,
390 'B_4_2': 4.180620258608441e-25,
391 'B_4_3': -3.4512946689359176e-28,
392 'B_4_4': -1.0034951748320315e-33,
393 'B_4_5': 5.8604050242447385e-37,
394 'B_5_0': -2.236401243933395e-22,
395 'B_5_1': -9.814956405914982e-26,
396 'B_5_2': -2.921874946735589e-30,
397 'B_5_3': -2.8112248331124586e-34,
398 'B_5_4': 1.1980683642446688e-38,
399 'B_6_0': 1.6355097123858816e-25,
400 'B_6_1': -1.2020157950352392e-28,
401 'B_6_2': -8.155965356039375e-34,
402 'B_6_3': 3.5708930267652927e-37,
403 'B_7_0': 1.1594106652283982e-29,
404 'B_7_1': 3.168975065824031e-34,
405 'B_7_2': 1.1734898293897363e-39,
406 'B_8_0': -2.184412963604129e-34,
407 'B_8_1': 1.019604698867224e-37,
408 'B_9_0': -3.408083556979094e-38,
409 'B_ORDER': 9,
410 'CD1_1': 4.034435112527688e-08,
411 'CD1_2': -4.688657441390689e-05,
412 'CD2_1': -4.687791920020537e-05,
413 'CD2_2': -5.0493192130940185e-08,
414 'CRPIX1': -5340.870233465987,
415 'CRPIX2': 17440.391355863132,
416 'CRVAL1': 150.11251452060122,
417 'CRVAL2': 2.2004651419030177,
418 'CTYPE1': 'RA---TAN-SIP',
419 'CTYPE2': 'DEC--TAN-SIP',
420 'CUNIT1': 'deg',
421 'CUNIT2': 'deg',
422 'EQUINOX': 2000.0,
423 'NAXES1': 2048,
424 'NAXES2': 4176,
425 'NAXIS': 2,
426 'RADESYS': 'FK5'
427 }
429 # 'jointcal' data are from the jointcal solution of HSC
430 # visit=30600 ccd=88 tract=16973.
431 # This doesn't have a direct TAN-SIP representation.
432 self.jointcal = SkyWcs.readFits(os.path.join(os.path.abspath(os.path.dirname(__file__)), "data",
433 "jointcal_wcs-0030600-088.fits"))
435 def compareSolution(self, md, approx):
436 for p, q in packedRange(md["A_ORDER"]):
437 self.assertFloatsAlmostEqual(md.get(f"A_{p}_{q}", 0.0), approx.getA(p, q),
438 rtol=1E-10, atol=1E-10, msg=f"for A_{p}_{q}")
439 for p, q in packedRange(md["B_ORDER"]):
440 self.assertFloatsAlmostEqual(md.get(f"B_{p}_{q}", 0.0), approx.getB(p, q),
441 rtol=1E-10, atol=1E-10, msg=f"for B_{p}_{q}")
442 for p, q in packedRange(md["AP_ORDER"]):
443 self.assertFloatsAlmostEqual(md.get(f"AP_{p}_{q}", 0.0), approx.getAP(p, q),
444 rtol=1E-10, atol=1E-10, msg=f"for AP_{p}_{q}")
445 for p, q in packedRange(md["BP_ORDER"]):
446 self.assertFloatsAlmostEqual(md.get(f"BP_{p}_{q}", 0.0), approx.getBP(p, q),
447 rtol=1E-10, atol=1E-10, msg=f"for BP_{p}_{q}")
449 def testSipDefinitions(self):
450 """Check that when we initialize a SipApproximation with coefficients from FITS
451 metadata the resulting transform agrees with the pixels to intermediate world
452 coordinates transform of a SkyWcs constructed with the same metadata.
453 """
454 def run(md):
455 kwds = extractCtorArgs(md)
456 order = max(md["A_ORDER"], md["B_ORDER"], md["AP_ORDER"], md["BP_ORDER"])
457 gridShape = Extent2I(5, 5)
458 a = np.zeros((order + 1, order + 1), dtype=float)
459 b = np.zeros((order + 1, order + 1), dtype=float)
460 ap = np.zeros((order + 1, order + 1), dtype=float)
461 bp = np.zeros((order + 1, order + 1), dtype=float)
462 for p, q in packedRange(order):
463 a[p, q] = md.get(f"A_{p}_{q}", 0.0)
464 b[p, q] = md.get(f"B_{p}_{q}", 0.0)
465 ap[p, q] = md.get(f"AP_{p}_{q}", 0.0)
466 bp[p, q] = md.get(f"BP_{p}_{q}", 0.0)
467 approx = SipApproximation(a=a, b=b, ap=ap, bp=bp, gridShape=gridShape, **kwds)
468 self.compareSolution(md, approx)
469 diffs = approx.computeMaxDeviation()
470 self.assertLess(diffs[0], 1E-10)
471 self.assertLess(diffs[1], 1E-10)
472 bbox = kwds["bbox"]
473 pix1 = [Point2D(x, y)
474 for x in np.linspace(bbox.getMinX(), bbox.getMaxX(), gridShape.getX())
475 for y in np.linspace(bbox.getMinY(), bbox.getMaxY(), gridShape.getY())]
476 iwc1a = kwds["pixelToIwc"].applyForward(pix1)
477 pix2a = kwds["pixelToIwc"].applyInverse(iwc1a)
478 iwc1b = approx.applyForward(pix1)
479 assert_allclose(iwc1a, iwc1b, rtol=1E-9, atol=1E-12)
480 pix2b = approx.applyInverse(iwc1a)
481 assert_allclose(pix2a, pix2b, rtol=1E-9, atol=1E-12)
483 run(self.identity)
484 run(self.linear)
485 run(self.calexp03)
486 run(self.wcs22)
488 def testExactFit(self):
489 """Check that we can exactly fit a TAN-SIP WCS when we use the same
490 or higher polynomial order.
491 """
492 def run(md, **kwds2):
493 kwds = extractCtorArgs(md)
494 kwds['order'] = max(md["A_ORDER"], md["B_ORDER"], md["AP_ORDER"], md["BP_ORDER"])
495 kwds.update(kwds2)
496 gridShape = Extent2I(10, 10)
497 approx = SipApproximation(gridShape=gridShape, **kwds)
498 diffs = approx.computeMaxDeviation()
499 self.compareSolution(md, approx)
500 self.assertLess(diffs[0], 1E-10)
501 self.assertLess(diffs[1], 1E-10)
503 run(self.identity)
504 run(self.linear)
505 run(self.calexp03)
506 # When x and y values get large (e.g. because CRVAL is not on the image)
507 # and the order is high, the fit becomes unstable and the residuals get
508 # large. As a result, we can't fit wcs22 exactly.
510 def testFitReducedOrder(self):
511 """Check that we can fit a TAN-SIP WCS to better than 0.1 pixels when
512 we use a lower polynomial order than the actual distortion.
513 """
514 def run(md, **kwds2):
515 kwds = extractCtorArgs(md)
516 kwds.update(kwds2)
517 gridShape = Extent2I(20, 20)
518 approx = SipApproximation(gridShape=gridShape, **kwds)
519 diffs = approx.computeMaxDeviation()
520 self.assertLess(diffs[0], 0.1)
521 self.assertLess(diffs[1], 0.1)
523 run(self.calexp03, order=3)
524 run(self.wcs22, order=8)
526 def testCalculateSipWcsHeader(self):
527 """Test the calculateSipWcsHeader function
529 This function not only generates the coefficients approximating the WCS,
530 but allows creating a new ``SkyWcs`` using those coefficients.
531 """
532 def run(original, width, height, order, spacing=20, skyTol=1.0e-4, pixTol=1.0e-8):
533 """Run calculateSipWcsHeader and evaluate performance
535 Parameters
536 ----------
537 original : `lsst.afw.geom.SkyWcs`
538 Original WCS to approximate with SIP.
539 width, height : `int`
540 Dimensions of the image.
541 order : `int`
542 SIP order (equal to the maximum sum of the polynomial exponents).
543 spacing : `float`
544 Spacing between sample points.
545 skyTol : `float`
546 Tolerance in arcseconds for comparing sky positions.
547 pixTol : `float`
548 Tolerance in pixels for comparing pixel positions.
550 Returns
551 -------
552 wcs : `lsst.afw.geom.SkyWcs`
553 SIP WCS.
554 """
555 # Get new SIP WCS
556 bbox = Box2I(Point2I(0, 0), Extent2I(width, height))
557 header = calculateSipWcsHeader(original, order, bbox, spacing)
558 wcs = makeSkyWcs(header)
560 # Evaluate performance
561 x_points, y_points = np.meshgrid(np.arange(0., width, spacing), np.arange(0., height, spacing))
562 x_points = x_points.ravel()
563 y_points = y_points.ravel()
565 original_sky = original.pixelToSkyArray(x_points, y_points)
566 wcs_sky = wcs.pixelToSkyArray(x_points, y_points)
568 # Convert to SpherePoint for tangent plane comparison.
569 coord1 = [SpherePoint(x, y, units=radians) for x, y in zip(*original_sky)]
570 coord2 = [SpherePoint(x, y, units=radians) for x, y in zip(*wcs_sky)]
571 offsets = (c1.getTangentPlaneOffset(c2) for c1, c2 in zip(coord1, coord2))
572 offsets = np.array([(off[0].asArcseconds(), off[1].asArcseconds()) for off in offsets])
574 self.assertFloatsAlmostEqual(offsets[0].mean(), 0.0, atol=skyTol)
575 self.assertFloatsAlmostEqual(offsets[1].mean(), 0.0, atol=skyTol)
576 self.assertFloatsAlmostEqual(offsets.mean(), 0.0, atol=skyTol)
577 self.assertFloatsAlmostEqual(offsets[0].std(), 0.0, atol=skyTol)
578 self.assertFloatsAlmostEqual(offsets[1].std(), 0.0, atol=skyTol)
579 self.assertFloatsAlmostEqual(offsets.std(), 0.0, atol=skyTol)
580 self.assertFloatsAlmostEqual(np.abs(offsets).max(), 0.0, atol=skyTol)
582 # Not comparing to absolute round-trip, but relative to the
583 # original WCS round-trip. This is an important distinction: we're
584 # recreating the original WCS, along with all its flaws.
585 roundTrip = np.vstack(wcs.skyToPixelArray(*wcs_sky))
586 roundTrip -= np.vstack(original.skyToPixelArray(*original_sky))
588 self.assertFloatsAlmostEqual(roundTrip[0].mean(), 0.0, atol=pixTol)
589 self.assertFloatsAlmostEqual(roundTrip[1].mean(), 0.0, atol=pixTol)
590 self.assertFloatsAlmostEqual(roundTrip.mean(), 0.0, atol=pixTol)
591 self.assertFloatsAlmostEqual(roundTrip[0].std(), 0.0, atol=pixTol)
592 self.assertFloatsAlmostEqual(roundTrip[1].std(), 0.0, atol=pixTol)
593 self.assertFloatsAlmostEqual(roundTrip.std(), 0.0, atol=pixTol)
594 self.assertFloatsAlmostEqual(np.abs(roundTrip).max(), 0.0, atol=pixTol)
596 return wcs
598 run(makeSkyWcs(makePropertyListFromDict(self.calexp03)),
599 self.calexp03["NAXES1"], self.calexp03["NAXES2"], order=4, skyTol=1.0e-4)
600 run(makeSkyWcs(makePropertyListFromDict(self.wcs22)),
601 self.wcs22["NAXES1"], self.wcs22["NAXES2"], order=8, skyTol=2.0e-4)
602 run(self.jointcal, 2048, 4176, order=9)
605class MemoryTester(lsst.utils.tests.MemoryTestCase):
606 pass
609def setup_module(module):
610 lsst.utils.tests.init()
613if __name__ == "__main__": 613 ↛ 614line 613 didn't jump to line 614, because the condition on line 613 was never true
614 lsst.utils.tests.init()
615 unittest.main()