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 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 

41import lsst.pex.exceptions as pexExcept 

42 

43from test_kernel import makeDeltaFunctionKernelList, makeGaussianKernelList 

44from lsst.log import Log 

45 

46import lsst.afw.display as afwDisplay 

47 

48Log.getLogger("afw.image.Mask").setLevel(Log.INFO) 

49afwDisplay.setDefaultMaskTransparency(75) 

50 

51try: 

52 display 

53except NameError: 

54 display = False 

55 

56try: 

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

58except pexExcept.NotFoundError: 

59 dataDir = None 

60else: 

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

62 FullMaskedImage = afwImage.MaskedImageF(InputMaskedImagePath) 

63 

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

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

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

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

68# fully overwrites it 

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

70 

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

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

73 

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

75# Set this to match the afw code 

76IgnoreKernelZeroPixels = True 

77 

78GarbageChars = string.punctuation + string.whitespace 

79 

80 

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

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

83 

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

85 

86 Inputs: 

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

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

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

90 - doNormalize: normalize the kernel 

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

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

93 """ 

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

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

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

97 # - Jim Bosch, 3/4/2011 

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

99 imMaskVar[1].transpose(), 

100 imMaskVar[2].transpose()) 

101 

102 if doCopyEdge: 

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

104 # pixels are overwritten below 

105 retImage = image.copy() 

106 retMask = mask.copy() 

107 retMask += EdgeMaskPixel 

108 retVariance = variance.copy() 

109 else: 

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

111 # overwritten below 

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

113 retImage[:, :] = numpy.nan 

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

115 retMask[:, :] = NoDataMaskPixel 

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

117 retVariance[:, :] = numpy.inf 

118 

119 kWidth = kernel.getWidth() 

120 kHeight = kernel.getHeight() 

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

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

123 if numCols < 0 or numRows < 0: 

124 raise RuntimeError( 

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

126 colRange = list(range(numCols)) 

127 

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

129 isSpatiallyVarying = kernel.isSpatiallyVarying() 

130 if not isSpatiallyVarying: 

131 kernel.computeImage(kImage, doNormalize) 

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

133 

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

135 for inRowBeg in range(numRows): 

136 inRowEnd = inRowBeg + kHeight 

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

138 if isSpatiallyVarying: 

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

140 for inColBeg in colRange: 

141 if isSpatiallyVarying: 

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

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

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

145 inColEnd = inColBeg + kWidth 

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

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

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

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

150 (kImArr * subImage).flat) 

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

152 (kImArr * kImArr * subVariance).flat) 

153 if IgnoreKernelZeroPixels: 

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

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

156 else: 

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

158 

159 retCol += 1 

160 retRow += 1 

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

162 

163 

164def sameMaskPlaneDicts(maskedImageA, maskedImageB): 

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

166 

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

168 """ 

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

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

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

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

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

174 return False 

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

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

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

178 return False 

179 return True 

180 

181 

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

183 

184 def setUp(self): 

185 if dataDir is not None: 

186 self.maskedImage = afwImage.MaskedImageF( 

187 FullMaskedImage, InputBBox, afwImage.LOCAL, True) 

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

189 # handling xy0 correctly. 

190 self.maskedImage.setXY0(300, 200) 

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

192 

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

194 # to verify that convolve overwrites all pixels; 

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

196 # self.inImage 

197 self.cnvMaskedImage = afwImage.MaskedImageF( 

198 FullMaskedImage, ShiftedBBox, afwImage.LOCAL, True) 

