Coverage for tests/test_convolve.py: 18%

322 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-10 02:46 -0800

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.getArrays() 

245 xy0 = self.maskedImage.getXY0() 

246 

247 refCnvImMaskVarArr = refConvolve( 

248 imMaskVar, xy0, refKernel, doNormalize, doCopyEdge) 

249 refMaskedImage = afwImage.makeMaskedImageFromArrays( 

250 *refCnvImMaskVarArr) 

251 

252 afwMath.convolve( 

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

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

255 

256 afwMath.convolve(self.cnvMaskedImage, 

257 self.maskedImage, kernel, convControl) 

258 

259 if display and False: 

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

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

262 title=self._testMethodName + " mosaic") 

263 if False: 

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

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

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

267 

268 self.assertImagesAlmostEqual( 

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

270 self.assertMaskedImagesAlmostEqual( 

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

272 

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

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

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

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

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

278 (kernelDescr, doNormalize, doCopyEdge, maxInterpDist, 

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

280 

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

282 maxInterpDist=10): 

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

284 

285 Inputs: 

286 - kernel: convolution kernel 

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

288 - kernelDescr: description of kernel 

289 - rtol: relative tolerance (see below) 

290 - atol: absolute tolerance (see below) 

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

292 

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

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

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

296 """ 

297 convControl = afwMath.ConvolutionControl() 

298 convControl.setMaxInterpolationDistance(maxInterpDist) 

299 

300 # verify dimension assertions: 

301 # - output image dimensions = input image dimensions 

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

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

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

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

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

307 continue 

308 inMaskedImage = afwImage.MaskedImageF( 

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

310 with self.assertRaises(Exception): 

311 afwMath.convolve(self.cnvMaskedImage, 

312 inMaskedImage, kernel) 

313 

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

315 convControl.setDoNormalize(doNormalize) 

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

317 convControl.setDoCopyEdge(doCopyEdge) 

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

319 kernelDescr=kernelDescr, rtol=rtol, atol=atol) 

320 

321 # verify that basicConvolve does not write to edge pixels 

322 self.runBasicConvolveEdgeTest(kernel, kernelDescr) 

323 

324 def runBasicConvolveEdgeTest(self, kernel, kernelDescr): 

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

326 """ 

327 fullBox = lsst.geom.Box2I( 

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

329 ShiftedBBox.getDimensions(), 

330 ) 

331 goodBox = kernel.shrinkBBox(fullBox) 

332 cnvMaskedImage = afwImage.MaskedImageF( 

333 FullMaskedImage, ShiftedBBox, afwImage.LOCAL, True) 

334 cnvMaskedImageCopy = afwImage.MaskedImageF( 

335 cnvMaskedImage, fullBox, afwImage.LOCAL, True) 

336 cnvMaskedImageCopyViewOfGoodRegion = afwImage.MaskedImageF( 

337 cnvMaskedImageCopy, goodBox, afwImage.LOCAL, False) 

338 

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

340 convControl = afwMath.ConvolutionControl() 

341 mathDetail.basicConvolve( 

342 cnvMaskedImage, self.maskedImage, kernel, convControl) 

343 

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

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

346 cnvMaskedImageGoodView = afwImage.MaskedImageF( 

347 cnvMaskedImage, goodBox, afwImage.LOCAL, False) 

348 cnvMaskedImageGoodView[:] = cnvMaskedImageCopyViewOfGoodRegion 

349 

350 # assert that these two are equal 

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

352 kernelDescr,) 

353 try: 

354 self.assertMaskedImagesAlmostEqual(cnvMaskedImage, cnvMaskedImageCopy, 

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

356 except Exception: 

357 # write out the images, then fail 

358 shortKernelDescr = self.removeGarbageChars(kernelDescr) 

359 cnvMaskedImage.writeFits( 

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

361 cnvMaskedImageCopy.writeFits( 

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

363 raise 

364 

365 def testConvolutionControl(self): 

366 """Test the ConvolutionControl object 

367 """ 

368 convControl = afwMath.ConvolutionControl() 

369 self.assertTrue(convControl.getDoNormalize()) 

370 for doNormalize in (False, True): 

371 convControl.setDoNormalize(doNormalize) 

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

373 

374 self.assertFalse(convControl.getDoCopyEdge()) 

375 for doCopyEdge in (False, True): 

376 convControl.setDoCopyEdge(doCopyEdge) 

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

378 

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

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

381 convControl.setMaxInterpolationDistance(maxInterpDist) 

382 self.assertEqual( 

383 convControl.getMaxInterpolationDistance(), maxInterpDist) 

384 

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

386 def testUnityConvolution(self): 

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

388 """ 

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

390 kFunc = afwMath.IntegerDeltaFunction2D(0.0, 0.0) 

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

392 convControl = afwMath.ConvolutionControl() 

393 convControl.setDoNormalize(False) 

394 convControl.setDoCopyEdge(False) 

395 

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

397 kernel, convControl) 

398 

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

400 kernel, convControl) 

401 cnvImMaskVarArr = self.cnvMaskedImage.getArrays() 

402 

403 skipMaskArr = numpy.array(numpy.isnan( 

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

405 

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

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

408 skipMask=skipMaskArr, msg=kernelDescr) 

409 self.assertMaskedImagesAlmostEqual(self.cnvMaskedImage, self.maskedImage, 

410 skipMask=skipMaskArr, msg=kernelDescr) 

411 

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

413 def testFixedKernelConvolve(self): 

414 """Test convolve with a fixed kernel 

415 """ 

416 kWidth = 6 

417 kHeight = 7 

418 

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

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

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

422 analyticKernel.computeImage(kernelImage, False) 

423 fixedKernel = afwMath.FixedKernel(kernelImage) 

424 

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

426 

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

428 def testSeparableConvolve(self): 

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

430 """ 

431 kWidth = 7 

432 kHeight = 6 

433 

434 gaussFunc1 = afwMath.GaussianFunction1D(1.0) 

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

436 separableKernel = afwMath.SeparableKernel( 

437 kWidth, kHeight, gaussFunc1, gaussFunc1) 

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

439 

440 self.runStdTest( 

441 separableKernel, 

442 refKernel=analyticKernel, 

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

444 

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

446 def testSpatiallyInvariantConvolve(self): 

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

448 """ 

449 kWidth = 6 

450 kHeight = 7 

451 

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

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

454 

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

456 

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

458 def testSpatiallyVaryingAnalyticConvolve(self): 

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

460 """ 

461 kWidth = 7 

462 kHeight = 6 

463 

464 # create spatial model 

465 sFunc = afwMath.PolynomialFunction2D(1) 

466 

467 minSigma = 1.5 

468 maxSigma = 1.501 

469 

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

471 # each entry is a list of spatial parameters 

472 sParams = ( 

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

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

475 (0.0, 0.0, 0.0), 

476 ) 

477 

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

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

480 kernel.setSpatialParameters(sParams) 

481 

482 for maxInterpDist, rtol, methodStr in ( 

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

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

485 ): 

486 self.runStdTest( 

487 kernel, 

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

489 methodStr,), 

490 maxInterpDist=maxInterpDist, 

491 rtol=rtol) 

492 

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

494 def testSpatiallyVaryingSeparableConvolve(self): 

495 """Test convolution with a spatially varying SeparableKernel 

496 """ 

497 kWidth = 7 

498 kHeight = 6 

499 

500 # create spatial model 

501 sFunc = afwMath.PolynomialFunction2D(1) 

502 

503 minSigma = 0.1 

504 maxSigma = 3.0 

505 

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

507 # each entry is a list of spatial parameters 

508 sParams = ( 

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

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

511 (0.0, 0.0, 0.0), 

512 ) 

513 

514 gaussFunc1 = afwMath.GaussianFunction1D(1.0) 

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

516 separableKernel = afwMath.SeparableKernel( 

517 kWidth, kHeight, gaussFunc1, gaussFunc1, sFunc) 

518 analyticKernel = afwMath.AnalyticKernel( 

519 kWidth, kHeight, gaussFunc2, sFunc) 

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

521 analyticKernel.setSpatialParameters(sParams) 

522 

523 self.runStdTest(separableKernel, refKernel=analyticKernel, 

524 kernelDescr="Spatially Varying Gaussian Separable Kernel") 

525 

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

527 def testDeltaConvolve(self): 

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

529 """ 

530 for kWidth in range(1, 4): 

531 for kHeight in range(1, 4): 

532 for activeCol in range(kWidth): 

533 for activeRow in range(kHeight): 

534 kernel = afwMath.DeltaFunctionKernel( 

535 kWidth, kHeight, 

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

537 if display and False: 

538 kim = afwImage.ImageD(kWidth, kHeight) 

539 kernel.computeImage(kim, False) 

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

541 

542 self.runStdTest( 

543 kernel, kernelDescr="Delta Function Kernel") 

544 

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

546 def testSpatiallyVaryingGaussianLinerCombination(self): 

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

548 """ 

549 kWidth = 5 

550 kHeight = 5 

551 

552 # create spatial model 

553 for nBasisKernels in (3, 4): 

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

555 sFunc = afwMath.PolynomialFunction2D(1) 

556 

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

558 # each entry is a list of spatial parameters 

559 sParams = ( 

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

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

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

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

564 )[:nBasisKernels] 

565 

566 gaussParamsList = ( 

567 (1.5, 1.5, 0.0), 

568 (2.5, 1.5, 0.0), 

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

570 (2.5, 2.5, 0.0), 

571 )[:nBasisKernels] 

572 basisKernelList = makeGaussianKernelList( 

573 kWidth, kHeight, gaussParamsList) 

574 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

575 kernel.setSpatialParameters(sParams) 

576 

577 for maxInterpDist, rtol, methodStr in ( 

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

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

580 ): 

581 self.runStdTest( 

582 kernel, 

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

584 ("Spatially Varying Gaussian Analytic Kernel", 

585 nBasisKernels, methodStr), 

586 maxInterpDist=maxInterpDist, 

587 rtol=rtol) 

588 

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

590 def testSpatiallyVaryingDeltaFunctionLinearCombination(self): 

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

592 """ 

593 kWidth = 2 

594 kHeight = 2 

595 

596 # create spatially model 

597 sFunc = afwMath.PolynomialFunction2D(1) 

598 

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

600 # each entry is a list of spatial parameters 

601 sParams = ( 

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

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

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

605 (0.5, 0.0, 0.0), 

606 ) 

607 

608 basisKernelList = makeDeltaFunctionKernelList(kWidth, kHeight) 

609 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

610 kernel.setSpatialParameters(sParams) 

611 

612 for maxInterpDist, rtol, methodStr in ( 

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

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

615 ): 

616 self.runStdTest( 

617 kernel, 

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

619 (methodStr,), 

620 maxInterpDist=maxInterpDist, 

621 rtol=rtol) 

622 

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

624 def testZeroWidthKernel(self): 

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

626 

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

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

629 """ 

630 kernelList = [ 

631 afwMath.FixedKernel(), 

632 afwMath.AnalyticKernel(), 

633 afwMath.SeparableKernel(), 

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

635 # default constructor 

636 afwMath.LinearCombinationKernel(), 

637 ] 

638 convolutionControl = afwMath.ConvolutionControl() 

639 for kernel in kernelList: 

640 with self.assertRaises(Exception): 

641 afwMath.convolve(self.cnvMaskedImage, 

642 self.maskedImage, kernel, convolutionControl) 

643 

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

645 def testTicket873(self): 

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

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

648 """ 

649 # create spatial model 

650 sFunc = afwMath.PolynomialFunction2D(1) 

651 

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

653 # each entry is a list of spatial parameters 

654 sParams = ( 

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

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

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

658 ) 

659 

660 # create three kernels with some non-overlapping pixels 

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

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

663 # is less extreme 

664 basisKernelList = [] 

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

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

667 kImArr[2, 2] = 1.0 

668 kImage = afwImage.makeImageFromArray(kImArr) 

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

670 kImArr[:, :] = 0.0 

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

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

673 kImage = afwImage.makeImageFromArray(kImArr) 

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

675 kImArr[:, :] = 0.0 

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

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

678 kImage = afwImage.makeImageFromArray(kImArr) 

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

680 

681 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

682 kernel.setSpatialParameters(sParams) 

683 

684 for maxInterpDist, rtol, methodStr in ( 

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

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

687 ): 

688 self.runStdTest( 

689 kernel, 

690 kernelDescr="Spatially varying LinearCombinationKernel of basis " 

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

692 methodStr,), 

693 maxInterpDist=maxInterpDist, 

694 rtol=rtol) 

695 

696 

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

698 pass 

699 

700 

701def setup_module(module): 

702 lsst.utils.tests.init() 

703 

704 

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

706 lsst.utils.tests.init() 

707 unittest.main()