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

1# This file is part of afw. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22"""Test warpExposure 

23""" 

24import os 

25import unittest 

26 

27import numpy as np 

28 

29import lsst.utils 

30import lsst.utils.tests 

31import lsst.daf.base as dafBase 

32from lsst.afw.coord import Observatory, Weather 

33import lsst.geom 

34import lsst.afw.geom as afwGeom 

35import lsst.afw.image as afwImage 

36import lsst.afw.math as afwMath 

37import lsst.pex.exceptions as pexExcept 

38import lsst.afw.display as afwDisplay 

39from lsst.log import Log 

40 

41# Change the level to Log.DEBUG to see debug messages 

42Log.getLogger("afw.image.Mask").setLevel(Log.INFO) 

43Log.getLogger("TRACE2.afw.math.warp").setLevel(Log.INFO) 

44Log.getLogger("TRACE3.afw.math.warp").setLevel(Log.INFO) 

45 

46afwDisplay.setDefaultMaskTransparency(75) 

47 

48display = False 

49# set True to save afw-warped images as FITS files 

50SAVE_FITS_FILES = False 

51# set True to save failed afw-warped images as FITS files even if 

52# SAVE_FITS_FILES is False 

53SAVE_FAILED_FITS_FILES = True 

54 

55try: 

56 afwdataDir = lsst.utils.getPackageDir("afwdata") 

57except pexExcept.NotFoundError: 

58 afwdataDir = None 

59else: 

60 dataDir = os.path.join(afwdataDir, "data") 

61 

62 originalExposureName = "medexp.fits" 

63 originalExposurePath = os.path.join(dataDir, originalExposureName) 

64 subExposureName = "medsub.fits" 

65 subExposurePath = os.path.join(dataDir, originalExposureName) 

66 originalFullExposureName = os.path.join( 

67 "CFHT", "D4", "cal-53535-i-797722_1.fits") 

68 originalFullExposurePath = os.path.join(dataDir, originalFullExposureName) 

69 

70 

71def makeVisitInfo(): 

72 """Return a non-NaN visitInfo.""" 

73 return afwImage.VisitInfo(exposureId=10313423, 

74 exposureTime=10.01, 

75 darkTime=11.02, 

76 date=dafBase.DateTime(65321.1, dafBase.DateTime.MJD, dafBase.DateTime.TAI), 

77 ut1=12345.1, 

78 era=45.1*lsst.geom.degrees, 

79 boresightRaDec=lsst.geom.SpherePoint(23.1, 73.2, lsst.geom.degrees), 

80 boresightAzAlt=lsst.geom.SpherePoint(134.5, 33.3, lsst.geom.degrees), 

81 boresightAirmass=1.73, 

82 boresightRotAngle=73.2*lsst.geom.degrees, 

83 rotType=afwImage.RotType.SKY, 

84 observatory=Observatory(11.1*lsst.geom.degrees, 22.2*lsst.geom.degrees, 0.333), 

85 weather=Weather(1.1, 2.2, 34.5), 

86 ) 

87 

88 

89class WarpExposureTestCase(lsst.utils.tests.TestCase): 

90 """Test case for warpExposure 

91 """ 

92 

93 def setUp(self): 

94 np.random.seed(0) 

95 

96 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

97 def testNullWarpExposure(self, interpLength=10): 

98 """Test that warpExposure maps an image onto itself. 

99 

100 Note: 

101 - NO_DATA and off-CCD pixels must be ignored 

102 - bad mask pixels get smeared out so we have to excluded all bad mask pixels 

103 from the output image when comparing masks. 

