Coverage for tests/test_wcsUtils.py: 11%

222 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-24 02:29 -0700

1# 

2# LSST Data Management System 

3# Copyright 2017 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

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 LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22import math 

23import unittest 

24 

25import numpy as np 

26from numpy.testing import assert_allclose 

27 

28from lsst.pex.exceptions import TypeError 

29from lsst.daf.base import PropertyList 

30import lsst.geom 

31import lsst.afw.geom as afwGeom 

32import lsst.utils.tests 

33from lsst.afw.geom import createTrivialWcsMetadata, deleteBasicWcsMetadata, \ 

34 getCdMatrixFromMetadata, getSipMatrixFromMetadata, getImageXY0FromMetadata, \ 

35 hasSipMatrix, makeSipMatrixMetadata, makeTanSipMetadata, \ 

36 computePixelToDistortedPixel 

37 

38 

39def makeRotationMatrix(angle, scale): 

40 angleRad = angle.asRadians() 

41 sinAng = math.sin(angleRad) 

42 cosAng = math.cos(angleRad) 

43 return np.array([ 

44 ([cosAng, sinAng]), 

45 ([-sinAng, cosAng]), 

46 ], dtype=float)*scale 

47 

48 

49class BaseTestCase(lsst.utils.tests.TestCase): 

50 """Base class for testing computePixelToDistortedPixel 

51 """ 

52 def setUp(self): 

53 # define the position and size of one CCD in the focal plane 

54 self.pixelSizeMm = 0.024 # mm/pixel 

55 self.ccdOrientation = 5*lsst.geom.degrees # orientation of pixel w.r.t. focal plane 

56 self.plateScale = 0.15*lsst.geom.arcseconds # angle/pixel 

57 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(2000, 4000)) 

58 self.crpix = lsst.geom.Point2D(1000, 2000) 

59 self.crval = lsst.geom.SpherePoint(10, 40, lsst.geom.degrees) 

60 self.orientation = -45*lsst.geom.degrees 

61 self.scale = 1.0*lsst.geom.arcseconds 

62 # position of 0,0 pixel position in focal plane 

63 self.ccdPositionMm = lsst.geom.Point2D(25.0, 10.0) 

64 self.pixelToFocalPlane = self.makeAffineTransform( 

65 offset=lsst.geom.Extent2D(self.ccdPositionMm), 

66 rotation=self.ccdOrientation, 

67 scale=self.pixelSizeMm, 

68 ) 

69 cdMatrix = afwGeom.makeCdMatrix(scale=self.scale, orientation=self.orientation) 

70 self.tanWcs = afwGeom.makeSkyWcs(crpix=self.crpix, crval=self.crval, cdMatrix=cdMatrix) 

71 self.radPerMm = self.plateScale.asRadians()/self.pixelSizeMm # at center of field 

72 bboxD = lsst.geom.Box2D(self.bbox) 

73 self.pixelPoints = bboxD.getCorners() 

74 self.pixelPoints.append(bboxD.getCenter()) 

75 

76 def makeAffineTransform(self, offset=(0, 0), rotation=0*lsst.geom.degrees, scale=1.0): 

77 """Make an affine TransformPoint2ToPoint2 that first adds the specified offset, 

78 then scales and rotates the result 

79 """ 

80 rotScale = lsst.geom.AffineTransform(lsst.geom.LinearTransform.makeScaling(scale) 

81 * lsst.geom.LinearTransform.makeRotation(rotation)) 

82 offset = lsst.geom.AffineTransform(lsst.geom.Extent2D(*offset)) 

83 # AffineTransform a*b = b.then(a) 

84 return afwGeom.makeTransform(rotScale*offset) 

85 

86 

87class ComputePixelToDistortedPixelTestCase(BaseTestCase): 

88 """Test lsst.afw.geom.computePixelToDistortedPixel 

89 """ 

90 

91 def testNoDistortion(self): 

92 """Test computePixelToDistortedPixel without distortion 

93 

94 Use an affine transform for pixelToFocalPlane; the transform returned 

95 by computePixelToDistortedPixel should be the identity transform 

96 """ 

97 focalPlaneToFieldAngle = self.makeAffineTransform(scale=self.radPerMm) 

98 pixelToDistortedPixel = computePixelToDistortedPixel( 

99 pixelToFocalPlane=self.pixelToFocalPlane, 

100 focalPlaneToFieldAngle=focalPlaneToFieldAngle, 

101 ) 

102 bboxD = lsst.geom.Box2D(self.bbox) 

