Coverage for tests/test_convolve.py: 23%

322 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-19 04:04 -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 lsst.afwMath.convolve 

23 

24Tests convolution of various kernels with Images and MaskedImages. 

25""" 

26import math 

27import os 

28import os.path 

29import unittest 

30import string 

31import re 

32 

33import numpy 

34 

35import lsst.utils 

36import lsst.utils.tests 

37import lsst.geom 

38import lsst.afw.image as afwImage 

39import lsst.afw.math as afwMath 

40import lsst.afw.math.detail as mathDetail 

41 

42from test_kernel import makeDeltaFunctionKernelList, makeGaussianKernelList 

43from lsst.log import Log 

44 

45import lsst.afw.display as afwDisplay 

46 

47Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO) 

48afwDisplay.setDefaultMaskTransparency(75) 

49 

50try: 

51 display 

52except NameError: 

53 display = False 

54 

55try: 

56 dataDir = os.path.join(lsst.utils.getPackageDir("afwdata"), "data") 

57except LookupError: 

58 dataDir = None 

59else: 

60 InputMaskedImagePath = os.path.join(dataDir, "medexp.fits") 

61 FullMaskedImage = afwImage.MaskedImageF(InputMaskedImagePath) 

62 

63# input image contains a saturated star, a bad column, and a faint star 

64InputBBox = lsst.geom.Box2I(lsst.geom.Point2I(52, 574), lsst.geom.Extent2I(76, 80)) 

65# the shifted BBox is for a same-sized region containing different pixels; 

66# this is used to initialize the convolved image, to make sure convolve 

67# fully overwrites it 

68ShiftedBBox = lsst.geom.Box2I(lsst.geom.Point2I(0, 460), lsst.geom.Extent2I(76, 80)) 

69 

70EdgeMaskPixel = 1 << afwImage.Mask.getMaskPlane("EDGE") 

71NoDataMaskPixel = afwImage.Mask.getPlaneBitMask("NO_DATA") 

72 

73# Ignore kernel pixels whose value is exactly 0 when smearing the mask plane? 

74# Set this to match the afw code 

75IgnoreKernelZeroPixels = True 

76 

77GarbageChars = string.punctuation + string.whitespace 

78 

79 

80def refConvolve(imMaskVar, xy0, kernel, doNormalize, doCopyEdge): 

81 """Reference code to convolve a kernel with a masked image. 

82 

83 Warning: slow (especially for spatially varying kernels). 

84 

85 Inputs: 

86 - imMaskVar: (image, mask, variance) numpy arrays 

87 - xy0: xy offset of imMaskVar relative to parent image 

88 - kernel: lsst::afw::Core.Kernel object 

89 - doNormalize: normalize the kernel 

90 - doCopyEdge: if True: copy edge pixels from input image to convolved image; 

91 if False: set edge pixels to the standard edge pixel (image=nan, var=inf, mask=EDGE) 