104 """ 

105 originalExposure = afwImage.ExposureF(originalExposurePath) 

106 originalExposure.getInfo().setVisitInfo(makeVisitInfo()) 

107 originalFilterLabel = afwImage.FilterLabel(band="i") 

108 originalPhotoCalib = afwImage.PhotoCalib(1.0e5, 1.0e3) 

109 originalExposure.setFilterLabel(originalFilterLabel) 

110 originalExposure.setPhotoCalib(originalPhotoCalib) 

111 afwWarpedExposure = afwImage.ExposureF( 

112 originalExposure.getBBox(), 

113 originalExposure.getWcs()) 

114 warpingControl = afwMath.WarpingControl( 

115 "lanczos4", "", 0, interpLength) 

116 afwMath.warpExposure( 

117 afwWarpedExposure, originalExposure, warpingControl) 

118 if SAVE_FITS_FILES: 

119 afwWarpedExposure.writeFits("afwWarpedExposureNull.fits") 

120 

121 self.assertEqual(afwWarpedExposure.getFilterLabel().bandLabel, 

122 originalFilterLabel.bandLabel) 

123 self.assertEqual(afwWarpedExposure.getPhotoCalib(), originalPhotoCalib) 

124 self.assertEqual(afwWarpedExposure.getInfo().getVisitInfo(), 

125 originalExposure.getInfo().getVisitInfo()) 

126 

127 afwWarpedMaskedImage = afwWarpedExposure.getMaskedImage() 

128 afwWarpedMask = afwWarpedMaskedImage.getMask() 

129 noDataBitMask = afwWarpedMask.getPlaneBitMask("NO_DATA") 

130 afwWarpedMaskedImageArrSet = afwWarpedMaskedImage.getArrays() 

131 afwWarpedMaskArr = afwWarpedMaskedImageArrSet[1] 

132 

133 # compare all non-DATA pixels of image and variance, but relax specs a bit 

134 # because of minor noise introduced by bad pixels 

135 noDataMaskArr = afwWarpedMaskArr & noDataBitMask 

136 msg = "afw null-warped MaskedImage (all pixels, relaxed tolerance)" 

137 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, originalExposure.getMaskedImage(), 

138 doMask=False, skipMask=noDataMaskArr, atol=1e-5, msg=msg) 

139 

140 # compare good pixels (mask=0) of image, mask and variance using full 

141 # tolerance 

142 msg = "afw null-warped MaskedImage (good pixels, max tolerance)" 

143 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, originalExposure.getMaskedImage(), 

144 skipMask=afwWarpedMask, msg=msg) 

145 

146 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

147 def testNullWarpImage(self, interpLength=10): 

148 """Test that warpImage maps an image onto itself. 

149 """ 

150 originalExposure = afwImage.ExposureF(originalExposurePath) 

151 afwWarpedExposure = afwImage.ExposureF(originalExposurePath) 

152 originalImage = originalExposure.getMaskedImage().getImage() 

153 afwWarpedImage = afwWarpedExposure.getMaskedImage().getImage() 

154 originalWcs = originalExposure.getWcs() 

155 afwWarpedWcs = afwWarpedExposure.getWcs() 

156 warpingControl = afwMath.WarpingControl( 

157 "lanczos4", "", 0, interpLength) 

158 afwMath.warpImage(afwWarpedImage, afwWarpedWcs, 

159 originalImage, originalWcs, warpingControl) 

160 if SAVE_FITS_FILES: 

161 afwWarpedImage.writeFits("afwWarpedImageNull.fits") 

162 afwWarpedImageArr = afwWarpedImage.getArray() 

163 noDataMaskArr = np.isnan(afwWarpedImageArr) 

164 # relax specs a bit because of minor noise introduced by bad pixels 

165 msg = "afw null-warped Image" 

166 self.assertImagesAlmostEqual(originalImage, afwWarpedImage, skipMask=noDataMaskArr, 

167 atol=1e-5, msg=msg) 

168 

169 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

170 def testNullWcs(self, interpLength=10): 

171 """Cannot warp from or into an exposure without a Wcs. 

172 """ 

173 exposureWithWcs = afwImage.ExposureF(originalExposurePath) 

174 mi = exposureWithWcs.getMaskedImage() 

175 exposureWithoutWcs = afwImage.ExposureF(mi.getDimensions()) 

176 warpingControl = afwMath.WarpingControl( 

177 "bilinear", "", 0, interpLength) 

178 

179 with self.assertRaises(pexExcept.InvalidParameterError): 

180 afwMath.warpExposure(exposureWithWcs, exposureWithoutWcs, warpingControl) 

181 

182 with self.assertRaises(pexExcept.InvalidParameterError): 

183 afwMath.warpExposure(exposureWithoutWcs, exposureWithWcs, warpingControl) 

184 

185 def testWarpIntoSelf(self, interpLength=10): 

186 """Cannot warp in-place 