103 pixelPoints = bboxD.getCorners() 

104 pixelPoints.append(bboxD.getCenter()) 

105 

106 assert_allclose(pixelToDistortedPixel.applyForward(pixelPoints), pixelPoints) 

107 assert_allclose(pixelToDistortedPixel.applyInverse(pixelPoints), pixelPoints) 

108 

109 def testDistortion(self): 

110 """Test computePixelToDistortedPixel with distortion 

111 

112 Compare the output of computePixelToDistortedPixel to a non-affine 

113 transform for pixelToFocalPlane 

114 """ 

115 # Compute a pixelToDistortedSky that matches self.tanWCS at the center of the field; 

116 # the amount of distortion is 10s of pixels over the detector 

117 fieldAngleToFocalPlane = afwGeom.makeRadialTransform([0.0, 1/self.radPerMm, 0.0, 1000/self.radPerMm]) 

118 focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() 

119 focalPlaneToTanFieldAngle = self.makeAffineTransform(scale=self.radPerMm) 

120 pixelToDistortedPixel = computePixelToDistortedPixel( 

121 pixelToFocalPlane=self.pixelToFocalPlane, 

122 focalPlaneToFieldAngle=focalPlaneToFieldAngle, 

123 ) 

124 

125 tanWcsTransform = afwGeom.TransformPoint2ToSpherePoint(self.tanWcs.getFrameDict()) 

126 pixelToDistortedSky = pixelToDistortedPixel.then(tanWcsTransform) 

127 

128 # At the center of the focal plane both WCS should give the same sky position 

129 pixelAtCtr = self.pixelToFocalPlane.applyInverse(lsst.geom.Point2D(0, 0)) 

130 tanSkyAtCtr = self.tanWcs.pixelToSky(pixelAtCtr) 

131 skyAtCtr = pixelToDistortedSky.applyForward(pixelAtCtr) 

132 self.assertPairsAlmostEqual(tanSkyAtCtr, skyAtCtr) 

133 

134 # At all reasonable sky points the following field angles should be almost equal: 

135 # sky -> tanWcs.skyToPixel -> pixelToFocalPlane -> focalPlaneToTanFieldAngle 

136 # sky -> pixelToDistortedPixel.applyInverse -> pixelToFocalPlane -> focalPlaneToFieldAngle 

137 # where focalPlaneToTanFieldAngle is the linear approximation to 

138 # focalPlaneToFieldAngle at the center of the field (where tanWcs and pixelToDistortedSky match), 

139 # since for a given pointing, field angle gives position on the sky 

140 skyPoints = self.tanWcs.pixelToSky(self.pixelPoints) 

141 

142 tanFieldAnglePoints = focalPlaneToTanFieldAngle.applyForward( 

143 self.pixelToFocalPlane.applyForward(self.tanWcs.skyToPixel(skyPoints))) 

144 fieldAnglePoints = focalPlaneToFieldAngle.applyForward( 

145 self.pixelToFocalPlane.applyForward(pixelToDistortedSky.applyInverse(skyPoints))) 

146 assert_allclose(tanFieldAnglePoints, fieldAnglePoints) 

147 

148 # The inverse should also be true: for a set of field angle points 

149 # the following sky positions should be almost equal: 

150 # fieldAngle -> fieldAngleToTanFocalPlane -> focalPlaneToPixel -> tanWcs.pixelToSky 

151 # fieldAngle -> fieldAngleToFocalPlane -> focalPlaneToPixel -> pixelToDistortedPixel.applyForward 

152 focalPlaneToPixel = self.pixelToFocalPlane.inverted() 

153 fieldAngleToTanFocalPlane = focalPlaneToTanFieldAngle.inverted() 

154 tanSkyPoints2 = self.tanWcs.pixelToSky( 

155 focalPlaneToPixel.applyForward( 

156 fieldAngleToTanFocalPlane.applyForward(fieldAnglePoints))) 

157 

158 skyPoints2 = pixelToDistortedSky.applyForward( 

159 focalPlaneToPixel.applyForward( 

160 fieldAngleToFocalPlane.applyForward(fieldAnglePoints))) 

161 

162 self.assertSpherePointListsAlmostEqual(tanSkyPoints2, skyPoints2) 

163 

164 

165class DetailTestCase(lsst.utils.tests.TestCase): 

166 """Test functions in the detail sub-namespace 

167 """ 

168 def setUp(self): 

169 # Actual WCS from processing Suprime-Cam 

170 self.width = 2048 

171 self.height = 4177 

