Coverage for tests/test_warpExposure.py: 16%

343 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-06-02 03:42 -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(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().setId(10313423) 

107 originalExposure.getInfo().setVisitInfo(makeVisitInfo()) 

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

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

110 originalExposure.setFilterLabel(originalFilterLabel) 

111 originalExposure.setPhotoCalib(originalPhotoCalib) 

112 afwWarpedExposure = afwImage.ExposureF( 

113 originalExposure.getBBox(), 

114 originalExposure.getWcs()) 

115 warpingControl = afwMath.WarpingControl( 

116 "lanczos4", "", 0, interpLength) 

117 afwMath.warpExposure( 

118 afwWarpedExposure, originalExposure, warpingControl) 

119 if SAVE_FITS_FILES: 

120 afwWarpedExposure.writeFits("afwWarpedExposureNull.fits") 

121 

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

123 originalFilterLabel.bandLabel) 

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

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

126 originalExposure.getInfo().getVisitInfo()) 

127 

128 afwWarpedMaskedImage = afwWarpedExposure.getMaskedImage() 

129 afwWarpedMask = afwWarpedMaskedImage.getMask() 

130 noDataBitMask = afwWarpedMask.getPlaneBitMask("NO_DATA") 

131 afwWarpedMaskedImageArrSet = afwWarpedMaskedImage.getArrays() 

132 afwWarpedMaskArr = afwWarpedMaskedImageArrSet[1] 

133 

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

135 # because of minor noise introduced by bad pixels 

136 noDataMaskArr = afwWarpedMaskArr & noDataBitMask 

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

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

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

140 

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

142 # tolerance 

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

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

145 skipMask=afwWarpedMask, msg=msg) 

146 

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

148 def testNullWarpImage(self, interpLength=10): 

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

150 """ 

151 originalExposure = afwImage.ExposureF(originalExposurePath) 

152 afwWarpedExposure = afwImage.ExposureF(originalExposurePath) 

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

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

155 originalWcs = originalExposure.getWcs() 

156 afwWarpedWcs = afwWarpedExposure.getWcs() 

157 warpingControl = afwMath.WarpingControl( 

158 "lanczos4", "", 0, interpLength) 

159 afwMath.warpImage(afwWarpedImage, afwWarpedWcs, 

160 originalImage, originalWcs, warpingControl) 

161 if SAVE_FITS_FILES: 

162 afwWarpedImage.writeFits("afwWarpedImageNull.fits") 

163 afwWarpedImageArr = afwWarpedImage.getArray() 

164 noDataMaskArr = np.isnan(afwWarpedImageArr) 

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

166 msg = "afw null-warped Image" 

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

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

169 

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

171 def testNullWcs(self, interpLength=10): 

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

173 """ 

174 exposureWithWcs = afwImage.ExposureF(originalExposurePath) 

175 mi = exposureWithWcs.getMaskedImage() 

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

177 warpingControl = afwMath.WarpingControl( 

178 "bilinear", "", 0, interpLength) 

179 

180 with self.assertRaises(pexExcept.InvalidParameterError): 

181 afwMath.warpExposure(exposureWithWcs, exposureWithoutWcs, warpingControl) 

182 

183 with self.assertRaises(pexExcept.InvalidParameterError): 

184 afwMath.warpExposure(exposureWithoutWcs, exposureWithWcs, warpingControl) 

185 

186 def testWarpIntoSelf(self, interpLength=10): 

187 """Cannot warp in-place 

188 """ 

189 wcs = afwGeom.makeSkyWcs( 

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

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

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

193 ) 

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

195 maskedImage = exposure.getMaskedImage() 

196 warpingControl = afwMath.WarpingControl( 

197 "bilinear", "", 0, interpLength) 

198 

199 with self.assertRaises(pexExcept.InvalidParameterError): 

200 afwMath.warpExposure(exposure, exposure, warpingControl) 

201 

202 with self.assertRaises(pexExcept.InvalidParameterError): 

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

204 

205 with self.assertRaises(pexExcept.InvalidParameterError): 

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

207 

208 def testWarpingControl(self): 

209 """Test the basic mechanics of WarpingControl 

210 """ 

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

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

