Coverage for tests/test_wcsUtils.py : 11%

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
25import numpy as np
26from numpy.testing import assert_allclose
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
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
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())
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)
88class MakeDistortedTanWcsTestCase(BaseTestCase):
89 """Test lsst.afw.geom.makeDistortedTanWcs
90 """
92 def testNoDistortion(self):
93 """Test makeDistortedTanWcs using an affine transform for pixelToFocalPlane
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)
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 )
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)
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)
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)
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)))
151 skyPoints2 = wcs.pixelToSky(
152 focalPlaneToPixel.applyForward(
153 fieldAngleToFocalPlane.applyForward(fieldAnglePoints)))
155 self.assertSpherePointListsAlmostEqual(tanSkyPoints2, skyPoints2)
158class ComputePixelToDistortedPixelTestCase(BaseTestCase):
159 """Test lsst.afw.geom.computePixelToDistortedPixel
160 """
162 def testNoDistortion(self):
163 """Test computePixelToDistortedPixel without distortion
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())
177 assert_allclose(pixelToDistortedPixel.applyForward(pixelPoints), pixelPoints)
178 assert_allclose(pixelToDistortedPixel.applyInverse(pixelPoints), pixelPoints)
180 def testDistortion(self):
181 """Test computePixelToDistortedPixel with distortion
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)
196 wcs = makeDistortedTanWcs(
197 tanWcs=self.tanWcs,
198 pixelToFocalPlane=self.pixelToFocalPlane,
199 focalPlaneToFieldAngle=focalPlaneToFieldAngle,
200 )
202 bboxD = lsst.geom.Box2D(self.bbox)
203 pixelPoints = bboxD.getCorners()
204 pixelPoints.append(bboxD.getCenter())
206 skyPoints1 = pixelToDistortedSky.applyForward(pixelPoints)
207 skyPoints2 = wcs.pixelToSky(pixelPoints)
208 self.assertSpherePointListsAlmostEqual(skyPoints1, skyPoints2)
210 pixelPoints1 = pixelToDistortedSky.applyInverse(skyPoints1)
211 pixelPoints2 = wcs.skyToPixel(skyPoints1)
212 assert_allclose(pixelPoints1, pixelPoints2)
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
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)
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)
353 # deleting data for a different WCS will delete nothing
354 deleteBasicWcsMetadata(metadata=metadata, wcsName="B")
355 self.assertEqual(len(metadata.names(True)), 14)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
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)
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))
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)
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"))
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)
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))
536class MemoryTester(lsst.utils.tests.MemoryTestCase):
537 pass
540def setup_module(module):
541 lsst.utils.tests.init()
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()