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 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().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 wc = afwMath.WarpingControl("lanczos4", "nearest", 64, 7, 42) 

234 self.assertTrue(wc.isPersistable()) 

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

236 wc.writeFits(tempFile) 

237 wc2 = afwMath.WarpingControl.readFits(tempFile) 

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

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

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

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

242 wc2.getWarpingKernel().getKernelParameters()) 

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

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

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

246 wc2.getMaskWarpingKernel().getKernelParameters()) 

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

248 

249 def testWarpingControlError(self): 

250 """Test error handling of WarpingControl 

251 """ 

252 # error: mask kernel smaller than main kernel 

253 for kernelName, maskKernelName in ( 

254 ("bilinear", "lanczos3"), 

255 ("bilinear", "lanczos4"), 

256 ("lanczos3", "lanczos4"), 

257 ): 

258 with self.assertRaises(pexExcept.InvalidParameterError): 

259 afwMath.WarpingControl(kernelName, maskKernelName) 

260 

261 # error: new mask kernel larger than main kernel 

262 warpingControl = afwMath.WarpingControl("bilinear") 

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

264 with self.assertRaises(pexExcept.InvalidParameterError): 

265 warpingControl.setMaskWarpingKernelName(maskKernelName) 

266 

267 # error: new kernel smaller than mask kernel 

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

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

270 with self.assertRaises(pexExcept.InvalidParameterError): 

271 warpingControl.setWarpingKernelName(kernelName) 

272 

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

274 for kernelName, maskKernelName in ( 

275 ("bilinear", "bilinear"), 

276 ("lanczos3", "lanczos3"), 

277 ("lanczos3", "bilinear"), 

278 ("lanczos4", "lanczos3"), 

279 ): 

280 # this should not raise any exception 

281 afwMath.WarpingControl(kernelName, maskKernelName) 

282 

283 # invalid kernel names 

284 for kernelName, maskKernelName in ( 

285 ("badname", ""), 

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

287 ("lanczos3", "badname"), 

288 ("lanczos3", "lanczos"), 

289 ): 

290 with self.assertRaises(pexExcept.InvalidParameterError): 

291 afwMath.WarpingControl(kernelName, maskKernelName) 

292 

293 def testWarpMask(self): 

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

295 """ 

296 for kernelName, maskKernelName in ( 

297 ("bilinear", "bilinear"), 

298 ("lanczos3", "lanczos3"), 

299 ("lanczos3", "bilinear"), 

300 ("lanczos4", "lanczos3"), 

301 ): 

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

303 self.verifyMaskWarp( 

304 kernelName=kernelName, 

305 maskKernelName=maskKernelName, 

306 growFullMask=growFullMask, 

307 ) 

308 

309 def testMatchSwarpBilinearImage(self): 

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

311 """ 

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

313 

314 def testMatchSwarpBilinearExposure(self): 

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

316 """ 

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

318 useSubregion=False, useDeepCopy=True) 

319 

320 def testMatchSwarpLanczos2Image(self): 

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

322 """ 

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

324 

325 def testMatchSwarpLanczos2Exposure(self): 

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

327 """ 

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

329 

330 def testMatchSwarpLanczos2SubExposure(self): 

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

332 """ 

333 for useDeepCopy in (False, True): 

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

335 useSubregion=True, useDeepCopy=useDeepCopy) 

336 

337 def testMatchSwarpLanczos3Image(self): 

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

339 """ 

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

341 

342 def testMatchSwarpLanczos3(self): 

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

344 """ 

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

346 

347 def testMatchSwarpLanczos4Image(self): 

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

349 """ 

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

351 

352 def testMatchSwarpLanczos4(self): 

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

354 """ 

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

356 

357 def testMatchSwarpNearestExposure(self): 

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

359 """ 

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

361 

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

363 def testTransformBasedWarp(self): 

364 """Test warping using TransformPoint2ToPoint2 