172 metadata = PropertyList() 

173 for name, value in ( 

174 ('NAXIS', 2), 

175 ('EQUINOX', 2000.0000000000), 

176 ('RADESYS', "ICRS"), 

177 ('CRPIX1', -3232.7544925483), 

178 ('CRPIX2', 4184.4881091129), 

179 ('CD1_1', -5.6123808607273e-05), 

180 ('CD1_2', 2.8951544956703e-07), 

181 ('CD2_1', 2.7343044348306e-07), 

182 ('CD2_2', 5.6100888336445e-05), 

183 ('CRVAL1', 5.6066137655191), 

184 ('CRVAL2', -0.60804032498548), 

185 ('CUNIT1', "deg"), 

186 ('CUNIT2', "deg"), 

187 ('A_ORDER', 5), 

188 ('A_0_2', 1.9749832126246e-08), 

189 ('A_0_3', 9.3734869173527e-12), 

190 ('A_0_4', 1.8812994578840e-17), 

191 ('A_0_5', -2.3524013652433e-19), 

192 ('A_1_1', -9.8443908806559e-10), 

193 ('A_1_2', -4.9278297504858e-10), 

194 ('A_1_3', -2.8491604610001e-16), 

195 ('A_1_4', 2.3185723720750e-18), 

196 ('A_2_0', 4.9546089730708e-08), 

197 ('A_2_1', -8.8592221672777e-12), 

198 ('A_2_2', 3.3560100338765e-16), 

199 ('A_2_3', 3.0469486185035e-21), 

200 ('A_3_0', -4.9332471706700e-10), 

201 ('A_3_1', -5.3126029725748e-16), 

202 ('A_3_2', 4.7795824885726e-18), 

203 ('A_4_0', 1.3128844828963e-16), 

204 ('A_4_1', 4.4014452170715e-19), 

205 ('A_5_0', 2.1781986904162e-18), 

206 ('B_ORDER', 5), 

207 ('B_0_2', -1.0607653075899e-08), 

208 ('B_0_3', -4.8693887937365e-10), 

209 ('B_0_4', -1.0363305097301e-15), 

210 ('B_0_5', 1.9621640066919e-18), 

211 ('B_1_1', 3.0340657679481e-08), 

212 ('B_1_2', -5.0763819284853e-12), 

213 ('B_1_3', 2.8987281654754e-16), 

214 ('B_1_4', 1.8253389678593e-19), 

215 ('B_2_0', -2.4772849184248e-08), 

216 ('B_2_1', -4.9775588352207e-10), 

217 ('B_2_2', -3.6806326254887e-16), 

218 ('B_2_3', 4.4136985315418e-18), 

219 ('B_3_0', -1.7807191001742e-11), 

220 ('B_3_1', -2.4136396882531e-16), 

221 ('B_3_2', 2.9165413645768e-19), 

222 ('B_4_0', 4.1029951148438e-16), 

223 ('B_4_1', 2.3711874424169e-18), 

224 ('B_5_0', 4.9333635889310e-19), 

225 ('AP_ORDER', 5), 

226 ('AP_0_1', -5.9740855298291e-06), 

227 ('AP_0_2', -2.0433429597268e-08), 

228 ('AP_0_3', -8.6810071023434e-12), 

229 ('AP_0_4', -2.4974690826778e-17), 

230 ('AP_0_5', 1.9819631102516e-19), 

231 ('AP_1_0', -4.5896648256716e-05), 

232 ('AP_1_1', -1.5248993348644e-09), 

233 ('AP_1_2', 5.0283116166943e-10), 

234 ('AP_1_3', 4.3796281513144e-16), 

235 ('AP_1_4', -2.1447889127908e-18), 

236 ('AP_2_0', -4.7550300344365e-08), 

237 ('AP_2_1', 1.0924172283232e-11), 

238 ('AP_2_2', -4.9862026098260e-16), 

239 ('AP_2_3', -5.4470851768869e-20), 

240 ('AP_3_0', 5.0130654116966e-10), 

241 ('AP_3_1', 6.8649554020012e-16), 

242 ('AP_3_2', -4.2759588436342e-18), 

243 ('AP_4_0', -3.6306802581471e-16), 

244 ('AP_4_1', -5.3885285875084e-19), 

245 ('AP_5_0', -1.8802693525108e-18), 

246 ('BP_ORDER', 5), 

247 ('BP_0_1', -2.6627855995942e-05), 

248 ('BP_0_2', 1.1143451873584e-08), 

249 ('BP_0_3', 4.9323396530135e-10), 

250 ('BP_0_4', 1.1785185735421e-15), 

251 ('BP_0_5', -1.6169957016415e-18), 

252 ('BP_1_0', -5.7914490267576e-06), 

253 ('BP_1_1', -3.0565765766244e-08), 

254 ('BP_1_2', 5.7727475030971e-12), 

255 ('BP_1_3', -4.0586821113726e-16), 

256 ('BP_1_4', -2.0662723654322e-19), 

257 ('BP_2_0', 2.3705520015164e-08), 

258 ('BP_2_1', 5.0530823594352e-10), 

259 ('BP_2_2', 3.8904979943489e-16), 

260 ('BP_2_3', -3.8346209540986e-18), 

261 ('BP_3_0', 1.9505421473262e-11), 

262 ('BP_3_1', 1.7583146713289e-16), 

263 ('BP_3_2', -3.4876779564534e-19), 

264 ('BP_4_0', -3.3690937119054e-16), 

265 ('BP_4_1', -2.0853007589561e-18), 

266 ('BP_5_0', -5.5344298912288e-19), 

267 ('CTYPE1', "RA---TAN-SIP"), 

268 ('CTYPE2', "DEC--TAN-SIP"), 

269 ): 

