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
« 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/>.
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
42from test_kernel import makeDeltaFunctionKernelList, makeGaussianKernelList
43from lsst.log import Log
45import lsst.afw.display as afwDisplay
47Log.getLogger("lsst.afw.image.Mask").setLevel(Log.INFO)
48afwDisplay.setDefaultMaskTransparency(75)
50try:
51 display
52except NameError:
53 display = False
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)
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))
70EdgeMaskPixel = 1 << afwImage.Mask.getMaskPlane("EDGE")
71NoDataMaskPixel = afwImage.Mask.getPlaneBitMask("NO_DATA")
73# Ignore kernel pixels whose value is exactly 0 when smearing the mask plane?
74# Set this to match the afw code
75IgnoreKernelZeroPixels = True
77GarbageChars = string.punctuation + string.whitespace
80def refConvolve(imMaskVar, xy0, kernel, doNormalize, doCopyEdge):
81 """Reference code to convolve a kernel with a masked image.
83 Warning: slow (especially for spatially varying kernels).
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())
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
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))
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()
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)
158 retCol += 1
159 retRow += 1
160 return [numpy.copy(numpy.transpose(arr), order="C") for arr in (retImage, retMask, retVariance)]
163def sameMaskPlaneDicts(maskedImageA, maskedImageB):
164 """Return True if the mask plane dicts are the same, False otherwise.
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
181class ConvolveTestCase(lsst.utils.tests.TestCase):
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()
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)
201 self.width = self.maskedImage.getWidth()
202 self.height = self.maskedImage.getHeight()
204 def tearDown(self):
205 if dataDir is not None:
206 del self.maskedImage
207 del self.cnvMaskedImage
208 del self.cnvImage
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)
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.
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)
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)
240 doNormalize = convControl.getDoNormalize()
241 doCopyEdge = convControl.getDoCopyEdge()
242 maxInterpDist = convControl.getMaxInterpolationDistance()
244 imMaskVar = self.maskedImage.getArrays()
245 xy0 = self.maskedImage.getXY0()
247 refCnvImMaskVarArr = refConvolve(
248 imMaskVar, xy0, refKernel, doNormalize, doCopyEdge)
249 refMaskedImage = afwImage.makeMaskedImageFromArrays(
250 *refCnvImMaskVarArr)
252 afwMath.convolve(
253 self.cnvImage, self.maskedImage.getImage(), kernel, convControl)
254 self.assertEqual(self.cnvImage.getXY0(), self.xy0)
256 afwMath.convolve(self.cnvMaskedImage,
257 self.maskedImage, kernel, convControl)
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]))
268 self.assertImagesAlmostEqual(
269 self.cnvImage, refMaskedImage.getImage(), atol=atol, rtol=rtol)
270 self.assertMaskedImagesAlmostEqual(
271 self.cnvMaskedImage, refMaskedImage, atol=atol, rtol=rtol)
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"))
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.
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
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)
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)
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)
321 # verify that basicConvolve does not write to edge pixels
322 self.runBasicConvolveEdgeTest(kernel, kernelDescr)
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)
339 # convolve with basicConvolve, which should leave the edge pixels alone
340 convControl = afwMath.ConvolutionControl()
341 mathDetail.basicConvolve(
342 cnvMaskedImage, self.maskedImage, kernel, convControl)
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
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
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)
374 self.assertFalse(convControl.getDoCopyEdge())
375 for doCopyEdge in (False, True):
376 convControl.setDoCopyEdge(doCopyEdge)
377 self.assertEqual(convControl.getDoCopyEdge(), doCopyEdge)
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)
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)
396 afwMath.convolve(self.cnvImage, self.maskedImage.getImage(),
397 kernel, convControl)
399 afwMath.convolve(self.cnvMaskedImage, self.maskedImage,
400 kernel, convControl)
401 cnvImMaskVarArr = self.cnvMaskedImage.getArrays()
403 skipMaskArr = numpy.array(numpy.isnan(
404 cnvImMaskVarArr[0]), dtype=numpy.uint16)
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)
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
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)
425 self.runStdTest(fixedKernel, kernelDescr="Gaussian FixedKernel")
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
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)
440 self.runStdTest(
441 separableKernel,
442 refKernel=analyticKernel,
443 kernelDescr="Gaussian Separable Kernel (compared to AnalyticKernel equivalent)")
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
452 kFunc = afwMath.GaussianFunction2D(2.5, 1.5, 0.5)
453 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc)
455 self.runStdTest(kernel, kernelDescr="Gaussian Analytic Kernel")
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
464 # create spatial model
465 sFunc = afwMath.PolynomialFunction2D(1)
467 minSigma = 1.5
468 maxSigma = 1.501
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 )
478 kFunc = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
479 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc, sFunc)
480 kernel.setSpatialParameters(sParams)
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)
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
500 # create spatial model
501 sFunc = afwMath.PolynomialFunction2D(1)
503 minSigma = 0.1
504 maxSigma = 3.0
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 )
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)
523 self.runStdTest(separableKernel, refKernel=analyticKernel,
524 kernelDescr="Spatially Varying Gaussian Separable Kernel")
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")
542 self.runStdTest(
543 kernel, kernelDescr="Delta Function Kernel")
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
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)
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]
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)
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)
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
596 # create spatially model
597 sFunc = afwMath.PolynomialFunction2D(1)
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 )
608 basisKernelList = makeDeltaFunctionKernelList(kWidth, kHeight)
609 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc)
610 kernel.setSpatialParameters(sParams)
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)
623 @unittest.skipIf(dataDir is None, "afwdata not setup")
624 def testZeroWidthKernel(self):
625 """Convolution by a 0x0 kernel should raise an exception.
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)
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)
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 )
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))
681 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc)
682 kernel.setSpatialParameters(sParams)
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)
697class MemoryTester(lsst.utils.tests.MemoryTestCase):
698 pass
701def setup_module(module):
702 lsst.utils.tests.init()
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()