Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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.wcsUtils import createTrivialWcsMetadata, deleteBasicWcsMetadata, \ 

34 getCdMatrixFromMetadata, getSipMatrixFromMetadata, getImageXY0FromMetadata, \ 

35 hasSipMatrix, makeSipMatrixMetadata, makeTanSipMetadata, \ 

36 computePixelToDistortedPixel, makeDistortedTanWcs 

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 makeDistortedTanWcs and 

51 computePixelToDistortedPixel 

52 """ 

53 def setUp(self): 

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

55 self.pixelSizeMm = 0.024 # mm/pixel 

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

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

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

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

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

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

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

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

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

65 self.pixelToFocalPlane = self.makeAffineTransform( 

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

67 rotation=self.ccdOrientation, 

68 scale=self.pixelSizeMm, 

69 ) 

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

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

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

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

74 self.pixelPoints = bboxD.getCorners() 

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

76 

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

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

79 then scales and rotates the result 

80 """ 

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

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

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

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

85 return afwGeom.makeTransform(rotScale*offset) 

86 

87 

88class MakeDistortedTanWcsTestCase(BaseTestCase): 

89 """Test lsst.afw.geom.makeDistortedTanWcs 

90 """ 

91 

92 def testNoDistortion(self): 

93 """Test makeDistortedTanWcs using an affine transform for pixelToFocalPlane 

94 

95 Construct pixelToFocalPlane to match the plate scale used to 

96 generate self.tanWcs, the input to makeDistortedTanWcs. Thus the WCS 

97 returned by makeDistortedTanWcs should match self.tanWcs. 

98 """ 

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

100 wcs = makeDistortedTanWcs( 

101 tanWcs=self.tanWcs, 

102 pixelToFocalPlane=self.pixelToFocalPlane, 

103 focalPlaneToFieldAngle=focalPlaneToFieldAngle, 

104 ) 

105 self.assertWcsAlmostEqualOverBBox(wcs, self.tanWcs, bbox=self.bbox) 

106 

107 def testDistortion(self): 

108 """Test makeDistortedTanWcs using a non-affine transform for pixelToFocalPlane 

109 """ 

110 # Compute a distorted wcs that matches self.tanWcs at the center of the field; 

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

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

113 focalPlaneToFieldAngle = fieldAngleToFocalPlane.inverted() 

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

115 wcs = makeDistortedTanWcs( 

116 tanWcs=self.tanWcs, 

117 pixelToFocalPlane=self.pixelToFocalPlane, 

118 focalPlaneToFieldAngle=focalPlaneToFieldAngle, 

119 ) 

120 

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

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

123 tanSkyAtCtr = self.tanWcs.pixelToSky(pixelAtCtr) 

124 skyAtCtr = wcs.pixelToSky(pixelAtCtr) 

125 self.assertPairsAlmostEqual(tanSkyAtCtr, skyAtCtr) 

126 

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

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

129 # sky -> wcs.skyToPixel -> pixelToFocalPlane -> focalPlaneToFieldAngle 

130 # where focalPlaneToTanFieldAngle is the linear approximation to 

131 # focalPlaneToFieldAngle at the center of the field (where tanWcs and wcs match), 

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

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

134 