213 self.assertFalse(wc.hasMaskWarpingKernel()) 

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

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

216 wc.setInterpLength(newInterpLength) 

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

218 

219 for cacheSize in (0, 100): 

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

221 self.assertTrue(wc.hasMaskWarpingKernel()) 

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

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

224 self.assertEqual( 

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

226 for newCacheSize in (1, 50): 

227 wc.setCacheSize(newCacheSize) 

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

229 self.assertEqual( 

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

231 self.assertEqual( 

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

233 

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

235 self.assertTrue(wc.isPersistable()) 

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

237 wc.writeFits(tempFile) 

238 wc2 = afwMath.WarpingControl.readFits(tempFile) 

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

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

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

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

243 wc2.getWarpingKernel().getKernelParameters()) 

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

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

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

247 wc2.getMaskWarpingKernel().getKernelParameters()) 

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

249 

250 def testWarpingControlError(self): 

251 """Test error handling of WarpingControl 

252 """ 

253 # error: mask kernel smaller than main kernel 

254 for kernelName, maskKernelName in ( 

255 ("bilinear", "lanczos3"), 

256 ("bilinear", "lanczos4"), 

257 ("lanczos3", "lanczos4"), 

258 ): 

259 with self.assertRaises(pexExcept.InvalidParameterError): 

260 afwMath.WarpingControl(kernelName, maskKernelName) 

261 

262 # error: new mask kernel larger than main kernel 

263 warpingControl = afwMath.WarpingControl("bilinear") 

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

265 with self.assertRaises(pexExcept.InvalidParameterError): 

266 warpingControl.setMaskWarpingKernelName(maskKernelName) 

267 

268 # error: new kernel smaller than mask kernel 

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

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

271 with self.assertRaises(pexExcept.InvalidParameterError): 

272 warpingControl.setWarpingKernelName(kernelName) 

273 

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

275 for kernelName, maskKernelName in ( 

276 ("bilinear", "bilinear"), 

277 ("lanczos3", "lanczos3"), 

278 ("lanczos3", "bilinear"), 

279 ("lanczos4", "lanczos3"), 

280 ): 

281 # this should not raise any exception 

282 afwMath.WarpingControl(kernelName, maskKernelName) 

283 

284 # invalid kernel names 

285 for kernelName, maskKernelName in ( 

286 ("badname", ""), 

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

288 ("lanczos3", "badname"), 

289 ("lanczos3", "lanczos"), 

290 ): 

291 with self.assertRaises(pexExcept.InvalidParameterError): 

292 afwMath.WarpingControl(kernelName, maskKernelName) 

293 

294 def testWarpMask(self): 

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

296 """ 

297 for kernelName, maskKernelName in ( 

298 ("bilinear", "bilinear"), 

299 ("lanczos3", "lanczos3"), 

300 ("lanczos3", "bilinear"), 

301 ("lanczos4", "lanczos3"), 

302 ): 

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

304 self.verifyMaskWarp( 

305 kernelName=kernelName, 

306 maskKernelName=maskKernelName, 

307 growFullMask=growFullMask, 

308 ) 

309 

310 def testMatchSwarpBilinearImage(self): 

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

312 """ 

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

314 

315 def testMatchSwarpBilinearExposure(self): 

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

317 """ 

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

319 useSubregion=False, useDeepCopy=True) 

320 

321 def testMatchSwarpLanczos2Image(self): 

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

323 """ 

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

325 

326 def testMatchSwarpLanczos2Exposure(self): 

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

328 """ 

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

330 

331 def testMatchSwarpLanczos2SubExposure(self): 

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

333 """ 

334 for useDeepCopy in (False, True): 

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

336 useSubregion=True, useDeepCopy=useDeepCopy) 

337 

338 def testMatchSwarpLanczos3Image(self): 

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

340 """ 

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

342 

343 def testMatchSwarpLanczos3(self): 

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

345 """ 

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

347 

348 def testMatchSwarpLanczos4Image(self): 

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

350 """ 

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

352 

353 def testMatchSwarpLanczos4(self): 

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

355 """ 

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

357 

358 def testMatchSwarpNearestExposure(self): 

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

360 """ 

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

362 

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

364 def testTransformBasedWarp(self): 

365 """Test warping using TransformPoint2ToPoint2 

