Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import itertools 

2import os 

3import sys 

4import unittest 

5 

6import astropy.io.fits 

7import astropy.coordinates 

8import astropy.wcs 

9import astshim as ast 

10from numpy.testing import assert_allclose 

11 

12import lsst.utils.tests 

13from lsst.daf.base import PropertyList 

14import lsst.geom 

15import lsst.afw.cameraGeom as cameraGeom 

16from lsst.afw.geom import wcsAlmostEqualOverBBox, \ 

17 TransformPoint2ToPoint2, TransformPoint2ToSpherePoint, makeRadialTransform, \ 

18 SkyWcs, makeSkyWcs, makeCdMatrix, makeWcsPairTransform, \ 

19 makeFlippedWcs, makeModifiedWcs, makeTanSipWcs, \ 

20 getIntermediateWorldCoordsToSky, getPixelToIntermediateWorldCoords 

21from lsst.afw.geom.wcsUtils import getCdMatrixFromMetadata, getSipMatrixFromMetadata, makeSimpleWcsMetadata 

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

23from lsst.afw.fits import makeLimitedFitsHeader 

24from lsst.afw.image import ExposureF 

25 

26 

27def addActualPixelsFrame(skyWcs, actualPixelsToPixels): 

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

29 

30 Parameters 

31 ---------- 

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

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

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

35 The transform from ACTUAL_PIXELS to PIXELS 

36 """ 

37 actualPixelsToPixelsMap = actualPixelsToPixels.getMapping() 

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

39 frameDict = skyWcs.getFrameDict() 

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

41 frameDict.setBase("ACTUAL_PIXELS") 

42 frameDict.setCurrent("SKY") 

43 return SkyWcs(frameDict) 

44 

45 

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

47 def checkPersistence(self, skyWcs, bbox): 

48 """Check persistence of a SkyWcs 

49 """ 

50 className = "SkyWcs" 

51 

52 # check writeString and readString 

53 skyWcsStr = skyWcs.writeString() 

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

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

56 self.assertEqual(serialClassName, className) 

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

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

59 skyWcs.readString(badStr1) 

60 badClassName = "x" + serialClassName 

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

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

63 skyWcs.readString(badStr2) 

64 skyWcsFromStr1 = skyWcs.readString(skyWcsStr) 

65 self.assertEqual(skyWcs, skyWcsFromStr1) 

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

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

68 

69 pixelPoints = [ 

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

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

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

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

74 ] 

75 skyPoints = skyWcs.pixelToSky(pixelPoints) 

76 pixelPoints2 = skyWcs.skyToPixel(skyPoints) 

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

78 

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

80 exposure = ExposureF(100, 100, skyWcs) 

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

82 exposure.writeFits(outFile) 

83 exposureRoundTrip = ExposureF(outFile) 

84 wcsFromExposure = exposureRoundTrip.getWcs() 

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

86 maxDiffSky=0*lsst.geom.radians) 

87 

88 def checkFrameDictConstructor(self, skyWcs, bbox): 

89 """Check that the FrameDict constructor works 

90 """ 

91 frameDict = skyWcs.getFrameDict() 

92 wcsFromFrameDict = SkyWcs(frameDict) 

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

94 maxDiffSky=0*lsst.geom.radians) 

95 

96 self.checkPersistence(wcsFromFrameDict, bbox) 

97 

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

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

100 badFrameDict = skyWcs.getFrameDict() 

101 badFrameDict.removeFrame(domain) 

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

103 SkyWcs(badFrameDict) 

104 

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

106 """Check makeFlippedWcs on the provided WCS 

107 """ 

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

109 # and does not include zero in the other axis 

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

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

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

113 # dict of (isRight, isTop): position 

114 minPos = bbox.getMin() 

115 maxPos = bbox.getMax() 

116 center = bbox.getCenter() 

117 cornerDict = { 

118 (False, False): minPos, 

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

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

121 (True, True): maxPos, 

122 } 

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

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

125 # the center is unchanged 

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

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

128 

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

130 origPos = cornerDict[(isR, isT)] 

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

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

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

134 

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

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

137 checkRoundTrip=True): 

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

139 """ 

