Coverage for tests/test_warpExposure.py: 17%

337 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-05-01 03:31 -0700

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("lsst.afw.image.Mask").setLevel(Log.INFO) 

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

44Log.getLogger("TRACE3.lsst.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 LookupError: 

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(exposureTime=10.01, 

74 darkTime=11.02, 

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

76 ut1=12345.1, 

77 era=45.1*lsst.geom.degrees, 

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

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

80 boresightAirmass=1.73, 

81 boresightRotAngle=73.2*lsst.geom.degrees, 

82 rotType=afwImage.RotType.SKY, 

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

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

85 ) 

86 

87 

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

89 """Test case for warpExposure 

90 """ 

91 

92 def setUp(self): 

93 np.random.seed(0) 

94 

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

96 def testNullWarpExposure(self, interpLength=10): 

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

98 

99 Note: 

100 - NO_DATA and off-CCD pixels must be ignored 

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

102 from the output image when comparing masks. 

103 """ 

104 originalExposure = afwImage.ExposureF(originalExposurePath) 

105 originalExposure.getInfo().setId(10313423) 

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

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

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

109 originalExposure.setFilter(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.getFilter().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 

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

132 # because of minor noise introduced by bad pixels 

133 noDataMaskArr = afwWarpedMaskedImage.mask.array & noDataBitMask 

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

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

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

137 

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

139 # tolerance 

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

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

142 skipMask=afwWarpedMask, msg=msg) 

143 

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

145 def testNullWarpImage(self, interpLength=10): 

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

147 """ 

148 originalExposure = afwImage.ExposureF(originalExposurePath) 

149 afwWarpedExposure = afwImage.ExposureF(originalExposurePath) 

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

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

152 originalWcs = originalExposure.getWcs() 

153 afwWarpedWcs = afwWarpedExposure.getWcs() 

154 warpingControl = afwMath.WarpingControl( 

155 "lanczos4", "", 0, interpLength) 

156 afwMath.warpImage(afwWarpedImage, afwWarpedWcs, 

157 originalImage, originalWcs, warpingControl) 

158 if SAVE_FITS_FILES: 

159 afwWarpedImage.writeFits("afwWarpedImageNull.fits") 

160 afwWarpedImageArr = afwWarpedImage.getArray() 

161 noDataMaskArr = np.isnan(afwWarpedImageArr) 

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

163 msg = "afw null-warped Image" 

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

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

166 

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

168 def testNullWcs(self, interpLength=10): 

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

170 """ 

171 exposureWithWcs = afwImage.ExposureF(originalExposurePath) 

172 mi = exposureWithWcs.getMaskedImage() 

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

174 warpingControl = afwMath.WarpingControl( 

175 "bilinear", "", 0, interpLength) 

176 

177 with self.assertRaises(pexExcept.InvalidParameterError): 

178 afwMath.warpExposure(exposureWithWcs, exposureWithoutWcs, warpingControl) 

179 

180 with self.assertRaises(pexExcept.InvalidParameterError): 

181 afwMath.warpExposure(exposureWithoutWcs, exposureWithWcs, warpingControl) 

182 

183 def testWarpIntoSelf(self, interpLength=10): 

184 """Cannot warp in-place 

185 """ 

186 wcs = afwGeom.makeSkyWcs( 

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

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

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

190 ) 

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

192 maskedImage = exposure.getMaskedImage() 

193 warpingControl = afwMath.WarpingControl( 

194 "bilinear", "", 0, interpLength) 

195 

196 with self.assertRaises(pexExcept.InvalidParameterError): 

197 afwMath.warpExposure(exposure, exposure, warpingControl) 

198 

199 with self.assertRaises(pexExcept.InvalidParameterError): 

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

201 

202 with self.assertRaises(pexExcept.InvalidParameterError): 

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

204 

205 def testWarpingControl(self): 

206 """Test the basic mechanics of WarpingControl 

207 """ 

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

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

210 self.assertFalse(wc.hasMaskWarpingKernel()) 

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

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

213 wc.setInterpLength(newInterpLength) 

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

215 

216 for cacheSize in (0, 100): 

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

218 self.assertTrue(wc.hasMaskWarpingKernel()) 

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

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

221 self.assertEqual( 

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

223 for newCacheSize in (1, 50): 

224 wc.setCacheSize(newCacheSize) 

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

226 self.assertEqual( 

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

228 self.assertEqual( 

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

230 

231 wc = afwMath.WarpingControl("lanczos4", "nearest", 64, 7, 42) 

232 self.assertTrue(wc.isPersistable()) 

233 with lsst.utils.tests.getTempFilePath(".fits", expectOutput=True) as tempFile: 

234 wc.writeFits(tempFile) 

235 wc2 = afwMath.WarpingControl.readFits(tempFile) 

236 self.assertEqual(wc.getCacheSize(), wc2.getCacheSize()) 

237 self.assertEqual(wc.getInterpLength(), wc2.getInterpLength()) 

238 self.assertEqual(wc.getWarpingKernel().getBBox(), wc2.getWarpingKernel().getBBox()) 

239 self.assertEqual(wc.getWarpingKernel().getKernelParameters(), 

240 wc2.getWarpingKernel().getKernelParameters()) 

241 self.assertEqual(wc.hasMaskWarpingKernel(), wc2.hasMaskWarpingKernel()) 

242 self.assertEqual(wc.getMaskWarpingKernel().getBBox(), wc2.getMaskWarpingKernel().getBBox()) 

243 self.assertEqual(wc.getMaskWarpingKernel().getKernelParameters(), 

244 wc2.getMaskWarpingKernel().getKernelParameters()) 

245 self.assertEqual(wc.getGrowFullMask(), wc2.getGrowFullMask()) 

246 

247 def testWarpingControlError(self): 

248 """Test error handling of WarpingControl 

249 """ 

250 # error: mask kernel smaller than main kernel 

251 for kernelName, maskKernelName in ( 

252 ("bilinear", "lanczos3"), 

253 ("bilinear", "lanczos4"), 

254 ("lanczos3", "lanczos4"), 

255 ): 

256 with self.assertRaises(pexExcept.InvalidParameterError): 

257 afwMath.WarpingControl(kernelName, maskKernelName) 

258 

259 # error: new mask kernel larger than main kernel 

260 warpingControl = afwMath.WarpingControl("bilinear") 

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

262 with self.assertRaises(pexExcept.InvalidParameterError): 

263 warpingControl.setMaskWarpingKernelName(maskKernelName) 

264 

265 # error: new kernel smaller than mask kernel 

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

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

268 with self.assertRaises(pexExcept.InvalidParameterError): 

269 warpingControl.setWarpingKernelName(kernelName) 

270 

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

272 for kernelName, maskKernelName in ( 

273 ("bilinear", "bilinear"), 

274 ("lanczos3", "lanczos3"), 

275 ("lanczos3", "bilinear"), 

276 ("lanczos4", "lanczos3"), 

277 ): 

278 # this should not raise any exception 

279 afwMath.WarpingControl(kernelName, maskKernelName) 

280 

281 # invalid kernel names 

282 for kernelName, maskKernelName in ( 

283 ("badname", ""), 

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

285 ("lanczos3", "badname"), 

286 ("lanczos3", "lanczos"), 

287 ): 

288 with self.assertRaises(pexExcept.InvalidParameterError): 

289 afwMath.WarpingControl(kernelName, maskKernelName) 

290 

291 def testWarpMask(self): 

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

293 """ 

294 for kernelName, maskKernelName in ( 

295 ("bilinear", "bilinear"), 

296 ("lanczos3", "lanczos3"), 

297 ("lanczos3", "bilinear"), 

298 ("lanczos4", "lanczos3"), 

299 ): 

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

301 self.verifyMaskWarp( 

302 kernelName=kernelName, 

303 maskKernelName=maskKernelName, 

304 growFullMask=growFullMask, 

305 ) 

306 

307 def testMatchSwarpBilinearImage(self): 

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

309 """ 

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

311 

312 def testMatchSwarpBilinearExposure(self): 

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

314 """ 

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

316 useSubregion=False, useDeepCopy=True) 

