Coverage for tests/test_skyWcs.py: 11%

550 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-22 02:38 -0700

1import itertools 

2import os 

3import sys 

4import unittest 

5 

6import astropy.io.fits 

7import astropy.coordinates 

8import astropy.wcs 

9import astshim as ast 

10import numpy as np 

11from numpy.testing import assert_allclose 

12 

13import lsst.utils.tests 

14from lsst.daf.base import PropertyList 

15import lsst.geom 

16import lsst.afw.cameraGeom as cameraGeom 

17from lsst.afw.geom import wcsAlmostEqualOverBBox, \ 

18 TransformPoint2ToPoint2, TransformPoint2ToSpherePoint, makeRadialTransform, \ 

19 SkyWcs, makeSkyWcs, makeCdMatrix, makeWcsPairTransform, \ 

20 makeFlippedWcs, makeModifiedWcs, makeTanSipWcs, \ 

21 getIntermediateWorldCoordsToSky, getPixelToIntermediateWorldCoords, \ 

22 stripWcsMetadata 

23from lsst.afw.geom import getCdMatrixFromMetadata, getSipMatrixFromMetadata, makeSimpleWcsMetadata 

24from lsst.afw.geom.testUtils import makeSipIwcToPixel, makeSipPixelToIwc 

25from lsst.afw.fits import makeLimitedFitsHeader 

26from lsst.afw.image import ExposureF 

27 

28 

29def addActualPixelsFrame(skyWcs, actualPixelsToPixels): 

30 """Add an "ACTUAL_PIXELS" frame to a SkyWcs and return the result 

31 

32 Parameters 

33 ---------- 

34 skyWcs : `lsst.afw.geom.SkyWcs` 

35 The WCS to which you wish to add an ACTUAL_PIXELS frame 

36 actualPixelsToPixels : `lsst.afw.geom.TransformPoint2ToPoint2` 

37 The transform from ACTUAL_PIXELS to PIXELS 

38 """ 

39 actualPixelsToPixelsMap = actualPixelsToPixels.getMapping() 

40 actualPixelsFrame = ast.Frame(2, "Domain=ACTUAL_PIXELS") 

41 frameDict = skyWcs.getFrameDict() 

42 frameDict.addFrame("PIXELS", actualPixelsToPixelsMap.inverted(), actualPixelsFrame) 

43 frameDict.setBase("ACTUAL_PIXELS") 

44 frameDict.setCurrent("SKY") 

45 return SkyWcs(frameDict) 

46 

47 

48class SkyWcsBaseTestCase(lsst.utils.tests.TestCase): 

49 def checkPersistence(self, skyWcs, bbox): 

50 """Check persistence of a SkyWcs 

51 """ 

52 className = "SkyWcs" 

53 

54 # check writeString and readString 

55 skyWcsStr = skyWcs.writeString() 

56 serialVersion, serialClassName, serialRest = skyWcsStr.split(" ", 2) 

57 self.assertEqual(int(serialVersion), 1) 

58 self.assertEqual(serialClassName, className) 

59 badStr1 = " ".join(["2", serialClassName, serialRest]) 

60 with self.assertRaises(lsst.pex.exceptions.TypeError): 

61 skyWcs.readString(badStr1) 

62 badClassName = "x" + serialClassName 

63 badStr2 = " ".join(["1", badClassName, serialRest]) 

64 with self.assertRaises(lsst.pex.exceptions.TypeError): 

65 skyWcs.readString(badStr2) 

66 skyWcsFromStr1 = skyWcs.readString(skyWcsStr) 

67 self.assertEqual(skyWcs, skyWcsFromStr1) 

68 self.assertEqual(type(skyWcs), type(skyWcsFromStr1)) 

69 self.assertEqual(skyWcs.getFrameDict(), skyWcsFromStr1.getFrameDict()) 

70 

71 pixelPoints = [ 

72 lsst.geom.Point2D(0, 0), 

73 lsst.geom.Point2D(1000, 0), 

74 lsst.geom.Point2D(0, 1000), 

75 lsst.geom.Point2D(-50, -50), 

76 ] 

77 skyPoints = skyWcs.pixelToSky(pixelPoints) 

78 pixelPoints2 = skyWcs.skyToPixel(skyPoints) 

79 assert_allclose(pixelPoints, pixelPoints2, atol=1e-7) 

80 

81 # check that WCS is properly saved as part of an exposure FITS file 

82 exposure = ExposureF(100, 100, skyWcs) 

83 with lsst.utils.tests.getTempFilePath(".fits") as outFile: 

84 exposure.writeFits(outFile) 

85 exposureRoundTrip = ExposureF(outFile) 

86 wcsFromExposure = exposureRoundTrip.getWcs() 

87 self.assertWcsAlmostEqualOverBBox(skyWcs, wcsFromExposure, bbox, maxDiffPix=0, 

88 maxDiffSky=0*lsst.geom.radians) 

89 

90 def checkFrameDictConstructor(self, skyWcs, bbox): 

91 """Check that the FrameDict constructor works 

92 """ 

93 frameDict = skyWcs.getFrameDict() 

94 wcsFromFrameDict = SkyWcs(frameDict) 

95 self.assertWcsAlmostEqualOverBBox(skyWcs, wcsFromFrameDict, bbox, maxDiffPix=0, 

96 maxDiffSky=0*lsst.geom.radians) 

97 

98 self.checkPersistence(wcsFromFrameDict, bbox) 

99 

100 # check that it is impossible to build a SkyWcs if a required frame is missing 

101 for domain in ("PIXELS", "IWC", "SKY"): 

102 badFrameDict = skyWcs.getFrameDict() 

103 badFrameDict.removeFrame(domain) 

104 with self.assertRaises(lsst.pex.exceptions.TypeError): 

105 SkyWcs(badFrameDict) 

106 

107 def checkMakeFlippedWcs(self, skyWcs, skyAtol=1e-7*lsst.geom.arcseconds): 

108 """Check makeFlippedWcs on the provided WCS 

109 """ 

110 # make an arbitrary bbox, but one that includes zero in one axis 

111 # and does not include zero in the other axis 

112 # the center of the bbox is used as the center of flipping 

113 # and the corners of the bbox are the input positions that are tested 

114 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-100, 1000), lsst.geom.Extent2D(2000, 1501)) 

115 # dict of (isRight, isTop): position 

116 minPos = bbox.getMin() 

117 maxPos = bbox.getMax() 

118 center = bbox.getCenter() 

119 cornerDict = { 

120 (False, False): minPos, 

121 (False, True): lsst.geom.Point2D(minPos[0], maxPos[1]), 

122 (True, False): lsst.geom.Point2D(maxPos[0], minPos[1]), 

123 (True, True): maxPos, 

124 } 

125 for flipLR, flipTB in itertools.product((False, True), (False, True)): 

126 flippedWcs = makeFlippedWcs(wcs=skyWcs, flipLR=flipLR, flipTB=flipTB, center=center) 

127 # the center is unchanged 

128 self.assertSpherePointsAlmostEqual(skyWcs.pixelToSky(center), 

129 flippedWcs.pixelToSky(center), maxSep=skyAtol) 

130 

131 for isR, isT in itertools.product((False, True), (False, True)): 

132 origPos = cornerDict[(isR, isT)] 

133 flippedPos = cornerDict[(isR ^ flipLR, isT ^ flipTB)] 

134 self.assertSpherePointsAlmostEqual(skyWcs.pixelToSky(origPos), 

135 flippedWcs.pixelToSky(flippedPos), maxSep=skyAtol) 

136 

137 def assertSkyWcsAstropyWcsAlmostEqual(self, skyWcs, astropyWcs, bbox, 

138 pixAtol=1e-4, skyAtol=1e-4*lsst.geom.arcseconds, 

139 checkRoundTrip=True): 

140 """Assert that a SkyWcs and the corresponding astropy.wcs.WCS agree over a specified bounding box 

141 """ 

142 bbox = lsst.geom.Box2D(bbox) 

143 center = bbox.getCenter() 

144 xArr = bbox.getMinX(), center[0], bbox.getMaxX() 

145 yArr = bbox.getMinY(), center[1], bbox.getMaxY() 

146 pixPosList = [lsst.geom.Point2D(x, y) for x, y in itertools.product(xArr, yArr)] 

147 