140 bbox = lsst.geom.Box2D(bbox) 

141 center = bbox.getCenter() 

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

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

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

145 

146 # pixelToSky 

147 skyPosList = skyWcs.pixelToSky(pixPosList) 

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

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

150 

151 if not checkRoundTrip: 

152 return 

153 

154 # astropy round trip 

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

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

157 

158 # SkyWcs round trip 

159 pixPosListRoundTrip = skyWcs.skyToPixel(skyPosList) 

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

161 

162 # skyToPixel astropy vs SkyWcs 

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

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

165 

166 def astropyPixelsToSky(self, astropyWcs, pixPosList): 

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

168 

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

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

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

172 

173 Converts the output to ICRS 

174 """ 

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

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

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

178 yp=yarr, 

179 wcs=astropyWcs, 

180 origin=0, 

181 mode="all") 

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

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

184 

185 def astropySkyToPixels(self, astropyWcs, skyPosList): 

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

187 

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

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

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

191 

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

193 """ 

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

195 c[1].asDegrees(), 

196 frame="icrs", 

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

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

199 wcs=astropyWcs, 

200 origin=0, 

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

202 # float is needed to avoid truncation to int 

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

204 

205 

206class SimpleSkyWcsTestCase(SkyWcsBaseTestCase): 

207 """Test the simple FITS version of makeSkyWcs 

208 """ 

209 

210 def setUp(self): 

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

212 self.crvalList = [ 

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

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

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

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

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

218 ] 

219 self.orientationList = [ 

220 0 * lsst.geom.degrees, 

221 0.00001 * lsst.geom.degrees, 

222 -0.00001 * lsst.geom.degrees, 

223 -45 * lsst.geom.degrees, 

224 90 * lsst.geom.degrees, 

225 ] 

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

227 self.tinyPixels = 1.0e-10 

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

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

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

231 

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

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

234 

235 Parameters 

236 ---------- 

237 crval : `lsst.geom.SpherePoint` 

238 Desired reference sky position. 

239 Must not be at either pole. 

240 orientation : `lsst.geom.Angle` 

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

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

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

244 flipX : `bool` 

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

246 

247 Returns 

248 ------- 

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

250 The generated pure TAN SkyWcs 

251 """ 

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

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

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

255 self.checkMakeFlippedWcs(wcs) 

256 

257 self.assertTrue(wcs.isFits) 

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

259 

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

261 

262 pixelList = [ 

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

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

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

266 ] 

267 skyList = wcs.pixelToSky(pixelList) 

268 

269 # check pixels to sky 

270 predSkyList = [ 

271 crval, 

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

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

274 ] 

275 self.assertSpherePointListsAlmostEqual(predSkyList, skyList) 

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

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

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

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

280 

281 # check sky to pixels 

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

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

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

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

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

287 

288 # check CRVAL round trip 

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

290 maxSep=self.tinyAngle) 

291 

292 crpix = wcs.getPixelOrigin() 

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

294 

295 self.assertFloatsAlmostEqual(wcs.getCdMatrix(), cdMatrix) 

296 

297 pixelScale = wcs.getPixelScale() 

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

299 

300 pixelScale = wcs.getPixelScale(self.crpix) 

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

302 

303 # check that getFitsMetadata can operate at high precision 

304 # and has axis order RA, Dec 

305 fitsMetadata = wcs.getFitsMetadata(True) 

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

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

308 

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

310 # The resulting sky origin should not change 

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

312 shiftedWcs = wcs.copyAtShiftedPixelOrigin(offset) 

313 self.assertTrue(shiftedWcs.isFits) 

314 predShiftedPixelOrigin = self.crpix + offset 

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

316 maxDiff=self.tinyPixels) 

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

318 

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

320 shiftedSkyList = shiftedWcs.pixelToSky(shiftedPixelList) 

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

322 

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

324 shiftedMetadata = shiftedWcs.getFitsMetadata(precise=True) 

325 shiftedWcsCopy = makeSkyWcs(shiftedMetadata) 

326 shiftedBBox = lsst.geom.Box2D(predShiftedPixelOrigin, 

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

328 self.assertWcsAlmostEqualOverBBox(shiftedWcs, shiftedWcsCopy, shiftedBBox) 

329 

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

331 self.assertTrue(wcsCopy.isFits) 

332 

333 return wcs 

334 

335 def checkNonFitsWcs(self, wcs): 

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

337 """ 

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

