Coverage for tests/test_sipApproximation.py: 16%

132 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-23 02:38 -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# 

21 

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) 

32 

33 

34def makePropertyListFromDict(md): 

35 result = PropertyList() 

36 for k, v in md.items(): 

37 result.add(k, v) 

38 return result 

39 

40 

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 

51 

52 

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 

57 

58 

59class SipApproximationTestCases(lsst.utils.tests.TestCase): 

60 

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 } 

429 

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")) 

435 

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}") 

449 

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) 

483 

484 run(self.identity) 

485 run(self.linear) 

486 run(self.calexp03) 

487 run(self.wcs22) 

488 

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) 

503 

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. 

510 

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) 

523 

524 run(self.calexp03, order=3) 

525 run(self.wcs22, order=8) 

526 

527 def testCalculateSipWcsHeader(self): 

528 """Test the calculateSipWcsHeader function 

529 

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 

535 

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. 

550 

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) 

560 

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]) 

568 

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) 

576 

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]) 

582 

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) 

590 

591 return wcs 

592 

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) 

598 

599 

600class MemoryTester(lsst.utils.tests.MemoryTestCase): 

601 pass 

602 

603 

604def setup_module(module): 

605 lsst.utils.tests.init() 

606 

607 

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()