366 """ 

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

368 kernelName = "lanczos3" 

369 rtol = 4e-5 

370 atol = 1e-2 

371 warpingControl = afwMath.WarpingControl( 

372 warpingKernelName=kernelName, 

373 interpLength=interpLength, 

374 ) 

375 

376 originalExposure = afwImage.ExposureF(originalExposurePath) 

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

378 originalSkyWcs = afwGeom.makeSkyWcs(originalMetadata) 

379 

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

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

382 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

383 swarpedImage = swarpedDecoratedImage.getImage() 

384 

385 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

386 warpedSkyWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

387 

388 # original image is source, warped image is destination 

389 srcToDest = afwGeom.makeWcsPairTransform(originalSkyWcs, warpedSkyWcs) 

390 

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

392 originalMaskedImage = originalExposure.getMaskedImage() 

393 

394 numGoodPix = afwMath.warpImage(afwWarpedMaskedImage, originalMaskedImage, 

395 srcToDest, warpingControl) 

396 self.assertGreater(numGoodPix, 50) 

397 

398 afwWarpedImage = afwWarpedMaskedImage.getImage() 

399 afwWarpedImageArr = afwWarpedImage.getArray() 

400 noDataMaskArr = np.isnan(afwWarpedImageArr) 

401 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

402 skipMask=noDataMaskArr, rtol=rtol, atol=atol) 

403 

404 def testTicket2441(self): 

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

406 fromWcs = afwGeom.makeSkyWcs( 

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

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

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

410 ) 

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

412 

413 toWcs = afwGeom.makeSkyWcs( 

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

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

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

417 projection="CEA", 

418 ) 

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

420 

421 warpControl = afwMath.WarpingControl("lanczos3") 

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

423 # exception: 

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

425 self.assertEqual(numGoodPix, 0) 

426 

427 def testTicketDM4063(self): 

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

429 """ 

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

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

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

433 orArr = acast | b 

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

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

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

437 # ambiguous 

438 try: 

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

440 except Exception as e: 

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

442 raise 

443 

444 def testSmallSrc(self): 

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

446 

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

448 """ 

449 fromWcs = afwGeom.makeSkyWcs( 

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

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

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

453 ) 

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

455 

456 toWcs = afwGeom.makeSkyWcs( 

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

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

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

460 ) 

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

462 

463 warpControl = afwMath.WarpingControl("lanczos3") 

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

465 # exception: 

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

467 self.assertEqual(numGoodPix, 0) 

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

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

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

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

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

473 

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

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

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

477 

478 Inputs: 

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

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

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

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

483 0 disables the cache 

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

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

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

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

488 """ 

489 srcWcs = afwGeom.makeSkyWcs( 

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

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

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

493 ) 

494 destWcs = afwGeom.makeSkyWcs( 

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

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

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

498 ) 

499 

500 srcMaskedImage = afwImage.MaskedImageF(100, 101) 

501 srcExposure = afwImage.ExposureF(srcMaskedImage, srcWcs) 

502 

503 srcArrays = srcMaskedImage.getArrays() 

504 shape = srcArrays[0].shape 

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

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

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

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

509 

510 warpControl = afwMath.WarpingControl( 

511 kernelName, 

512 maskKernelName, 

513 cacheSize, 

514 interpLength, 

515 growFullMask 

516 ) 

517 destMaskedImage = afwImage.MaskedImageF(110, 121) 

518 destExposure = afwImage.ExposureF(destMaskedImage, destWcs) 

519 afwMath.warpExposure(destExposure, srcExposure, warpControl) 

520 

521 # now compute with two separate mask planes 

522 warpControl.setGrowFullMask(0) 

523 narrowMaskedImage = afwImage.MaskedImageF(110, 121) 

524 narrowExposure = afwImage.ExposureF(narrowMaskedImage, destWcs) 

525 afwMath.warpExposure(narrowExposure, srcExposure, warpControl) 

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

527 

528 warpControl.setMaskWarpingKernelName("") 

529 broadMaskedImage = afwImage.MaskedImageF(110, 121) 

530 broadExposure = afwImage.ExposureF(broadMaskedImage, destWcs) 