339 self.assertFalse(wcs.isFits) 

340 with self.assertRaises(RuntimeError): 

341 wcs.getFitsMetadata(True) 

342 

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

344 wcsFromMetadata = makeSkyWcs(wcs.getFitsMetadata()) 

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

346 

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

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

349 # approximation to the original WCS 

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

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

352 

353 def testTanWcs(self): 

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

355 """ 

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

357 self.orientationList, 

358 (False, True)): 

359 self.checkTanWcs(crval=crval, 

360 orientation=orientation, 

361 flipX=flipX, 

362 ) 

363 

364 def testTanWcsFromFrameDict(self): 

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

366 """ 

367 cdMatrix = makeCdMatrix(scale=self.scale) 

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

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

370 

371 def testGetFrameDict(self): 

372 """Test that getFrameDict returns a deep copy 

373 """ 

374 cdMatrix = makeCdMatrix(scale=self.scale) 

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

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

377 frameDict = skyWcs.getFrameDict() 

378 frameDict.removeFrame(domain) 

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

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

381 

382 def testMakeModifiedWcsNoActualPixels(self): 

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

384 """ 

385 cdMatrix = makeCdMatrix(scale=self.scale) 

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

387 originalFrameDict = originalWcs.getFrameDict() 

388 

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

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

391 # the result of the insertion should be as follows 

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

393 

394 pixPointList = ( # arbitrary but reasonable 

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

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

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

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

399 ) 

400 for modifyActualPixels in (False, True): 

401 modifiedWcs = makeModifiedWcs(pixelTransform=pixelTransform, 

402 wcs=originalWcs, 

403 modifyActualPixels=modifyActualPixels) 

404 modifiedFrameDict = modifiedWcs.getFrameDict() 

405 skyList = modifiedWcs.pixelToSky(pixPointList) 

406 

407 # compare pixels to sky 

408 desiredSkyList = desiredPixelsToSky.applyForward(pixPointList) 

409 self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList) 

410 

411 # compare pixels to IWC 

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

413 desiredPixelsToIwc = TransformPoint2ToPoint2( 

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

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

416 desiredPixelsToIwc.applyForward(pixPointList)) 

417 

418 self.checkNonFitsWcs(modifiedWcs) 

419 

420 def testMakeModifiedWcsWithActualPixels(self): 

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

422 """ 

423 cdMatrix = makeCdMatrix(scale=self.scale) 

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

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

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

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

428 originalWcs = addActualPixelsFrame(baseWcs, actualPixelsToPixels) 

429 originalFrameDict = originalWcs.getFrameDict() 

430 

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

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

433 

434 pixPointList = ( # arbitrary but reasonable 

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

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

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

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

439 ) 

440 for modifyActualPixels in (True, False): 

441 modifiedWcs = makeModifiedWcs(pixelTransform=pixelTransform, 

442 wcs=originalWcs, 

443 modifyActualPixels=modifyActualPixels) 

444 modifiedFrameDict = modifiedWcs.getFrameDict() 

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

446 modifiedActualPixelsToPixels = \ 

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

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

449 

450 # compare pixels to sky 

451 skyList = modifiedWcs.pixelToSky(pixPointList) 

452 if modifyActualPixels: 

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

454 else: 

455 originalPixelsToSky = \ 

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

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

458 desiredSkyList = desiredPixelsToSky.applyForward(pixPointList) 

459 self.assertSpherePointListsAlmostEqual(skyList, desiredSkyList) 

460 

461 # compare ACTUAL_PIXELS to PIXELS and PIXELS to IWC 

462 if modifyActualPixels: 

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