92 """ 

93 # Note: the original version of this function was written when numpy/image conversions were the 

94 # transpose of what they are today. Rather than transpose the logic in this function or put 

95 # transposes throughout the rest of the file, I have transposed only the inputs and outputs. 

96 # - Jim Bosch, 3/4/2011 

97 image, mask, variance = (imMaskVar[0].transpose(), 

98 imMaskVar[1].transpose(), 

99 imMaskVar[2].transpose()) 

100 

101 if doCopyEdge: 

102 # copy input arrays to output arrays and set EDGE bit of mask; non-edge 

103 # pixels are overwritten below 

104 retImage = image.copy() 

105 retMask = mask.copy() 

106 retMask += EdgeMaskPixel 

107 retVariance = variance.copy() 

108 else: 

109 # initialize output arrays to all edge pixels; non-edge pixels will be 

110 # overwritten below 

111 retImage = numpy.zeros(image.shape, dtype=image.dtype) 

112 retImage[:, :] = numpy.nan 

113 retMask = numpy.zeros(mask.shape, dtype=mask.dtype) 

114 retMask[:, :] = NoDataMaskPixel 

115 retVariance = numpy.zeros(variance.shape, dtype=image.dtype) 

116 retVariance[:, :] = numpy.inf 

117 

118 kWidth = kernel.getWidth() 

119 kHeight = kernel.getHeight() 

120 numCols = image.shape[0] + 1 - kWidth 

121 numRows = image.shape[1] + 1 - kHeight 

122 if numCols < 0 or numRows < 0: 

123 raise RuntimeError( 

124 "image must be larger than kernel in both dimensions") 

125 colRange = list(range(numCols)) 

126 

127 kImage = afwImage.ImageD(lsst.geom.Extent2I(kWidth, kHeight)) 

128 isSpatiallyVarying = kernel.isSpatiallyVarying() 

129 if not isSpatiallyVarying: 

130 kernel.computeImage(kImage, doNormalize) 

131 kImArr = kImage.getArray().transpose() 

132 

133 retRow = kernel.getCtr().getY() 

134 for inRowBeg in range(numRows): 

135 inRowEnd = inRowBeg + kHeight 

136 retCol = kernel.getCtr().getX() 

137 if isSpatiallyVarying: 

138 rowPos = afwImage.indexToPosition(retRow) + xy0[1] 

139 for inColBeg in colRange: 

140 if isSpatiallyVarying: 

141 colPos = afwImage.indexToPosition(retCol) + xy0[0] 

142 kernel.computeImage(kImage, doNormalize, colPos, rowPos) 

143 kImArr = kImage.getArray().transpose() 

144 inColEnd = inColBeg + kWidth 

145 subImage = image[inColBeg:inColEnd, inRowBeg:inRowEnd] 

146 subVariance = variance[inColBeg:inColEnd, inRowBeg:inRowEnd] 

147 subMask = mask[inColBeg:inColEnd, inRowBeg:inRowEnd] 

148 retImage[retCol, retRow] = numpy.add.reduce( 

149 (kImArr * subImage).flat) 

150 retVariance[retCol, retRow] = numpy.add.reduce( 

151 (kImArr * kImArr * subVariance).flat) 

152 if IgnoreKernelZeroPixels: 

153 retMask[retCol, retRow] = numpy.bitwise_or.reduce( 

154 (subMask * (kImArr != 0)).flat) 

155 else: 

156 retMask[retCol, retRow] = numpy.bitwise_or.reduce(subMask.flat) 

157 

158 retCol += 1 

159 retRow += 1 

160 return [numpy.copy(numpy.transpose(arr), order="C") for arr in (retImage, retMask, retVariance)] 

161 

162 

163def sameMaskPlaneDicts(maskedImageA, maskedImageB): 

164 """Return True if the mask plane dicts are the same, False otherwise. 

165 

166 Handles the fact that one cannot directly compare maskPlaneDicts using == 

167 """ 

168 mpDictA = maskedImageA.getMask().getMaskPlaneDict() 

169 mpDictB = maskedImageB.getMask().getMaskPlaneDict() 

170 if list(mpDictA.keys()) != list(mpDictB.keys()): 

171 print("mpDictA.keys() ", mpDictA.keys()) 

172 print("mpDictB.keys() ", mpDictB.keys()) 

173 return False 

174 if list(mpDictA.values()) != list(mpDictB.values()): 

175 print("mpDictA.values()", mpDictA.values()) 

176 print("mpDictB.values()", mpDictB.values()) 

177 return False 

178 return True 

179 

180 

181class ConvolveTestCase(lsst.utils.tests.TestCase): 

182 

183 def setUp(self): 

184 if dataDir is not None: 

185 self.maskedImage = afwImage.MaskedImageF( 

186 FullMaskedImage, InputBBox, afwImage.LOCAL, True) 

187 # use a huge XY0 to make emphasize any errors related to not 

188 # handling xy0 correctly. 

189 self.maskedImage.setXY0(300, 200) 

190 self.xy0 = self.maskedImage.getXY0() 

191 

192 # provide destinations for the convolved MaskedImage and Image that contain junk 

193 # to verify that convolve overwrites all pixels; 

194 # make them deep copies so we can mess with them without affecting 

195 # self.inImage 

196 self.cnvMaskedImage = afwImage.MaskedImageF( 

197 FullMaskedImage, ShiftedBBox, afwImage.LOCAL, True) 

198 self.cnvImage = afwImage.ImageF( 

199 FullMaskedImage.getImage(), ShiftedBBox, afwImage.LOCAL, True) 

200 

201 self.width = self.maskedImage.getWidth() 

202 self.height = self.maskedImage.getHeight() 

203 

204 def tearDown(self): 

205 if dataDir is not None: 

206 del self.maskedImage 

207 del self.cnvMaskedImage 

208 del self.cnvImage 

209 

210 @staticmethod 

211 def _removeGarbageChars(instring): 

212 # str.translate on python2 differs to that on python3 

213 # Performance is not critical in this helper function so use a regex 

214 print("Translating '{}' -> '{}'".format(instring, 

215 re.sub("[" + GarbageChars + "]", "", instring))) 

216 return re.sub("[" + GarbageChars + "]", "", instring) 

217 

218 def runBasicTest(self, kernel, convControl, refKernel=None, 

219 kernelDescr="", rtol=1.0e-05, atol=1e-08): 

220 """Assert that afwMath::convolve gives the same result as reference convolution for a given kernel. 