187 """ 

188 wcs = afwGeom.makeSkyWcs( 

189 crpix=lsst.geom.Point2D(0, 0), 

190 crval=lsst.geom.SpherePoint(359, 0, lsst.geom.degrees), 

191 cdMatrix=afwGeom.makeCdMatrix(1.0e-8*lsst.geom.degrees), 

192 ) 

193 exposure = afwImage.ExposureF(lsst.geom.Extent2I(100, 100), wcs) 

194 maskedImage = exposure.getMaskedImage() 

195 warpingControl = afwMath.WarpingControl( 

196 "bilinear", "", 0, interpLength) 

197 

198 with self.assertRaises(pexExcept.InvalidParameterError): 

199 afwMath.warpExposure(exposure, exposure, warpingControl) 

200 

201 with self.assertRaises(pexExcept.InvalidParameterError): 

202 afwMath.warpImage(maskedImage, wcs, maskedImage, wcs, warpingControl) 

203 

204 with self.assertRaises(pexExcept.InvalidParameterError): 

205 afwMath.warpImage(maskedImage.getImage(), wcs, maskedImage.getImage(), wcs, warpingControl) 

206 

207 def testWarpingControl(self): 

208 """Test the basic mechanics of WarpingControl 

209 """ 

210 for interpLength in (0, 1, 52): 

211 wc = afwMath.WarpingControl("lanczos3", "", 0, interpLength) 

212 self.assertFalse(wc.hasMaskWarpingKernel()) 

213 self.assertEqual(wc.getInterpLength(), interpLength) 

214 for newInterpLength in (3, 7, 9): 

215 wc.setInterpLength(newInterpLength) 

216 self.assertEqual(wc.getInterpLength(), newInterpLength) 

217 

218 for cacheSize in (0, 100): 

219 wc = afwMath.WarpingControl("lanczos3", "bilinear", cacheSize) 

220 self.assertTrue(wc.hasMaskWarpingKernel()) 

221 self.assertEqual(wc.getCacheSize(), cacheSize) 

222 self.assertEqual(wc.getWarpingKernel().getCacheSize(), cacheSize) 

223 self.assertEqual( 

224 wc.getMaskWarpingKernel().getCacheSize(), cacheSize) 

225 for newCacheSize in (1, 50): 

226 wc.setCacheSize(newCacheSize) 

227 self.assertEqual(wc.getCacheSize(), newCacheSize) 

228 self.assertEqual( 

229 wc.getWarpingKernel().getCacheSize(), newCacheSize) 

230 self.assertEqual( 

231 wc.getMaskWarpingKernel().getCacheSize(), newCacheSize) 

232 

233 def testWarpingControlError(self): 

234 """Test error handling of WarpingControl 

235 """ 

236 # error: mask kernel smaller than main kernel 

237 for kernelName, maskKernelName in ( 

238 ("bilinear", "lanczos3"), 

239 ("bilinear", "lanczos4"), 

240 ("lanczos3", "lanczos4"), 

241 ): 

242 with self.assertRaises(pexExcept.InvalidParameterError): 

243 afwMath.WarpingControl(kernelName, maskKernelName) 

244 

245 # error: new mask kernel larger than main kernel 

246 warpingControl = afwMath.WarpingControl("bilinear") 

247 for maskKernelName in ("lanczos3", "lanczos4"): 

248 with self.assertRaises(pexExcept.InvalidParameterError): 

249 warpingControl.setMaskWarpingKernelName(maskKernelName) 

250 

251 # error: new kernel smaller than mask kernel 

252 warpingControl = afwMath.WarpingControl("lanczos4", "lanczos4") 

253 for kernelName in ("bilinear", "lanczos3"): 

254 with self.assertRaises(pexExcept.InvalidParameterError): 

255 warpingControl.setWarpingKernelName(kernelName) 

256 

257 # OK: main kernel at least as big as mask kernel 

258 for kernelName, maskKernelName in ( 

259 ("bilinear", "bilinear"), 

260 ("lanczos3", "lanczos3"), 

261 ("lanczos3", "bilinear"), 

262 ("lanczos4", "lanczos3"), 

263 ): 

264 # this should not raise any exception 

265 afwMath.WarpingControl(kernelName, maskKernelName) 

266 

267 # invalid kernel names 

268 for kernelName, maskKernelName in ( 

269 ("badname", ""), 

270 ("lanczos", ""), # no digit after lanczos 

271 ("lanczos3", "badname"), 

272 ("lanczos3", "lanczos"), 

273 ): 

274 with self.assertRaises(pexExcept.InvalidParameterError): 

275 afwMath.WarpingControl(kernelName, maskKernelName) 

276 

277 def testWarpMask(self): 

278 """Test that warping the mask plane with a different kernel does the right thing 