464 desiredActualPixelsToPixels = pixelTransform.then(actualPixelsToPixels) 

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

466 desiredActualPixelsToPixels.applyForward(pixPointList)) 

467 

468 # check that PIXELS to IWC is unchanged 

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

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

471 originalPixelsToIwc.applyForward(pixPointList)) 

472 

473 else: 

474 # check that ACTUAL_PIXELS to PIXELS is unchanged 

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

476 actualPixelsToPixels.applyForward(pixPointList)) 

477 

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

479 desiredPixelsToIwc = TransformPoint2ToPoint2( 

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

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

482 desiredPixelsToIwc.applyForward(pixPointList)) 

483 

484 self.checkNonFitsWcs(modifiedWcs) 

485 

486 def testMakeSkyWcsFromPixelsToFieldAngle(self): 

487 """Test makeSkyWcs from a pixelsToFieldAngle transform 

488 """ 

489 pixelSizeMm = 25e-3 

490 # place the detector in several positions at several orientations 

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

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

493 flipX, projection in itertools.product( 

494 (lsst.geom.Point2D(0, 0), lsst.geom.Point2D(-100, 500)), 

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

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

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

498 crval=crval, orientation=pixelOrientation): 

499 pixelsToFocalPlane = cameraGeom.Orientation( 

500 fpPosition=fpPosition, 

501 yaw=yaw, 

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

503 # Compute crpix before adding optical distortion, 

504 # since it is not affected by such distortion 

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

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

507 focalPlaneToFieldAngle = lsst.afw.geom.makeTransform( 

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

509 pixelsToFieldAngle = pixelsToFocalPlane.then(focalPlaneToFieldAngle) 

510 

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

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

513 

514 if addOpticalDistortion: 

515 # Model optical distortion as a pixel transform, 

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

517 # cdMatrix, etc. using makeModifiedWcs 

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

519 pixelsToFieldAngle = pixelTransform.then(pixelsToFieldAngle) 

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

521 

522 # orientation is with respect to detector x, y 

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

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

525 wcs2 = makeSkyWcs(pixelsToFieldAngle=pixelsToFieldAngle, 

526 orientation=focalPlaneOrientation, 

527 flipX=flipX, 

528 boresight=crval, 

529 projection=projection) 

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

531 

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

533 def testAgainstAstropyWcs(self): 

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

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

536 self.orientationList, 

537 (False, True), 

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

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

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

541 projection=projection) 

542 header = makeLimitedFitsHeader(metadata) 

543 astropyWcs = astropy.wcs.WCS(header) 

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

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

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

547 

548 def testStr(self): 

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

550 """ 

551 cdMatrix = makeCdMatrix(scale=self.scale) 

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

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

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

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

556 

557 

558class MetadataWcsTestCase(SkyWcsBaseTestCase): 

559 """Test metadata constructor of SkyWcs 

560 """ 

561 

562 def setUp(self): 

563 metadata = PropertyList() 

564 for name, value in ( 

565 ("RADESYS", "ICRS"), 

566 ("EQUINOX", 2000.), 

567 ("CRVAL1", 215.604025685476), 

568 ("CRVAL2", 53.1595451514076), 

569 ("CRPIX1", 1109.99981456774), 

570 ("CRPIX2", 560.018167811613), 

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

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

573 ("CUNIT1", "deg"), 

574 ("CUNIT2", "deg"), 

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

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

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

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

579 ): 

580 metadata.set(name, value) 

581 self.metadata = metadata 

582 

583 def tearDown(self): 

584 del self.metadata 

585 

586 def checkWcs(self, skyWcs): 

587 pixelOrigin = skyWcs.getPixelOrigin() 

588 skyOrigin = skyWcs.getSkyOrigin() 

589 for i in range(2): 

590 # subtract 1 from FITS CRPIX to get LSST convention 

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

592 self.assertAnglesAlmostEqual(skyOrigin[i], 

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

594 cdMatrix = skyWcs.getCdMatrix() 

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

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

597 

598 self.assertTrue(skyWcs.isFits) 

599 

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

601 self.assertTrue(skyWcsCopy.isFits) 

602 self.checkMakeFlippedWcs(skyWcs) 

603 

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

605 def testAgainstAstropyWcs(self): 

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

607 header = makeLimitedFitsHeader(self.metadata) 

608 astropyWcs = astropy.wcs.WCS(header) 

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

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

611 

612 def testLinearizeMethods(self): 

613 skyWcs = makeSkyWcs(self.metadata) 

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

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

616 pix00 = skyWcs.skyToPixel(sky00) 

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

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

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

620 linSkyToPix1 = skyWcs.linearizeSkyToPixel(sky00, skyUnit) 

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

622 

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

624 linSky = linPixToSky1(pixel) 

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

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

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

628 

629 sky00Doubles = sky00.getPosition(skyUnit) 

630 pix00gApprox = linSkyToPix1(sky00Doubles) 

631 self.assertPairsAlmostEqual(pix00gApprox, pix00) 

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

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

634 pixelScale = skyWcs.getPixelScale(pix00) 

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

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

637 self.assertAlmostEqual(pixelArea, predictedPixelArea) 

638 

639 def testBasics(self): 

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

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

642 self.checkWcs(skyWcs) 

643 makeSkyWcs(self.metadata, strip=True) 

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

645 

646 def testNormalizationFk5(self): 

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

648 """ 

649 equinox = 1975.0 

650 metadata = self.metadata 

651 

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

653 metadata.set("EQUINOX", equinox) 

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

655 # record the original CRVAL before reading and stripping metadata 

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

657 

658 # create the wcs and retrieve crval 

659 skyWcs = makeSkyWcs(metadata) 

660 crval = skyWcs.getSkyOrigin() 

661 

662 # compare to computed crval 

663 computedCrval = skyWcs.pixelToSky(crpix) 

664 self.assertSpherePointsAlmostEqual(crval, computedCrval) 

665 

666 # get predicted crval by converting with astropy 

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

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

669 predictedCrvalIcrs = crvalFk5.icrs 

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

671 lsst.geom.radians) 

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