221 

222 Inputs: 

223 - kernel: convolution kernel 

224 - convControl: convolution control parameters (afwMath.ConvolutionControl) 

225 - refKernel: kernel to use for refConvolve (if None then kernel is used) 

226 - kernelDescr: description of kernel 

227 - rtol: relative tolerance (see below) 

228 - atol: absolute tolerance (see below) 

229 

230 rtol and atol are positive, typically very small numbers. 

231 The relative difference (rtol * abs(b)) and the absolute difference "atol" are added together 

232 to compare against the absolute difference between "a" and "b". 

233 """ 

234 if refKernel is None: 

235 refKernel = kernel 

236 # strip garbage characters (whitespace and punctuation) to make a short 

237 # description for saving files 

238 shortKernelDescr = self._removeGarbageChars(kernelDescr) 

239 

240 doNormalize = convControl.getDoNormalize() 

241 doCopyEdge = convControl.getDoCopyEdge() 

242 maxInterpDist = convControl.getMaxInterpolationDistance() 

243 

244 imMaskVar = (self.maskedImage.image.array, 

245 self.maskedImage.mask.array, 

246 self.maskedImage.variance.array) 

247 xy0 = self.maskedImage.getXY0() 

248 

249 refCnvImMaskVarArr = refConvolve( 

250 imMaskVar, xy0, refKernel, doNormalize, doCopyEdge) 

251 refMaskedImage = afwImage.makeMaskedImageFromArrays( 

252 *refCnvImMaskVarArr) 

253 

254 afwMath.convolve( 

255 self.cnvImage, self.maskedImage.getImage(), kernel, convControl) 

256 self.assertEqual(self.cnvImage.getXY0(), self.xy0) 

257 

258 afwMath.convolve(self.cnvMaskedImage, 

259 self.maskedImage, kernel, convControl) 

260 

261 if display and False: 

262 afwDisplay.Display(frame=0).mtv(afwDisplay.utils.Mosaic().makeMosaic([ 

263 self.maskedImage, refMaskedImage, self.cnvMaskedImage]), 

264 title=self._testMethodName + " mosaic") 

265 if False: 

266 for (x, y) in ((0, 0), (1, 0), (0, 1), (50, 50)): 

267 print("Mask(%d,%d) 0x%x 0x%x" % (x, y, refMaskedImage.getMask()[x, y, afwImage.LOCAL], 

268 self.cnvMaskedImage.getMask()[x, y, afwImage.LOCAL])) 

269 

270 self.assertImagesAlmostEqual( 

271 self.cnvImage, refMaskedImage.getImage(), atol=atol, rtol=rtol) 

272 self.assertMaskedImagesAlmostEqual( 

273 self.cnvMaskedImage, refMaskedImage, atol=atol, rtol=rtol) 

274 

275 if not sameMaskPlaneDicts(self.cnvMaskedImage, self.maskedImage): 

276 self.cnvMaskedImage.writeFits("act%s" % (shortKernelDescr,)) 

277 refMaskedImage.writeFits("des%s" % (shortKernelDescr,)) 

278 self.fail("convolve(MaskedImage, kernel=%s, doNormalize=%s, " 

279 "doCopyEdge=%s, maxInterpDist=%s) failed:\n%s" % 

280 (kernelDescr, doNormalize, doCopyEdge, maxInterpDist, 

281 "convolved mask dictionary does not match input")) 

282 

283 def runStdTest(self, kernel, refKernel=None, kernelDescr="", rtol=1.0e-05, atol=1e-08, 

284 maxInterpDist=10): 

285 """Assert that afwMath::convolve gives the same result as reference convolution for a given kernel. 