531 afwMath.warpExposure(broadExposure, srcExposure, warpControl) 

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

533 

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

535 # we expect the mask planes to differ 

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

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

538 

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

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

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

542 predExposure = afwImage.makeMaskedImageFromArrays(*predArraySet) 

543 

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

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

546 doImage=True, doMask=True, doVariance=True, 

547 rtol=rtol, atol=atol, msg=msg) 

548 

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

550 def compareToSwarp(self, kernelName, 

551 useWarpExposure=True, useSubregion=False, useDeepCopy=False, 

552 interpLength=10, cacheSize=100000, 

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

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

555 

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

557 

558 Inputs: 

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

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

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

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

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

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

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

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

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

568 0 disables the cache 

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

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

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

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

573 """ 

574 warpingControl = afwMath.WarpingControl( 

575 kernelName, 

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

577 cacheSize, 

578 interpLength, 

579 ) 

580 if useSubregion: 

581 originalFullExposure = afwImage.ExposureF(originalExposurePath) 

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

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

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

585 originalExposure = afwImage.ExposureF( 

586 originalFullExposure, bbox, afwImage.LOCAL, useDeepCopy) 

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

588 else: 

589 originalExposure = afwImage.ExposureF(originalExposurePath) 

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

591 

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

593 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

594 swarpedImage = swarpedDecoratedImage.getImage() 

595 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

596 warpedWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

597 

598 if useWarpExposure: 

599 # path for saved afw-warped image 

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

601 

602 afwWarpedMaskedImage = afwImage.MaskedImageF( 

603 swarpedImage.getDimensions()) 

604 afwWarpedExposure = afwImage.ExposureF( 

605 afwWarpedMaskedImage, warpedWcs) 

606 afwMath.warpExposure( 

607 afwWarpedExposure, originalExposure, warpingControl) 

608 afwWarpedMask = afwWarpedMaskedImage.getMask() 

609 if SAVE_FITS_FILES: 

610 afwWarpedExposure.writeFits(afwWarpedImagePath) 

611 if display: 

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

613 

614 swarpedMaskedImage = afwImage.MaskedImageF(swarpedImage) 

615 

616 if display: 

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

618 

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

620 try: 

621 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, swarpedMaskedImage, 

622 doImage=True, doMask=False, doVariance=False, 

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

624 except Exception: 

625 if SAVE_FAILED_FITS_FILES: 

626 afwWarpedExposure.writeFits(afwWarpedImagePath) 

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

628 raise 

629 else: 

630 # path for saved afw-warped image 

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

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

633 

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

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

636 originalWcs = originalExposure.getWcs() 

637 afwMath.warpImage(afwWarpedImage, warpedWcs, originalImage, 

638 originalWcs, warpingControl) 

639 if display: 

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

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

642 diff = swarpedImage.Factory(swarpedImage, True) 

643 diff -= afwWarpedImage 

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

645 if SAVE_FITS_FILES: 

646 afwWarpedImage.writeFits(afwWarpedImagePath) 

647 

648 afwWarpedImageArr = afwWarpedImage.getArray() 

649 noDataMaskArr = np.isnan(afwWarpedImageArr) 

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

651 try: 

652 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

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

654 except Exception: 

655 if SAVE_FAILED_FITS_FILES: 

656 # save the image anyway 

657 afwWarpedImage.writeFits(afwWarpedImagePath) 

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

659 raise 

660 

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

662 srcToDest = afwGeom.makeWcsPairTransform(originalWcs, warpedWcs) 

663 afwMath.warpImage(afwWarpedImage2, originalImage, 

664 srcToDest, warpingControl) 

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

666 try: 

667 self.assertImagesAlmostEqual(afwWarpedImage2, afwWarpedImage, 

668 rtol=rtol, atol=atol, msg=msg) 

669 except Exception: 

670 if SAVE_FAILED_FITS_FILES: 

671 # save the image anyway 

672 afwWarpedImage.writeFits(afwWarpedImage2) 

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

674 raise 

675 

676 

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

678 pass 

679 

680 

681def setup_module(module): 

682 lsst.utils.tests.init() 

683 

684 

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

686 lsst.utils.tests.init() 

687 unittest.main()