148 # pixelToSky 

149 skyPosList = skyWcs.pixelToSky(pixPosList) 

150 astropySkyPosList = self.astropyPixelsToSky(astropyWcs=astropyWcs, pixPosList=pixPosList) 

151 self.assertSpherePointListsAlmostEqual(skyPosList, astropySkyPosList, maxSep=skyAtol) 

152 

153 if not checkRoundTrip: 

154 return 

155 

156 # astropy round trip 

157 astropyPixPosRoundTrip = self.astropySkyToPixels(astropyWcs=astropyWcs, skyPosList=astropySkyPosList) 

158 self.assertPairListsAlmostEqual(pixPosList, astropyPixPosRoundTrip, maxDiff=pixAtol) 

159 

160 # SkyWcs round trip 

161 pixPosListRoundTrip = skyWcs.skyToPixel(skyPosList) 

162 self.assertPairListsAlmostEqual(pixPosList, pixPosListRoundTrip, maxDiff=pixAtol) 

163 

164 # skyToPixel astropy vs SkyWcs 

165 astropyPixPosList2 = self.astropySkyToPixels(astropyWcs=astropyWcs, skyPosList=skyPosList) 

166 self.assertPairListsAlmostEqual(pixPosListRoundTrip, astropyPixPosList2, maxDiff=pixAtol) 

167 

168 def astropyPixelsToSky(self, astropyWcs, pixPosList): 

169 """Use an astropy wcs to convert pixels to sky 

170 

171 @param[in] astropyWcs a celestial astropy.wcs.WCS with 2 axes in RA, Dec order 

172 @param[in] pixPosList 0-based pixel positions as lsst.geom.Point2D or similar pairs 

173 @returns sky coordinates as a list of lsst.geom.SpherePoint 

174 

175 Converts the output to ICRS 

176 """ 

177 xarr = [p[0] for p in pixPosList] 

178 yarr = [p[1] for p in pixPosList] 

179 skyCoordList = astropy.wcs.utils.pixel_to_skycoord(xp=xarr, 

180 yp=yarr, 

181 wcs=astropyWcs, 

182 origin=0, 

183 mode="all") 

184 icrsList = [sc.transform_to("icrs") for sc in skyCoordList] 

185 return [lsst.geom.SpherePoint(sc.ra.deg, sc.dec.deg, lsst.geom.degrees) for sc in icrsList] 

186 

187 def astropySkyToPixels(self, astropyWcs, skyPosList): 

188 """Use an astropy wcs to convert pixels to sky 

189 

190 @param[in] astropyWcs a celestial astropy.wcs.WCS with 2 axes in RA, Dec order 

191 @param[in] skyPosList ICRS sky coordinates as a list of lsst.geom.SpherePoint 

192 @returns a list of lsst.geom.Point2D, 0-based pixel positions 

193 

194 Converts the input from ICRS to the coordinate system of the wcs 

195 """ 

196 skyCoordList = [astropy.coordinates.SkyCoord(c[0].asDegrees(), 

197 c[1].asDegrees(), 

198 frame="icrs", 

199 unit="deg") for c in skyPosList] 

200 xyArr = [astropy.wcs.utils.skycoord_to_pixel(coords=sc, 

201 wcs=astropyWcs, 

202 origin=0, 

203 mode="all") for sc in skyCoordList] 

204 # float is needed to avoid truncation to int 

205 return [lsst.geom.Point2D(float(x), float(y)) for x, y in xyArr] 

206 

207 

208class SimpleSkyWcsTestCase(SkyWcsBaseTestCase): 

209 """Test the simple FITS version of makeSkyWcs 

210 """ 

211 

212 def setUp(self): 

213 self.crpix = lsst.geom.Point2D(100, 100) 

214 self.crvalList = [ 

215 lsst.geom.SpherePoint(0, 45, lsst.geom.degrees), 

216 lsst.geom.SpherePoint(0.00001, 45, lsst.geom.degrees), 

217 lsst.geom.SpherePoint(359.99999, 45, lsst.geom.degrees), 

218 lsst.geom.SpherePoint(30, 89.99999, lsst.geom.degrees), 

219 lsst.geom.SpherePoint(30, -89.99999, lsst.geom.degrees), 

220 ] 

221 self.orientationList = [ 

222 0 * lsst.geom.degrees, 

223 0.00001 * lsst.geom.degrees, 

224 -0.00001 * lsst.geom.degrees, 

225 -45 * lsst.geom.degrees, 

226 90 * lsst.geom.degrees, 

227 ] 

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

229 self.tinyPixels = 1.0e-10 

230 self.tinyAngle = 1.0e-10 * lsst.geom.radians 

231 self.bbox = lsst.geom.Box2I(lsst.geom.Point2I(-1000, -1000), 

232 lsst.geom.Extent2I(2000, 2000)) # arbitrary but reasonable 

233 

234 def checkTanWcs(self, crval, orientation, flipX): 

235 """Construct a pure TAN SkyWcs and check that it operates as specified 

236 

237 Parameters 

238 ---------- 

239 crval : `lsst.geom.SpherePoint` 

240 Desired reference sky position. 

241 Must not be at either pole. 

242 orientation : `lsst.geom.Angle` 

243 Position angle of pixel +Y, measured from N through E. 

244 At 0 degrees, +Y is along N and +X is along E/W if flipX false/true 

245 At 90 degrees, +Y is along E and +X is along S/N if flipX false/true 

246 flipX : `bool` 

247 Flip x axis? See `orientation` for details. 

248 

249 Returns 

250 ------- 

251 wcs : `lsst.afw.geom.SkyWcs` 

252 The generated pure TAN SkyWcs 

253 """ 

254 cdMatrix = makeCdMatrix(scale=self.scale, orientation=orientation, flipX=flipX) 

255 wcs = makeSkyWcs(crpix=self.crpix, crval=crval, cdMatrix=cdMatrix) 

256 self.checkPersistence(wcs, bbox=self.bbox) 

257 self.checkMakeFlippedWcs(wcs) 

258 

259 self.assertTrue(wcs.isFits) 

260 self.assertEqual(wcs.isFlipped, bool(flipX)) 

261 

262 xoffAng = 0*lsst.geom.degrees if flipX else 180*lsst.geom.degrees 

263 

264 pixelList = [ 

265 lsst.geom.Point2D(self.crpix[0], self.crpix[1]), 

266 lsst.geom.Point2D(self.crpix[0] + 1, self.crpix[1]), 

267 lsst.geom.Point2D(self.crpix[0], self.crpix[1] + 1), 

268 ] 

269 skyList = wcs.pixelToSky(pixelList) 

270 

271 # check pixels to sky 

272 predSkyList = [ 

273 crval, 

274 crval.offset(xoffAng - orientation, self.scale), 

275 crval.offset(90*lsst.geom.degrees - orientation, self.scale), 

276 ] 

277 self.assertSpherePointListsAlmostEqual(predSkyList, skyList) 

278 self.assertSpherePointListsAlmostEqual(predSkyList, wcs.pixelToSky(pixelList)) 

279 for pixel, predSky in zip(pixelList, predSkyList): 

280 self.assertSpherePointsAlmostEqual(predSky, wcs.pixelToSky(pixel)) 

281 self.assertSpherePointsAlmostEqual(predSky, wcs.pixelToSky(pixel[0], pixel[1])) 

282 

283 # check sky to pixels 

284 self.assertPairListsAlmostEqual(pixelList, wcs.skyToPixel(skyList)) 

285 self.assertPairListsAlmostEqual(pixelList, wcs.skyToPixel(skyList)) 

286 for pixel, sky in zip(pixelList, skyList): 

287 self.assertPairsAlmostEqual(pixel, wcs.skyToPixel(sky)) 

288 # self.assertPairsAlmostEqual(pixel, wcs.skyToPixel(sky[0], sky[1])) 

289 

290 # check CRVAL round trip 

291 self.assertSpherePointsAlmostEqual(wcs.getSkyOrigin(), crval, 

292 maxSep=self.tinyAngle) 

293 

294 crpix = wcs.getPixelOrigin() 

295 self.assertPairsAlmostEqual(crpix, self.crpix, maxDiff=self.tinyPixels) 

296 

297 self.assertFloatsAlmostEqual(wcs.getCdMatrix(), cdMatrix, atol=1e-15, rtol=1e-11) 