286 

287 Inputs: 

288 - kernel: convolution kernel 

289 - refKernel: kernel to use for refConvolve (if None then kernel is used) 

290 - kernelDescr: description of kernel 

291 - rtol: relative tolerance (see below) 

292 - atol: absolute tolerance (see below) 

293 - maxInterpDist: maximum allowed distance for linear interpolation during convolution 

294 

295 rtol and atol are positive, typically very small numbers. 

296 The relative difference (rtol * abs(b)) and the absolute difference "atol" are added together 

297 to compare against the absolute difference between "a" and "b". 

298 """ 

299 convControl = afwMath.ConvolutionControl() 

300 convControl.setMaxInterpolationDistance(maxInterpDist) 

301 

302 # verify dimension assertions: 

303 # - output image dimensions = input image dimensions 

304 # - input image width and height >= kernel width and height 

305 # Note: the assertion kernel size > 0 is tested elsewhere 

306 for inWidth in (kernel.getWidth() - 1, self.width-1, self.width, self.width + 1): 

307 for inHeight in (kernel.getHeight() - 1, self.width-1, self.width, self.width + 1): 

308 if (inWidth == self.width) and (inHeight == self.height): 

309 continue 

310 inMaskedImage = afwImage.MaskedImageF( 

311 lsst.geom.Extent2I(inWidth, inHeight)) 

312 with self.assertRaises(Exception): 

313 afwMath.convolve(self.cnvMaskedImage, 

314 inMaskedImage, kernel) 

315 

316 for doNormalize in (True,): # (False, True): 

317 convControl.setDoNormalize(doNormalize) 

318 for doCopyEdge in (False,): # (False, True): 

319 convControl.setDoCopyEdge(doCopyEdge) 

320 self.runBasicTest(kernel, convControl=convControl, refKernel=refKernel, 

321 kernelDescr=kernelDescr, rtol=rtol, atol=atol) 

322 

323 # verify that basicConvolve does not write to edge pixels 

324 self.runBasicConvolveEdgeTest(kernel, kernelDescr) 

325 

326 def runBasicConvolveEdgeTest(self, kernel, kernelDescr): 

327 """Verify that basicConvolve does not write to edge pixels for this kind of kernel 

328 """ 

329 fullBox = lsst.geom.Box2I( 

330 lsst.geom.Point2I(0, 0), 

331 ShiftedBBox.getDimensions(), 

332 ) 

333 goodBox = kernel.shrinkBBox(fullBox) 

334 cnvMaskedImage = afwImage.MaskedImageF( 

335 FullMaskedImage, ShiftedBBox, afwImage.LOCAL, True) 

336 cnvMaskedImageCopy = afwImage.MaskedImageF( 

337 cnvMaskedImage, fullBox, afwImage.LOCAL, True) 

338 cnvMaskedImageCopyViewOfGoodRegion = afwImage.MaskedImageF( 

339 cnvMaskedImageCopy, goodBox, afwImage.LOCAL, False) 

340 

341 # convolve with basicConvolve, which should leave the edge pixels alone 

342 convControl = afwMath.ConvolutionControl() 

343 mathDetail.basicConvolve( 

344 cnvMaskedImage, self.maskedImage, kernel, convControl) 

345 

346 # reset the good region to the original convolved image; 

347 # this should reset the entire convolved image to its original self 

348 cnvMaskedImageGoodView = afwImage.MaskedImageF( 

349 cnvMaskedImage, goodBox, afwImage.LOCAL, False) 

350 cnvMaskedImageGoodView[:] = cnvMaskedImageCopyViewOfGoodRegion 

351 

352 # assert that these two are equal 

353 msg = "basicConvolve(MaskedImage, kernel=%s) wrote to edge pixels" % ( 

354 kernelDescr,) 

355 try: 

356 self.assertMaskedImagesAlmostEqual(cnvMaskedImage, cnvMaskedImageCopy, 

357 doVariance=True, rtol=0, atol=0, msg=msg) 

358 except Exception: 

359 # write out the images, then fail 

360 shortKernelDescr = self.removeGarbageChars(kernelDescr) 

361 cnvMaskedImage.writeFits( 

362 "actBasicConvolve%s" % (shortKernelDescr,)) 

363 cnvMaskedImageCopy.writeFits( 

364 "desBasicConvolve%s" % (shortKernelDescr,)) 

365 raise 

366 

367 def testConvolutionControl(self): 

368 """Test the ConvolutionControl object 

