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