298 

299 pixelScale = wcs.getPixelScale() 

300 self.assertAnglesAlmostEqual(self.scale, pixelScale, maxDiff=self.tinyAngle) 

301 

302 pixelScale = wcs.getPixelScale(self.crpix) 

303 self.assertAnglesAlmostEqual(self.scale, pixelScale, maxDiff=self.tinyAngle) 

304 

305 # check that getFitsMetadata can operate at high precision 

306 # and has axis order RA, Dec 

307 fitsMetadata = wcs.getFitsMetadata(True) 

308 self.assertEqual(fitsMetadata.getScalar("CTYPE1")[0:4], "RA--") 

309 self.assertEqual(fitsMetadata.getScalar("CTYPE2")[0:4], "DEC-") 

310 

311 # Compute a WCS with the pixel origin shifted by an arbitrary amount 

312 # The resulting sky origin should not change 

313 offset = lsst.geom.Extent2D(500, -322) # arbitrary 

314 shiftedWcs = wcs.copyAtShiftedPixelOrigin(offset) 

315 self.assertTrue(shiftedWcs.isFits) 

316 predShiftedPixelOrigin = self.crpix + offset 

317 self.assertPairsAlmostEqual(shiftedWcs.getPixelOrigin(), predShiftedPixelOrigin, 

318 maxDiff=self.tinyPixels) 

319 self.assertSpherePointsAlmostEqual(shiftedWcs.getSkyOrigin(), crval, maxSep=self.tinyAngle) 

320 

321 shiftedPixelList = [p + offset for p in pixelList] 

322 shiftedSkyList = shiftedWcs.pixelToSky(shiftedPixelList) 

323 self.assertSpherePointListsAlmostEqual(skyList, shiftedSkyList, maxSep=self.tinyAngle) 

324 

325 # Check that the shifted WCS can be round tripped as FITS metadata 

326 shiftedMetadata = shiftedWcs.getFitsMetadata(precise=True) 

327 shiftedWcsCopy = makeSkyWcs(shiftedMetadata) 

328 shiftedBBox = lsst.geom.Box2D(predShiftedPixelOrigin, 

329 predShiftedPixelOrigin + lsst.geom.Extent2I(2000, 2000)) 

330 self.assertWcsAlmostEqualOverBBox(shiftedWcs, shiftedWcsCopy, shiftedBBox) 

331 

332 wcsCopy = SkyWcs.readString(wcs.writeString()) 

333 self.assertTrue(wcsCopy.isFits) 

334 

335 return wcs 

336 

337 def checkNonFitsWcs(self, wcs): 

338 """Check SkyWcs.getFitsMetadata for a WCS that cannot be represented as a FITS-WCS 

339 """ 

340 # the modified WCS should not be representable as pure FITS-WCS 

341 self.assertFalse(wcs.isFits) 

342 with self.assertRaises(RuntimeError): 

343 wcs.getFitsMetadata(True) 

344 

345 # the approximation returned by getFitsMetadata is poor (pure TAN) until DM-13170 

346 wcsFromMetadata = makeSkyWcs(wcs.getFitsMetadata()) 

347 self.assertFalse(wcsAlmostEqualOverBBox(wcs, wcsFromMetadata, bbox=self.bbox)) 

348 

349 # the approximation returned by getFitsMetadata is pure TAN until DM-13170, 

350 # after DM-13170 change this test to check that wcsFromMetadata is a reasonable 

351 # approximation to the original WCS 

352 approxWcs = wcs.getTanWcs(wcs.getPixelOrigin()) 

353 self.assertWcsAlmostEqualOverBBox(approxWcs, wcsFromMetadata, bbox=self.bbox) 

354 

355 def testTanWcs(self): 

356 """Check a variety of TanWcs, with crval not at a pole. 

357 """ 

358 for crval, orientation, flipX in itertools.product(self.crvalList, 

359 self.orientationList, 

360 (False, True)): 

361 self.checkTanWcs(crval=crval, 

362 orientation=orientation, 

363 flipX=flipX, 

364 ) 

365 

366 def testTanWcsFromFrameDict(self): 

367 """Test making a TAN WCS from a FrameDict 

368 """ 

369 cdMatrix = makeCdMatrix(scale=self.scale) 

370 skyWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) 

371 self.checkFrameDictConstructor(skyWcs, bbox=self.bbox) 

372 

373 def testGetFrameDict(self): 

374 """Test that getFrameDict returns a deep copy 

375 """ 

376 cdMatrix = makeCdMatrix(scale=self.scale) 

377 skyWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) 

378 for domain in ("PIXELS", "IWC", "SKY"): 

379 frameDict = skyWcs.getFrameDict() 

380 frameDict.removeFrame(domain) 

381 self.assertFalse(frameDict.hasDomain(domain)) 

382 self.assertTrue(skyWcs.getFrameDict().hasDomain(domain)) 

383 

384 def testMakeModifiedWcsNoActualPixels(self): 

385 """Test makeModifiedWcs on a SkyWcs that has no ACTUAL_PIXELS frame 

386 """ 

387 cdMatrix = makeCdMatrix(scale=self.scale) 

388 originalWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) 

389 originalFrameDict = originalWcs.getFrameDict() 

390 

391 # make an arbitrary but reasonable transform to insert using makeModifiedWcs 

392 pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011]) 

393 # the result of the insertion should be as follows 

394 desiredPixelsToSky = pixelTransform.then(originalWcs.getTransform()) 

395 

396 pixPointList = ( # arbitrary but reasonable 

397 lsst.geom.Point2D(0.0, 0.0), 

398 lsst.geom.Point2D(1000.0, 0.0), 

399 lsst.geom.Point2D(0.0, 2000.0), 

400 lsst.geom.Point2D(-1111.0, -2222.0), 

401 ) 

402 for modifyActualPixels in (False, True): 

403 modifiedWcs = makeModifiedWcs(pixelTransform=pixelTransform, 

404 wcs=originalWcs, 

405 modifyActualPixels=modifyActualPixels) 

406 modifiedFrameDict = modifiedWcs.getFrameDict() 

407 skyList = modifiedWcs.pixelToSky(pixPointList) 

408 

409 # compare pixels to sky 

410 desiredSkyList = desiredPixelsToSky.applyForward(pixPointList) 

411 self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList) 

412 

413 # compare pixels to IWC 

414 pixelsToIwc = TransformPoint2ToPoint2(modifiedFrameDict.getMapping("PIXELS", "IWC")) 

415 desiredPixelsToIwc = TransformPoint2ToPoint2( 

416 pixelTransform.getMapping().then(originalFrameDict.getMapping("PIXELS", "IWC"))) 

417 self.assertPairListsAlmostEqual(pixelsToIwc.applyForward(pixPointList), 

418 desiredPixelsToIwc.applyForward(pixPointList)) 

419 

420 self.checkNonFitsWcs(modifiedWcs) 

421 

422 def testMakeModifiedWcsWithActualPixels(self): 

423 """Test makeModifiedWcs on a SkyWcs that has an ACTUAL_PIXELS frame 

424 """ 

425 cdMatrix = makeCdMatrix(scale=self.scale) 

426 baseWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) 

427 # model actual pixels to pixels as an arbitrary zoom factor; 

428 # this is not realistic, but is fine for a unit test 

429 actualPixelsToPixels = TransformPoint2ToPoint2(ast.ZoomMap(2, 0.72)) 

430 originalWcs = addActualPixelsFrame(baseWcs, actualPixelsToPixels) 

431 originalFrameDict = originalWcs.getFrameDict() 

432 

433 # make an arbitrary but reasonable transform to insert using makeModifiedWcs 

434 pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011]) # arbitrary but reasonable 

435 

436 pixPointList = ( # arbitrary but reasonable 

437 lsst.geom.Point2D(0.0, 0.0), 

438 lsst.geom.Point2D(1000.0, 0.0), 

439 lsst.geom.Point2D(0.0, 2000.0), 

440 lsst.geom.Point2D(-1111.0, -2222.0), 

441 ) 

442 for modifyActualPixels in (True, False): 

443 modifiedWcs = makeModifiedWcs(pixelTransform=pixelTransform, 

444 wcs=originalWcs, 

445 modifyActualPixels=modifyActualPixels) 

