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 doNormalize = False 

394 doCopyEdge = False 

395 

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

397 kernel, doNormalize, doCopyEdge) 

398 

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

400 kernel, doNormalize, doCopyEdge) 

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