673 

674 def testNormalizationDecRa(self): 

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

676 """ 

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

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

679 

680 # swap RA, Decaxes in metadata 

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

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

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

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

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

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

687 

688 # create the wcs 

689 skyWcs = makeSkyWcs(self.metadata) 

690 

691 # compare pixel origin to input crval 

692 crval = skyWcs.getSkyOrigin() 

693 self.assertSpherePointsAlmostEqual(crval, crvalIn) 

694 

695 # compare to computed crval 

696 computedCrval = skyWcs.pixelToSky(crpix) 

697 self.assertSpherePointsAlmostEqual(crval, computedCrval) 

698 

699 def testReadDESHeader(self): 

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

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

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

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

704 

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

706 self.checkWcs(skyWcs) 

707 

708 def testCD_PC(self): 

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

710 md = PropertyList() 

711 for k, v in ( 

712 ("EQUINOX", 2000.0), 

713 ("RADESYS", "ICRS"), 

714 ("CRPIX1", 5353.0), 

715 ("CRPIX2", -35.0), 

716 ("CD1_1", 0.0), 

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

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

719 ("CD2_2", -0.0), 

720 ("CRVAL1", 4.5789875), 

721 ("CRVAL2", 16.30004444), 

722 ("CUNIT1", "deg"), 

723 ("CUNIT2", "deg"), 

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

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

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

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

728 ): 

729 md.set(k, v) 

730 

731 wcs = makeSkyWcs(md, strip=False) 

732 

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

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

735 

736 skyPos = wcs.pixelToSky(pixPos) 

737 self.assertSpherePointsAlmostEqual(skyPos, pred_skyPos) 

738 

739 for badPC in (False, True): 

740 for k, v in ( 

741 ("PC001001", 0.0), 

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

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

744 ("PC002002", 0.0), 

745 ): 

746 md.set(k, v) 

747 

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

749 if not badPC: 

750 for i in (1, 2,): 

751 for j in (1, 2,): 

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

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

754 

755 wcs2 = makeSkyWcs(md, strip=False) 

756 skyPos2 = wcs2.pixelToSky(pixPos) 

757 self.assertSpherePointsAlmostEqual(skyPos2, pred_skyPos) 

758 

759 def testNoEpoch(self): 

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

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

762 skyWcs = makeSkyWcs(self.metadata) 

763 header = skyWcs.getFitsMetadata() 

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

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

766 

767 def testCdMatrix(self): 

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

769 self.metadata.remove("CD1_2") 

770 self.metadata.remove("CD2_1") 

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

772 header = skyWcs.getFitsMetadata() 

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

774 # There's some mild rounding going on 

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

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

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

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

779 

780 

781class TestTanSipTestCase(SkyWcsBaseTestCase): 

782 

783 def setUp(self): 

784 metadata = PropertyList() 

785 # the following was fit using CreateWcsWithSip from meas_astrom 

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

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

788 for name, value in ( 

789 ("RADESYS", "ICRS"), 

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

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

792 ("CRPIX1", 1531.1824767147), 

793 ("CRPIX2", 1531.1824767147), 

794 ("CRVAL1", 43.035511801383), 

795 ("CRVAL2", 44.305697682784), 

796 ("CUNIT1", "deg"), 

797 ("CUNIT2", "deg"), 

798 ("CD1_1", 0.00027493991598151), 

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

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

801 ("CD2_2", 0.00027493937506632), 

802 ("A_ORDER", 5), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

821 ("B_ORDER", 5), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

840 ("AP_ORDER", 6), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

869 ("BP_ORDER", 6), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

898 ): 

899 metadata.set(name, value) 

900 self.metadata = metadata 

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

902 

903 def testTanSipFromFrameDict(self): 

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

905 """ 

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

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