446 modifiedFrameDict = modifiedWcs.getFrameDict() 

447 self.assertEqual(modifiedFrameDict.getFrame(modifiedFrameDict.BASE).domain, "ACTUAL_PIXELS") 

448 modifiedActualPixelsToPixels = \ 

449 TransformPoint2ToPoint2(modifiedFrameDict.getMapping("ACTUAL_PIXELS", "PIXELS")) 

450 modifiedPixelsToIwc = TransformPoint2ToPoint2(modifiedFrameDict.getMapping("PIXELS", "IWC")) 

451 

452 # compare pixels to sky 

453 skyList = modifiedWcs.pixelToSky(pixPointList) 

454 if modifyActualPixels: 

455 desiredPixelsToSky = pixelTransform.then(originalWcs.getTransform()) 

456 else: 

457 originalPixelsToSky = \ 

458 TransformPoint2ToSpherePoint(originalFrameDict.getMapping("PIXELS", "SKY")) 

459 desiredPixelsToSky = actualPixelsToPixels.then(pixelTransform).then(originalPixelsToSky) 

460 desiredSkyList = desiredPixelsToSky.applyForward(pixPointList) 

461 self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList) 

462 

463 # compare ACTUAL_PIXELS to PIXELS and PIXELS to IWC 

464 if modifyActualPixels: 

465 # check that ACTUAL_PIXELS to PIXELS has been modified as expected 

466 desiredActualPixelsToPixels = pixelTransform.then(actualPixelsToPixels) 

467 self.assertPairListsAlmostEqual(modifiedActualPixelsToPixels.applyForward(pixPointList), 

468 desiredActualPixelsToPixels.applyForward(pixPointList)) 

469 

470 # check that PIXELS to IWC is unchanged 

471 originalPixelsToIwc = TransformPoint2ToPoint2(originalFrameDict.getMapping("PIXELS", "IWC")) 

472 self.assertPairListsAlmostEqual(modifiedPixelsToIwc.applyForward(pixPointList), 

473 originalPixelsToIwc.applyForward(pixPointList)) 

474 

475 else: 

476 # check that ACTUAL_PIXELS to PIXELS is unchanged 

477 self.assertPairListsAlmostEqual(actualPixelsToPixels.applyForward(pixPointList), 

478 actualPixelsToPixels.applyForward(pixPointList)) 

479 

480 # check that PIXELS to IWC has been modified as expected 

481 desiredPixelsToIwc = TransformPoint2ToPoint2( 

482 pixelTransform.getMapping().then(originalFrameDict.getMapping("PIXELS", "IWC"))) 

483 self.assertPairListsAlmostEqual(modifiedPixelsToIwc.applyForward(pixPointList), 

484 desiredPixelsToIwc.applyForward(pixPointList)) 

485 

486 self.checkNonFitsWcs(modifiedWcs) 

487 

488 def testMakeSkyWcsFromPixelsToFieldAngle(self): 

489 """Test makeSkyWcs from a pixelsToFieldAngle transform 

490 """ 

491 pixelSizeMm = 25e-3 

492 # place the detector in several positions at several orientations 

493 # use fewer CRVAL and orientations to speed up the test 

494 for fpPosition, yaw, addOpticalDistortion, crval, pixelOrientation, \ 

495 flipX, projection in itertools.product( 

496 (lsst.geom.Point3D(0, 0, 0), lsst.geom.Point3D(-100, 500, 1.5)), 

497 (0*lsst.geom.degrees, 71*lsst.geom.degrees), (False, True), 

498 self.crvalList[0:2], self.orientationList[0:2], (False, True), ("TAN", "STG")): 

499 with self.subTest(fpPosition=fpPosition, yaw=yaw, addOpticalDistortion=addOpticalDistortion, 

500 crval=crval, orientation=pixelOrientation): 

501 pixelsToFocalPlane = cameraGeom.Orientation( 

502 fpPosition=fpPosition, 

503 yaw=yaw, 

504 ).makePixelFpTransform(lsst.geom.Extent2D(pixelSizeMm, pixelSizeMm)) 

505 # Compute crpix before adding optical distortion, 

506 # since it is not affected by such distortion 

507 crpix = pixelsToFocalPlane.applyInverse(lsst.geom.Point2D(0, 0)) 

508 radiansPerMm = self.scale.asRadians() / pixelSizeMm 

509 focalPlaneToFieldAngle = lsst.afw.geom.makeTransform( 

510 lsst.geom.AffineTransform(lsst.geom.LinearTransform.makeScaling(radiansPerMm))) 

511 pixelsToFieldAngle = pixelsToFocalPlane.then(focalPlaneToFieldAngle) 

512 

513 cdMatrix = makeCdMatrix(scale=self.scale, orientation=pixelOrientation, flipX=flipX) 

514 wcs1 = makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix, projection=projection) 

515 

516 if addOpticalDistortion: 

517 # Model optical distortion as a pixel transform, 

518 # so it can be added to the WCS created from crpix, 

519 # cdMatrix, etc. using makeModifiedWcs 

520 pixelTransform = makeRadialTransform([0.0, 1.0, 0.0, 0.0011]) 

521 pixelsToFieldAngle = pixelTransform.then(pixelsToFieldAngle) 

522 wcs1 = makeModifiedWcs(pixelTransform=pixelTransform, wcs=wcs1, modifyActualPixels=False) 

523 

524 # orientation is with respect to detector x, y 

525 # but this flavor of makeSkyWcs needs it with respect to focal plane x, y 

526 focalPlaneOrientation = pixelOrientation + (yaw if flipX else -yaw) 

527 wcs2 = makeSkyWcs(pixelsToFieldAngle=pixelsToFieldAngle, 

528 orientation=focalPlaneOrientation, 

529 flipX=flipX, 

530 boresight=crval, 

531 projection=projection) 

532 self.assertWcsAlmostEqualOverBBox(wcs1, wcs2, self.bbox) 

533 

534 @unittest.skipIf(sys.version_info[0] < 3, "astropy.wcs rejects the header on py2") 

535 def testAgainstAstropyWcs(self): 

536 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(2000, 2000)) 

537 for crval, orientation, flipX, projection in itertools.product(self.crvalList, 

538 self.orientationList, 

539 (False, True), 

540 ("TAN", "STG", "CEA", "AIT")): 

541 cdMatrix = makeCdMatrix(scale=self.scale, orientation=orientation, flipX=flipX) 

542 metadata = makeSimpleWcsMetadata(crpix=self.crpix, crval=crval, cdMatrix=cdMatrix, 

543 projection=projection) 

544 header = makeLimitedFitsHeader(metadata) 

545 astropyWcs = astropy.wcs.WCS(header) 

546 skyWcs = makeSkyWcs(crpix=self.crpix, crval=crval, cdMatrix=cdMatrix, projection=projection) 

547 # Most projections only seem to agree to within 1e-4 in the round trip test 

548 self.assertSkyWcsAstropyWcsAlmostEqual(skyWcs=skyWcs, astropyWcs=astropyWcs, bbox=bbox) 

549 

550 def testPixelToSkyArray(self): 

551 """Test the numpy-array version of pixelToSky 

552 """ 

553 cdMatrix = makeCdMatrix(scale=self.scale) 

554 wcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) 

555 

556 xPoints = np.array([0.0, 1000.0, 0.0, -1111.0]) 

557 yPoints = np.array([0.0, 0.0, 2000.0, -2222.0]) 

558 

559 pixPointList = [lsst.geom.Point2D(x, y) for x, y in zip(xPoints, yPoints)] 

560 

561 spherePoints = wcs.pixelToSky(pixPointList) 

562 

563 ra, dec = wcs.pixelToSkyArray(xPoints, yPoints, degrees=False) 

564 for r, d, spherePoint in zip(ra, dec, spherePoints): 

565 self.assertAlmostEqual(r, spherePoint.getRa().asRadians()) 

566 self.assertAlmostEqual(d, spherePoint.getDec().asRadians()) 

567 

568 ra, dec = wcs.pixelToSkyArray(xPoints, yPoints, degrees=True) 

569 for r, d, spherePoint in zip(ra, dec, spherePoints): 

570 self.assertAlmostEqual(r, spherePoint.getRa().asDegrees()) 

571 self.assertAlmostEqual(d, spherePoint.getDec().asDegrees()) 