199 self.cnvImage = afwImage.ImageF( 

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

201 

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

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

204 

205 def tearDown(self): 

206 if dataDir is not None: 

207 del self.maskedImage 

208 del self.cnvMaskedImage 

209 del self.cnvImage 

210 

211 @staticmethod 

212 def _removeGarbageChars(instring): 

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

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

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

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

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

218 

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

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

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

222 

223 Inputs: 

224 - kernel: convolution kernel 

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

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

227 - kernelDescr: description of kernel 

228 - rtol: relative tolerance (see below) 

229 - atol: absolute tolerance (see below) 

230 

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

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

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

234 """ 

235 if refKernel is None: 

236 refKernel = kernel 

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

238 # description for saving files 

239 shortKernelDescr = self._removeGarbageChars(kernelDescr) 

240 

241 doNormalize = convControl.getDoNormalize() 

242 doCopyEdge = convControl.getDoCopyEdge() 

243 maxInterpDist = convControl.getMaxInterpolationDistance() 

244 

245 imMaskVar = self.maskedImage.getArrays() 

246 xy0 = self.maskedImage.getXY0() 

247 

248 refCnvImMaskVarArr = refConvolve( 

249 imMaskVar, xy0, refKernel, doNormalize, doCopyEdge) 

250 refMaskedImage = afwImage.makeMaskedImageFromArrays( 

251 *refCnvImMaskVarArr) 

252 

253 afwMath.convolve( 

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

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

256 

257 afwMath.convolve(self.cnvMaskedImage, 

258 self.maskedImage, kernel, convControl) 

259 

260 if display and False: 

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

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

263 title=self._testMethodName + " mosaic") 

264 if False: 

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

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

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

268 

269 self.assertImagesAlmostEqual( 

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

271 self.assertMaskedImagesAlmostEqual( 

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

273 

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

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

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

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

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

279 (kernelDescr, doNormalize, doCopyEdge, maxInterpDist, 

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

281 

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

283 maxInterpDist=10): 

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

285 

286 Inputs: 

287 - kernel: convolution kernel 

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

289 - kernelDescr: description of kernel 

290 - rtol: relative tolerance (see below) 

291 - atol: absolute tolerance (see below) 

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

293 

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

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

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

297 """ 

298 convControl = afwMath.ConvolutionControl() 

299 convControl.setMaxInterpolationDistance(maxInterpDist) 

300 

301 # verify dimension assertions: 

302 # - output image dimensions = input image dimensions 

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

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

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

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

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

308 continue 

309 inMaskedImage = afwImage.MaskedImageF( 

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

311 with self.assertRaises(Exception): 

312 afwMath.convolve(self.cnvMaskedImage, 

313 inMaskedImage, kernel) 

314 

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

316 convControl.setDoNormalize(doNormalize) 

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

318 convControl.setDoCopyEdge(doCopyEdge) 

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

320 kernelDescr=kernelDescr, rtol=rtol, atol=atol) 

321 

322 # verify that basicConvolve does not write to edge pixels 

323 self.runBasicConvolveEdgeTest(kernel, kernelDescr) 

324 

325 def runBasicConvolveEdgeTest(self, kernel, kernelDescr): 

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

327 """ 

328 fullBox = lsst.geom.Box2I( 

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

330 ShiftedBBox.getDimensions(), 

331 ) 

332 goodBox = kernel.shrinkBBox(fullBox) 

333 cnvMaskedImage = afwImage.MaskedImageF( 

334 FullMaskedImage, ShiftedBBox, afwImage.LOCAL, True) 

335 cnvMaskedImageCopy = afwImage.MaskedImageF( 

336 cnvMaskedImage, fullBox, afwImage.LOCAL, True) 

337 cnvMaskedImageCopyViewOfGoodRegion = afwImage.MaskedImageF( 

338 cnvMaskedImageCopy, goodBox, afwImage.LOCAL, False) 

339 

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

341 convControl = afwMath.ConvolutionControl() 

342 mathDetail.basicConvolve( 

343 cnvMaskedImage, self.maskedImage, kernel, convControl) 

344 

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

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

347 cnvMaskedImageGoodView = afwImage.MaskedImageF( 

348 cnvMaskedImage, goodBox, afwImage.LOCAL, False) 

349 cnvMaskedImageGoodView[:] = cnvMaskedImageCopyViewOfGoodRegion 

350 

351 # assert that these two are equal 

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

353 kernelDescr,) 

354 try: 

355 self.assertMaskedImagesAlmostEqual(cnvMaskedImage, cnvMaskedImageCopy, 

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

357 except Exception: 

358 # write out the images, then fail 

359 shortKernelDescr = self.removeGarbageChars(kernelDescr) 

360 cnvMaskedImage.writeFits( 

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

362 cnvMaskedImageCopy.writeFits( 

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

364 raise 

365 

366 def testConvolutionControl(self): 

367 """Test the ConvolutionControl object 

368 """ 

369 convControl = afwMath.ConvolutionControl() 

370 self.assertTrue(convControl.getDoNormalize()) 

371 for doNormalize in (False, True): 

372 convControl.setDoNormalize(doNormalize) 

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

374 

375 self.assertFalse(convControl.getDoCopyEdge()) 

376 for doCopyEdge in (False, True): 

377 convControl.setDoCopyEdge(doCopyEdge) 

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

379 

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

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

382 convControl.setMaxInterpolationDistance(maxInterpDist) 

383 self.assertEqual( 

384 convControl.getMaxInterpolationDistance(), maxInterpDist) 

385 

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

387 def testUnityConvolution(self): 

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

389 """ 

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

391 kFunc = afwMath.IntegerDeltaFunction2D(0.0, 0.0) 

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

393 convControl = afwMath.ConvolutionControl() 

394 convControl.setDoNormalize(False) 

395 convControl.setDoCopyEdge(False) 

396 

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

398 kernel, convControl) 