270 metadata.set(name, value) 

271 self.metadata = metadata 

272 

273 def testCreateTrivialWcsAsPropertySet(self): 

274 wcsName = "Z" # arbitrary 

275 xy0 = lsst.geom.Point2I(47, -200) # arbitrary 

276 metadata = createTrivialWcsMetadata(wcsName=wcsName, xy0=xy0) 

277 desiredNameValueList = ( # names are missing wcsName suffix 

278 ("CRPIX1", 1.0), 

279 ("CRPIX2", 1.0), 

280 ("CRVAL1", xy0[0]), 

281 ("CRVAL2", xy0[1]), 

282 ("CTYPE1", "LINEAR"), 

283 ("CTYPE2", "LINEAR"), 

284 ("CUNIT1", "PIXEL"), 

285 ("CUNIT2", "PIXEL"), 

286 ) 

287 self.assertEqual(len(metadata.names(True)), len(desiredNameValueList)) 

288 for name, value in desiredNameValueList: 

289 self.assertEqual(metadata.getScalar(name + wcsName), value) 

290 

291 def testDeleteBasicWcsMetadata(self): 

292 wcsName = "Q" # arbitrary 

293 metadata = createTrivialWcsMetadata(wcsName=wcsName, xy0=lsst.geom.Point2I(0, 0)) 

294 # add the other keywords that will be deleted 

295 for i in range(2): 

296 for j in range(2): 

297 metadata.set(f"CD{i+1}_{j+1}{wcsName}", 0.2) # arbitrary nonzero value 

298 metadata.set(f"WCSAXES{wcsName}", 2) 

299 # add a keyword that will not be deleted 

300 metadata.set("NAXIS1", 100) 

301 self.assertEqual(len(metadata.names(True)), 14) 

302 

303 # deleting data for a different WCS will delete nothing 

304 deleteBasicWcsMetadata(metadata=metadata, wcsName="B") 

305 self.assertEqual(len(metadata.names(True)), 14) 

306 

307 # deleting data for the right WCS deletes all but one keyword 

308 deleteBasicWcsMetadata(metadata=metadata, wcsName=wcsName) 

309 self.assertEqual(len(metadata.names(True)), 1) 

310 self.assertEqual(metadata.getScalar("NAXIS1"), 100) 

311 

312 # try with a smattering of keywords (should silently ignore the missing ones) 

313 metadata.set(f"WCSAXES{wcsName}", 2) 

314 metadata.set(f"CD1_2{wcsName}", 0.5) 

315 metadata.set(f"CRPIX2{wcsName}", 5) 

316 metadata.set(f"CRVAL1{wcsName}", 55) 

317 deleteBasicWcsMetadata(metadata=metadata, wcsName=wcsName) 

318 self.assertEqual(len(metadata.names(True)), 1) 

319 self.assertEqual(metadata.getScalar("NAXIS1"), 100) 

320 

321 def testGetImageXY0FromMetadata(self): 

322 wcsName = "Z" # arbitrary 

323 xy0 = lsst.geom.Point2I(47, -200) # arbitrary with a negative value to check rounding 

324 metadata = createTrivialWcsMetadata(wcsName=wcsName, xy0=xy0) 

325 

326 # reading the wrong wcsName should be treated as no data available 