572 

573 def testSkyToPixelArray(self): 

574 """Test the numpy-array version of skyToPixel 

575 """ 

576 cdMatrix = makeCdMatrix(scale=self.scale) 

577 wcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) 

578 

579 raPoints = np.array([3.92646679e-02, 3.59646622e+02, 

580 3.96489283e-02, 4.70419353e-01]) 

581 decPoints = np.array([44.9722155, 44.97167735, 

582 45.52775599, 44.3540619]) 

583 

584 spherePointList = [lsst.geom.SpherePoint(ra*lsst.geom.degrees, 

585 dec*lsst.geom.degrees) 

586 for ra, dec in zip(raPoints, decPoints)] 

587 

588 pixPoints = wcs.skyToPixel(spherePointList) 

589 

590 x, y = wcs.skyToPixelArray(np.deg2rad(raPoints), np.deg2rad(decPoints)) 

591 for x0, y0, pixPoint in zip(x, y, pixPoints): 

592 self.assertAlmostEqual(x0, pixPoint.getX()) 

593 self.assertAlmostEqual(y0, pixPoint.getY()) 

594 

595 x, y = wcs.skyToPixelArray(raPoints, decPoints, degrees=True) 

596 for x0, y0, pixPoint in zip(x, y, pixPoints): 

597 self.assertAlmostEqual(x0, pixPoint.getX()) 

598 self.assertAlmostEqual(y0, pixPoint.getY()) 

599 

600 def testStr(self): 

601 """Test that we can get something coherent when printing a SkyWcs. 

602 """ 

603 cdMatrix = makeCdMatrix(scale=self.scale) 

604 skyWcs = makeSkyWcs(crpix=self.crpix, crval=self.crvalList[0], cdMatrix=cdMatrix) 

605 self.assertIn(f"Sky Origin: {self.crvalList[0]}", str(skyWcs)) 

606 self.assertIn(f"Pixel Origin: {self.crpix}", str(skyWcs)) 

607 self.assertIn("Pixel Scale: ", str(skyWcs)) 

608 

609 

610class MetadataWcsTestCase(SkyWcsBaseTestCase): 

611 """Test metadata constructor of SkyWcs 

612 """ 

613 

614 def setUp(self): 

615 metadata = PropertyList() 

616 for name, value in ( 

617 ("RADESYS", "ICRS"), 

618 ("EQUINOX", 2000.), 

619 ("CRVAL1", 215.604025685476), 

620 ("CRVAL2", 53.1595451514076), 

621 ("CRPIX1", 1109.99981456774), 

622 ("CRPIX2", 560.018167811613), 

623 ("CTYPE1", "RA---TAN"), 

624 ("CTYPE2", "DEC--TAN"), 

625 ("CUNIT1", "deg"), 

626 ("CUNIT2", "deg"), 

627 ("CD1_1", 5.10808596133527E-05), 

628 ("CD1_2", 1.85579539217196E-07), 

629 ("CD2_2", -5.10281493481982E-05), 

630 ("CD2_1", -1.85579539217196E-07), 

631 ): 

632 metadata.set(name, value) 

633 self.metadata = metadata 

634 

635 def tearDown(self): 

636 del self.metadata 

637 

638 def checkWcs(self, skyWcs): 

639 pixelOrigin = skyWcs.getPixelOrigin() 

640 skyOrigin = skyWcs.getSkyOrigin() 

641 for i in range(2): 

642 # subtract 1 from FITS CRPIX to get LSST convention 

643 self.assertAlmostEqual(pixelOrigin[i], self.metadata.getScalar(f"CRPIX{i+1}") - 1) 

644 self.assertAnglesAlmostEqual(skyOrigin[i], 

645 self.metadata.getScalar(f"CRVAL{i+1}")*lsst.geom.degrees) 

646 cdMatrix = skyWcs.getCdMatrix() 

647 for i, j in itertools.product(range(2), range(2)): 

648 self.assertAlmostEqual(cdMatrix[i, j], self.metadata.getScalar(f"CD{i+1}_{j+1}")) 

649 

650 self.assertTrue(skyWcs.isFits) 

651 

652 skyWcsCopy = SkyWcs.readString(skyWcs.writeString()) 

653 self.assertTrue(skyWcsCopy.isFits) 

654 self.checkMakeFlippedWcs(skyWcs) 

655 

656 @unittest.skipIf(sys.version_info[0] < 3, "astropy.wcs rejects the header on py2") 

657 def testAgainstAstropyWcs(self): 

658 skyWcs = makeSkyWcs(self.metadata, strip=False) 

659 header = makeLimitedFitsHeader(self.metadata) 

660 astropyWcs = astropy.wcs.WCS(header) 

661 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000)) 

662 self.assertSkyWcsAstropyWcsAlmostEqual(skyWcs=skyWcs, astropyWcs=astropyWcs, bbox=bbox) 

663 

664 def testLinearizeMethods(self): 

665 skyWcs = makeSkyWcs(self.metadata) 

666 # use a sky position near, but not at, the WCS origin 

667 sky00 = skyWcs.getSkyOrigin().offset(45 * lsst.geom.degrees, 1.2 * lsst.geom.degrees) 

668 pix00 = skyWcs.skyToPixel(sky00) 

669 for skyUnit in (lsst.geom.degrees, lsst.geom.radians): 

670 linPixToSky1 = skyWcs.linearizePixelToSky(sky00, skyUnit) # should match inverse of linSkyToPix1 

671 linPixToSky2 = skyWcs.linearizePixelToSky(pix00, skyUnit) # should match inverse of linSkyToPix1 

672 linSkyToPix1 = skyWcs.linearizeSkyToPixel(sky00, skyUnit) 

673 linSkyToPix2 = skyWcs.linearizeSkyToPixel(pix00, skyUnit) # should match linSkyToPix1 

674 

675 for pixel in (pix00, pix00 + lsst.geom.Extent2D(1000, -1230)): 

676 linSky = linPixToSky1(pixel) 

677 self.assertPairsAlmostEqual(linPixToSky2(pixel), linSky) 

678 self.assertPairsAlmostEqual(linSkyToPix1(linSky), pixel) 

679 self.assertPairsAlmostEqual(linSkyToPix2(linSky), pixel) 

680 

681 sky00Doubles = sky00.getPosition(skyUnit) 

682 pix00gApprox = linSkyToPix1(sky00Doubles) 

683 self.assertPairsAlmostEqual(pix00gApprox, pix00) 

684 self.assertAlmostEqual(pix00.getX(), pix00gApprox.getX()) 

685 self.assertAlmostEqual(pix00.getY(), pix00gApprox.getY()) 

686 pixelScale = skyWcs.getPixelScale(pix00) 

687 pixelArea = pixelScale.asAngularUnits(skyUnit)**2 

688 predictedPixelArea = 1 / linSkyToPix1.getLinear().computeDeterminant() 

689 self.assertAlmostEqual(pixelArea, predictedPixelArea) 

690 

691 def testBasics(self): 

692 skyWcs = makeSkyWcs(self.metadata, strip=False) 

693 self.assertEqual(len(self.metadata.names(False)), 14) 

694 self.checkWcs(skyWcs) 

695 makeSkyWcs(self.metadata, strip=True) 

696 self.assertEqual(len(self.metadata.names(False)), 0) 

697 

698 def testBasicsStrip(self): 

699 stripWcsMetadata(self.metadata) 

700 self.assertEqual(len(self.metadata.names(False)), 0) 

701 # The metadata should be unchanged if we attempt to strip it again 

702 metadataCopy = self.metadata.deepCopy() 

703 stripWcsMetadata(self.metadata) 

704 for key in self.metadata.keys(): 

705 self.assertEqual(self.metadata[key], metadataCopy[key]) 

706 

707 def testNormalizationFk5(self): 

708 """Test that readLsstSkyWcs correctly normalizes FK5 1975 to ICRS 

709 """ 

710 equinox = 1975.0 

711 metadata = self.metadata 

712 

713 metadata.set("RADESYS", "FK5") 

714 metadata.set("EQUINOX", equinox) 

715 crpix = lsst.geom.Point2D(metadata.getScalar("CRPIX1") - 1, metadata.getScalar("CRPIX2") - 1) 

716 # record the original CRVAL before reading and stripping metadata 