317 

318 def testMatchSwarpLanczos2Image(self): 

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

320 """ 

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

322 

323 def testMatchSwarpLanczos2Exposure(self): 

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

325 """ 

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

327 

328 def testMatchSwarpLanczos2SubExposure(self): 

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

330 """ 

331 for useDeepCopy in (False, True): 

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

333 useSubregion=True, useDeepCopy=useDeepCopy) 

334 

335 def testMatchSwarpLanczos3Image(self): 

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

337 """ 

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

339 

340 def testMatchSwarpLanczos3(self): 

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

342 """ 

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

344 

345 def testMatchSwarpLanczos4Image(self): 

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

347 """ 

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

349 

350 def testMatchSwarpLanczos4(self): 

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

352 """ 

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

354 

355 def testMatchSwarpNearestExposure(self): 

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

357 """ 

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

359 

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

361 def testTransformBasedWarp(self): 

362 """Test warping using TransformPoint2ToPoint2 

363 """ 

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

365 kernelName = "lanczos3" 

366 rtol = 4e-5 

367 atol = 1e-2 

368 warpingControl = afwMath.WarpingControl( 

369 warpingKernelName=kernelName, 

370 interpLength=interpLength, 

371 ) 

372 

373 originalExposure = afwImage.ExposureF(originalExposurePath) 

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