135 tanFieldAnglePoints = focalPlaneToTanFieldAngle.applyForward( 

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

137 fieldAnglePoints = focalPlaneToFieldAngle.applyForward( 

138 self.pixelToFocalPlane.applyForward(wcs.skyToPixel(skyPoints))) 

139 assert_allclose(tanFieldAnglePoints, fieldAnglePoints) 

140 

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

142 # the following sky positions should be almost equal: 

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

144 # fieldAngle -> fieldAngleToFocalPlane -> focalPlaneToPixel -> wcs.pixelToSky 

145 focalPlaneToPixel = self.pixelToFocalPlane.inverted() 

146 fieldAngleToTanFocalPlane = focalPlaneToTanFieldAngle.inverted() 

147 tanSkyPoints2 = self.tanWcs.pixelToSky( 

148 focalPlaneToPixel.applyForward( 

149 fieldAngleToTanFocalPlane.applyForward(fieldAnglePoints))) 

150 

151 skyPoints2 = wcs.pixelToSky( 

152 focalPlaneToPixel.applyForward( 

153 fieldAngleToFocalPlane.applyForward(fieldAnglePoints))) 

154 

155 self.assertSpherePointListsAlmostEqual(tanSkyPoints2, skyPoints2) 

156 

157 

158class ComputePixelToDistortedPixelTestCase(BaseTestCase): 

159 """Test lsst.afw.geom.computePixelToDistortedPixel 

160 """ 

161 

162 def testNoDistortion(self): 

163 """Test computePixelToDistortedPixel without distortion 

164 

165 Use an affine transform for pixelToFocalPlane; the transform 

166 returned by computePixelToDistortedPixel should be the identity transform 

167 """ 

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

169 pixelToDistortedPixel = computePixelToDistortedPixel( 

170 pixelToFocalPlane=self.pixelToFocalPlane, 

171 focalPlaneToFieldAngle=focalPlaneToFieldAngle, 

172 ) 

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

174 pixelPoints = bboxD.getCorners() 

175 pixelPoints.append(bboxD.getCenter()) 

176 

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

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

179 

180 def testDistortion(self): 

181 """Test computePixelToDistortedPixel with distortion 

182 

183 pixelToDistortedPixel -> self.tanWcs should match a WCS 

184 created with makeDistortedTanWcs 

185 """ 

186 focalPlaneToFieldAngle = afwGeom.makeRadialTransform([0.0, self.radPerMm, 0.0, self.radPerMm]) 

187 pixelToDistortedPixel = computePixelToDistortedPixel( 

188 pixelToFocalPlane=self.pixelToFocalPlane, 

189 focalPlaneToFieldAngle=focalPlaneToFieldAngle, 

190 ) 

191 # Do not try to make pixelToDistortedPixel -> self.tanWcs into a WCS 

192 # because the frame names will be wrong; use a TransformPoint2Tolsst.geom.SpherePoint instead 

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

194 pixelToDistortedSky = pixelToDistortedPixel.then(tanWcsTransform) 

195 

196 wcs = makeDistortedTanWcs( 

197 tanWcs=self.tanWcs, 

198 pixelToFocalPlane=self.pixelToFocalPlane, 

199 focalPlaneToFieldAngle=focalPlaneToFieldAngle, 

200 ) 

201 

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

203 pixelPoints = bboxD.getCorners() 

204 pixelPoints.append(bboxD.getCenter()) 

205 

206 skyPoints1 = pixelToDistortedSky.applyForward(pixelPoints) 

207 skyPoints2 = wcs.pixelToSky(pixelPoints) 

208 self.assertSpherePointListsAlmostEqual(skyPoints1, skyPoints2) 

209 

210 pixelPoints1 = pixelToDistortedSky.applyInverse(skyPoints1) 

211 pixelPoints2 = wcs.skyToPixel(skyPoints1) 

212 assert_allclose(pixelPoints1, pixelPoints2) 

213 

214 

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

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

217 """ 

218 def setUp(self): 

219 # Actual WCS from processing Suprime-Cam 

220 self.width = 2048 

221 self.height = 4177 

222 metadata = PropertyList() 

223 for name, value in ( 

224 ('NAXIS', 2), 

225 ('EQUINOX', 2000.0000000000), 

226 ('RADESYS', "ICRS"), 

227 ('CRPIX1', -3232.7544925483), 

228 ('CRPIX2', 4184.4881091129), 

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

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

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

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

233 ('CRVAL1', 5.6066137655191), 

234 ('CRVAL2', -0.60804032498548), 

235 ('CUNIT1', "deg"), 

236 ('CUNIT2', "deg"), 

237 ('A_ORDER', 5), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

256 ('B_ORDER', 5), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

275 ('AP_ORDER', 5), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

296 ('BP_ORDER', 5), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

319 ): 

320 metadata.set(name, value) 

321 self.metadata = metadata 

322 

323 def testCreateTrivialWcsAsPropertySet(self): 

324 wcsName = "Z" # arbitrary 

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

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

327 desiredNameValueList = ( # names are missing wcsName suffix 

328 ("CRPIX1", 1.0), 

329 ("CRPIX2", 1.0), 

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

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

332 ("CTYPE1", "LINEAR"), 

333 ("CTYPE2", "LINEAR"), 

334 ("CUNIT1", "PIXEL"), 

335 ("CUNIT2", "PIXEL"), 

336 ) 

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

338 for name, value in desiredNameValueList: 

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

340 

341 def testDeleteBasicWcsMetadata(self): 

342 wcsName = "Q" # arbitrary 

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

344 # add the other keywords that will be deleted 

345 for i in range(2): 

346 for j in range(2): 

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

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

349 # add a keyword that will not be deleted 

350 metadata.set("NAXIS1", 100) 

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

352 

353 # deleting data for a different WCS will delete nothing 

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

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

356 

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

358 deleteBasicWcsMetadata(metadata=metadata, wcsName=wcsName) 

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

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

361 

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

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

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

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

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

367 deleteBasicWcsMetadata(metadata=metadata, wcsName=wcsName) 

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

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

370 

371 def testGetImageXY0FromMetadata(self): 

372 wcsName = "Z" # arbitrary 

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

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

375 

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

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

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

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

380 

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

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

383 nameToRemove = namePrefixToRemove + wcsName 

384 removedValue = metadata.getScalar(nameToRemove) 

385 metadata.remove(nameToRemove) 

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

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

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

389 # restore removed item 

390 metadata.set(nameToRemove, removedValue) 

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

392 

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

394 for i in (1, 2): 

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

396 metadata.set(nameToChange, 1.1) 

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

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

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

400 # restore altered CRPIX value 

401 metadata.set(nameToChange, 1.0) 

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

403 

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

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

406 self.assertEqual(xy0RightWcsName, xy0) 

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

408 

409 # use the correct WCS and strip usable metadata 

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

411 self.assertEqual(xy0RightWcsName, xy0) 

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

413 

414 def testGetSipMatrixFromMetadata(self): 

415 """Test getSipMatrixFromMetadata and makeSipMatrixMetadata 

416 """ 

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

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

419 with self.assertRaises(TypeError): 

420 getSipMatrixFromMetadata(self.metadata, badName) 

421 

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

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

424 sipMatrix = getSipMatrixFromMetadata(self.metadata, name) 

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

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

427 for i in range(width): 

428 for j in range(width): 

429 # SIP matrix terms use 0-based indexing 

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

431 if self.metadata.exists(cardName): 

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

433 else: 

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

435 

436 metadata = makeSipMatrixMetadata(sipMatrix, name) 

437 for name in metadata.names(False): 

438 value = metadata.getScalar(name) 

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

440 self.assertEqual(width, value + 1) 

441 else: 

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

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

444 

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

446 # except for the invalid case of order < 0 

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

448 metadata2 = PropertyList() 

449 metadata2.set("W_ORDER", order) 

450 if order < 0: 

451 # invalid order 

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

453 with self.assertRaises(TypeError): 

454 getSipMatrixFromMetadata(metadata2, "W") 

455 else: 

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

457 zeroMatrix = getSipMatrixFromMetadata(metadata2, "W") 

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

459 for i in range(order + 1): 

460 for j in range(order + 1): 

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

462 

463 def testGetCdMatrixFromMetadata(self): 

464 cdMatrix = getCdMatrixFromMetadata(self.metadata) 

465 for i in range(2): 

466 for j in range(2): 

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

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

469 

470 metadata = PropertyList() 

471 with self.assertRaises(TypeError): 

472 getCdMatrixFromMetadata(metadata) 

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

474 cdMatrix2 = getCdMatrixFromMetadata(metadata) 

475 for i in range(2): 

476 for j in range(2): 

477 # CD matrix terms use 1-based indexing 

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

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

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

481 else: 

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

483 

484 def testMakeTanSipMetadata(self): 

485 """Test makeTanSipMetadata 

486 """ 

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

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

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

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

491 cdMatrix = getCdMatrixFromMetadata(self.metadata) 

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

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

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

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

496 forwardMetadata = makeTanSipMetadata( 

497 crpix=crpix, 

498 crval=crval, 

499 cdMatrix=cdMatrix, 

500 sipA=sipA, 

501 sipB=sipB, 

502 ) 

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

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

505 

506 fullMetadata = makeTanSipMetadata( 

507 crpix=crpix, 

508 crval=crval, 

509 cdMatrix=cdMatrix, 

510 sipA=sipA, 

511 sipB=sipB, 

512 sipAp=sipAp, 

513 sipBp=sipBp, 

514 ) 

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

516 "CUNIT1", "CUNIT2", "RADESYS"): 

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

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

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

520 self.checkSipMetadata(name, matrix, forwardMetadata) 

521 self.checkSipMetadata(name, matrix, fullMetadata) 

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

523 self.checkSipMetadata(name, matrix, fullMetadata) 

524 

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

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

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

528 for i in range(width): 

529 for j in range(width): 

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

531 value = sipMatrix[i, j] 

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

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

534 

535 

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

537 pass 

538 

539 

540def setup_module(module): 

541 lsst.utils.tests.init() 

542 

543 

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

545 lsst.utils.tests.init() 

546 unittest.main()