908 

909 def testFitsMetadata(self): 

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

911 """ 

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

913 self.assertTrue(skyWcs.isFits) 

914 fitsMetadata = skyWcs.getFitsMetadata(precise=True) 

915 skyWcsCopy = makeSkyWcs(fitsMetadata) 

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

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

918 

919 def testGetIntermediateWorldCoordsToSky(self): 

920 """Test getIntermediateWorldCoordsToSky and getPixelToIntermediateWorldCoords 

921 """ 

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

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

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

925 for simplify in (False, True): 

926 pixelToIwc = getPixelToIntermediateWorldCoords(skyWcs, simplify) 

927 iwcToSky = getIntermediateWorldCoordsToSky(skyWcs, simplify) 

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

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

930 if simplify: 

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

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

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

934 # so don't assert isSimple is false 

935 

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

937 # in the forward and inverse direction 

938 pixPosList = [] 

939 for dx in (0, 1000): 

940 for dy in (0, 1000): 

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

942 iwcPosList = pixelToIwc.applyForward(pixPosList) 

943 skyPosList = iwcToSky.applyForward(iwcPosList) 

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

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

946 skyWcs.skyToPixel(skyPosList)) 

947 

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

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

950 

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

952 ourPixelToIwc = makeSipPixelToIwc(self.metadata) 

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

954 ourPixelToIwc.applyForward(pixPosList)) 

955 

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

957 ourIwcToPixel = makeSipIwcToPixel(self.metadata) 

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

959 ourIwcToPixel.applyForward(iwcPosList)) 

960 

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

962 def testAgainstAstropyWcs(self): 

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

964 header = makeLimitedFitsHeader(self.metadata) 

965 astropyWcs = astropy.wcs.WCS(header) 

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

967 

968 def testMakeTanSipWcs(self): 

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

970 

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

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

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

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

975 cdMatrix = getCdMatrixFromMetadata(self.metadata) 

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

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

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

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

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

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

982 sipAp=sipAp, sipBp=sipBp) 

983 

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

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

986 self.checkMakeFlippedWcs(skyWcs1) 

987 self.checkMakeFlippedWcs(skyWcs2) 

988 

989 def testReadWriteFits(self): 

990 wcsFromMetadata = makeSkyWcs(self.metadata) 

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

992 wcsFromMetadata.writeFits(filePath) 

993 wcsFromFits = SkyWcs.readFits(filePath) 

994 

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

996 maxDiffSky=0*lsst.geom.radians) 

997 

998 def testReadOldTanSipFits(self): 

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

1000 

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

1002 """ 

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

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

