Coverage for tests/test_skyWcs.py: 11%
550 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-08 03:13 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-08 03:13 -0700
1import itertools
2import os
3import sys
4import unittest
6import astropy.io.fits
7import astropy.coordinates
8import astropy.wcs
9import astshim as ast
10import numpy as np
11from numpy.testing import assert_allclose
13import lsst.utils.tests
14from lsst.daf.base import PropertyList
15import lsst.geom
16import lsst.afw.cameraGeom as cameraGeom
17from lsst.afw.geom import wcsAlmostEqualOverBBox, \
18 TransformPoint2ToPoint2, TransformPoint2ToSpherePoint, makeRadialTransform, \
19 SkyWcs, makeSkyWcs, makeCdMatrix, makeWcsPairTransform, \
20 makeFlippedWcs, makeModifiedWcs, makeTanSipWcs, \
21 getIntermediateWorldCoordsToSky, getPixelToIntermediateWorldCoords, \
22 stripWcsMetadata
23from lsst.afw.geom import getCdMatrixFromMetadata, getSipMatrixFromMetadata, makeSimpleWcsMetadata
24from lsst.afw.geom.testUtils import makeSipIwcToPixel, makeSipPixelToIwc
25from lsst.afw.fits import makeLimitedFitsHeader
26from lsst.afw.image import ExposureF
29def addActualPixelsFrame(skyWcs, actualPixelsToPixels):
30 """Add an "ACTUAL_PIXELS" frame to a SkyWcs and return the result
32 Parameters
33 ----------
34 skyWcs : `lsst.afw.geom.SkyWcs`
35 The WCS to which you wish to add an ACTUAL_PIXELS frame
36 actualPixelsToPixels : `lsst.afw.geom.TransformPoint2ToPoint2`
37 The transform from ACTUAL_PIXELS to PIXELS
38 """
39 actualPixelsToPixelsMap = actualPixelsToPixels.getMapping()
40 actualPixelsFrame = ast.Frame(2, "Domain=ACTUAL_PIXELS")
41 frameDict = skyWcs.getFrameDict()
42 frameDict.addFrame("PIXELS", actualPixelsToPixelsMap.inverted(), actualPixelsFrame)
43 frameDict.setBase("ACTUAL_PIXELS")
44 frameDict.setCurrent("SKY")
45 return SkyWcs(frameDict)
48class SkyWcsBaseTestCase(lsst.utils.tests.TestCase):
49 def checkPersistence(self, skyWcs, bbox):
50 """Check persistence of a SkyWcs
51 """
52 className = "SkyWcs"
54 # check writeString and readString
55 skyWcsStr = skyWcs.writeString()
56 serialVersion, serialClassName, serialRest = skyWcsStr.split(" ", 2)
57 self.assertEqual(int(serialVersion), 1)
58 self.assertEqual(serialClassName, className)
59 badStr1 = " ".join(["2", serialClassName, serialRest])
60 with self.assertRaises(lsst.pex.exceptions.TypeError):
61 skyWcs.readString(badStr1)
62 badClassName = "x" + serialClassName
63 badStr2 = " ".join(["1", badClassName, serialRest])
64 with self.assertRaises(lsst.pex.exceptions.TypeError):
65 skyWcs.readString(badStr2)
66 skyWcsFromStr1 = skyWcs.readString(skyWcsStr)
67 self.assertEqual(skyWcs, skyWcsFromStr1)
68 self.assertEqual(type(skyWcs), type(skyWcsFromStr1))
69 self.assertEqual(skyWcs.getFrameDict(), skyWcsFromStr1.getFrameDict())
71 pixelPoints = [
72 lsst.geom.Point2D(0, 0),
73 lsst.geom.Point2D(1000, 0),
74 lsst.geom.Point2D(0, 1000),
75 lsst.geom.Point2D(-50, -50),
76 ]
77 skyPoints = skyWcs.pixelToSky(pixelPoints)
78 pixelPoints2 = skyWcs.skyToPixel(skyPoints)
79 assert_allclose(pixelPoints, pixelPoints2, atol=1e-7)
81 # check that WCS is properly saved as part of an exposure FITS file
82 exposure = ExposureF(100, 100, skyWcs)
83 with lsst.utils.tests.getTempFilePath(".fits") as outFile:
84 exposure.writeFits(outFile)
85 exposureRoundTrip = ExposureF(outFile)
86 wcsFromExposure = exposureRoundTrip.getWcs()
87 self.assertWcsAlmostEqualOverBBox(skyWcs, wcsFromExposure, bbox, maxDiffPix=0,
88 maxDiffSky=0*lsst.geom.radians)
90 def checkFrameDictConstructor(self, skyWcs, bbox):
91 """Check that the FrameDict constructor works
92 """
93 frameDict = skyWcs.getFrameDict()
94 wcsFromFrameDict = SkyWcs(frameDict)
95 self.assertWcsAlmostEqualOverBBox(skyWcs, wcsFromFrameDict, bbox, maxDiffPix=0,
96 maxDiffSky=0*lsst.geom.radians)
98 self.checkPersistence(wcsFromFrameDict, bbox)
100 # check that it is impossible to build a SkyWcs if a required frame is missing
101 for domain in ("PIXELS", "IWC", "SKY"):
102 badFrameDict = skyWcs.getFrameDict()
103 badFrameDict.removeFrame(domain)
104 with self.assertRaises(lsst.pex.exceptions.TypeError):
105 SkyWcs(badFrameDict)
107 def checkMakeFlippedWcs(self, skyWcs, skyAtol=1e-7*lsst.geom.arcseconds):
108 """Check makeFlippedWcs on the provided WCS
109 """
110 # make an arbitrary bbox, but one that includes zero in one axis
111 # and does not include zero in the other axis
112 # the center of the bbox is used as the center of flipping
113 # and the corners of the bbox are the input positions that are tested
114 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-100, 1000), lsst.geom.Extent2D(2000, 1501))
115 # dict of (isRight, isTop): position
116 minPos = bbox.getMin()
117 maxPos = bbox.getMax()
118 center = bbox.getCenter()
119 cornerDict = {
120 (False, False): minPos,
121 (False, True): lsst.geom.Point2D(minPos[0], maxPos[1]),
122 (True, False): lsst.geom.Point2D(maxPos[0], minPos[1]),
123 (True, True): maxPos,
124 }
125 for flipLR, flipTB in itertools.product((False, True), (False, True)):
126 flippedWcs = makeFlippedWcs(wcs=skyWcs, flipLR=flipLR, flipTB=flipTB, center=center)
127 # the center is unchanged
128 self.assertSpherePointsAlmostEqual(skyWcs.pixelToSky(center),
129 flippedWcs.pixelToSky(center), maxSep=skyAtol)
131 for isR, isT in itertools.product((False, True), (False, True)):
132 origPos = cornerDict[(isR, isT)]
133 flippedPos = cornerDict[(isR ^ flipLR, isT ^ flipTB)]
134 self.assertSpherePointsAlmostEqual(skyWcs.pixelToSky(origPos),
135 flippedWcs.pixelToSky(flippedPos), maxSep=skyAtol)
137 def assertSkyWcsAstropyWcsAlmostEqual(self, skyWcs, astropyWcs, bbox,
138 pixAtol=1e-4, skyAtol=1e-4*lsst.geom.arcseconds,
139 checkRoundTrip=True):
140 """Assert that a SkyWcs and the corresponding astropy.wcs.WCS agree over a specified bounding box
141 """
142 bbox = lsst.geom.Box2D(bbox)
143 center = bbox.getCenter()
144 xArr = bbox.getMinX(), center[0], bbox.getMaxX()
145 yArr = bbox.getMinY(), center[1], bbox.getMaxY()
146 pixPosList = [lsst.geom.Point2D(x, y) for x, y in itertools.product(xArr, yArr)]
148 # pixelToSky
149 skyPosList = skyWcs.pixelToSky(pixPosList)
150 astropySkyPosList = self.astropyPixelsToSky(astropyWcs=astropyWcs, pixPosList=pixPosList)
151 self.assertSpherePointListsAlmostEqual(skyPosList, astropySkyPosList, maxSep=skyAtol)
153 if not checkRoundTrip:
154 return
156 # astropy round trip
157 astropyPixPosRoundTrip = self.astropySkyToPixels(astropyWcs=astropyWcs, skyPosList=astropySkyPosList)
158 self.assertPairListsAlmostEqual(pixPosList, astropyPixPosRoundTrip, maxDiff=pixAtol)
160 # SkyWcs round trip
161 pixPosListRoundTrip = skyWcs.skyToPixel(skyPosList)
162 self.assertPairListsAlmostEqual(pixPosList, pixPosListRoundTrip, maxDiff=pixAtol)
164 # skyToPixel astropy vs SkyWcs
165 astropyPixPosList2 = self.astropySkyToPixels(astropyWcs=astropyWcs, skyPosList=skyPosList)
166 self.assertPairListsAlmostEqual(pixPosListRoundTrip, astropyPixPosList2, maxDiff=pixAtol)
168 def astropyPixelsToSky(self, astropyWcs, pixPosList):
169 """Use an astropy wcs to convert pixels to sky
171 @param[in] astropyWcs a celestial astropy.wcs.WCS with 2 axes in RA, Dec order
172 @param[in] pixPosList 0-based pixel positions as lsst.geom.Point2D or similar pairs
173 @returns sky coordinates as a list of lsst.geom.SpherePoint
175 Converts the output to ICRS
176 """
177 xarr = [p[0] for p in pixPosList]
178 yarr = [p[1] for p in pixPosList]
179 skyCoordList = astropy.wcs.utils.pixel_to_skycoord(xp=xarr,
180 yp=yarr,
181 wcs=astropyWcs,
182 origin=0,
183 mode="all")
184 icrsList = [sc.transform_to("icrs") for sc in skyCoordList]
185 return [lsst.geom.SpherePoint(sc.ra.deg, sc.dec.deg, lsst.geom.degrees) for sc in icrsList]
187 def astropySkyToPixels(self, astropyWcs, skyPosList):
188 """Use an astropy wcs to convert pixels to sky
190 @param[in] astropyWcs a celestial astropy.wcs.WCS with 2 axes in RA, Dec order
191 @param[in] skyPosList ICRS sky coordinates as a list of lsst.geom.SpherePoint
192 @returns a list of lsst.geom.Point2D, 0-based pixel positions
194 Converts the input from ICRS to the coordinate system of the wcs
195 """
196 skyCoordList = [astropy.coordinates.SkyCoord(c[0].asDegrees(),
197 c[1].asDegrees(),
198 frame="icrs",
199 unit="deg") for c in skyPosList]
200 xyArr = [astropy.wcs.utils.skycoord_to_pixel(coords=sc,
201 wcs=astropyWcs,
202 origin=0,
203 mode="all") for sc in skyCoordList]
204 # float is needed to avoid truncation to int
205 return [lsst.geom.Point2D(float(x), float(y)) for x, y in xyArr]
208class SimpleSkyWcsTestCase(SkyWcsBaseTestCase):
209 """Test the simple FITS version of makeSkyWcs
210 """
212 def setUp(self):
213 self.crpix = lsst.geom.Point2D(100, 100)
214 self.crvalList = [
215 lsst.geom.SpherePoint(0, 45, lsst.geom.degrees),
216 lsst.geom.SpherePoint(0.00001, 45, lsst.geom.degrees),
217 lsst.geom.SpherePoint(359.99999, 45, lsst.geom.degrees),
218 lsst.geom.SpherePoint(30, 89.99999, lsst.geom.degrees),
219 lsst.geom.SpherePoint(30, -89.99999, lsst.geom.degrees),
220 ]
221 self.orientationList = [
222 0 * lsst.geom.degrees,
223 0.00001 * lsst.geom.degrees,
224 -0.00001 * lsst.geom.degrees,
225 -45 * lsst.geom.degrees,
226 90 * lsst.geom.degrees,
227 ]
228 self.scale = 1.0 * lsst.geom.arcseconds
229 self.tinyPixels = 1.0e-10
230 self.tinyAngle = 1.0e-10 * lsst.geom.radians
231 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-1000, -1000),
232 lsst.geom.Extent2I(2000, 2000)) # arbitrary but reasonable
234 def checkTanWcs(self, crval, orientation, flipX):
235 """Construct a pure TAN SkyWcs and check that it operates as specified
237 Parameters
238 ----------
239 crval : `lsst.geom.SpherePoint`
240 Desired reference sky position.
241 Must not be at either pole.
242 orientation : `lsst.geom.Angle`
243 Position angle of pixel +Y, measured from N through E.
244 At 0 degrees, +Y is along N and +X is along E/W if flipX false/true
245 At 90 degrees, +Y is along E and +X is along S/N if flipX false/true
246 flipX : `bool`
247 Flip x axis? See `orientation` for details.
249 Returns
250 -------
251 wcs : `lsst.afw.geom.SkyWcs`
252 The generated pure TAN SkyWcs
253 """
254 cdMatrix = makeCdMatrix(scale=self.scale, orientation=orientation, flipX=flipX)
255 wcs = makeSkyWcs(crpix=self.crpix, crval=crval, cdMatrix=cdMatrix)
256 self.checkPersistence(wcs, bbox=self.bbox)
257 self.checkMakeFlippedWcs(wcs)
259 self.assertTrue(wcs.isFits)
260 self.assertEqual(wcs.isFlipped, bool(flipX))
262 xoffAng = 0*lsst.geom.degrees if flipX else 180*lsst.geom.degrees
264 pixelList = [
265 lsst.geom.Point2D(self.crpix[0], self.crpix[1]),
266 lsst.geom.Point2D(self.crpix[0] + 1, self.crpix[1]),
267 lsst.geom.Point2D(self.crpix[0], self.crpix[1] + 1),
268 ]
269 skyList = wcs.pixelToSky(pixelList)
271 # check pixels to sky
272 predSkyList = [
273 crval,
274 crval.offset(xoffAng - orientation, self.scale),
275 crval.offset(90*lsst.geom.degrees - orientation, self.scale),
276 ]
277 self.assertSpherePointListsAlmostEqual(predSkyList, skyList)
278 self.assertSpherePointListsAlmostEqual(predSkyList, wcs.pixelToSky(pixelList))
279 for pixel, predSky in zip(pixelList, predSkyList):
280 self.assertSpherePointsAlmostEqual(predSky, wcs.pixelToSky(pixel))
281 self.assertSpherePointsAlmostEqual(predSky, wcs.pixelToSky(pixel[0], pixel[1]))
283 # check sky to pixels
284 self.assertPairListsAlmostEqual(pixelList, wcs.skyToPixel(skyList))
285 self.assertPairListsAlmostEqual(pixelList, wcs.skyToPixel(skyList))
286 for pixel, sky in zip(pixelList, skyList):
287 self.assertPairsAlmostEqual(pixel, wcs.skyToPixel(sky))
288 # self.assertPairsAlmostEqual(pixel, wcs.skyToPixel(sky[0], sky[1]))
290 # check CRVAL round trip
291 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), crval,
292 maxSep=self.tinyAngle)
294 crpix = wcs.getPixelOrigin()
295 self.assertPairsAlmostEqual(crpix, self.crpix, maxDiff=self.tinyPixels)
297 self.assertFloatsAlmostEqual(wcs.getCdMatrix(), cdMatrix, atol=1e-15, rtol=1e-11)
299 pixelScale = wcs.getPixelScale()
300 self.assertAnglesAlmostEqual(self.scale, pixelScale, maxDiff=self.tinyAngle)
302 pixelScale = wcs.getPixelScale(self.crpix)
303 self.assertAnglesAlmostEqual(self.scale, pixelScale, maxDiff=self.tinyAngle)
305 # check that getFitsMetadata can operate at high precision
306 # and has axis order RA, Dec
307 fitsMetadata = wcs.getFitsMetadata(True)
308 self.assertEqual(fitsMetadata.getScalar("CTYPE1")[0:4], "RA--")
309 self.assertEqual(fitsMetadata.getScalar("CTYPE2")[0:4], "DEC-")
311 # Compute a WCS with the pixel origin shifted by an arbitrary amount
312 # The resulting sky origin should not change
313 offset = lsst.geom.Extent2D(500, -322) # arbitrary
314 shiftedWcs = wcs.copyAtShiftedPixelOrigin(offset)
315 self.assertTrue(shiftedWcs.isFits)
316 predShiftedPixelOrigin = self.crpix + offset
317 self.assertPairsAlmostEqual(shiftedWcs.getPixelOrigin(), predShiftedPixelOrigin,
318 maxDiff=self.tinyPixels)
319 self.assertSpherePointsAlmostEqual(shiftedWcs.getSkyOrigin(), crval, maxSep=self.tinyAngle)
321 shiftedPixelList = [p + offset for p in pixelList]
322 shiftedSkyList = shiftedWcs.pixelToSky(shiftedPixelList)
323 self.assertSpherePointListsAlmostEqual(skyList, shiftedSkyList, maxSep=self.tinyAngle)
325 # Check that the shifted WCS can be round tripped as FITS metadata
326 shiftedMetadata = shiftedWcs.getFitsMetadata(precise=True)
327 shiftedWcsCopy = makeSkyWcs(shiftedMetadata)
328 shiftedBBox = lsst.geom.Box2D(predShiftedPixelOrigin,
329 predShiftedPixelOrigin + lsst.geom.Extent2I(2000, 2000))
330 self.assertWcsAlmostEqualOverBBox(shiftedWcs, shiftedWcsCopy, shiftedBBox)
332 wcsCopy = SkyWcs.readString(wcs.writeString())
333 self.assertTrue(wcsCopy.isFits)
335 return wcs
337 def checkNonFitsWcs(self, wcs):
338 """Check SkyWcs.getFitsMetadata for a WCS that cannot be represented as a FITS-WCS
339 """
340 # the modified WCS should not be representable as pure FITS-WCS
341 self.assertFalse(wcs.isFits)
342 with self.assertRaises(RuntimeError):
343 wcs.getFitsMetadata(True)
345 # the approximation returned by getFitsMetadata is poor (pure TAN) until DM-13170
346 wcsFromMetadata = makeSkyWcs(wcs.getFitsMetadata())
347 self.assertFalse(wcsAlmostEqualOverBBox(wcs, wcsFromMetadata, bbox=self.bbox))
349 # the approximation returned by getFitsMetadata is pure TAN until DM-13170,
350 # after DM-13170 change this test to check that wcsFromMetadata is a reasonable
351 # approximation to the original WCS
352 approxWcs = wcs.getTanWcs(wcs.getPixelOrigin())
353 self.assertWcsAlmostEqualOverBBox(approxWcs, wcsFromMetadata, bbox=self.bbox)
355 def testTanWcs(self):
356 """Check a variety of TanWcs, with crval not at a pole.
357 """
358 for crval, orientation, flipX in itertools.product(self.crvalList,
359 self.orientationList,
360 (False, True)):
361 self.checkTanWcs(crval=crval,
362 orientation=orientation,
363 flipX=flipX,
364 )
366 def testTanWcsFromFrameDict(self):
367 """Test making a TAN WCS from a FrameDict
368 """
369 cdMatrix = makeCdMatrix(scale=self.scale)
370 skyWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix)
371 self.checkFrameDictConstructor(skyWcs, bbox=self.bbox)
373 def testGetFrameDict(self):
374 """Test that getFrameDict returns a deep copy
375 """
376 cdMatrix = makeCdMatrix(scale=self.scale)
377 skyWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix)
378 for domain in ("PIXELS", "IWC", "SKY"):
379 frameDict = skyWcs.getFrameDict()
380 frameDict.removeFrame(domain)
381 self.assertFalse(frameDict.hasDomain(domain))
382 self.assertTrue(skyWcs.getFrameDict().hasDomain(domain))
384 def testMakeModifiedWcsNoActualPixels(self):
385 """Test makeModifiedWcs on a SkyWcs that has no ACTUAL_PIXELS frame
386 """
387 cdMatrix = makeCdMatrix(scale=self.scale)
388 originalWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix)
389 originalFrameDict = originalWcs.getFrameDict()
391 # make an arbitrary but reasonable transform to insert using makeModifiedWcs
392 pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011])
393 # the result of the insertion should be as follows
394 desiredPixelsToSky = pixelTransform.then(originalWcs.getTransform())
396 pixPointList = ( # arbitrary but reasonable
397 lsst.geom.Point2D(0.0, 0.0),
398 lsst.geom.Point2D(1000.0, 0.0),
399 lsst.geom.Point2D(0.0, 2000.0),
400 lsst.geom.Point2D(-1111.0, -2222.0),
401 )
402 for modifyActualPixels in (False, True):
403 modifiedWcs = makeModifiedWcs(pixelTransform=pixelTransform,
404 wcs=originalWcs,
405 modifyActualPixels=modifyActualPixels)
406 modifiedFrameDict = modifiedWcs.getFrameDict()
407 skyList = modifiedWcs.pixelToSky(pixPointList)
409 # compare pixels to sky
410 desiredSkyList = desiredPixelsToSky.applyForward(pixPointList)
411 self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList)
413 # compare pixels to IWC
414 pixelsToIwc = TransformPoint2ToPoint2(modifiedFrameDict.getMapping("PIXELS", "IWC"))
415 desiredPixelsToIwc = TransformPoint2ToPoint2(
416 pixelTransform.getMapping().then(originalFrameDict.getMapping("PIXELS", "IWC")))
417 self.assertPairListsAlmostEqual(pixelsToIwc.applyForward(pixPointList),
418 desiredPixelsToIwc.applyForward(pixPointList))
420 self.checkNonFitsWcs(modifiedWcs)
422 def testMakeModifiedWcsWithActualPixels(self):
423 """Test makeModifiedWcs on a SkyWcs that has an ACTUAL_PIXELS frame
424 """
425 cdMatrix = makeCdMatrix(scale=self.scale)
426 baseWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix)
427 # model actual pixels to pixels as an arbitrary zoom factor;
428 # this is not realistic, but is fine for a unit test
429 actualPixelsToPixels = TransformPoint2ToPoint2(ast.ZoomMap(2, 0.72))
430 originalWcs = addActualPixelsFrame(baseWcs, actualPixelsToPixels)
431 originalFrameDict = originalWcs.getFrameDict()
433 # make an arbitrary but reasonable transform to insert using makeModifiedWcs
434 pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011]) # arbitrary but reasonable
436 pixPointList = ( # arbitrary but reasonable
437 lsst.geom.Point2D(0.0, 0.0),
438 lsst.geom.Point2D(1000.0, 0.0),
439 lsst.geom.Point2D(0.0, 2000.0),
440 lsst.geom.Point2D(-1111.0, -2222.0),
441 )
442 for modifyActualPixels in (True, False):
443 modifiedWcs = makeModifiedWcs(pixelTransform=pixelTransform,
444 wcs=originalWcs,
445 modifyActualPixels=modifyActualPixels)
446 modifiedFrameDict = modifiedWcs.getFrameDict()
447 self.assertEqual(modifiedFrameDict.getFrame(modifiedFrameDict.BASE).domain, "ACTUAL_PIXELS")
448 modifiedActualPixelsToPixels = \
449 TransformPoint2ToPoint2(modifiedFrameDict.getMapping("ACTUAL_PIXELS", "PIXELS"))
450 modifiedPixelsToIwc = TransformPoint2ToPoint2(modifiedFrameDict.getMapping("PIXELS", "IWC"))
452 # compare pixels to sky
453 skyList = modifiedWcs.pixelToSky(pixPointList)
454 if modifyActualPixels:
455 desiredPixelsToSky = pixelTransform.then(originalWcs.getTransform())
456 else:
457 originalPixelsToSky = \
458 TransformPoint2ToSpherePoint(originalFrameDict.getMapping("PIXELS", "SKY"))
459 desiredPixelsToSky = actualPixelsToPixels.then(pixelTransform).then(originalPixelsToSky)
460 desiredSkyList = desiredPixelsToSky.applyForward(pixPointList)
461 self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList)
463 # compare ACTUAL_PIXELS to PIXELS and PIXELS to IWC
464 if modifyActualPixels:
465 # check that ACTUAL_PIXELS to PIXELS has been modified as expected
466 desiredActualPixelsToPixels = pixelTransform.then(actualPixelsToPixels)
467 self.assertPairListsAlmostEqual(modifiedActualPixelsToPixels.applyForward(pixPointList),
468 desiredActualPixelsToPixels.applyForward(pixPointList))
470 # check that PIXELS to IWC is unchanged
471 originalPixelsToIwc = TransformPoint2ToPoint2(originalFrameDict.getMapping("PIXELS", "IWC"))
472 self.assertPairListsAlmostEqual(modifiedPixelsToIwc.applyForward(pixPointList),
473 originalPixelsToIwc.applyForward(pixPointList))
475 else:
476 # check that ACTUAL_PIXELS to PIXELS is unchanged
477 self.assertPairListsAlmostEqual(actualPixelsToPixels.applyForward(pixPointList),
478 actualPixelsToPixels.applyForward(pixPointList))
480 # check that PIXELS to IWC has been modified as expected
481 desiredPixelsToIwc = TransformPoint2ToPoint2(
482 pixelTransform.getMapping().then(originalFrameDict.getMapping("PIXELS", "IWC")))
483 self.assertPairListsAlmostEqual(modifiedPixelsToIwc.applyForward(pixPointList),
484 desiredPixelsToIwc.applyForward(pixPointList))
486 self.checkNonFitsWcs(modifiedWcs)
488 def testMakeSkyWcsFromPixelsToFieldAngle(self):
489 """Test makeSkyWcs from a pixelsToFieldAngle transform
490 """
491 pixelSizeMm = 25e-3
492 # place the detector in several positions at several orientations
493 # use fewer CRVAL and orientations to speed up the test
494 for fpPosition, yaw, addOpticalDistortion, crval, pixelOrientation, \
495 flipX, projection in itertools.product(
496 (lsst.geom.Point3D(0, 0, 0), lsst.geom.Point3D(-100, 500, 1.5)),
497 (0*lsst.geom.degrees, 71*lsst.geom.degrees), (False, True),
498 self.crvalList[0:2], self.orientationList[0:2], (False, True), ("TAN", "STG")):
499 with self.subTest(fpPosition=fpPosition, yaw=yaw, addOpticalDistortion=addOpticalDistortion,
500 crval=crval, orientation=pixelOrientation):
501 pixelsToFocalPlane = cameraGeom.Orientation(
502 fpPosition=fpPosition,
503 yaw=yaw,
504 ).makePixelFpTransform(lsst.geom.Extent2D(pixelSizeMm, pixelSizeMm))
505 # Compute crpix before adding optical distortion,
506 # since it is not affected by such distortion
507 crpix = pixelsToFocalPlane.applyInverse(lsst.geom.Point2D(0, 0))
508 radiansPerMm = self.scale.asRadians() / pixelSizeMm
509 focalPlaneToFieldAngle = lsst.afw.geom.makeTransform(
510 lsst.geom.AffineTransform(lsst.geom.LinearTransform.makeScaling(radiansPerMm)))
511 pixelsToFieldAngle = pixelsToFocalPlane.then(focalPlaneToFieldAngle)
513 cdMatrix = makeCdMatrix(scale=self.scale, orientation=pixelOrientation, flipX=flipX)
514 wcs1 = makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix, projection=projection)
516 if addOpticalDistortion:
517 # Model optical distortion as a pixel transform,
518 # so it can be added to the WCS created from crpix,
519 # cdMatrix, etc. using makeModifiedWcs
520 pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011])
521 pixelsToFieldAngle = pixelTransform.then(pixelsToFieldAngle)
522 wcs1 = makeModifiedWcs(pixelTransform=pixelTransform, wcs=wcs1, modifyActualPixels=False)
524 # orientation is with respect to detector x, y
525 # but this flavor of makeSkyWcs needs it with respect to focal plane x, y
526 focalPlaneOrientation = pixelOrientation + (yaw if flipX else -yaw)
527 wcs2 = makeSkyWcs(pixelsToFieldAngle=pixelsToFieldAngle,
528 orientation=focalPlaneOrientation,
529 flipX=flipX,
530 boresight=crval,
531 projection=projection)
532 self.assertWcsAlmostEqualOverBBox(wcs1, wcs2, self.bbox)
534 @unittest.skipIf(sys.version_info[0] < 3, "astropy.wcs rejects the header on py2")
535 def testAgainstAstropyWcs(self):
536 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(2000, 2000))
537 for crval, orientation, flipX, projection in itertools.product(self.crvalList,
538 self.orientationList,
539 (False, True),
540 ("TAN", "STG", "CEA", "AIT")):
541 cdMatrix = makeCdMatrix(scale=self.scale, orientation=orientation, flipX=flipX)
542 metadata = makeSimpleWcsMetadata(crpix=self.crpix, crval=crval, cdMatrix=cdMatrix,
543 projection=projection)
544 header = makeLimitedFitsHeader(metadata)
545 astropyWcs = astropy.wcs.WCS(header)
546 skyWcs = makeSkyWcs(crpix=self.crpix, crval=crval, cdMatrix=cdMatrix, projection=projection)
547 # Most projections only seem to agree to within 1e-4 in the round trip test
548 self.assertSkyWcsAstropyWcsAlmostEqual(skyWcs=skyWcs, astropyWcs=astropyWcs, bbox=bbox)
550 def testPixelToSkyArray(self):
551 """Test the numpy-array version of pixelToSky
552 """
553 cdMatrix = makeCdMatrix(scale=self.scale)
554 wcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix)
556 xPoints = np.array([0.0, 1000.0, 0.0, -1111.0])
557 yPoints = np.array([0.0, 0.0, 2000.0, -2222.0])
559 pixPointList = [lsst.geom.Point2D(x, y) for x, y in zip(xPoints, yPoints)]
561 spherePoints = wcs.pixelToSky(pixPointList)
563 ra, dec = wcs.pixelToSkyArray(xPoints, yPoints, degrees=False)
564 for r, d, spherePoint in zip(ra, dec, spherePoints):
565 self.assertAlmostEqual(r, spherePoint.getRa().asRadians())
566 self.assertAlmostEqual(d, spherePoint.getDec().asRadians())
568 ra, dec = wcs.pixelToSkyArray(xPoints, yPoints, degrees=True)
569 for r, d, spherePoint in zip(ra, dec, spherePoints):
570 self.assertAlmostEqual(r, spherePoint.getRa().asDegrees())
571 self.assertAlmostEqual(d, spherePoint.getDec().asDegrees())
573 def testSkyToPixelArray(self):
574 """Test the numpy-array version of skyToPixel
575 """
576 cdMatrix = makeCdMatrix(scale=self.scale)
577 wcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix)
579 raPoints = np.array([3.92646679e-02, 3.59646622e+02,
580 3.96489283e-02, 4.70419353e-01])
581 decPoints = np.array([44.9722155, 44.97167735,
582 45.52775599, 44.3540619])
584 spherePointList = [lsst.geom.SpherePoint(ra*lsst.geom.degrees,
585 dec*lsst.geom.degrees)
586 for ra, dec in zip(raPoints, decPoints)]
588 pixPoints = wcs.skyToPixel(spherePointList)
590 x, y = wcs.skyToPixelArray(np.deg2rad(raPoints), np.deg2rad(decPoints))
591 for x0, y0, pixPoint in zip(x, y, pixPoints):
592 self.assertAlmostEqual(x0, pixPoint.getX())
593 self.assertAlmostEqual(y0, pixPoint.getY())
595 x, y = wcs.skyToPixelArray(raPoints, decPoints, degrees=True)
596 for x0, y0, pixPoint in zip(x, y, pixPoints):
597 self.assertAlmostEqual(x0, pixPoint.getX())
598 self.assertAlmostEqual(y0, pixPoint.getY())
600 def testStr(self):
601 """Test that we can get something coherent when printing a SkyWcs.
602 """
603 cdMatrix = makeCdMatrix(scale=self.scale)
604 skyWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix)
605 self.assertIn(f"Sky Origin: {self.crvalList[0]}", str(skyWcs))
606 self.assertIn(f"Pixel Origin: {self.crpix}", str(skyWcs))
607 self.assertIn("Pixel Scale: ", str(skyWcs))
610class MetadataWcsTestCase(SkyWcsBaseTestCase):
611 """Test metadata constructor of SkyWcs
612 """
614 def setUp(self):
615 metadata = PropertyList()
616 for name, value in (
617 ("RADESYS", "ICRS"),
618 ("EQUINOX", 2000.),
619 ("CRVAL1", 215.604025685476),
620 ("CRVAL2", 53.1595451514076),
621 ("CRPIX1", 1109.99981456774),
622 ("CRPIX2", 560.018167811613),
623 ("CTYPE1", "RA---TAN"),
624 ("CTYPE2", "DEC--TAN"),
625 ("CUNIT1", "deg"),
626 ("CUNIT2", "deg"),
627 ("CD1_1", 5.10808596133527E-05),
628 ("CD1_2", 1.85579539217196E-07),
629 ("CD2_2", -5.10281493481982E-05),
630 ("CD2_1", -1.85579539217196E-07),
631 ):
632 metadata.set(name, value)
633 self.metadata = metadata
635 def tearDown(self):
636 del self.metadata
638 def checkWcs(self, skyWcs):
639 pixelOrigin = skyWcs.getPixelOrigin()
640 skyOrigin = skyWcs.getSkyOrigin()
641 for i in range(2):
642 # subtract 1 from FITS CRPIX to get LSST convention
643 self.assertAlmostEqual(pixelOrigin[i], self.metadata.getScalar(f"CRPIX{i+1}") - 1)
644 self.assertAnglesAlmostEqual(skyOrigin[i],
645 self.metadata.getScalar(f"CRVAL{i+1}")*lsst.geom.degrees)
646 cdMatrix = skyWcs.getCdMatrix()
647 for i, j in itertools.product(range(2), range(2)):
648 self.assertAlmostEqual(cdMatrix[i, j], self.metadata.getScalar(f"CD{i+1}_{j+1}"))
650 self.assertTrue(skyWcs.isFits)
652 skyWcsCopy = SkyWcs.readString(skyWcs.writeString())
653 self.assertTrue(skyWcsCopy.isFits)
654 self.checkMakeFlippedWcs(skyWcs)
656 @unittest.skipIf(sys.version_info[0] < 3, "astropy.wcs rejects the header on py2")
657 def testAgainstAstropyWcs(self):
658 skyWcs = makeSkyWcs(self.metadata, strip=False)
659 header = makeLimitedFitsHeader(self.metadata)
660 astropyWcs = astropy.wcs.WCS(header)
661 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000))
662 self.assertSkyWcsAstropyWcsAlmostEqual(skyWcs=skyWcs, astropyWcs=astropyWcs, bbox=bbox)
664 def testLinearizeMethods(self):
665 skyWcs = makeSkyWcs(self.metadata)
666 # use a sky position near, but not at, the WCS origin
667 sky00 = skyWcs.getSkyOrigin().offset(45 * lsst.geom.degrees, 1.2 * lsst.geom.degrees)
668 pix00 = skyWcs.skyToPixel(sky00)
669 for skyUnit in (lsst.geom.degrees, lsst.geom.radians):
670 linPixToSky1 = skyWcs.linearizePixelToSky(sky00, skyUnit) # should match inverse of linSkyToPix1
671 linPixToSky2 = skyWcs.linearizePixelToSky(pix00, skyUnit) # should match inverse of linSkyToPix1
672 linSkyToPix1 = skyWcs.linearizeSkyToPixel(sky00, skyUnit)
673 linSkyToPix2 = skyWcs.linearizeSkyToPixel(pix00, skyUnit) # should match linSkyToPix1
675 for pixel in (pix00, pix00 + lsst.geom.Extent2D(1000, -1230)):
676 linSky = linPixToSky1(pixel)
677 self.assertPairsAlmostEqual(linPixToSky2(pixel), linSky)
678 self.assertPairsAlmostEqual(linSkyToPix1(linSky), pixel)
679 self.assertPairsAlmostEqual(linSkyToPix2(linSky), pixel)
681 sky00Doubles = sky00.getPosition(skyUnit)
682 pix00gApprox = linSkyToPix1(sky00Doubles)
683 self.assertPairsAlmostEqual(pix00gApprox, pix00)
684 self.assertAlmostEqual(pix00.getX(), pix00gApprox.getX())
685 self.assertAlmostEqual(pix00.getY(), pix00gApprox.getY())
686 pixelScale = skyWcs.getPixelScale(pix00)
687 pixelArea = pixelScale.asAngularUnits(skyUnit)**2
688 predictedPixelArea = 1 / linSkyToPix1.getLinear().computeDeterminant()
689 self.assertAlmostEqual(pixelArea, predictedPixelArea)
691 def testBasics(self):
692 skyWcs = makeSkyWcs(self.metadata, strip=False)
693 self.assertEqual(len(self.metadata.names(False)), 14)
694 self.checkWcs(skyWcs)
695 makeSkyWcs(self.metadata, strip=True)
696 self.assertEqual(len(self.metadata.names(False)), 0)
698 def testBasicsStrip(self):
699 stripWcsMetadata(self.metadata)
700 self.assertEqual(len(self.metadata.names(False)), 0)
701 # The metadata should be unchanged if we attempt to strip it again
702 metadataCopy = self.metadata.deepCopy()
703 stripWcsMetadata(self.metadata)
704 for key in self.metadata.keys():
705 self.assertEqual(self.metadata[key], metadataCopy[key])
707 def testNormalizationFk5(self):
708 """Test that readLsstSkyWcs correctly normalizes FK5 1975 to ICRS
709 """
710 equinox = 1975.0
711 metadata = self.metadata
713 metadata.set("RADESYS", "FK5")
714 metadata.set("EQUINOX", equinox)
715 crpix = lsst.geom.Point2D(metadata.getScalar("CRPIX1") - 1, metadata.getScalar("CRPIX2") - 1)
716 # record the original CRVAL before reading and stripping metadata
717 crvalFk5Deg = (metadata.getScalar("CRVAL1"), metadata.getScalar("CRVAL2"))
719 # create the wcs and retrieve crval
720 skyWcs = makeSkyWcs(metadata)
721 crval = skyWcs.getSkyOrigin()
723 # compare to computed crval
724 computedCrval = skyWcs.pixelToSky(crpix)
725 self.assertSpherePointsAlmostEqual(crval, computedCrval)
727 # get predicted crval by converting with astropy
728 crvalFk5 = astropy.coordinates.SkyCoord(crvalFk5Deg[0], crvalFk5Deg[1], frame="fk5",
729 equinox=f"J{equinox}", unit="deg")
730 predictedCrvalIcrs = crvalFk5.icrs
731 predictedCrval = lsst.geom.SpherePoint(predictedCrvalIcrs.ra.radian, predictedCrvalIcrs.dec.radian,
732 lsst.geom.radians)
733 self.assertSpherePointsAlmostEqual(crval, predictedCrval, maxSep=0.002*lsst.geom.arcseconds)
735 def testNormalizationDecRa(self):
736 """Test that a Dec, RA WCS is normalized to RA, Dec
737 """
738 crpix = lsst.geom.Point2D(self.metadata.getScalar("CRPIX1") - 1,
739 self.metadata.getScalar("CRPIX2") - 1)
741 # swap RA, Decaxes in metadata
742 crvalIn = lsst.geom.SpherePoint(self.metadata.getScalar("CRVAL1"),
743 self.metadata.getScalar("CRVAL2"), lsst.geom.degrees)
744 self.metadata.set("CRVAL1", crvalIn[1].asDegrees())
745 self.metadata.set("CRVAL2", crvalIn[0].asDegrees())
746 self.metadata.set("CTYPE1", "DEC--TAN")
747 self.metadata.set("CTYPE2", "RA---TAN")
749 # create the wcs
750 skyWcs = makeSkyWcs(self.metadata)
752 # compare pixel origin to input crval
753 crval = skyWcs.getSkyOrigin()
754 self.assertSpherePointsAlmostEqual(crval, crvalIn)
756 # compare to computed crval
757 computedCrval = skyWcs.pixelToSky(crpix)
758 self.assertSpherePointsAlmostEqual(crval, computedCrval)
760 def testReadDESHeader(self):
761 """Verify that we can read a DES header"""
762 self.metadata.set("RADESYS", "ICRS ") # note trailing white space
763 self.metadata.set("CTYPE1", "RA---TPV")
764 self.metadata.set("CTYPE2", "DEC--TPV")
766 skyWcs = makeSkyWcs(self.metadata, strip=False)
767 self.checkWcs(skyWcs)
769 def testCD_PC(self):
770 """Test that we can read a FITS file with both CD and PC keys (like early Suprimecam files)"""
771 md = PropertyList()
772 for k, v in (
773 ("EQUINOX", 2000.0),
774 ("RADESYS", "ICRS"),
775 ("CRPIX1", 5353.0),
776 ("CRPIX2", -35.0),
777 ("CD1_1", 0.0),
778 ("CD1_2", -5.611E-05),
779 ("CD2_1", -5.611E-05),
780 ("CD2_2", -0.0),
781 ("CRVAL1", 4.5789875),
782 ("CRVAL2", 16.30004444),
783 ("CUNIT1", "deg"),
784 ("CUNIT2", "deg"),
785 ("CTYPE1", "RA---TAN"),
786 ("CTYPE2", "DEC--TAN"),
787 ("CDELT1", -5.611E-05),
788 ("CDELT2", 5.611E-05),
789 ):
790 md.set(k, v)
792 wcs = makeSkyWcs(md, strip=False)
794 pixPos = lsst.geom.Point2D(1000, 2000)
795 pred_skyPos = lsst.geom.SpherePoint(4.459815023498577, 16.544199850984768, lsst.geom.degrees)
797 skyPos = wcs.pixelToSky(pixPos)
798 self.assertSpherePointsAlmostEqual(skyPos, pred_skyPos)
800 for badPC in (False, True):
801 for k, v in (
802 ("PC001001", 0.0),
803 ("PC001002", -1.0 if badPC else 1.0),
804 ("PC002001", 1.0 if badPC else -1.0),
805 ("PC002002", 0.0),
806 ):
807 md.set(k, v)
809 # Check Greisen and Calabretta A&A 395 1061 (2002), Eq. 3
810 if not badPC:
811 for i in (1, 2,):
812 for j in (1, 2,):
813 self.assertEqual(md.getScalar(f"CD{i}_{j}"),
814 md.getScalar(f"CDELT{i}")*md.getScalar(f"PC00{i}00{j}"))
816 wcs2 = makeSkyWcs(md, strip=False)
817 skyPos2 = wcs2.pixelToSky(pixPos)
818 self.assertSpherePointsAlmostEqual(skyPos2, pred_skyPos)
820 def testNoEpoch(self):
821 """Ensure we're not writing epoch headers (DATE-OBS, MJD-OBS)"""
822 self.metadata.set("EQUINOX", 2000.0) # Triggers AST writing DATE-OBS, MJD-OBS
823 skyWcs = makeSkyWcs(self.metadata)
824 header = skyWcs.getFitsMetadata()
825 self.assertFalse(header.exists("DATE-OBS"))
826 self.assertFalse(header.exists("MJD-OBS"))
828 def testCdMatrix(self):
829 """Ensure we're writing CD matrix elements even if they're zero"""
830 self.metadata.remove("CD1_2")
831 self.metadata.remove("CD2_1")
832 skyWcs = makeSkyWcs(self.metadata, strip=False)
833 header = skyWcs.getFitsMetadata()
834 for keyword in ("CD1_1", "CD2_2"):
835 # There's some mild rounding going on
836 self.assertFloatsAlmostEqual(header.get(keyword), self.metadata.get(keyword), atol=1.0e-16)
837 for keyword in ("CD1_2", "CD2_1"):
838 self.assertTrue(header.exists(keyword))
839 self.assertEqual(header.get(keyword), 0.0)
842class TestTanSipTestCase(SkyWcsBaseTestCase):
844 def setUp(self):
845 metadata = PropertyList()
846 # the following was fit using CreateWcsWithSip from meas_astrom
847 # and is valid over this bbox: (minimum=(0, 0), maximum=(3030, 3030))
848 # This same metadata was used to create testdata/oldTanSipwWs.fits
849 for name, value in (
850 ("RADESYS", "ICRS"),
851 ("CTYPE1", "RA---TAN-SIP"),
852 ("CTYPE2", "DEC--TAN-SIP"),
853 ("CRPIX1", 1531.1824767147),
854 ("CRPIX2", 1531.1824767147),
855 ("CRVAL1", 43.035511801383),
856 ("CRVAL2", 44.305697682784),
857 ("CUNIT1", "deg"),
858 ("CUNIT2", "deg"),
859 ("CD1_1", 0.00027493991598151),
860 ("CD1_2", -3.2758487104158e-06),
861 ("CD2_1", 3.2301310675830e-06),
862 ("CD2_2", 0.00027493937506632),
863 ("A_ORDER", 5),
864 ("A_0_2", -1.7769487466972e-09),
865 ("A_0_3", 5.3745894718340e-13),
866 ("A_0_4", -7.2921116596880e-17),
867 ("A_0_5", 8.6947236956136e-21),
868 ("A_1_1", 5.4246387438098e-08),
869 ("A_1_2", -1.5689083084641e-12),
870 ("A_1_3", 1.2424130500997e-16),
871 ("A_1_4", 3.9982572658006e-20),
872 ("A_2_0", 4.9268299826160e-08),
873 ("A_2_1", 1.6365657558495e-12),
874 ("A_2_2", 1.1976983061953e-16),
875 ("A_2_3", -1.7262037266467e-19),
876 ("A_3_0", -5.9235031179999e-13),
877 ("A_3_1", -3.4444326387310e-16),
878 ("A_3_2", 1.4377441160800e-19),
879 ("A_4_0", 1.8736407845095e-16),
880 ("A_4_1", 2.9213314172884e-20),
881 ("A_5_0", -5.3601346091084e-20),
882 ("B_ORDER", 5),
883 ("B_0_2", 4.9268299822979e-08),
884 ("B_0_3", -5.9235032026906e-13),
885 ("B_0_4", 1.8736407776035e-16),
886 ("B_0_5", -5.3601341373220e-20),
887 ("B_1_1", 5.4246387435453e-08),
888 ("B_1_2", 1.6365657531115e-12),
889 ("B_1_3", -3.4444326228808e-16),
890 ("B_1_4", 2.9213312399941e-20),
891 ("B_2_0", -1.7769487494962e-09),
892 ("B_2_1", -1.5689082999319e-12),
893 ("B_2_2", 1.1976983393279e-16),
894 ("B_2_3", 1.4377441169892e-19),
895 ("B_3_0", 5.3745894237186e-13),
896 ("B_3_1", 1.2424130479929e-16),
897 ("B_3_2", -1.7262036838229e-19),
898 ("B_4_0", -7.2921117326608e-17),
899 ("B_4_1", 3.9982566975450e-20),
900 ("B_5_0", 8.6947240592408e-21),
901 ("AP_ORDER", 6),
902 ("AP_0_0", -5.4343024221207e-11),
903 ("AP_0_1", 5.5722265946666e-12),
904 ("AP_0_2", 1.7769484042400e-09),
905 ("AP_0_3", -5.3773609554820e-13),
906 ("AP_0_4", 7.3035278852156e-17),
907 ("AP_0_5", -8.7151153799062e-21),
908 ("AP_0_6", 3.2535945427624e-27),
909 ("AP_1_0", -3.8944805432871e-12),
910 ("AP_1_1", -5.4246388067582e-08),
911 ("AP_1_2", 1.5741716194971e-12),
912 ("AP_1_3", -1.2447067748187e-16),
913 ("AP_1_4", -3.9960260822306e-20),
914 ("AP_1_5", 1.1297941471380e-26),
915 ("AP_2_0", -4.9268299293185e-08),
916 ("AP_2_1", -1.6256111849359e-12),
917 ("AP_2_2", -1.1973373130440e-16),
918 ("AP_2_3", 1.7266948205700e-19),
919 ("AP_2_4", -3.7059606160753e-26),
920 ("AP_3_0", 5.9710911995811e-13),
921 ("AP_3_1", 3.4464427650041e-16),
922 ("AP_3_2", -1.4381853884204e-19),
923 ("AP_3_3", -7.6527426974322e-27),
924 ("AP_4_0", -1.8748435698960e-16),
925 ("AP_4_1", -2.9267280226373e-20),
926 ("AP_4_2", 4.8004317051259e-26),
927 ("AP_5_0", 5.3657330221120e-20),
928 ("AP_5_1", -1.6904065766661e-27),
929 ("AP_6_0", -1.9484495120493e-26),
930 ("BP_ORDER", 6),
931 ("BP_0_0", -5.4291220607725e-11),
932 ("BP_0_1", -3.8944871307931e-12),
933 ("BP_0_2", -4.9268299290361e-08),
934 ("BP_0_3", 5.9710912831833e-13),
935 ("BP_0_4", -1.8748435594265e-16),
936 ("BP_0_5", 5.3657325543368e-20),
937 ("BP_0_6", -1.9484577299247e-26),
938 ("BP_1_0", 5.5722051513577e-12),
939 ("BP_1_1", -5.4246388065000e-08),
940 ("BP_1_2", -1.6256111821465e-12),
941 ("BP_1_3", 3.4464427499767e-16),
942 ("BP_1_4", -2.9267278448109e-20),
943 ("BP_1_5", -1.6904244067295e-27),
944 ("BP_2_0", 1.7769484069376e-09),
945 ("BP_2_1", 1.5741716110182e-12),
946 ("BP_2_2", -1.1973373446176e-16),
947 ("BP_2_3", -1.4381853893526e-19),
948 ("BP_2_4", 4.8004294492911e-26),
949 ("BP_3_0", -5.3773609074713e-13),
950 ("BP_3_1", -1.2447067726801e-16),
951 ("BP_3_2", 1.7266947774875e-19),
952 ("BP_3_3", -7.6527556667042e-27),
953 ("BP_4_0", 7.3035279660505e-17),
954 ("BP_4_1", -3.9960255158200e-20),
955 ("BP_4_2", -3.7059659675039e-26),
956 ("BP_5_0", -8.7151157361284e-21),
957 ("BP_5_1", 1.1297944388060e-26),
958 ("BP_6_0", 3.2535788867488e-27),
959 ):
960 metadata.set(name, value)
961 self.metadata = metadata
962 self.bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000))
964 def testTanSipFromFrameDict(self):
965 """Test making a TAN-SIP WCS from a FrameDict
966 """
967 skyWcs = makeSkyWcs(self.metadata, strip=False)
968 self.checkFrameDictConstructor(skyWcs, bbox=self.bbox)
970 def testFitsMetadata(self):
971 """Test that getFitsMetadata works for TAN-SIP
972 """
973 skyWcs = makeSkyWcs(self.metadata, strip=False)
974 self.assertTrue(skyWcs.isFits)
975 fitsMetadata = skyWcs.getFitsMetadata(precise=True)
976 skyWcsCopy = makeSkyWcs(fitsMetadata)
977 self.assertWcsAlmostEqualOverBBox(skyWcs, skyWcsCopy, self.bbox)
978 self.checkPersistence(skyWcs, bbox=self.bbox)
980 def testGetIntermediateWorldCoordsToSky(self):
981 """Test getIntermediateWorldCoordsToSky and getPixelToIntermediateWorldCoords
982 """
983 crpix = lsst.geom.Extent2D(self.metadata.getScalar("CRPIX1") - 1,
984 self.metadata.getScalar("CRPIX2") - 1)
985 skyWcs = makeSkyWcs(self.metadata, strip=False)
986 for simplify in (False, True):
987 pixelToIwc = getPixelToIntermediateWorldCoords(skyWcs, simplify)
988 iwcToSky = getIntermediateWorldCoordsToSky(skyWcs, simplify)
989 self.assertTrue(isinstance(pixelToIwc, TransformPoint2ToPoint2))
990 self.assertTrue(isinstance(iwcToSky, TransformPoint2ToSpherePoint))
991 if simplify:
992 self.assertTrue(pixelToIwc.getMapping().isSimple)
993 self.assertTrue(iwcToSky.getMapping().isSimple)
994 # else the mapping may have already been simplified inside the WCS,
995 # so don't assert isSimple is false
997 # check that the chained transforms produce the same results as the WCS
998 # in the forward and inverse direction
999 pixPosList = []
1000 for dx in (0, 1000):
1001 for dy in (0, 1000):
1002 pixPosList.append(lsst.geom.Point2D(dx, dy) + crpix)
1003 iwcPosList = pixelToIwc.applyForward(pixPosList)
1004 skyPosList = iwcToSky.applyForward(iwcPosList)
1005 self.assertSpherePointListsAlmostEqual(skyPosList, skyWcs.pixelToSky(pixPosList))
1006 self.assertPairListsAlmostEqual(pixelToIwc.applyInverse(iwcToSky.applyInverse(skyPosList)),
1007 skyWcs.skyToPixel(skyPosList))
1009 self.assertPairListsAlmostEqual(iwcPosList, iwcToSky.applyInverse(skyPosList))
1010 self.assertPairListsAlmostEqual(pixPosList, pixelToIwc.applyInverse(iwcPosList))
1012 # compare extracted pixelToIwc to a version of pixelToIwc computed directly from the metadata
1013 ourPixelToIwc = makeSipPixelToIwc(self.metadata)
1014 self.assertPairListsAlmostEqual(pixelToIwc.applyForward(pixPosList),
1015 ourPixelToIwc.applyForward(pixPosList))
1017 # compare extracted iwcToPixel to a version of iwcToPixel computed directly from the metadata
1018 ourIwcToPixel = makeSipIwcToPixel(self.metadata)
1019 self.assertPairListsAlmostEqual(pixelToIwc.applyInverse(iwcPosList),
1020 ourIwcToPixel.applyForward(iwcPosList))
1022 @unittest.skipIf(sys.version_info[0] < 3, "astropy.wcs rejects the header on py2")
1023 def testAgainstAstropyWcs(self):
1024 skyWcs = makeSkyWcs(self.metadata, strip=False)
1025 header = makeLimitedFitsHeader(self.metadata)
1026 astropyWcs = astropy.wcs.WCS(header)
1027 self.assertSkyWcsAstropyWcsAlmostEqual(skyWcs=skyWcs, astropyWcs=astropyWcs, bbox=self.bbox)
1029 def testMakeTanSipWcs(self):
1030 referenceWcs = makeSkyWcs(self.metadata, strip=False)
1032 crpix = lsst.geom.Point2D(self.metadata.getScalar("CRPIX1") - 1,
1033 self.metadata.getScalar("CRPIX2") - 1)
1034 crval = lsst.geom.SpherePoint(self.metadata.getScalar("CRVAL1"),
1035 self.metadata.getScalar("CRVAL2"), lsst.geom.degrees)
1036 cdMatrix = getCdMatrixFromMetadata(self.metadata)
1037 sipA = getSipMatrixFromMetadata(self.metadata, "A")
1038 sipB = getSipMatrixFromMetadata(self.metadata, "B")
1039 sipAp = getSipMatrixFromMetadata(self.metadata, "AP")
1040 sipBp = getSipMatrixFromMetadata(self.metadata, "BP")
1041 skyWcs1 = makeTanSipWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix, sipA=sipA, sipB=sipB)
1042 skyWcs2 = makeTanSipWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix, sipA=sipA, sipB=sipB,
1043 sipAp=sipAp, sipBp=sipBp)
1045 self.assertWcsAlmostEqualOverBBox(referenceWcs, skyWcs1, self.bbox)
1046 self.assertWcsAlmostEqualOverBBox(referenceWcs, skyWcs2, self.bbox)
1047 self.checkMakeFlippedWcs(skyWcs1)
1048 self.checkMakeFlippedWcs(skyWcs2)
1050 def testReadWriteFits(self):
1051 wcsFromMetadata = makeSkyWcs(self.metadata)
1052 with lsst.utils.tests.getTempFilePath(".fits") as filePath:
1053 wcsFromMetadata.writeFits(filePath)
1054 wcsFromFits = SkyWcs.readFits(filePath)
1056 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, self.bbox, maxDiffPix=0,
1057 maxDiffSky=0*lsst.geom.radians)
1059 def testReadOldTanSipFits(self):
1060 """Test reading a FITS file containing data for an lsst::afw::image::TanWcs
1062 That file was made using the same metadata as this test
1063 """
1064 dataDir = os.path.join(os.path.split(__file__)[0], "data")
1065 filePath = os.path.join(dataDir, "oldTanSipWcs.fits")
1066 wcsFromFits = SkyWcs.readFits(filePath)
1068 wcsFromMetadata = makeSkyWcs(self.metadata)
1070 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000))
1071 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, bbox)
1073 def testReadOldTanFits(self):
1074 """Test reading a FITS file containing data for an lsst::afw::image::TanWcs
1076 That file was made using the same metadata follows
1077 (like self.metadata without the distortion)
1078 """
1079 tanMetadata = PropertyList()
1080 # the following was fit using CreateWcsWithSip from meas_astrom
1081 # and is valid over this bbox: (minimum=(0, 0), maximum=(3030, 3030))
1082 # This same metadata was used to create testdata/oldTanSipwWs.fits
1083 for name, value in (
1084 ("RADESYS", "ICRS"),
1085 ("CTYPE1", "RA---TAN"),
1086 ("CTYPE2", "DEC--TAN"),
1087 ("CRPIX1", 1531.1824767147),
1088 ("CRPIX2", 1531.1824767147),
1089 ("CRVAL1", 43.035511801383),
1090 ("CRVAL2", 44.305697682784),
1091 ("CUNIT1", "deg"),
1092 ("CUNIT2", "deg"),
1093 ("CD1_1", 0.00027493991598151),
1094 ("CD1_2", -3.2758487104158e-06),
1095 ("CD2_1", 3.2301310675830e-06),
1096 ("CD2_2", 0.00027493937506632),
1097 ):
1098 tanMetadata.set(name, value)
1100 dataDir = os.path.join(os.path.split(__file__)[0], "data")
1101 filePath = os.path.join(dataDir, "oldTanWcs.fits")
1102 wcsFromFits = SkyWcs.readFits(filePath)
1104 wcsFromMetadata = makeSkyWcs(tanMetadata)
1106 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000))
1107 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, bbox)
1110class WcsPairTransformTestCase(SkyWcsBaseTestCase):
1111 """Test functionality of makeWcsPairTransform.
1112 """
1113 def setUp(self):
1114 SkyWcsBaseTestCase.setUp(self)
1115 crpix = lsst.geom.Point2D(100, 100)
1116 crvalList = [
1117 lsst.geom.SpherePoint(0, 45, lsst.geom.degrees),
1118 lsst.geom.SpherePoint(0.00001, 45, lsst.geom.degrees),
1119 lsst.geom.SpherePoint(359.99999, 45, lsst.geom.degrees),
1120 lsst.geom.SpherePoint(30, 89.99999, lsst.geom.degrees),
1121 ]
1122 orientationList = [
1123 0 * lsst.geom.degrees,
1124 0.00001 * lsst.geom.degrees,
1125 -0.00001 * lsst.geom.degrees,
1126 -45 * lsst.geom.degrees,
1127 90 * lsst.geom.degrees,
1128 ]
1129 scale = 1.0 * lsst.geom.arcseconds
1131 self.wcsList = []
1132 for crval in crvalList:
1133 for orientation in orientationList:
1134 cd = makeCdMatrix(scale=scale, orientation=orientation)
1135 self.wcsList.append(makeSkyWcs(
1136 crpix=crpix,
1137 crval=crval,
1138 cdMatrix=cd))
1139 self.pixelPoints = [lsst.geom.Point2D(x, y) for x, y in
1140 itertools.product((0.0, -2.0, 42.5, 1042.3),
1141 (27.6, -0.1, 0.0, 196.0))]
1143 def testGenericWcs(self):
1144 """Test that input and output points represent the same sky position.
1146 Would prefer a black-box test, but don't have the numbers for it.
1147 """
1148 inPoints = self.pixelPoints
1149 for wcs1 in self.wcsList:
1150 for wcs2 in self.wcsList:
1151 transform = makeWcsPairTransform(wcs1, wcs2)
1152 outPoints = transform.applyForward(inPoints)
1153 inPointsRoundTrip = transform.applyInverse(outPoints)
1154 self.assertPairListsAlmostEqual(inPoints, inPointsRoundTrip)
1155 self.assertSpherePointListsAlmostEqual(wcs1.pixelToSky(inPoints),
1156 wcs2.pixelToSky(outPoints))
1158 def testSameWcs(self):
1159 """Confirm that pairing two identical Wcs gives an identity transform.
1160 """
1161 for wcs in self.wcsList:
1162 transform = makeWcsPairTransform(wcs, wcs)
1163 # check that the transform has been simplified
1164 self.assertTrue(transform.getMapping().isSimple)
1165 # check the transform
1166 outPoints1 = transform.applyForward(self.pixelPoints)
1167 outPoints2 = transform.applyInverse(outPoints1)
1168 self.assertPairListsAlmostEqual(self.pixelPoints, outPoints1)
1169 self.assertPairListsAlmostEqual(outPoints1, outPoints2)
1172class TestMemory(lsst.utils.tests.MemoryTestCase):
1173 pass
1176def setup_module(module):
1177 lsst.utils.tests.init()
1180if __name__ == "__main__": 1180 ↛ 1181line 1180 didn't jump to line 1181, because the condition on line 1180 was never true
1181 lsst.utils.tests.init()
1182 unittest.main()