375 originalSkyWcs = afwGeom.makeSkyWcs(originalMetadata) 

376 

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

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

379 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

380 swarpedImage = swarpedDecoratedImage.getImage() 

381 

382 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

383 warpedSkyWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

384 

385 # original image is source, warped image is destination 

386 srcToDest = afwGeom.makeWcsPairTransform(originalSkyWcs, warpedSkyWcs) 

387 

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

389 originalMaskedImage = originalExposure.getMaskedImage() 

390 

391 numGoodPix = afwMath.warpImage(afwWarpedMaskedImage, originalMaskedImage, 

392 srcToDest, warpingControl) 

393 self.assertGreater(numGoodPix, 50) 

394 

395 afwWarpedImage = afwWarpedMaskedImage.getImage() 

396 afwWarpedImageArr = afwWarpedImage.getArray() 

397 noDataMaskArr = np.isnan(afwWarpedImageArr) 

398 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

399 skipMask=noDataMaskArr, rtol=rtol, atol=atol) 

400 

401 def testTicket2441(self): 

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

403 fromWcs = afwGeom.makeSkyWcs( 

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

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

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

407 ) 

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

409 

410 toWcs = afwGeom.makeSkyWcs( 

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

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

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

414 projection="CEA", 

415 ) 

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

417 

418 warpControl = afwMath.WarpingControl("lanczos3") 

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

420 # exception: 

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

422 self.assertEqual(numGoodPix, 0) 

423 

424 def testTicketDM4063(self): 

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

426 """ 

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

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

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

430 orArr = acast | b 

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

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

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

434 # ambiguous 

435 try: 

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

437 except Exception as e: 

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

439 raise 

440 

441 def testSmallSrc(self): 

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

443 

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

445 """ 

446 fromWcs = afwGeom.makeSkyWcs( 

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

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

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

450 ) 

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

452 

453 toWcs = afwGeom.makeSkyWcs( 

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

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

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

457 ) 

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

459 

460 warpControl = afwMath.WarpingControl("lanczos3") 

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

462 # exception: 

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

464 self.assertEqual(numGoodPix, 0) 

465 self.assertTrue(np.all(np.isnan(toExp.image.array))) 

466 self.assertTrue(np.all(np.isinf(toExp.variance.array))) 

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

468 self.assertTrue(np.all(toExp.mask.array == noDataBitMask)) 

469 

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

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

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

473 

474 Inputs: 

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

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

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

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

479 0 disables the cache 

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

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

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

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

484 """ 

485 srcWcs = afwGeom.makeSkyWcs( 

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

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

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

489 ) 

490 destWcs = afwGeom.makeSkyWcs( 

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

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

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

494 ) 

495 

496 srcMaskedImage = afwImage.MaskedImageF(100, 101) 

497 srcExposure = afwImage.ExposureF(srcMaskedImage, srcWcs) 

498 

499 shape = srcMaskedImage.image.array.shape 

500 srcMaskedImage.image.array[:] = np.random.normal(10000, 1000, size=shape) 

501 srcMaskedImage.variance.array[:] = np.random.normal(9000, 900, size=shape) 

502 srcMaskedImage.mask.array[:] = np.reshape( 

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

504 

505 warpControl = afwMath.WarpingControl( 

506 kernelName, 

507 maskKernelName, 

508 cacheSize, 

509 interpLength, 

510 growFullMask 

511 ) 

512 destMaskedImage = afwImage.MaskedImageF(110, 121) 

513 destExposure = afwImage.ExposureF(destMaskedImage, destWcs) 

514 afwMath.warpExposure(destExposure, srcExposure, warpControl) 

515 

516 # now compute with two separate mask planes 

517 warpControl.setGrowFullMask(0) 

518 narrowMaskedImage = afwImage.MaskedImageF(110, 121) 

519 narrowExposure = afwImage.ExposureF(narrowMaskedImage, destWcs) 

520 afwMath.warpExposure(narrowExposure, srcExposure, warpControl) 

521 

522 warpControl.setMaskWarpingKernelName("") 

523 broadMaskedImage = afwImage.MaskedImageF(110, 121) 

524 broadExposure = afwImage.ExposureF(broadMaskedImage, destWcs) 

525 afwMath.warpExposure(broadExposure, srcExposure, warpControl) 

526 

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

528 # we expect the mask planes to differ 

529 if np.all(narrowExposure.mask.array == broadExposure.mask.array): 

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

531 

532 predMask = (broadExposure.mask.array & growFullMask) | ( 

533 narrowExposure.mask.array & ~growFullMask).astype(np.uint16) 

534 predArraySet = (broadExposure.image.array, predMask, broadExposure.variance.array) 

535 predExposure = afwImage.makeMaskedImageFromArrays(*predArraySet) 

536 

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

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

539 doImage=True, doMask=True, doVariance=True, 

540 rtol=rtol, atol=atol, msg=msg) 

541 

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

543 def compareToSwarp(self, kernelName, 

544 useWarpExposure=True, useSubregion=False, useDeepCopy=False, 

545 interpLength=10, cacheSize=100000, 

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

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

548 

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

550 

551 Inputs: 

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

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

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

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

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

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

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

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

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

561 0 disables the cache 

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

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

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

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

566 """ 

