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