717 crvalFk5Deg = (metadata.getScalar("CRVAL1"), metadata.getScalar("CRVAL2")) 

718 

719 # create the wcs and retrieve crval 

720 skyWcs = makeSkyWcs(metadata) 

721 crval = skyWcs.getSkyOrigin() 

722 

723 # compare to computed crval 

724 computedCrval = skyWcs.pixelToSky(crpix) 

725 self.assertSpherePointsAlmostEqual(crval, computedCrval) 

726 

727 # get predicted crval by converting with astropy 

728 crvalFk5 = astropy.coordinates.SkyCoord(crvalFk5Deg[0], crvalFk5Deg[1], frame="fk5", 

729 equinox=f"J{equinox}", unit="deg") 

730 predictedCrvalIcrs = crvalFk5.icrs 

731 predictedCrval = lsst.geom.SpherePoint(predictedCrvalIcrs.ra.radian, predictedCrvalIcrs.dec.radian, 

732 lsst.geom.radians) 

733 self.assertSpherePointsAlmostEqual(crval, predictedCrval, maxSep=0.002*lsst.geom.arcseconds) 

734 

735 def testNormalizationDecRa(self): 

736 """Test that a Dec, RA WCS is normalized to RA, Dec 

737 """ 

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

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

740 

741 # swap RA, Decaxes in metadata 

742 crvalIn = lsst.geom.SpherePoint(self.metadata.getScalar("CRVAL1"), 

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

744 self.metadata.set("CRVAL1", crvalIn[1].asDegrees()) 

745 self.metadata.set("CRVAL2", crvalIn[0].asDegrees()) 

746 self.metadata.set("CTYPE1", "DEC--TAN") 

747 self.metadata.set("CTYPE2", "RA---TAN") 

748 

749 # create the wcs 

750 skyWcs = makeSkyWcs(self.metadata) 

751 

752 # compare pixel origin to input crval 

753 crval = skyWcs.getSkyOrigin() 

754 self.assertSpherePointsAlmostEqual(crval, crvalIn) 

755 

756 # compare to computed crval 

757 computedCrval = skyWcs.pixelToSky(crpix) 

758 self.assertSpherePointsAlmostEqual(crval, computedCrval) 

759 

760 def testReadDESHeader(self): 

761 """Verify that we can read a DES header""" 

762 self.metadata.set("RADESYS", "ICRS ") # note trailing white space 

763 self.metadata.set("CTYPE1", "RA---TPV") 

764 self.metadata.set("CTYPE2", "DEC--TPV") 

765 

766 skyWcs = makeSkyWcs(self.metadata, strip=False) 

767 self.checkWcs(skyWcs) 

768 

769 def testCD_PC(self): 

770 """Test that we can read a FITS file with both CD and PC keys (like early Suprimecam files)""" 

771 md = PropertyList() 

772 for k, v in ( 

773 ("EQUINOX", 2000.0), 

774 ("RADESYS", "ICRS"), 

775 ("CRPIX1", 5353.0), 

776 ("CRPIX2", -35.0), 

777 ("CD1_1", 0.0), 

778 ("CD1_2", -5.611E-05), 

779 ("CD2_1", -5.611E-05), 

780 ("CD2_2", -0.0), 

781 ("CRVAL1", 4.5789875), 

782 ("CRVAL2", 16.30004444), 

783 ("CUNIT1", "deg"), 

784 ("CUNIT2", "deg"), 

785 ("CTYPE1", "RA---TAN"), 

786 ("CTYPE2", "DEC--TAN"), 

787 ("CDELT1", -5.611E-05), 

788 ("CDELT2", 5.611E-05), 

789 ): 

790 md.set(k, v) 

791 

792 wcs = makeSkyWcs(md, strip=False) 

793 

794 pixPos = lsst.geom.Point2D(1000, 2000) 

795 pred_skyPos = lsst.geom.SpherePoint(4.459815023498577, 16.544199850984768, lsst.geom.degrees) 

796 

797 skyPos = wcs.pixelToSky(pixPos) 

798 self.assertSpherePointsAlmostEqual(skyPos, pred_skyPos) 

799 

800 for badPC in (False, True): 

801 for k, v in ( 

802 ("PC001001", 0.0), 

803 ("PC001002", -1.0 if badPC else 1.0), 

804 ("PC002001", 1.0 if badPC else -1.0), 

805 ("PC002002", 0.0), 

806 ): 

807 md.set(k, v) 

808 

809 # Check Greisen and Calabretta A&A 395 1061 (2002), Eq. 3 

810 if not badPC: 

811 for i in (1, 2,): 

812 for j in (1, 2,): 

813 self.assertEqual(md.getScalar(f"CD{i}_{j}"), 

814 md.getScalar(f"CDELT{i}")*md.getScalar(f"PC00{i}00{j}")) 

815 

816 wcs2 = makeSkyWcs(md, strip=False) 

817 skyPos2 = wcs2.pixelToSky(pixPos) 

818 self.assertSpherePointsAlmostEqual(skyPos2, pred_skyPos) 

819 

820 def testNoEpoch(self): 

821 """Ensure we're not writing epoch headers (DATE-OBS, MJD-OBS)""" 

822 self.metadata.set("EQUINOX", 2000.0) # Triggers AST writing DATE-OBS, MJD-OBS 

823 skyWcs = makeSkyWcs(self.metadata) 

824 header = skyWcs.getFitsMetadata() 

825 self.assertFalse(header.exists("DATE-OBS")) 

826 self.assertFalse(header.exists("MJD-OBS")) 

827 

828 def testCdMatrix(self): 

829 """Ensure we're writing CD matrix elements even if they're zero""" 

830 self.metadata.remove("CD1_2") 

831 self.metadata.remove("CD2_1") 

832 skyWcs = makeSkyWcs(self.metadata, strip=False) 

833 header = skyWcs.getFitsMetadata() 

834 for keyword in ("CD1_1", "CD2_2"): 

835 # There's some mild rounding going on 

836 self.assertFloatsAlmostEqual(header.get(keyword), self.metadata.get(keyword), atol=1.0e-16) 

837 for keyword in ("CD1_2", "CD2_1"): 

838 self.assertTrue(header.exists(keyword)) 

839 self.assertEqual(header.get(keyword), 0.0) 

840 

841 

842class TestTanSipTestCase(SkyWcsBaseTestCase): 

843 

844 def setUp(self): 

845 metadata = PropertyList() 

846 # the following was fit using CreateWcsWithSip from meas_astrom 

847 # and is valid over this bbox: (minimum=(0, 0), maximum=(3030, 3030)) 

848 # This same metadata was used to create testdata/oldTanSipwWs.fits 

849 for name, value in ( 

850 ("RADESYS", "ICRS"), 

851 ("CTYPE1", "RA---TAN-SIP"), 

852 ("CTYPE2", "DEC--TAN-SIP"), 

853 ("CRPIX1", 1531.1824767147), 

854 ("CRPIX2", 1531.1824767147), 

855 ("CRVAL1", 43.035511801383), 

856 ("CRVAL2", 44.305697682784), 

857 ("CUNIT1", "deg"), 

858 ("CUNIT2", "deg"), 

859 ("CD1_1", 0.00027493991598151), 

860 ("CD1_2", -3.2758487104158e-06), 

861 ("CD2_1", 3.2301310675830e-06), 

862 ("CD2_2", 0.00027493937506632), 

863 ("A_ORDER", 5), 

864 ("A_0_2", -1.7769487466972e-09), 

865 ("A_0_3", 5.3745894718340e-13), 

866 ("A_0_4", -7.2921116596880e-17), 

867 ("A_0_5", 8.6947236956136e-21), 

868 ("A_1_1", 5.4246387438098e-08), 

869 ("A_1_2", -1.5689083084641e-12), 

870 ("A_1_3", 1.2424130500997e-16), 

871 ("A_1_4", 3.9982572658006e-20), 

872 ("A_2_0", 4.9268299826160e-08), 

873 ("A_2_1", 1.6365657558495e-12), 

874 ("A_2_2", 1.1976983061953e-16), 

875 ("A_2_3", -1.7262037266467e-19), 

876 ("A_3_0", -5.9235031179999e-13), 

877 ("A_3_1", -3.4444326387310e-16), 

878 ("A_3_2", 1.4377441160800e-19), 

879 ("A_4_0", 1.8736407845095e-16), 

880 ("A_4_1", 2.9213314172884e-20), 

881 ("A_5_0", -5.3601346091084e-20), 

882 ("B_ORDER", 5), 

883 ("B_0_2", 4.9268299822979e-08), 

884 ("B_0_3", -5.9235032026906e-13), 

885 ("B_0_4", 1.8736407776035e-16), 

886 ("B_0_5", -5.3601341373220e-20), 

887 ("B_1_1", 5.4246387435453e-08), 

888 ("B_1_2", 1.6365657531115e-12), 

889 ("B_1_3", -3.4444326228808e-16), 

890 ("B_1_4", 2.9213312399941e-20), 

891 ("B_2_0", -1.7769487494962e-09), 

892 ("B_2_1", -1.5689082999319e-12), 

893 ("B_2_2", 1.1976983393279e-16), 

894 ("B_2_3", 1.4377441169892e-19), 

895 ("B_3_0", 5.3745894237186e-13), 

896 ("B_3_1", 1.2424130479929e-16), 

897 ("B_3_2", -1.7262036838229e-19), 

898 ("B_4_0", -7.2921117326608e-17), 

899 ("B_4_1", 3.9982566975450e-20), 

900 ("B_5_0", 8.6947240592408e-21), 

901 ("AP_ORDER", 6), 

902 ("AP_0_0", -5.4343024221207e-11), 

903 ("AP_0_1", 5.5722265946666e-12), 

904 ("AP_0_2", 1.7769484042400e-09), 

905 ("AP_0_3", -5.3773609554820e-13), 

906 ("AP_0_4", 7.3035278852156e-17), 

907 ("AP_0_5", -8.7151153799062e-21), 

908 ("AP_0_6", 3.2535945427624e-27), 

909 ("AP_1_0", -3.8944805432871e-12), 

910 ("AP_1_1", -5.4246388067582e-08), 

911 ("AP_1_2", 1.5741716194971e-12), 

912 ("AP_1_3", -1.2447067748187e-16), 

913 ("AP_1_4", -3.9960260822306e-20), 

914 ("AP_1_5", 1.1297941471380e-26), 

915 ("AP_2_0", -4.9268299293185e-08), 

916 ("AP_2_1", -1.6256111849359e-12), 

917 ("AP_2_2", -1.1973373130440e-16), 

918 ("AP_2_3", 1.7266948205700e-19), 

919 ("AP_2_4", -3.7059606160753e-26), 

920 ("AP_3_0", 5.9710911995811e-13), 

921 ("AP_3_1", 3.4464427650041e-16), 

922 ("AP_3_2", -1.4381853884204e-19), 

923 ("AP_3_3", -7.6527426974322e-27), 

924 ("AP_4_0", -1.8748435698960e-16), 

925 ("AP_4_1", -2.9267280226373e-20), 

926 ("AP_4_2", 4.8004317051259e-26), 

927 ("AP_5_0", 5.3657330221120e-20), 

928 ("AP_5_1", -1.6904065766661e-27), 

929 ("AP_6_0", -1.9484495120493e-26), 

930 ("BP_ORDER", 6), 

931 ("BP_0_0", -5.4291220607725e-11), 

932 ("BP_0_1", -3.8944871307931e-12), 

933 ("BP_0_2", -4.9268299290361e-08), 

934 ("BP_0_3", 5.9710912831833e-13), 

935 ("BP_0_4", -1.8748435594265e-16), 

936 ("BP_0_5", 5.3657325543368e-20), 

937 ("BP_0_6", -1.9484577299247e-26), 

938 ("BP_1_0", 5.5722051513577e-12), 

939 ("BP_1_1", -5.4246388065000e-08), 

940 ("BP_1_2", -1.6256111821465e-12), 

941 ("BP_1_3", 3.4464427499767e-16), 

942 ("BP_1_4", -2.9267278448109e-20), 

943 ("BP_1_5", -1.6904244067295e-27), 

944 ("BP_2_0", 1.7769484069376e-09), 

945 ("BP_2_1", 1.5741716110182e-12), 

946 ("BP_2_2", -1.1973373446176e-16), 

947 ("BP_2_3", -1.4381853893526e-19), 

948 ("BP_2_4", 4.8004294492911e-26), 

949 ("BP_3_0", -5.3773609074713e-13), 

950 ("BP_3_1", -1.2447067726801e-16), 

951 ("BP_3_2", 1.7266947774875e-19), 

952 ("BP_3_3", -7.6527556667042e-27), 

953 ("BP_4_0", 7.3035279660505e-17), 

954 ("BP_4_1", -3.9960255158200e-20), 

955 ("BP_4_2", -3.7059659675039e-26), 

956 ("BP_5_0", -8.7151157361284e-21), 

957 ("BP_5_1", 1.1297944388060e-26), 

958 ("BP_6_0", 3.2535788867488e-27), 

959 ): 

960 metadata.set(name, value) 

961 self.metadata = metadata 

962 self.bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000)) 

