Coverage for tests/test_convolve.py : 18%

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/>.
22"""Test lsst.afwMath.convolve
24Tests convolution of various kernels with Images and MaskedImages.
25"""
26import math
27import os
28import os.path
29import unittest
30import string
31import re
33import numpy
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
43from test_kernel import makeDeltaFunctionKernelList, makeGaussianKernelList
44from lsst.log import Log
46import lsst.afw.display as afwDisplay
48Log.getLogger("afw.image.Mask").setLevel(Log.INFO)
49afwDisplay.setDefaultMaskTransparency(75)
51try:
52 display
53except NameError:
54 display = False
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)
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))
71EdgeMaskPixel = 1 << afwImage.Mask.getMaskPlane("EDGE")
72NoDataMaskPixel = afwImage.Mask.getPlaneBitMask("NO_DATA")
74# Ignore kernel pixels whose value is exactly 0 when smearing the mask plane?
75# Set this to match the afw code
76IgnoreKernelZeroPixels = True
78GarbageChars = string.punctuation + string.whitespace
81def refConvolve(imMaskVar, xy0, kernel, doNormalize, doCopyEdge):
82 """Reference code to convolve a kernel with a masked image.
84 Warning: slow (especially for spatially varying kernels).
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())
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
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))
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()
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)
159 retCol += 1
160 retRow += 1
161 return [numpy.copy(numpy.transpose(arr), order="C") for arr in (retImage, retMask, retVariance)]
164def sameMaskPlaneDicts(maskedImageA, maskedImageB):
165 """Return True if the mask plane dicts are the same, False otherwise.
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
182class ConvolveTestCase(lsst.utils.tests.TestCase):
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()
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)
202 self.width = self.maskedImage.getWidth()
203 self.height = self.maskedImage.getHeight()
205 def tearDown(self):
206 if dataDir is not None:
207 del self.maskedImage
208 del self.cnvMaskedImage
209 del self.cnvImage
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)
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.
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)
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)
241 doNormalize = convControl.getDoNormalize()
242 doCopyEdge = convControl.getDoCopyEdge()
243 maxInterpDist = convControl.getMaxInterpolationDistance()
245 imMaskVar = self.maskedImage.getArrays()
246 xy0 = self.maskedImage.getXY0()
248 refCnvImMaskVarArr = refConvolve(
249 imMaskVar, xy0, refKernel, doNormalize, doCopyEdge)
250 refMaskedImage = afwImage.makeMaskedImageFromArrays(
251 *refCnvImMaskVarArr)
253 afwMath.convolve(
254 self.cnvImage, self.maskedImage.getImage(), kernel, convControl)
255 self.assertEqual(self.cnvImage.getXY0(), self.xy0)
257 afwMath.convolve(self.cnvMaskedImage,
258 self.maskedImage, kernel, convControl)
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]))
269 self.assertImagesAlmostEqual(
270 self.cnvImage, refMaskedImage.getImage(), atol=atol, rtol=rtol)
271 self.assertMaskedImagesAlmostEqual(
272 self.cnvMaskedImage, refMaskedImage, atol=atol, rtol=rtol)
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"))
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.
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
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)
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)
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)
322 # verify that basicConvolve does not write to edge pixels
323 self.runBasicConvolveEdgeTest(kernel, kernelDescr)
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)
340 # convolve with basicConvolve, which should leave the edge pixels alone
341 convControl = afwMath.ConvolutionControl()
342 mathDetail.basicConvolve(
343 cnvMaskedImage, self.maskedImage, kernel, convControl)
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
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
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)
375 self.assertFalse(convControl.getDoCopyEdge())
376 for doCopyEdge in (False, True):
377 convControl.setDoCopyEdge(doCopyEdge)
378 self.assertEqual(convControl.getDoCopyEdge(), doCopyEdge)
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)
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)
397 afwMath.convolve(self.cnvImage, self.maskedImage.getImage(),
398 kernel, convControl)
400 afwMath.convolve(self.cnvMaskedImage, self.maskedImage,
401 kernel, convControl)
402 cnvImMaskVarArr = self.cnvMaskedImage.getArrays()
404 skipMaskArr = numpy.array(numpy.isnan(
405 cnvImMaskVarArr[0]), dtype=numpy.uint16)
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)
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
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)
426 self.runStdTest(fixedKernel, kernelDescr="Gaussian FixedKernel")
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
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)
441 self.runStdTest(
442 separableKernel,
443 refKernel=analyticKernel,
444 kernelDescr="Gaussian Separable Kernel (compared to AnalyticKernel equivalent)")
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
453 kFunc = afwMath.GaussianFunction2D(2.5, 1.5, 0.5)
454 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc)
456 self.runStdTest(kernel, kernelDescr="Gaussian Analytic Kernel")
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
465 # create spatial model
466 sFunc = afwMath.PolynomialFunction2D(1)
468 minSigma = 1.5
469 maxSigma = 1.501
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 )
479 kFunc = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
480 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc, sFunc)
481 kernel.setSpatialParameters(sParams)
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)
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
501 # create spatial model
502 sFunc = afwMath.PolynomialFunction2D(1)
504 minSigma = 0.1
505 maxSigma = 3.0
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 )
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)
524 self.runStdTest(separableKernel, refKernel=analyticKernel,
525 kernelDescr="Spatially Varying Gaussian Separable Kernel")
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")
543 self.runStdTest(
544 kernel, kernelDescr="Delta Function Kernel")
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
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)
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]
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)
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)
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
597 # create spatially model
598 sFunc = afwMath.PolynomialFunction2D(1)
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 )
609 basisKernelList = makeDeltaFunctionKernelList(kWidth, kHeight)
610 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc)
611 kernel.setSpatialParameters(sParams)
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)
624 @unittest.skipIf(dataDir is None, "afwdata not setup")
625 def testZeroWidthKernel(self):
626 """Convolution by a 0x0 kernel should raise an exception.
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)
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)
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 )
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))
682 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc)
683 kernel.setSpatialParameters(sParams)
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)
698class MemoryTester(lsst.utils.tests.MemoryTestCase):
699 pass
702def setup_module(module):
703 lsst.utils.tests.init()
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()