327 xy0WrongWcsName = getImageXY0FromMetadata(metadata=metadata, wcsName="X", strip=True) 

328 self.assertEqual(xy0WrongWcsName, lsst.geom.Point2I(0, 0)) 

329 self.assertEqual(len(metadata.names(True)), 8) 

330 

331 # deleting one of the required keywords should be treated as no data available 

332 for namePrefixToRemove in ("CRPIX1", "CRPIX2", "CRVAL1", "CRVAL2"): 

333 nameToRemove = namePrefixToRemove + wcsName 

334 removedValue = metadata.getScalar(nameToRemove) 

335 metadata.remove(nameToRemove) 

336 xy0MissingWcsKey = getImageXY0FromMetadata(metadata=metadata, wcsName=wcsName, strip=True) 

337 self.assertEqual(xy0MissingWcsKey, lsst.geom.Point2I(0, 0)) 

338 self.assertEqual(len(metadata.names(True)), 7) 

339 # restore removed item 

340 metadata.set(nameToRemove, removedValue) 

341 self.assertEqual(len(metadata.names(True)), 8) 

342 

343 # setting CRPIX1, 2 to something other than 1 should be treated as no data available 

344 for i in (1, 2): 

345 nameToChange = f"CRPIX{i}{wcsName}" 

346 metadata.set(nameToChange, 1.1) 

347 xy0WrongWcsName = getImageXY0FromMetadata(metadata=metadata, wcsName=wcsName, strip=True) 

348 self.assertEqual(xy0WrongWcsName, lsst.geom.Point2I(0, 0)) 

349 self.assertEqual(len(metadata.names(True)), 8) 

350 # restore altered CRPIX value 

351 metadata.set(nameToChange, 1.0) 

352 self.assertEqual(len(metadata.names(True)), 8) 

353 

354 # use the correct WCS name but don't strip 

355 xy0RightWcsName = getImageXY0FromMetadata(metadata, wcsName, strip=False) 

356 self.assertEqual(xy0RightWcsName, xy0) 

357 self.assertEqual(len(metadata.names(True)), 8) 

358 

359 # use the correct WCS and strip usable metadata 

360 xy0RightWcsName = getImageXY0FromMetadata(metadata, wcsName, strip=True) 

361 self.assertEqual(xy0RightWcsName, xy0) 

362 self.assertEqual(len(metadata.names(True)), 0) 

363 

364 def testGetSipMatrixFromMetadata(self): 

365 """Test getSipMatrixFromMetadata and makeSipMatrixMetadata 

366 """ 

367 for badName in ("X", "AA"): 

368 self.assertFalse(hasSipMatrix(self.metadata, badName)) 

369 with self.assertRaises(TypeError): 

370 getSipMatrixFromMetadata(self.metadata, badName) 

371 

372 for name in ("A", "B", "AP", "BP"): 

373 self.assertTrue(hasSipMatrix(self.metadata, name)) 

374 sipMatrix = getSipMatrixFromMetadata(self.metadata, name) 

375 width = self.metadata.getScalar(f"{name}_ORDER") + 1 

376 self.assertEqual(sipMatrix.shape, (width, width)) 

377 for i in range(width): 

378 for j in range(width): 

379 # SIP matrix terms use 0-based indexing 

380 cardName = f"{name}_{i}_{j}" 

381 if self.metadata.exists(cardName): 

382 self.assertEqual(sipMatrix[i, j], self.metadata.getScalar(cardName)) 

383 else: 

384 self.assertEqual(sipMatrix[i, j], 0.0) 

385 

386 metadata = makeSipMatrixMetadata(sipMatrix, name) 

387 for name in metadata.names(False): 

388 value = metadata.getScalar(name) 

389 if (name.endswith("ORDER")): 

390 self.assertEqual(width, value + 1) 

391 else: 

392 self.assertEqual(value, self.metadata.getScalar(name)) 

393 self.assertNotEqual(value, 0.0) # 0s are omitted 

394 

395 # try metadata with only the ORDER keyword; the matrix should be all zeros 

396 # except for the invalid case of order < 0 

397 for order in (-3, -1, 0, 3): 

398 metadata2 = PropertyList() 

399 metadata2.set("W_ORDER", order) 

400 if order < 0: 

401 # invalid order 

402 self.assertFalse(hasSipMatrix(metadata2, "W")) 

403 with self.assertRaises(TypeError): 

404 getSipMatrixFromMetadata(metadata2, "W") 

405 else: 

406 self.assertTrue(hasSipMatrix(metadata2, "W")) 

