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