279 """ 

280 for kernelName, maskKernelName in ( 

281 ("bilinear", "bilinear"), 

282 ("lanczos3", "lanczos3"), 

283 ("lanczos3", "bilinear"), 

284 ("lanczos4", "lanczos3"), 

285 ): 

286 for growFullMask in (0, 1, 3, 0xFFFF): 

287 self.verifyMaskWarp( 

288 kernelName=kernelName, 

289 maskKernelName=maskKernelName, 

290 growFullMask=growFullMask, 

291 ) 

292 

293 def testMatchSwarpBilinearImage(self): 

294 """Test that warpExposure matches swarp using a bilinear warping kernel 

295 """ 

296 self.compareToSwarp("bilinear", useWarpExposure=False, atol=0.15) 

297 

298 def testMatchSwarpBilinearExposure(self): 

299 """Test that warpExposure matches swarp using a bilinear warping kernel 

300 """ 

301 self.compareToSwarp("bilinear", useWarpExposure=True, 

302 useSubregion=False, useDeepCopy=True) 

303 

304 def testMatchSwarpLanczos2Image(self): 

305 """Test that warpExposure matches swarp using a lanczos2 warping kernel 

306 """ 

307 self.compareToSwarp("lanczos2", useWarpExposure=False) 

308 

309 def testMatchSwarpLanczos2Exposure(self): 

310 """Test that warpExposure matches swarp using a lanczos2 warping kernel. 

311 """ 

312 self.compareToSwarp("lanczos2", useWarpExposure=True) 

313 

314 def testMatchSwarpLanczos2SubExposure(self): 

315 """Test that warpExposure matches swarp using a lanczos2 warping kernel with a subexposure 

316 """ 

317 for useDeepCopy in (False, True): 

318 self.compareToSwarp("lanczos2", useWarpExposure=True, 

319 useSubregion=True, useDeepCopy=useDeepCopy) 

320 

321 def testMatchSwarpLanczos3Image(self): 

322 """Test that warpExposure matches swarp using a lanczos2 warping kernel 

323 """ 

324 self.compareToSwarp("lanczos3", useWarpExposure=False) 

325 

326 def testMatchSwarpLanczos3(self): 

327 """Test that warpExposure matches swarp using a lanczos4 warping kernel. 

328 """ 

329 self.compareToSwarp("lanczos3", useWarpExposure=True) 

330 

331 def testMatchSwarpLanczos4Image(self): 

332 """Test that warpExposure matches swarp using a lanczos2 warping kernel 

333 """ 

334 self.compareToSwarp("lanczos4", useWarpExposure=False) 

335 

336 def testMatchSwarpLanczos4(self): 

337 """Test that warpExposure matches swarp using a lanczos4 warping kernel. 

338 """ 

339 self.compareToSwarp("lanczos4", useWarpExposure=True) 

340 

341 def testMatchSwarpNearestExposure(self): 

342 """Test that warpExposure matches swarp using a nearest neighbor warping kernel 

343 """ 

344 self.compareToSwarp("nearest", useWarpExposure=True, atol=60) 

345 

346 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

347 def testTransformBasedWarp(self): 

348 """Test warping using TransformPoint2ToPoint2 