963 

964 def testTanSipFromFrameDict(self): 

965 """Test making a TAN-SIP WCS from a FrameDict 

966 """ 

967 skyWcs = makeSkyWcs(self.metadata, strip=False) 

968 self.checkFrameDictConstructor(skyWcs, bbox=self.bbox) 

969 

970 def testFitsMetadata(self): 

971 """Test that getFitsMetadata works for TAN-SIP 

972 """ 

973 skyWcs = makeSkyWcs(self.metadata, strip=False) 

974 self.assertTrue(skyWcs.isFits) 

975 fitsMetadata = skyWcs.getFitsMetadata(precise=True) 

976 skyWcsCopy = makeSkyWcs(fitsMetadata) 

977 self.assertWcsAlmostEqualOverBBox(skyWcs, skyWcsCopy, self.bbox) 

978 self.checkPersistence(skyWcs, bbox=self.bbox) 

979 

980 def testGetIntermediateWorldCoordsToSky(self): 

981 """Test getIntermediateWorldCoordsToSky and getPixelToIntermediateWorldCoords 

982 """ 

983 crpix = lsst.geom.Extent2D(self.metadata.getScalar("CRPIX1") - 1, 

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

985 skyWcs = makeSkyWcs(self.metadata, strip=False) 

986 for simplify in (False, True): 

987 pixelToIwc = getPixelToIntermediateWorldCoords(skyWcs, simplify) 

988 iwcToSky = getIntermediateWorldCoordsToSky(skyWcs, simplify) 

989 self.assertTrue(isinstance(pixelToIwc, TransformPoint2ToPoint2)) 

990 self.assertTrue(isinstance(iwcToSky, TransformPoint2ToSpherePoint)) 

991 if simplify: 

992 self.assertTrue(pixelToIwc.getMapping().isSimple) 

993 self.assertTrue(iwcToSky.getMapping().isSimple) 

994 # else the mapping may have already been simplified inside the WCS, 

995 # so don't assert isSimple is false 

996 

997 # check that the chained transforms produce the same results as the WCS 

998 # in the forward and inverse direction 

999 pixPosList = [] 

1000 for dx in (0, 1000): 

1001 for dy in (0, 1000): 

1002 pixPosList.append(lsst.geom.Point2D(dx, dy) + crpix) 

1003 iwcPosList = pixelToIwc.applyForward(pixPosList) 

1004 skyPosList = iwcToSky.applyForward(iwcPosList) 

1005 self.assertSpherePointListsAlmostEqual(skyPosList, skyWcs.pixelToSky(pixPosList)) 

1006 self.assertPairListsAlmostEqual(pixelToIwc.applyInverse(iwcToSky.applyInverse(skyPosList)), 

1007 skyWcs.skyToPixel(skyPosList)) 

1008 

1009 self.assertPairListsAlmostEqual(iwcPosList, iwcToSky.applyInverse(skyPosList)) 

1010 self.assertPairListsAlmostEqual(pixPosList, pixelToIwc.applyInverse(iwcPosList)) 

1011 

1012 # compare extracted pixelToIwc to a version of pixelToIwc computed directly from the metadata 

1013 ourPixelToIwc = makeSipPixelToIwc(self.metadata) 

1014 self.assertPairListsAlmostEqual(pixelToIwc.applyForward(pixPosList), 

1015 ourPixelToIwc.applyForward(pixPosList)) 

1016 

1017 # compare extracted iwcToPixel to a version of iwcToPixel computed directly from the metadata 

1018 ourIwcToPixel = makeSipIwcToPixel(self.metadata) 

1019 self.assertPairListsAlmostEqual(pixelToIwc.applyInverse(iwcPosList), 

1020 ourIwcToPixel.applyForward(iwcPosList)) 

1021 

1022 @unittest.skipIf(sys.version_info[0] < 3, "astropy.wcs rejects the header on py2") 

1023 def testAgainstAstropyWcs(self): 

1024 skyWcs = makeSkyWcs(self.metadata, strip=False) 

1025 header = makeLimitedFitsHeader(self.metadata) 

1026 astropyWcs = astropy.wcs.WCS(header) 

1027 self.assertSkyWcsAstropyWcsAlmostEqual(skyWcs=skyWcs, astropyWcs=astropyWcs, bbox=self.bbox) 

1028 

1029 def testMakeTanSipWcs(self): 

1030 referenceWcs = makeSkyWcs(self.metadata, strip=False) 

1031 

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

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

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

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

1036 cdMatrix = getCdMatrixFromMetadata(self.metadata) 

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

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

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

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

1041 skyWcs1 = makeTanSipWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix, sipA=sipA, sipB=sipB) 