365 """ 

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

367 kernelName = "lanczos3" 

368 rtol = 4e-5 

369 atol = 1e-2 

370 warpingControl = afwMath.WarpingControl( 

371 warpingKernelName=kernelName, 

372 interpLength=interpLength, 

373 ) 

374 

375 originalExposure = afwImage.ExposureF(originalExposurePath) 

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

377 originalSkyWcs = afwGeom.makeSkyWcs(originalMetadata) 

378 

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

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

381 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

382 swarpedImage = swarpedDecoratedImage.getImage() 

383 

384 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

385 warpedSkyWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

386 

387 # original image is source, warped image is destination 

388 srcToDest = afwGeom.makeWcsPairTransform(originalSkyWcs, warpedSkyWcs) 

389 

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

391 originalMaskedImage = originalExposure.getMaskedImage() 

392 

393 numGoodPix = afwMath.warpImage(afwWarpedMaskedImage, originalMaskedImage, 

394 srcToDest, warpingControl) 

395 self.assertGreater(numGoodPix, 50) 

396 

397 afwWarpedImage = afwWarpedMaskedImage.getImage() 

398 afwWarpedImageArr = afwWarpedImage.getArray() 

399 noDataMaskArr = np.isnan(afwWarpedImageArr) 

400 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

401 skipMask=noDataMaskArr, rtol=rtol, atol=atol) 

402 

403 def testTicket2441(self): 

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

405 fromWcs = afwGeom.makeSkyWcs( 

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

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

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

409 ) 

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

411 

412 toWcs = afwGeom.makeSkyWcs( 

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

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

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

416 projection="CEA", 

417 ) 

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

419 

420 warpControl = afwMath.WarpingControl("lanczos3") 

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

422 # exception: 

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

424 self.assertEqual(numGoodPix, 0) 

425 

426 def testTicketDM4063(self): 

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

428 """ 

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

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

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

432 orArr = acast | b 

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

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

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

436 # ambiguous 

437 try: 

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

439 except Exception as e: 

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

441 raise 

442 

443 def testSmallSrc(self): 

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

445 

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

447 """ 

448 fromWcs = afwGeom.makeSkyWcs( 

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

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

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

452 ) 

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

454 

455 toWcs = afwGeom.makeSkyWcs( 

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

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

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

459 ) 

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

461 

462 warpControl = afwMath.WarpingControl("lanczos3") 

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

464 # exception: 

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

466 self.assertEqual(numGoodPix, 0) 

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

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

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

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

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

472 

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

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

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

476 

477 Inputs: 

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

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

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

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

482 0 disables the cache 

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

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

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

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

487 """ 

488 srcWcs = afwGeom.makeSkyWcs( 

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

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

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

492 ) 

493 destWcs = afwGeom.makeSkyWcs( 

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

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

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

497 ) 

498 

499 srcMaskedImage = afwImage.MaskedImageF(100, 101) 

500 srcExposure = afwImage.ExposureF(srcMaskedImage, srcWcs) 

501 

502 srcArrays = srcMaskedImage.getArrays() 

503 shape = srcArrays[0].shape 

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

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

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

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

508 

509 warpControl = afwMath.WarpingControl( 

510 kernelName, 

511 maskKernelName, 

512 cacheSize, 

513 interpLength, 

514 growFullMask 

515 ) 

516 destMaskedImage = afwImage.MaskedImageF(110, 121) 

517 destExposure = afwImage.ExposureF(destMaskedImage, destWcs) 

518 afwMath.warpExposure(destExposure, srcExposure, warpControl) 

519 

520 # now compute with two separate mask planes 

521 warpControl.setGrowFullMask(0) 

522 narrowMaskedImage = afwImage.MaskedImageF(110, 121) 

523 narrowExposure = afwImage.ExposureF(narrowMaskedImage, destWcs) 

524 afwMath.warpExposure(narrowExposure, srcExposure, warpControl) 

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

526 

527 warpControl.setMaskWarpingKernelName("") 

528 broadMaskedImage = afwImage.MaskedImageF(110, 121) 

529 broadExposure = afwImage.ExposureF(broadMaskedImage, destWcs) 

530 afwMath.warpExposure(broadExposure, srcExposure, warpControl) 

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

532 

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

534 # we expect the mask planes to differ 

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

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

537 

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

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

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

541 predExposure = afwImage.makeMaskedImageFromArrays(*predArraySet) 

542 

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

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

545 doImage=True, doMask=True, doVariance=True, 

546 rtol=rtol, atol=atol, msg=msg) 