399 

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

401 kernel, convControl) 

402 cnvImMaskVarArr = self.cnvMaskedImage.getArrays() 

403 

404 skipMaskArr = numpy.array(numpy.isnan( 

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

406 

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

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

409 skipMask=skipMaskArr, msg=kernelDescr) 

410 self.assertMaskedImagesAlmostEqual(self.cnvMaskedImage, self.maskedImage, 

411 skipMask=skipMaskArr, msg=kernelDescr) 

412 

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

414 def testFixedKernelConvolve(self): 

415 """Test convolve with a fixed kernel 

416 """ 

417 kWidth = 6 

418 kHeight = 7 

419 

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

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

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

423 analyticKernel.computeImage(kernelImage, False) 

424 fixedKernel = afwMath.FixedKernel(kernelImage) 

425 

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

427 

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

429 def testSeparableConvolve(self): 

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

431 """ 

432 kWidth = 7 

433 kHeight = 6 

434 

435 gaussFunc1 = afwMath.GaussianFunction1D(1.0) 

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

437 separableKernel = afwMath.SeparableKernel( 

438 kWidth, kHeight, gaussFunc1, gaussFunc1) 

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

440 

441 self.runStdTest( 

442 separableKernel, 

443 refKernel=analyticKernel, 

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

445 

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

447 def testSpatiallyInvariantConvolve(self): 

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

449 """ 

450 kWidth = 6 

451 kHeight = 7 

452 

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

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

455 

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

457 

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

459 def testSpatiallyVaryingAnalyticConvolve(self): 

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

461 """ 

462 kWidth = 7 

463 kHeight = 6 

464 

465 # create spatial model 

466 sFunc = afwMath.PolynomialFunction2D(1) 

467 

468 minSigma = 1.5 

469 maxSigma = 1.501 

470 

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

472 # each entry is a list of spatial parameters 

473 sParams = ( 

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

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

476 (0.0, 0.0, 0.0), 

477 ) 

478 

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

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

481 kernel.setSpatialParameters(sParams) 

482 

483 for maxInterpDist, rtol, methodStr in ( 

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

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

486 ): 

487 self.runStdTest( 

488 kernel, 

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

490 methodStr,), 

491 maxInterpDist=maxInterpDist, 

492 rtol=rtol) 

493 

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

495 def testSpatiallyVaryingSeparableConvolve(self): 

496 """Test convolution with a spatially varying SeparableKernel 

497 """ 

498 kWidth = 7 

499 kHeight = 6 

500 

501 # create spatial model 

502 sFunc = afwMath.PolynomialFunction2D(1) 

503 

504 minSigma = 0.1 

505 maxSigma = 3.0 

506 

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

508 # each entry is a list of spatial parameters 

509 sParams = ( 

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

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

512 (0.0, 0.0, 0.0), 

513 ) 

514 

515 gaussFunc1 = afwMath.GaussianFunction1D(1.0) 

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

517 separableKernel = afwMath.SeparableKernel( 

518 kWidth, kHeight, gaussFunc1, gaussFunc1, sFunc) 

519 analyticKernel = afwMath.AnalyticKernel( 

520 kWidth, kHeight, gaussFunc2, sFunc) 

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

522 analyticKernel.setSpatialParameters(sParams) 

523 

524 self.runStdTest(separableKernel, refKernel=analyticKernel, 

525 kernelDescr="Spatially Varying Gaussian Separable Kernel") 

526 

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

528 def testDeltaConvolve(self): 

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

530 """ 

531 for kWidth in range(1, 4): 

532 for kHeight in range(1, 4): 

533 for activeCol in range(kWidth): 

534 for activeRow in range(kHeight): 

535 kernel = afwMath.DeltaFunctionKernel( 

536 kWidth, kHeight, 

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

538 if display and False: 

539 kim = afwImage.ImageD(kWidth, kHeight) 

540 kernel.computeImage(kim, False) 

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

542 

543 self.runStdTest( 

544 kernel, kernelDescr="Delta Function Kernel") 

545 

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

547 def testSpatiallyVaryingGaussianLinerCombination(self): 

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

549 """ 

550 kWidth = 5 

551 kHeight = 5 

552 

553 # create spatial model 

554 for nBasisKernels in (3, 4): 

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

556 sFunc = afwMath.PolynomialFunction2D(1) 

557 

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

559 # each entry is a list of spatial parameters 

560 sParams = ( 

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

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

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

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

565 )[:nBasisKernels] 

566 

567 gaussParamsList = ( 

568 (1.5, 1.5, 0.0), 

569 (2.5, 1.5, 0.0), 

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

571 (2.5, 2.5, 0.0), 

572 )[:nBasisKernels] 

573 basisKernelList = makeGaussianKernelList( 

574 kWidth, kHeight, gaussParamsList) 

575 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

576 kernel.setSpatialParameters(sParams) 

577 

578 for maxInterpDist, rtol, methodStr in ( 

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

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

581 ): 

582 self.runStdTest( 

583 kernel, 

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

585 ("Spatially Varying Gaussian Analytic Kernel", 

586 nBasisKernels, methodStr), 

587 maxInterpDist=maxInterpDist, 

588 rtol=rtol) 

589 

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

591 def testSpatiallyVaryingDeltaFunctionLinearCombination(self): 

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

593 """ 

594 kWidth = 2 

595 kHeight = 2 

596 

597 # create spatially model 

598 sFunc = afwMath.PolynomialFunction2D(1) 

599 

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

601 # each entry is a list of spatial parameters 

602 sParams = ( 

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

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

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

606 (0.5, 0.0, 0.0), 

607 ) 

608 

609 basisKernelList = makeDeltaFunctionKernelList(kWidth, kHeight) 

610 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

611 kernel.setSpatialParameters(sParams) 

612 

613 for maxInterpDist, rtol, methodStr in ( 

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

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

616 ): 

617 self.runStdTest( 

618 kernel, 

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

620 (methodStr,), 

621 maxInterpDist=maxInterpDist, 

622 rtol=rtol) 

623 

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

625 def testZeroWidthKernel(self): 

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

627 

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

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

630 """ 

631 kernelList = [ 

632 afwMath.FixedKernel(), 

633 afwMath.AnalyticKernel(), 

634 afwMath.SeparableKernel(), 

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

636 # default constructor 

637 afwMath.LinearCombinationKernel(), 

638 ] 

639 convolutionControl = afwMath.ConvolutionControl() 

640 for kernel in kernelList: 

641 with self.assertRaises(Exception): 

642 afwMath.convolve(self.cnvMaskedImage, 

643 self.maskedImage, kernel, convolutionControl) 

644 

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

646 def testTicket873(self): 

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

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

649 """ 

650 # create spatial model 

651 sFunc = afwMath.PolynomialFunction2D(1) 

652 

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

654 # each entry is a list of spatial parameters 

655 sParams = ( 

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

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

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

659 ) 

660 

661 # create three kernels with some non-overlapping pixels 

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

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

664 # is less extreme 

665 basisKernelList = [] 

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

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

668 kImArr[2, 2] = 1.0 

669 kImage = afwImage.makeImageFromArray(kImArr) 

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

671 kImArr[:, :] = 0.0 

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

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

674 kImage = afwImage.makeImageFromArray(kImArr) 

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

676 kImArr[:, :] = 0.0 

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

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

679 kImage = afwImage.makeImageFromArray(kImArr) 

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

681 

682 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc) 

683 kernel.setSpatialParameters(sParams) 

684 

685 for maxInterpDist, rtol, methodStr in ( 

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

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

688 ): 

689 self.runStdTest( 

690 kernel, 

691 kernelDescr="Spatially varying LinearCombinationKernel of basis " 

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

693 methodStr,), 

694 maxInterpDist=maxInterpDist, 

695 rtol=rtol) 

696 

697 

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

699 pass 

700 

701 

702def setup_module(module): 

703 lsst.utils.tests.init() 

704 

705 

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

707 lsst.utils.tests.init() 

708 unittest.main()