567 warpingControl = afwMath.WarpingControl( 

568 kernelName, 

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

570 cacheSize, 

571 interpLength, 

572 ) 

573 if useSubregion: 

574 originalFullExposure = afwImage.ExposureF(originalExposurePath) 

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

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

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

578 originalExposure = afwImage.ExposureF( 

579 originalFullExposure, bbox, afwImage.LOCAL, useDeepCopy) 

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

581 else: 

582 originalExposure = afwImage.ExposureF(originalExposurePath) 

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

584 

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

586 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

587 swarpedImage = swarpedDecoratedImage.getImage() 

588 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

589 warpedWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

590 

591 if useWarpExposure: 

592 # path for saved afw-warped image 

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

594 

595 afwWarpedMaskedImage = afwImage.MaskedImageF( 

596 swarpedImage.getDimensions()) 

597 afwWarpedExposure = afwImage.ExposureF( 

598 afwWarpedMaskedImage, warpedWcs) 

599 afwMath.warpExposure( 

600 afwWarpedExposure, originalExposure, warpingControl) 

601 afwWarpedMask = afwWarpedMaskedImage.getMask() 

602 if SAVE_FITS_FILES: 

603 afwWarpedExposure.writeFits(afwWarpedImagePath) 

604 if display: 

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

606 

607 swarpedMaskedImage = afwImage.MaskedImageF(swarpedImage) 

608 

609 if display: 

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

611 

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

613 try: 

614 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, swarpedMaskedImage, 

615 doImage=True, doMask=False, doVariance=False, 

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

617 except Exception: 

618 if SAVE_FAILED_FITS_FILES: 

619 afwWarpedExposure.writeFits(afwWarpedImagePath) 

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

621 raise 

622 else: 

623 # path for saved afw-warped image 

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

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

626 

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

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

629 originalWcs = originalExposure.getWcs() 

630 afwMath.warpImage(afwWarpedImage, warpedWcs, originalImage, 

631 originalWcs, warpingControl) 

632 if display: 

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

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

635 diff = swarpedImage.Factory(swarpedImage, True) 

636 diff -= afwWarpedImage 

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

638 if SAVE_FITS_FILES: 

639 afwWarpedImage.writeFits(afwWarpedImagePath) 

640 

641 afwWarpedImageArr = afwWarpedImage.getArray() 

642 noDataMaskArr = np.isnan(afwWarpedImageArr) 

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

644 try: 

645 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

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

647 except Exception: 

648 if SAVE_FAILED_FITS_FILES: 

649 # save the image anyway 

650 afwWarpedImage.writeFits(afwWarpedImagePath) 

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

652 raise 

653 

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

655 srcToDest = afwGeom.makeWcsPairTransform(originalWcs, warpedWcs) 

656 afwMath.warpImage(afwWarpedImage2, originalImage, 

657 srcToDest, warpingControl) 

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

659 try: 

660 self.assertImagesAlmostEqual(afwWarpedImage2, afwWarpedImage, 

661 rtol=rtol, atol=atol, msg=msg) 

662 except Exception: 

663 if SAVE_FAILED_FITS_FILES: 

664 # save the image anyway 

665 afwWarpedImage.writeFits(afwWarpedImage2) 

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

667 raise 

668 

669 

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

671 pass 

672 

673 

674def setup_module(module): 

675 lsst.utils.tests.init() 

676 

677 

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

679 lsst.utils.tests.init() 

680 unittest.main()