349 """ 

350 for interpLength in (0, 1, 2, 4): 

351 kernelName = "lanczos3" 

352 rtol = 4e-5 

353 atol = 1e-2 

354 warpingControl = afwMath.WarpingControl( 

355 warpingKernelName=kernelName, 

356 interpLength=interpLength, 

357 ) 

358 

359 originalExposure = afwImage.ExposureF(originalExposurePath) 

360 originalMetadata = afwImage.DecoratedImageF(originalExposurePath).getMetadata() 

361 originalSkyWcs = afwGeom.makeSkyWcs(originalMetadata) 

362 

363 swarpedImageName = f"medswarp1{kernelName}.fits" 

364 swarpedImagePath = os.path.join(dataDir, swarpedImageName) 

365 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

366 swarpedImage = swarpedDecoratedImage.getImage() 

367 

368 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

369 warpedSkyWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

370 

371 # original image is source, warped image is destination 

372 srcToDest = afwGeom.makeWcsPairTransform(originalSkyWcs, warpedSkyWcs) 

373 

374 afwWarpedMaskedImage = afwImage.MaskedImageF(swarpedImage.getDimensions()) 

375 originalMaskedImage = originalExposure.getMaskedImage() 

376 

377 numGoodPix = afwMath.warpImage(afwWarpedMaskedImage, originalMaskedImage, 

378 srcToDest, warpingControl) 

379 self.assertGreater(numGoodPix, 50) 

380 

381 afwWarpedImage = afwWarpedMaskedImage.getImage() 

382 afwWarpedImageArr = afwWarpedImage.getArray() 

383 noDataMaskArr = np.isnan(afwWarpedImageArr) 

384 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

385 skipMask=noDataMaskArr, rtol=rtol, atol=atol) 

386 

387 def testTicket2441(self): 

388 """Test ticket 2441: warpExposure sometimes mishandles zero-extent dest exposures""" 

389 fromWcs = afwGeom.makeSkyWcs( 

390 crpix=lsst.geom.Point2D(0, 0), 

391 crval=lsst.geom.SpherePoint(359, 0, lsst.geom.degrees), 

392 cdMatrix=afwGeom.makeCdMatrix(scale=1.0e-8*lsst.geom.degrees), 

393 ) 

394 fromExp = afwImage.ExposureF(afwImage.MaskedImageF(10, 10), fromWcs) 

395 

396 toWcs = afwGeom.makeSkyWcs( 

397 crpix=lsst.geom.Point2D(410000, 11441), 

398 crval=lsst.geom.SpherePoint(45, 0, lsst.geom.degrees), 

399 cdMatrix=afwGeom.makeCdMatrix(scale=0.00011*lsst.geom.degrees, flipX=True), 

400 projection="CEA", 

401 ) 

402 toExp = afwImage.ExposureF(afwImage.MaskedImageF(0, 0), toWcs) 

403 

404 warpControl = afwMath.WarpingControl("lanczos3") 

405 # if a bug described in ticket #2441 is present, this will raise an 

406 # exception: 

407 numGoodPix = afwMath.warpExposure(toExp, fromExp, warpControl) 

408 self.assertEqual(numGoodPix, 0) 

409 

410 def testTicketDM4063(self): 

411 """Test that a uint16 array can be cast to a bool array, to avoid DM-4063 

412 """ 

413 a = np.array([0, 1, 0, 23], dtype=np.uint16) 

414 b = np.array([True, True, False, False], dtype=bool) 

415 acast = np.array(a != 0, dtype=bool) 

416 orArr = acast | b 

417 desOrArr = np.array([True, True, False, True], dtype=bool) 

418 # Note: assertEqual(bool arr, bool arr) fails with: 

419 # ValueError: The truth value of an array with more than one element is 

420 # ambiguous 

421 try: 

422 self.assertTrue(np.all(orArr == desOrArr)) 

423 except Exception as e: 

424 print(f"Failed: {orArr!r} != {desOrArr!r}: {e}") 

425 raise 

426 

427 def testSmallSrc(self): 

428 """Verify that a source image that is too small will not raise an exception 

429 

430 This tests another bug that was fixed in ticket #2441 