1005 wcsFromFits = SkyWcs.readFits(filePath) 

1006 

1007 wcsFromMetadata = makeSkyWcs(self.metadata) 

1008 

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

1010 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, bbox) 

1011 

1012 def testReadOldTanFits(self): 

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

1014 

1015 That file was made using the same metadata follows 

1016 (like self.metadata without the distortion) 

1017 """ 

1018 tanMetadata = PropertyList() 

1019 # the following was fit using CreateWcsWithSip from meas_astrom 

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

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

1022 for name, value in ( 

1023 ("RADESYS", "ICRS"), 

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

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

1026 ("CRPIX1", 1531.1824767147), 

1027 ("CRPIX2", 1531.1824767147), 

1028 ("CRVAL1", 43.035511801383), 

1029 ("CRVAL2", 44.305697682784), 

1030 ("CUNIT1", "deg"), 

1031 ("CUNIT2", "deg"), 

1032 ("CD1_1", 0.00027493991598151), 

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

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

1035 ("CD2_2", 0.00027493937506632), 

1036 ): 

1037 tanMetadata.set(name, value) 

1038 

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

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

1041 wcsFromFits = SkyWcs.readFits(filePath) 

1042 

1043 wcsFromMetadata = makeSkyWcs(tanMetadata) 

1044 

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

1046 self.assertWcsAlmostEqualOverBBox(wcsFromFits, wcsFromMetadata, bbox) 

1047 

1048 

1049class WcsPairTransformTestCase(SkyWcsBaseTestCase): 

1050 """Test functionality of makeWcsPairTransform. 

1051 """ 

1052 def setUp(self): 

1053 SkyWcsBaseTestCase.setUp(self) 

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

1055 crvalList = [ 

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

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

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

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

1060 ] 

1061 orientationList = [ 

1062 0 * lsst.geom.degrees, 

1063 0.00001 * lsst.geom.degrees, 

1064 -0.00001 * lsst.geom.degrees, 

1065 -45 * lsst.geom.degrees, 

1066 90 * lsst.geom.degrees, 

1067 ] 

1068 scale = 1.0 * lsst.geom.arcseconds 

1069 

1070 self.wcsList = [] 

1071 for crval in crvalList: 

1072 for orientation in orientationList: 

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

1074 self.wcsList.append(makeSkyWcs( 

1075 crpix=crpix, 

1076 crval=crval, 

1077 cdMatrix=cd)) 

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

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

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

1081 

1082 def testGenericWcs(self): 

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

1084 

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

1086 """ 

1087 inPoints = self.pixelPoints 

1088 for wcs1 in self.wcsList: 

1089 for wcs2 in self.wcsList: 

1090 transform = makeWcsPairTransform(wcs1, wcs2) 

1091 outPoints = transform.applyForward(inPoints) 

1092 inPointsRoundTrip = transform.applyInverse(outPoints) 

1093 self.assertPairListsAlmostEqual(inPoints, inPointsRoundTrip) 

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

1095 wcs2.pixelToSky(outPoints)) 

1096 

1097 def testSameWcs(self): 

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

1099 """ 

1100 for wcs in self.wcsList: 

1101 transform = makeWcsPairTransform(wcs, wcs) 

1102 # check that the transform has been simplified 

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

1104 # check the transform 

1105 outPoints1 = transform.applyForward(self.pixelPoints) 

1106 outPoints2 = transform.applyInverse(outPoints1) 

1107 self.assertPairListsAlmostEqual(self.pixelPoints, outPoints1) 

1108 self.assertPairListsAlmostEqual(outPoints1, outPoints2) 

1109 

1110 

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

1112 pass 

1113 

1114 

1115def setup_module(module): 

1116 lsst.utils.tests.init() 

1117 

1118 

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

1120 lsst.utils.tests.init() 

1121 unittest.main()