369 """ 

370 convControl = afwMath.ConvolutionControl() 

371 self.assertTrue(convControl.getDoNormalize()) 

372 for doNormalize in (False, True): 

373 convControl.setDoNormalize(doNormalize) 

374 self.assertEqual(convControl.getDoNormalize(), doNormalize) 

375 

376 self.assertFalse(convControl.getDoCopyEdge()) 

377 for doCopyEdge in (False, True): 

378 convControl.setDoCopyEdge(doCopyEdge) 

379 self.assertEqual(convControl.getDoCopyEdge(), doCopyEdge) 

380 

381 self.assertEqual(convControl.getMaxInterpolationDistance(), 10) 

382 for maxInterpDist in (0, 1, 2, 10, 100): 

383 convControl.setMaxInterpolationDistance(maxInterpDist) 

384 self.assertEqual( 

385 convControl.getMaxInterpolationDistance(), maxInterpDist) 

386 

387 @unittest.skipIf(dataDir is None, "afwdata not setup") 

388 def testUnityConvolution(self): 

389 """Verify that convolution with a centered delta function reproduces the original. 

390 """ 

391 # create a delta function kernel that has 1,1 in the center 

392 kFunc = afwMath.IntegerDeltaFunction2D(0.0, 0.0) 

393 kernel = afwMath.AnalyticKernel(3, 3, kFunc) 

394 convControl = afwMath.ConvolutionControl() 

395 convControl.setDoNormalize(False) 

396 convControl.setDoCopyEdge(False) 

397 

398 afwMath.convolve(self.cnvImage, self.maskedImage.getImage(), 

399 kernel, convControl) 

400 

401 afwMath.convolve(self.cnvMaskedImage, self.maskedImage, 

402 kernel, convControl) 

403 cnvImMaskVarArr = (self.cnvMaskedImage.image.array, 

404 self.cnvMaskedImage.mask.array, 

405 self.cnvMaskedImage.variance.array) 

406 

407 skipMaskArr = numpy.array(numpy.isnan( 

408 cnvImMaskVarArr[0]), dtype=numpy.uint16) 

409 

410 kernelDescr = "Centered DeltaFunctionKernel (testing unity convolution)" 

411 self.assertImagesAlmostEqual(self.cnvImage, self.maskedImage.getImage(), 

412 skipMask=skipMaskArr, msg=kernelDescr) 

413 self.assertMaskedImagesAlmostEqual(self.cnvMaskedImage, self.maskedImage, 

414 skipMask=skipMaskArr, msg=kernelDescr) 

415 

416 @unittest.skipIf(dataDir is None, "afwdata not setup") 

417 def testFixedKernelConvolve(self): 

418 """Test convolve with a fixed kernel 

419 """ 

420 kWidth = 6 

421 kHeight = 7 

422 

423 kFunc = afwMath.GaussianFunction2D(2.5, 1.5, 0.5) 

424 analyticKernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc) 

425 kernelImage = afwImage.ImageD(lsst.geom.Extent2I(kWidth, kHeight)) 

426 analyticKernel.computeImage(kernelImage, False) 

427 fixedKernel = afwMath.FixedKernel(kernelImage) 

428 

429 self.runStdTest(fixedKernel, kernelDescr="Gaussian FixedKernel") 

430 

431 @unittest.skipIf(dataDir is None, "afwdata not setup") 

432 def testSeparableConvolve(self): 

433 """Test convolve of a separable kernel with a spatially invariant Gaussian function 