407 zeroMatrix = getSipMatrixFromMetadata(metadata2, "W") 

408 self.assertEqual(zeroMatrix.shape, (order + 1, order + 1)) 

409 for i in range(order + 1): 

410 for j in range(order + 1): 

411 self.assertEqual(zeroMatrix[i, j], 0.0) 

412 

413 def testGetCdMatrixFromMetadata(self): 

414 cdMatrix = getCdMatrixFromMetadata(self.metadata) 

415 for i in range(2): 

416 for j in range(2): 

417 cardName = f"CD{i + 1}_{j + 1}" 

418 self.assertEqual(cdMatrix[i, j], self.metadata.getScalar(cardName)) 

419 

420 metadata = PropertyList() 

421 with self.assertRaises(TypeError): 

422 getCdMatrixFromMetadata(metadata) 

423 metadata.add("CD2_1", 0.56) # just one term, with an arbitrary value 

424 cdMatrix2 = getCdMatrixFromMetadata(metadata) 

425 for i in range(2): 

426 for j in range(2): 

427 # CD matrix terms use 1-based indexing 

428 cardName = f"CD{i + 1}_{j + 1}" 

429 if i == 1 and j == 0: 

430 self.assertEqual(cdMatrix2[i, j], 0.56) 

431 else: 

432 self.assertEqual(cdMatrix2[i, j], 0.0) 

433 

434 def testMakeTanSipMetadata(self): 

435 """Test makeTanSipMetadata 

436 """ 

437 crpix = lsst.geom.Point2D(self.metadata.getScalar("CRPIX1") - 1, 

438 self.metadata.getScalar("CRPIX2") - 1) 

439 crval = lsst.geom.SpherePoint(self.metadata.getScalar("CRVAL1"), 

440 self.metadata.getScalar("CRVAL2"), lsst.geom.degrees) 

441 cdMatrix = getCdMatrixFromMetadata(self.metadata) 

442 sipA = getSipMatrixFromMetadata(self.metadata, "A") 

443 sipB = getSipMatrixFromMetadata(self.metadata, "B") 

444 sipAp = getSipMatrixFromMetadata(self.metadata, "AP") 

445 sipBp = getSipMatrixFromMetadata(self.metadata, "BP") 

446 forwardMetadata = makeTanSipMetadata( 

447 crpix=crpix, 

448 crval=crval, 

449 cdMatrix=cdMatrix, 

450 sipA=sipA, 

451 sipB=sipB, 

452 ) 

453 self.assertFalse(forwardMetadata.exists("AP_ORDER")) 

454 self.assertFalse(forwardMetadata.exists("BP_ORDER")) 

455 

456 fullMetadata = makeTanSipMetadata( 

457 crpix=crpix, 

458 crval=crval, 

459 cdMatrix=cdMatrix, 

460 sipA=sipA, 

461 sipB=sipB, 

462 sipAp=sipAp, 

463 sipBp=sipBp, 

464 ) 

465 for cardName in ("CRPIX1", "CRPIX2", "CRVAL1", "CRVAL2", "CTYPE1", "CTYPE2", 

466 "CUNIT1", "CUNIT2", "RADESYS"): 

467 self.assertTrue(forwardMetadata.exists(cardName)) 

468 self.assertTrue(fullMetadata.exists(cardName)) 

469 for name, matrix in (("A", sipA), ("B", sipB)): 

470 self.checkSipMetadata(name, matrix, forwardMetadata) 

471 self.checkSipMetadata(name, matrix, fullMetadata) 

472 for name, matrix in (("AP", sipAp), ("BP", sipBp)): 

473 self.checkSipMetadata(name, matrix, fullMetadata) 

474 

475 def checkSipMetadata(self, name, sipMatrix, metadata): 

476 width = metadata.getScalar(f"{name}_ORDER") + 1 

477 self.assertEqual(width, sipMatrix.shape[0]) 

478 for i in range(width): 

479 for j in range(width): 

480 cardName = f"{name}_{i}_{j}" 

481 value = sipMatrix[i, j] 

482 if value != 0 or metadata.exists(cardName): 

483 self.assertEqual(value, metadata.getScalar(cardName)) 

484 

485 

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

487 pass 

488 

489 

490def setup_module(module): 

491 lsst.utils.tests.init() 

492 

493 

494if __name__ == "__main__": 494 ↛ 495line 494 didn't jump to line 495, because the condition on line 494 was never true

495 lsst.utils.tests.init() 

496 unittest.main()