547 

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

549 def compareToSwarp(self, kernelName, 

550 useWarpExposure=True, useSubregion=False, useDeepCopy=False, 

551 interpLength=10, cacheSize=100000, 

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

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

554 

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

556 

557 Inputs: 

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

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

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

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

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

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

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

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

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

567 0 disables the cache 

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

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

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

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

572 """ 

573 warpingControl = afwMath.WarpingControl( 

574 kernelName, 

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

576 cacheSize, 

577 interpLength, 

578 ) 

579 if useSubregion: 

580 originalFullExposure = afwImage.ExposureF(originalExposurePath) 

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

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

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

584 originalExposure = afwImage.ExposureF( 

585 originalFullExposure, bbox, afwImage.LOCAL, useDeepCopy) 

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

587 else: 

588 originalExposure = afwImage.ExposureF(originalExposurePath) 

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

590 

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

592 swarpedDecoratedImage = afwImage.DecoratedImageF(swarpedImagePath) 

593 swarpedImage = swarpedDecoratedImage.getImage() 

594 swarpedMetadata = swarpedDecoratedImage.getMetadata() 

595 warpedWcs = afwGeom.makeSkyWcs(swarpedMetadata) 

596 

597 if useWarpExposure: 

598 # path for saved afw-warped image 

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

600 

601 afwWarpedMaskedImage = afwImage.MaskedImageF( 

602 swarpedImage.getDimensions()) 

603 afwWarpedExposure = afwImage.ExposureF( 

604 afwWarpedMaskedImage, warpedWcs) 

605 afwMath.warpExposure( 

606 afwWarpedExposure, originalExposure, warpingControl) 

607 afwWarpedMask = afwWarpedMaskedImage.getMask() 

608 if SAVE_FITS_FILES: 

609 afwWarpedExposure.writeFits(afwWarpedImagePath) 

610 if display: 

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

612 

613 swarpedMaskedImage = afwImage.MaskedImageF(swarpedImage) 

614 

615 if display: 

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

617 

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

619 try: 

620 self.assertMaskedImagesAlmostEqual(afwWarpedMaskedImage, swarpedMaskedImage, 

621 doImage=True, doMask=False, doVariance=False, 

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

623 except Exception: 

624 if SAVE_FAILED_FITS_FILES: 

625 afwWarpedExposure.writeFits(afwWarpedImagePath) 

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

627 raise 

628 else: 

629 # path for saved afw-warped image 

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

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

632 

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

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

635 originalWcs = originalExposure.getWcs() 

636 afwMath.warpImage(afwWarpedImage, warpedWcs, originalImage, 

637 originalWcs, warpingControl) 

638 if display: 

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

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

641 diff = swarpedImage.Factory(swarpedImage, True) 

642 diff -= afwWarpedImage 

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

644 if SAVE_FITS_FILES: 

645 afwWarpedImage.writeFits(afwWarpedImagePath) 

646 

647 afwWarpedImageArr = afwWarpedImage.getArray() 

648 noDataMaskArr = np.isnan(afwWarpedImageArr) 

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

650 try: 

651 self.assertImagesAlmostEqual(afwWarpedImage, swarpedImage, 

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

653 except Exception: 

654 if SAVE_FAILED_FITS_FILES: 

655 # save the image anyway 

656 afwWarpedImage.writeFits(afwWarpedImagePath) 

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

658 raise 

659 

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

661 srcToDest = afwGeom.makeWcsPairTransform(originalWcs, warpedWcs) 

662 afwMath.warpImage(afwWarpedImage2, originalImage, 

663 srcToDest, warpingControl) 

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

665 try: 

666 self.assertImagesAlmostEqual(afwWarpedImage2, afwWarpedImage, 

667 rtol=rtol, atol=atol, msg=msg) 

668 except Exception: 

669 if SAVE_FAILED_FITS_FILES: 

670 # save the image anyway 

671 afwWarpedImage.writeFits(afwWarpedImage2) 

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

673 raise 

674 

675 

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

677 pass 

678 

679 

680def setup_module(module): 

681 lsst.utils.tests.init() 

682 

683 

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

685 lsst.utils.tests.init() 

686 unittest.main()