434 """ 

435 kWidth = 7 

436 kHeight = 6 

437 

438 gaussFunc1 = afwMath.GaussianFunction1D(1.0) 

439 gaussFunc2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0) 

440 separableKernel = afwMath.SeparableKernel( 

441 kWidth, kHeight, gaussFunc1, gaussFunc1) 

442 analyticKernel = afwMath.AnalyticKernel(kWidth, kHeight, gaussFunc2) 

443 

444 self.runStdTest( 

445 separableKernel, 

446 refKernel=analyticKernel, 

447 kernelDescr="Gaussian Separable Kernel (compared to AnalyticKernel equivalent)") 

448 

449 @unittest.skipIf(dataDir is None, "afwdata not setup") 

450 def testSpatiallyInvariantConvolve(self): 

451 """Test convolution with a spatially invariant Gaussian function 

452 """ 

453 kWidth = 6 

454 kHeight = 7 

455 

456 kFunc = afwMath.GaussianFunction2D(2.5, 1.5, 0.5) 

457 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc) 

458 

459 self.runStdTest(kernel, kernelDescr="Gaussian Analytic Kernel") 

460 

461 @unittest.skipIf(dataDir is None, "afwdata not setup") 

462 def testSpatiallyVaryingAnalyticConvolve(self): 

463 """Test in-place convolution with a spatially varying AnalyticKernel 

464 """ 

465 kWidth = 7 

466 kHeight = 6 

467 

468 # create spatial model 

469 sFunc = afwMath.PolynomialFunction2D(1) 

470 

471 minSigma = 1.5 

472 maxSigma = 1.501 

473 

474 # spatial parameters are a list of entries, one per kernel parameter; 

475 # each entry is a list of spatial parameters 

476 sParams = ( 

477 (minSigma, (maxSigma - minSigma) / self.width, 0.0), 

478 (minSigma, 0.0, (maxSigma - minSigma) / self.height), 

479 (0.0, 0.0, 0.0), 

480 ) 

481 

482 kFunc = afwMath.GaussianFunction2D(1.0, 1.0, 0.0) 

483 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc, sFunc) 

484 kernel.setSpatialParameters(sParams) 

485 

486 for maxInterpDist, rtol, methodStr in ( 

487 (0, 1.0e-5, "brute force"), 

488 (10, 1.0e-5, "interpolation over 10 x 10 pixels"), 

489 ): 

490 self.runStdTest( 

491 kernel, 

492 kernelDescr="Spatially Varying Gaussian Analytic Kernel using %s" % ( 

493 methodStr,), 

494 maxInterpDist=maxInterpDist, 

495 rtol=rtol) 

496 

497 @unittest.skipIf(dataDir is None, "afwdata not setup") 

498 def testSpatiallyVaryingSeparableConvolve(self): 

499 """Test convolution with a spatially varying SeparableKernel 

500 """ 

501 kWidth = 7 

502 kHeight = 6 

503 

504 # create spatial model 

505 sFunc = afwMath.PolynomialFunction2D(1) 

506 

507 minSigma = 0.1 

508 maxSigma = 3.0 

509 

510 # spatial parameters are a list of entries, one per kernel parameter; 

511 # each entry is a list of spatial parameters 

512 sParams = ( 

513 (minSigma, (maxSigma - minSigma) / self.width, 0.0), 

514 (minSigma, 0.0, (maxSigma - minSigma) / self.height), 

515 (0.0, 0.0, 0.0), 

516 ) 

517 

518 gaussFunc1 = afwMath.GaussianFunction1D(1.0) 

519 gaussFunc2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0) 

520 separableKernel = afwMath.SeparableKernel( 

521 kWidth, kHeight, gaussFunc1, gaussFunc1, sFunc) 

522 analyticKernel = afwMath.AnalyticKernel( 

523 kWidth, kHeight, gaussFunc2, sFunc) 

524 separableKernel.setSpatialParameters(sParams[0:2]) 

525 analyticKernel.setSpatialParameters(sParams) 

526 

527 self.runStdTest(separableKernel, refKernel=analyticKernel, 

528 kernelDescr="Spatially Varying Gaussian Separable Kernel") 

529 

530 @unittest.skipIf(dataDir is None, "afwdata not setup") 

531 def testDeltaConvolve(self): 

532 """Test convolution with various delta function kernels using optimized code 