431 """ 

432 fromWcs = afwGeom.makeSkyWcs( 

433 crpix=lsst.geom.Point2D(0, 0), 

434 crval=lsst.geom.SpherePoint(359, 0, lsst.geom.degrees), 

435 cdMatrix=afwGeom.makeCdMatrix(scale=1.0e-8*lsst.geom.degrees), 

436 ) 

437 fromExp = afwImage.ExposureF(afwImage.MaskedImageF(1, 1), fromWcs) 

438 

439 toWcs = afwGeom.makeSkyWcs( 

440 crpix=lsst.geom.Point2D(0, 0), 

441 crval=lsst.geom.SpherePoint(358, 0, lsst.geom.degrees), 

442 cdMatrix=afwGeom.makeCdMatrix(scale=1.1e-8*lsst.geom.degrees), 

443 ) 

444 toExp = afwImage.ExposureF(afwImage.MaskedImageF(10, 10), toWcs) 

445 

446 warpControl = afwMath.WarpingControl("lanczos3") 

447 # if a bug described in ticket #2441 is present, this will raise an 

448 # exception: 

449 numGoodPix = afwMath.warpExposure(toExp, fromExp, warpControl) 

450 self.assertEqual(numGoodPix, 0) 

451 imArr, maskArr, varArr = toExp.getMaskedImage().getArrays() 

452 self.assertTrue(np.all(np.isnan(imArr))) 

453 self.assertTrue(np.all(np.isinf(varArr))) 

454 noDataBitMask = afwImage.Mask.getPlaneBitMask("NO_DATA") 

455 self.assertTrue(np.all(maskArr == noDataBitMask)) 

456 

457 def verifyMaskWarp(self, kernelName, maskKernelName, growFullMask, interpLength=10, cacheSize=100000, 

458 rtol=4e-05, atol=1e-2): 

459 """Verify that using a separate mask warping kernel produces the correct results 

460 

461 Inputs: 

462 - kernelName: name of warping kernel in the form used by afwImage.makeKernel 

463 - maskKernelName: name of mask warping kernel in the form used by afwImage.makeKernel 

464 - interpLength: interpLength argument for lsst.afw.math.WarpingControl 

465 - cacheSize: cacheSize argument for lsst.afw.math.WarpingControl; 

466 0 disables the cache 

467 10000 gives some speed improvement but less accurate results (atol must be increased) 

468 100000 gives better accuracy but no speed improvement in this test 

469 - rtol: relative tolerance as used by np.allclose 

470 - atol: absolute tolerance as used by np.allclose 

471 """ 

472 srcWcs = afwGeom.makeSkyWcs( 

473 crpix=lsst.geom.Point2D(10, 11), 

474 crval=lsst.geom.SpherePoint(41.7, 32.9, lsst.geom.degrees), 

475 cdMatrix=afwGeom.makeCdMatrix(scale=0.2*lsst.geom.degrees), 

476 ) 

477 destWcs = afwGeom.makeSkyWcs( 

478 crpix=lsst.geom.Point2D(9, 10), 

479 crval=lsst.geom.SpherePoint(41.65, 32.95, lsst.geom.degrees), 

480 cdMatrix=afwGeom.makeCdMatrix(scale=0.17*lsst.geom.degrees), 

481 ) 

482 

483 srcMaskedImage = afwImage.MaskedImageF(100, 101) 

484 srcExposure = afwImage.ExposureF(srcMaskedImage, srcWcs) 

485 

486 srcArrays = srcMaskedImage.getArrays() 

487 shape = srcArrays[0].shape 

488 srcArrays[0][:] = np.random.normal(10000, 1000, size=shape) 

489 srcArrays[2][:] = np.random.normal(9000, 900, size=shape) 

490 srcArrays[1][:] = np.reshape( 

491 np.arange(0, shape[0] * shape[1], 1, dtype=np.uint16), shape) 

492 

493 warpControl = afwMath.WarpingControl( 

494 kernelName, 

495 maskKernelName, 

496 cacheSize, 

497 interpLength, 

498 growFullMask 

499 ) 

500 destMaskedImage = afwImage.MaskedImageF(110, 121) 

501 destExposure = afwImage.ExposureF(destMaskedImage, destWcs) 

502 afwMath.warpExposure(destExposure, srcExposure, warpControl) 

503 

504 # now compute with two separate mask planes 

505 warpControl.setGrowFullMask(0) 

506 narrowMaskedImage = afwImage.MaskedImageF(110, 121) 

507 narrowExposure = afwImage.ExposureF(narrowMaskedImage, destWcs) 

508 afwMath.warpExposure(narrowExposure, srcExposure, warpControl) 

509 narrowArrays = narrowExposure.getMaskedImage().getArrays() 

510 

511 warpControl.setMaskWarpingKernelName("") 

512 broadMaskedImage = afwImage.MaskedImageF(110, 121) 

513 broadExposure = afwImage.ExposureF(broadMaskedImage, destWcs) 

514 afwMath.warpExposure(broadExposure, srcExposure, warpControl) 

515 broadArrays = broadExposure.getMaskedImage().getArrays() 

516 

517 if (kernelName != maskKernelName) and (growFullMask != 0xFFFF): 

518 # we expect the mask planes to differ 

519 if np.all(narrowArrays[1] == broadArrays[1]): 

520 self.fail("No difference between broad and narrow mask") 

521 

522 predMask = (broadArrays[1] & growFullMask) | ( 

523 narrowArrays[1] & ~growFullMask).astype(np.uint16) 

524 predArraySet = (broadArrays[0], predMask, broadArrays[2]) 

525 predExposure = afwImage.makeMaskedImageFromArrays(*predArraySet) 

526 

527 msg = f"Separate mask warping failed; warpingKernel={kernelName}; maskWarpingKernel={maskKernelName}" 

528 self.assertMaskedImagesAlmostEqual(destExposure.getMaskedImage(), predExposure, 

529 doImage=True, doMask=True, doVariance=True, 

530 rtol=rtol, atol=atol, msg=msg) 

531 

532 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

533 def compareToSwarp(self, kernelName, 

534 useWarpExposure=True, useSubregion=False, useDeepCopy=False, 

535 interpLength=10, cacheSize=100000, 

536 rtol=4e-05, atol=1e-2): 

537 """Compare warpExposure to swarp for given warping kernel. 