1042 skyWcs2 = makeTanSipWcs(crpix=crpix, crval=crval, cdMatrix=cdMatrix, sipA=sipA, sipB=sipB, 

1043 sipAp=sipAp, sipBp=sipBp) 

1044 

1045 self.assertWcsAlmostEqualOverBBox(referenceWcs, skyWcs1, self.bbox) 

1046 self.assertWcsAlmostEqualOverBBox(referenceWcs, skyWcs2, self.bbox) 

1047 self.checkMakeFlippedWcs(skyWcs1) 

1048 self.checkMakeFlippedWcs(skyWcs2) 

1049 

1050 def testReadWriteFits(self): 

1051 wcsFromMetadata = makeSkyWcs(self.metadata) 

1052 with lsst.utils.tests.getTempFilePath(".fits") as filePath: 

1053 wcsFromMetadata.writeFits(filePath) 

1054 wcsFromFits = SkyWcs.readFits(filePath) 

1055 

1056 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, self.bbox, maxDiffPix=0, 

1057 maxDiffSky=0*lsst.geom.radians) 

1058 

1059 def testReadOldTanSipFits(self): 

1060 """Test reading a FITS file containing data for an lsst::afw::image::TanWcs 

1061 

1062 That file was made using the same metadata as this test 

1063 """ 

1064 dataDir = os.path.join(os.path.split(__file__)[0], "data") 

1065 filePath = os.path.join(dataDir, "oldTanSipWcs.fits") 

1066 wcsFromFits = SkyWcs.readFits(filePath) 

1067 

1068 wcsFromMetadata = makeSkyWcs(self.metadata) 

1069 

1070 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000)) 

1071 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, bbox) 

1072 

1073 def testReadOldTanFits(self): 

1074 """Test reading a FITS file containing data for an lsst::afw::image::TanWcs 

1075 

1076 That file was made using the same metadata follows 

1077 (like self.metadata without the distortion) 

1078 """ 

1079 tanMetadata = PropertyList() 

1080 # the following was fit using CreateWcsWithSip from meas_astrom 

1081 # and is valid over this bbox: (minimum=(0, 0), maximum=(3030, 3030)) 

1082 # This same metadata was used to create testdata/oldTanSipwWs.fits 

1083 for name, value in ( 

1084 ("RADESYS", "ICRS"), 

1085 ("CTYPE1", "RA---TAN"), 

1086 ("CTYPE2", "DEC--TAN"), 

1087 ("CRPIX1", 1531.1824767147), 

1088 ("CRPIX2", 1531.1824767147), 

1089 ("CRVAL1", 43.035511801383), 

1090 ("CRVAL2", 44.305697682784), 

1091 ("CUNIT1", "deg"), 

1092 ("CUNIT2", "deg"), 

1093 ("CD1_1", 0.00027493991598151), 

1094 ("CD1_2", -3.2758487104158e-06), 

1095 ("CD2_1", 3.2301310675830e-06), 

1096 ("CD2_2", 0.00027493937506632), 

1097 ): 

1098 tanMetadata.set(name, value) 

1099 

1100 dataDir = os.path.join(os.path.split(__file__)[0], "data") 

1101 filePath = os.path.join(dataDir, "oldTanWcs.fits") 

1102 wcsFromFits = SkyWcs.readFits(filePath) 

1103 

1104 wcsFromMetadata = makeSkyWcs(tanMetadata) 

1105 

1106 bbox = lsst.geom.Box2D(lsst.geom.Point2D(-1000, -1000), lsst.geom.Extent2D(3000, 3000)) 

1107 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, bbox) 

1108 

1109 

1110class WcsPairTransformTestCase(SkyWcsBaseTestCase): 

1111 """Test functionality of makeWcsPairTransform. 

1112 """ 

1113 def setUp(self): 

1114 SkyWcsBaseTestCase.setUp(self) 

1115 crpix = lsst.geom.Point2D(100, 100) 

1116 crvalList = [ 

1117 lsst.geom.SpherePoint(0, 45, lsst.geom.degrees), 

1118 lsst.geom.SpherePoint(0.00001, 45, lsst.geom.degrees), 

1119 lsst.geom.SpherePoint(359.99999, 45, lsst.geom.degrees), 

1120 lsst.geom.SpherePoint(30, 89.99999, lsst.geom.degrees), 

1121 ] 

1122 orientationList = [ 

1123 0 * lsst.geom.degrees, 

1124 0.00001 * lsst.geom.degrees, 

1125 -0.00001 * lsst.geom.degrees, 

1126 -45 * lsst.geom.degrees, 

1127 90 * lsst.geom.degrees, 

1128 ] 

1129 scale = 1.0 * lsst.geom.arcseconds 

1130 

1131 self.wcsList = [] 

1132 for crval in crvalList: 

1133 for orientation in orientationList: 

1134 cd = makeCdMatrix(scale=scale, orientation=orientation) 

1135 self.wcsList.append(makeSkyWcs( 

1136 crpix=crpix, 

1137 crval=crval, 

1138 cdMatrix=cd)) 

1139 self.pixelPoints = [lsst.geom.Point2D(x, y) for x, y in 

1140 itertools.product((0.0, -2.0, 42.5, 1042.3), 

1141 (27.6, -0.1, 0.0, 196.0))] 

1142 

1143 def testGenericWcs(self): 

1144 """Test that input and output points represent the same sky position. 

1145 

1146 Would prefer a black-box test, but don't have the numbers for it. 

1147 """ 

1148 inPoints = self.pixelPoints 

1149 for wcs1 in self.wcsList: 

1150 for wcs2 in self.wcsList: 

1151 transform = makeWcsPairTransform(wcs1, wcs2) 

1152 outPoints = transform.applyForward(inPoints) 

1153 inPointsRoundTrip = transform.applyInverse(outPoints) 

1154 self.assertPairListsAlmostEqual(inPoints, inPointsRoundTrip) 

1155 self.assertSpherePointListsAlmostEqual(wcs1.pixelToSky(inPoints), 

1156 wcs2.pixelToSky(outPoints)) 

1157 

1158 def testSameWcs(self): 

1159 """Confirm that pairing two identical Wcs gives an identity transform. 

1160 """ 

1161 for wcs in self.wcsList: 

1162 transform = makeWcsPairTransform(wcs, wcs) 

1163 # check that the transform has been simplified 

1164 self.assertTrue(transform.getMapping().isSimple) 

1165 # check the transform 

1166 outPoints1 = transform.applyForward(self.pixelPoints) 

1167 outPoints2 = transform.applyInverse(outPoints1) 

1168 self.assertPairListsAlmostEqual(self.pixelPoints, outPoints1) 

1169 self.assertPairListsAlmostEqual(outPoints1, outPoints2) 

1170 

1171 

1172class TestMemory(lsst.utils.tests.MemoryTestCase): 

1173 pass 

1174 

1175 

1176def setup_module(module): 

1177 lsst.utils.tests.init() 

1178 

1179 

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

1181 lsst.utils.tests.init() 

1182 unittest.main()