533 """ 

534 for kWidth in range(1, 4): 

535 for kHeight in range(1, 4): 

536 for activeCol in range(kWidth): 

537 for activeRow in range(kHeight): 

538 kernel = afwMath.DeltaFunctionKernel( 

539 kWidth, kHeight, 

540 lsst.geom.Point2I(activeCol, activeRow)) 

541 if display and False: 

542 kim = afwImage.ImageD(kWidth, kHeight) 

543 kernel.computeImage(kim, False) 

544 afwDisplay.Display(frame=1).mtv(kim, title=self._testMethodName + " image") 

545 

546 self.runStdTest( 

547 kernel, kernelDescr="Delta Function Kernel") 

548 

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

550 def testSpatiallyVaryingGaussianLinerCombination(self): 

551 """Test convolution with a spatially varying LinearCombinationKernel of two Gaussian basis kernels. 

552 """ 

553 kWidth = 5 

554 kHeight = 5 

555 

556 # create spatial model 

557 for nBasisKernels in (3, 4): 

558 # at 3 the kernel will not be refactored, at 4 it will be 

559 sFunc = afwMath.PolynomialFunction2D(1) 

560 

561 # spatial parameters are a list of entries, one per kernel parameter; 

562 # each entry is a list of spatial parameters 

563 sParams = ( 

564 (1.0, -0.01/self.width, -0.01/self.height), 

565 (0.0, 0.01/self.width, 0.0/self.height), 

566 (0.0, 0.0/self.width, 0.01/self.height), 

567 (0.5, 0.005/self.width, -0.005/self.height), 

568 )[:nBasisKernels] 

569 

570 gaussParamsList = ( 

571 (1.5, 1.5, 0.0), 

572 (2.5, 1.5, 0.0), 

573 (2.5, 1.5, math.pi / 2.0), 

574 (2.5, 2.5, 0.0), 

575 )[:nBasisKernels] 

576 basisKernelList = makeGaussianKernelList( 

577 kWidth, kHeight, gaussParamsList) 

578 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

579 kernel.setSpatialParameters(sParams) 

580 

581 for maxInterpDist, rtol, methodStr in ( 

582 (0, 1.0e-5, "brute force"), 

583 (10, 1.0e-5, "interpolation over 10 x 10 pixels"), 

584 ): 

585 self.runStdTest( 

586 kernel, 

587 kernelDescr="%s with %d basis kernels convolved using %s" % 

588 ("Spatially Varying Gaussian Analytic Kernel", 

589 nBasisKernels, methodStr), 

590 maxInterpDist=maxInterpDist, 

591 rtol=rtol) 

592 

593 @unittest.skipIf(dataDir is None, "afwdata not setup") 

594 def testSpatiallyVaryingDeltaFunctionLinearCombination(self): 

595 """Test convolution with a spatially varying LinearCombinationKernel of delta function basis kernels. 

596 """ 

597 kWidth = 2 

598 kHeight = 2 

599 

600 # create spatially model 

601 sFunc = afwMath.PolynomialFunction2D(1) 

602 

603 # spatial parameters are a list of entries, one per kernel parameter; 

604 # each entry is a list of spatial parameters 

605 sParams = ( 

606 (1.0, -0.5/self.width, -0.5/self.height), 

607 (0.0, 1.0/self.width, 0.0/self.height), 

608 (0.0, 0.0/self.width, 1.0/self.height), 

609 (0.5, 0.0, 0.0), 

610 ) 

611 

612 basisKernelList = makeDeltaFunctionKernelList(kWidth, kHeight) 

613 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

614 kernel.setSpatialParameters(sParams) 

615 

616 for maxInterpDist, rtol, methodStr in ( 

617 (0, 1.0e-5, "brute force"), 

618 (10, 1.0e-3, "interpolation over 10 x 10 pixels"), 

619 ): 

620 self.runStdTest( 

621 kernel, 

622 kernelDescr="Spatially varying LinearCombinationKernel of delta function kernels using %s" % 

623 (methodStr,), 

624 maxInterpDist=maxInterpDist, 

625 rtol=rtol) 

626 

627 @unittest.skipIf(dataDir is None, "afwdata not setup") 

628 def testZeroWidthKernel(self): 

629 """Convolution by a 0x0 kernel should raise an exception. 