538 

539 Note that swarp only warps the image plane, so only test that plane. 

540 

541 Inputs: 

542 - kernelName: name of kernel in the form used by afwImage.makeKernel 

543 - useWarpExposure: if True, call warpExposure to warp an ExposureF, 

544 else call warpImage to warp an ImageF and also call the Transform version 

545 - useSubregion: if True then the original source exposure (from which the usual 

546 test exposure was extracted) is read and the correct subregion extracted 

547 - useDeepCopy: if True then the copy of the subimage is a deep copy, 

548 else it is a shallow copy; ignored if useSubregion is False 

549 - interpLength: interpLength argument for lsst.afw.math.WarpingControl 

550 - cacheSize: cacheSize argument for lsst.afw.math.WarpingControl; 

551 0 disables the cache 

552 10000 gives some speed improvement but less accurate results (atol must be increased) 

553 100000 gives better accuracy but no speed improvement in this test 

554 - rtol: relative tolerance as used by np.allclose 

555 - atol: absolute tolerance as used by np.allclose 

556 """ 

557 warpingControl = afwMath.WarpingControl( 

558 kernelName, 

559 "", # there is no point to a separate mask kernel since we aren't testing the mask plane 

560 cacheSize, 

561 interpLength, 

562 ) 

563 if useSubregion: 

564 originalFullExposure = afwImage.ExposureF(originalExposurePath) 

565 # "medsub" is a subregion of med starting at 0-indexed pixel (40, 150) of size 145 x 200 

566 bbox = lsst.geom.Box2I(lsst.geom.Point2I(40, 150), 

567 lsst.geom.Extent2I(145, 200)) 

568 originalExposure = afwImage.ExposureF( 

569 originalFullExposure, bbox, afwImage.LOCAL, useDeepCopy) 

570 swarpedImageName = f"medsubswarp1{kernelName}.fits" 

571 else: 

572 originalExposure = afwImage.ExposureF(originalExposurePath) 

573 swarpedImageName = f"medswarp1{kernelName}.fits" 

574 

575 swarpedImagePath = os.path.join(dataDir, swarpedImageName) 

576 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

577 swarpedImage = swarpedDecoratedImage.getImage() 

578 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

579 warpedWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

580 

581 if useWarpExposure: 

582 # path for saved afw-warped image 

583 afwWarpedImagePath = f"afwWarpedExposure1{kernelName}.fits" 

584 

585 afwWarpedMaskedImage = afwImage.MaskedImageF( 

586 swarpedImage.getDimensions()) 

587 afwWarpedExposure = afwImage.ExposureF( 

588 afwWarpedMaskedImage, warpedWcs) 

589 afwMath.warpExposure( 

590 afwWarpedExposure, originalExposure, warpingControl) 

591 afwWarpedMask = afwWarpedMaskedImage.getMask() 

592 if SAVE_FITS_FILES: 

593 afwWarpedExposure.writeFits(afwWarpedImagePath) 

594 if display: 

595 afwDisplay.Display(frame=1).mtv(afwWarpedExposure, title="Warped") 

596 

597 swarpedMaskedImage = afwImage.MaskedImageF(swarpedImage) 

598 

599 if display: 

600 afwDisplay.Display(frame=2).mtv(swarpedMaskedImage, title="SWarped") 

601 

602 msg = f"afw and swarp {kernelName}-warped differ (ignoring bad pixels)" 

603 try: 

604 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, swarpedMaskedImage, 

605 doImage=True, doMask=False, doVariance=False, 

606 skipMask=afwWarpedMask, rtol=rtol, atol=atol, msg=msg) 

607 except Exception: 

608 if SAVE_FAILED_FITS_FILES: 

609 afwWarpedExposure.writeFits(afwWarpedImagePath) 

610 print(f"Saved failed afw-warped exposure as: {afwWarpedImagePath}") 

611 raise 

612 else: 

613 # path for saved afw-warped image 

614 afwWarpedImagePath = f"afwWarpedImage1{kernelName}.fits" 

615 afwWarpedImage2Path = f"afwWarpedImage1{kernelName}_xyTransform.fits" 

616 

617 afwWarpedImage = afwImage.ImageF(swarpedImage.getDimensions()) 

618 originalImage = originalExposure.getMaskedImage().getImage() 

619 originalWcs = originalExposure.getWcs() 

620 afwMath.warpImage(afwWarpedImage, warpedWcs, originalImage, 

621 originalWcs, warpingControl) 

622 if display: 

623 afwDisplay.Display(frame=1).mtv(afwWarpedImage, title="Warped") 

624 afwDisplay.Display(frame=2).mtv(swarpedImage, title="SWarped") 

625 diff = swarpedImage.Factory(swarpedImage, True) 

626 diff -= afwWarpedImage 

627 afwDisplay.Display(frame=3).mtv(diff, title="swarp - afw") 

628 if SAVE_FITS_FILES: 

629 afwWarpedImage.writeFits(afwWarpedImagePath) 

630 

631 afwWarpedImageArr = afwWarpedImage.getArray() 

632 noDataMaskArr = np.isnan(afwWarpedImageArr) 

633 msg = f"afw and swarp {kernelName}-warped images do not match (ignoring NaN pixels)" 

634 try: 

635 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

636 skipMask=noDataMaskArr, rtol=rtol, atol=atol, msg=msg) 

637 except Exception: 

638 if SAVE_FAILED_FITS_FILES: 

639 # save the image anyway 

640 afwWarpedImage.writeFits(afwWarpedImagePath) 

641 print(f"Saved failed afw-warped image as: {afwWarpedImagePath}") 

642 raise 

643 

644 afwWarpedImage2 = afwImage.ImageF(swarpedImage.getDimensions()) 

645 srcToDest = afwGeom.makeWcsPairTransform(originalWcs, warpedWcs) 

646 afwMath.warpImage(afwWarpedImage2, originalImage, 

647 srcToDest, warpingControl) 

648 msg = f"afw transform-based and WCS-based {kernelName}-warped images do not match" 

649 try: 

650 self.assertImagesAlmostEqual(afwWarpedImage2, afwWarpedImage, 

651 rtol=rtol, atol=atol, msg=msg) 

652 except Exception: 

653 if SAVE_FAILED_FITS_FILES: 

654 # save the image anyway 

655 afwWarpedImage.writeFits(afwWarpedImage2) 

656 print(f"Saved failed afw-warped image as: {afwWarpedImage2Path}") 

657 raise 

658 

659 

660class MemoryTester(lsst.utils.tests.MemoryTestCase): 

661 pass 

662 

663 

664def setup_module(module): 

665 lsst.utils.tests.init() 

666 

667 

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

669 lsst.utils.tests.init() 

670 unittest.main()