630 

631 The only way to produce a 0x0 kernel is to use the default constructor 

632 (which exists only to support persistence; it does not produce a useful kernel). 

633 """ 

634 kernelList = [ 

635 afwMath.FixedKernel(), 

636 afwMath.AnalyticKernel(), 

637 afwMath.SeparableKernel(), 

638 # afwMath.DeltaFunctionKernel(), # DeltaFunctionKernel has no 

639 # default constructor 

640 afwMath.LinearCombinationKernel(), 

641 ] 

642 convolutionControl = afwMath.ConvolutionControl() 

643 for kernel in kernelList: 

644 with self.assertRaises(Exception): 

645 afwMath.convolve(self.cnvMaskedImage, 

646 self.maskedImage, kernel, convolutionControl) 

647 

648 @unittest.skipIf(dataDir is None, "afwdata not setup") 

649 def testTicket873(self): 

650 """Demonstrate ticket 873: convolution of a MaskedImage with a spatially varying 

651 LinearCombinationKernel of basis kernels with low covariance gives incorrect variance. 

652 """ 

653 # create spatial model 

654 sFunc = afwMath.PolynomialFunction2D(1) 

655 

656 # spatial parameters are a list of entries, one per kernel parameter; 

657 # each entry is a list of spatial parameters 

658 sParams = ( 

659 (1.0, -0.5/self.width, -0.5/self.height), 

660 (0.0, 1.0/self.width, 0.0/self.height), 

661 (0.0, 0.0/self.width, 1.0/self.height), 

662 ) 

663 

664 # create three kernels with some non-overlapping pixels 

665 # (non-zero pixels in one kernel vs. zero pixels in other kernels); 

666 # note: the extreme example of this is delta function kernels, but this 

667 # is less extreme 

668 basisKernelList = [] 

669 kImArr = numpy.zeros([5, 5], dtype=float) 

670 kImArr[1:4, 1:4] = 0.5 

671 kImArr[2, 2] = 1.0 

672 kImage = afwImage.makeImageFromArray(kImArr) 

673 basisKernelList.append(afwMath.FixedKernel(kImage)) 

674 kImArr[:, :] = 0.0 

675 kImArr[0:2, 0:2] = 0.125 

676 kImArr[3:5, 3:5] = 0.125 

677 kImage = afwImage.makeImageFromArray(kImArr) 

678 basisKernelList.append(afwMath.FixedKernel(kImage)) 

679 kImArr[:, :] = 0.0 

680 kImArr[0:2, 3:5] = 0.125 

681 kImArr[3:5, 0:2] = 0.125 

682 kImage = afwImage.makeImageFromArray(kImArr) 

683 basisKernelList.append(afwMath.FixedKernel(kImage)) 

684 

685 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

686 kernel.setSpatialParameters(sParams) 

687 

688 for maxInterpDist, rtol, methodStr in ( 

689 (0, 1.0e-5, "brute force"), 

690 (10, 3.0e-3, "interpolation over 10 x 10 pixels"), 

691 ): 

692 self.runStdTest( 

693 kernel, 

694 kernelDescr="Spatially varying LinearCombinationKernel of basis " 

695 "kernels with low covariance, using %s" % ( 

696 methodStr,), 

697 maxInterpDist=maxInterpDist, 

698 rtol=rtol) 

699 

700 

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

702 pass 

703 

704 

705def setup_module(module): 

706 lsst.utils.tests.init() 

707 

708 

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

710 lsst.utils.tests.init() 

711 unittest.main()