Coverage for tests/test_convolve.py: 23%
322 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 04:03 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-06 04:03 -0700
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.image.array,
245 self.maskedImage.mask.array,
246 self.maskedImage.variance.array)
247 xy0 = self.maskedImage.getXY0()
249 refCnvImMaskVarArr = refConvolve(
250 imMaskVar, xy0, refKernel, doNormalize, doCopyEdge)
251 refMaskedImage = afwImage.makeMaskedImageFromArrays(
252 *refCnvImMaskVarArr)
254 afwMath.convolve(
255 self.cnvImage, self.maskedImage.getImage(), kernel, convControl)
256 self.assertEqual(self.cnvImage.getXY0(), self.xy0)
258 afwMath.convolve(self.cnvMaskedImage,
259 self.maskedImage, kernel, convControl)
261 if display and False:
262 afwDisplay.Display(frame=0).mtv(afwDisplay.utils.Mosaic().makeMosaic([
263 self.maskedImage, refMaskedImage, self.cnvMaskedImage]),
264 title=self._testMethodName + " mosaic")
265 if False:
266 for (x, y) in ((0, 0), (1, 0), (0, 1), (50, 50)):
267 print("Mask(%d,%d) 0x%x 0x%x" % (x, y, refMaskedImage.getMask()[x, y, afwImage.LOCAL],
268 self.cnvMaskedImage.getMask()[x, y, afwImage.LOCAL]))
270 self.assertImagesAlmostEqual(
271 self.cnvImage, refMaskedImage.getImage(), atol=atol, rtol=rtol)
272 self.assertMaskedImagesAlmostEqual(
273 self.cnvMaskedImage, refMaskedImage, atol=atol, rtol=rtol)
275 if not sameMaskPlaneDicts(self.cnvMaskedImage, self.maskedImage):
276 self.cnvMaskedImage.writeFits("act%s" % (shortKernelDescr,))
277 refMaskedImage.writeFits("des%s" % (shortKernelDescr,))
278 self.fail("convolve(MaskedImage, kernel=%s, doNormalize=%s, "
279 "doCopyEdge=%s, maxInterpDist=%s) failed:\n%s" %
280 (kernelDescr, doNormalize, doCopyEdge, maxInterpDist,
281 "convolved mask dictionary does not match input"))
283 def runStdTest(self, kernel, refKernel=None, kernelDescr="", rtol=1.0e-05, atol=1e-08,
284 maxInterpDist=10):
285 """Assert that afwMath::convolve gives the same result as reference convolution for a given kernel.
287 Inputs:
288 - kernel: convolution kernel
289 - refKernel: kernel to use for refConvolve (if None then kernel is used)
290 - kernelDescr: description of kernel
291 - rtol: relative tolerance (see below)
292 - atol: absolute tolerance (see below)
293 - maxInterpDist: maximum allowed distance for linear interpolation during convolution
295 rtol and atol are positive, typically very small numbers.
296 The relative difference (rtol * abs(b)) and the absolute difference "atol" are added together
297 to compare against the absolute difference between "a" and "b".
298 """
299 convControl = afwMath.ConvolutionControl()
300 convControl.setMaxInterpolationDistance(maxInterpDist)
302 # verify dimension assertions:
303 # - output image dimensions = input image dimensions
304 # - input image width and height >= kernel width and height
305 # Note: the assertion kernel size > 0 is tested elsewhere
306 for inWidth in (kernel.getWidth() - 1, self.width-1, self.width, self.width + 1):
307 for inHeight in (kernel.getHeight() - 1, self.width-1, self.width, self.width + 1):
308 if (inWidth == self.width) and (inHeight == self.height):
309 continue
310 inMaskedImage = afwImage.MaskedImageF(
311 lsst.geom.Extent2I(inWidth, inHeight))
312 with self.assertRaises(Exception):
313 afwMath.convolve(self.cnvMaskedImage,
314 inMaskedImage, kernel)
316 for doNormalize in (True,): # (False, True):
317 convControl.setDoNormalize(doNormalize)
318 for doCopyEdge in (False,): # (False, True):
319 convControl.setDoCopyEdge(doCopyEdge)
320 self.runBasicTest(kernel, convControl=convControl, refKernel=refKernel,
321 kernelDescr=kernelDescr, rtol=rtol, atol=atol)
323 # verify that basicConvolve does not write to edge pixels
324 self.runBasicConvolveEdgeTest(kernel, kernelDescr)
326 def runBasicConvolveEdgeTest(self, kernel, kernelDescr):
327 """Verify that basicConvolve does not write to edge pixels for this kind of kernel
328 """
329 fullBox = lsst.geom.Box2I(
330 lsst.geom.Point2I(0, 0),
331 ShiftedBBox.getDimensions(),
332 )
333 goodBox = kernel.shrinkBBox(fullBox)
334 cnvMaskedImage = afwImage.MaskedImageF(
335 FullMaskedImage, ShiftedBBox, afwImage.LOCAL, True)
336 cnvMaskedImageCopy = afwImage.MaskedImageF(
337 cnvMaskedImage, fullBox, afwImage.LOCAL, True)
338 cnvMaskedImageCopyViewOfGoodRegion = afwImage.MaskedImageF(
339 cnvMaskedImageCopy, goodBox, afwImage.LOCAL, False)
341 # convolve with basicConvolve, which should leave the edge pixels alone
342 convControl = afwMath.ConvolutionControl()
343 mathDetail.basicConvolve(
344 cnvMaskedImage, self.maskedImage, kernel, convControl)
346 # reset the good region to the original convolved image;
347 # this should reset the entire convolved image to its original self
348 cnvMaskedImageGoodView = afwImage.MaskedImageF(
349 cnvMaskedImage, goodBox, afwImage.LOCAL, False)
350 cnvMaskedImageGoodView[:] = cnvMaskedImageCopyViewOfGoodRegion
352 # assert that these two are equal
353 msg = "basicConvolve(MaskedImage, kernel=%s) wrote to edge pixels" % (
354 kernelDescr,)
355 try:
356 self.assertMaskedImagesAlmostEqual(cnvMaskedImage, cnvMaskedImageCopy,
357 doVariance=True, rtol=0, atol=0, msg=msg)
358 except Exception:
359 # write out the images, then fail
360 shortKernelDescr = self.removeGarbageChars(kernelDescr)
361 cnvMaskedImage.writeFits(
362 "actBasicConvolve%s" % (shortKernelDescr,))
363 cnvMaskedImageCopy.writeFits(
364 "desBasicConvolve%s" % (shortKernelDescr,))
365 raise
367 def testConvolutionControl(self):
368 """Test the ConvolutionControl object
369 """
370 convControl = afwMath.ConvolutionControl()
371 self.assertTrue(convControl.getDoNormalize())
372 for doNormalize in (False, True):
373 convControl.setDoNormalize(doNormalize)
374 self.assertEqual(convControl.getDoNormalize(), doNormalize)
376 self.assertFalse(convControl.getDoCopyEdge())
377 for doCopyEdge in (False, True):
378 convControl.setDoCopyEdge(doCopyEdge)
379 self.assertEqual(convControl.getDoCopyEdge(), doCopyEdge)
381 self.assertEqual(convControl.getMaxInterpolationDistance(), 10)
382 for maxInterpDist in (0, 1, 2, 10, 100):
383 convControl.setMaxInterpolationDistance(maxInterpDist)
384 self.assertEqual(
385 convControl.getMaxInterpolationDistance(), maxInterpDist)
387 @unittest.skipIf(dataDir is None, "afwdata not setup")
388 def testUnityConvolution(self):
389 """Verify that convolution with a centered delta function reproduces the original.
390 """
391 # create a delta function kernel that has 1,1 in the center
392 kFunc = afwMath.IntegerDeltaFunction2D(0.0, 0.0)
393 kernel = afwMath.AnalyticKernel(3, 3, kFunc)
394 convControl = afwMath.ConvolutionControl()
395 convControl.setDoNormalize(False)
396 convControl.setDoCopyEdge(False)
398 afwMath.convolve(self.cnvImage, self.maskedImage.getImage(),
399 kernel, convControl)
401 afwMath.convolve(self.cnvMaskedImage, self.maskedImage,
402 kernel, convControl)
403 cnvImMaskVarArr = (self.cnvMaskedImage.image.array,
404 self.cnvMaskedImage.mask.array,
405 self.cnvMaskedImage.variance.array)
407 skipMaskArr = numpy.array(numpy.isnan(
408 cnvImMaskVarArr[0]), dtype=numpy.uint16)
410 kernelDescr = "Centered DeltaFunctionKernel (testing unity convolution)"
411 self.assertImagesAlmostEqual(self.cnvImage, self.maskedImage.getImage(),
412 skipMask=skipMaskArr, msg=kernelDescr)
413 self.assertMaskedImagesAlmostEqual(self.cnvMaskedImage, self.maskedImage,
414 skipMask=skipMaskArr, msg=kernelDescr)
416 @unittest.skipIf(dataDir is None, "afwdata not setup")
417 def testFixedKernelConvolve(self):
418 """Test convolve with a fixed kernel
419 """
420 kWidth = 6
421 kHeight = 7
423 kFunc = afwMath.GaussianFunction2D(2.5, 1.5, 0.5)
424 analyticKernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc)
425 kernelImage = afwImage.ImageD(lsst.geom.Extent2I(kWidth, kHeight))
426 analyticKernel.computeImage(kernelImage, False)
427 fixedKernel = afwMath.FixedKernel(kernelImage)
429 self.runStdTest(fixedKernel, kernelDescr="Gaussian FixedKernel")
431 @unittest.skipIf(dataDir is None, "afwdata not setup")
432 def testSeparableConvolve(self):
433 """Test convolve of a separable kernel with a spatially invariant Gaussian function
434 """
435 kWidth = 7
436 kHeight = 6
438 gaussFunc1 = afwMath.GaussianFunction1D(1.0)
439 gaussFunc2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
440 separableKernel = afwMath.SeparableKernel(
441 kWidth, kHeight, gaussFunc1, gaussFunc1)
442 analyticKernel = afwMath.AnalyticKernel(kWidth, kHeight, gaussFunc2)
444 self.runStdTest(
445 separableKernel,
446 refKernel=analyticKernel,
447 kernelDescr="Gaussian Separable Kernel (compared to AnalyticKernel equivalent)")
449 @unittest.skipIf(dataDir is None, "afwdata not setup")
450 def testSpatiallyInvariantConvolve(self):
451 """Test convolution with a spatially invariant Gaussian function
452 """
453 kWidth = 6
454 kHeight = 7
456 kFunc = afwMath.GaussianFunction2D(2.5, 1.5, 0.5)
457 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc)
459 self.runStdTest(kernel, kernelDescr="Gaussian Analytic Kernel")
461 @unittest.skipIf(dataDir is None, "afwdata not setup")
462 def testSpatiallyVaryingAnalyticConvolve(self):
463 """Test in-place convolution with a spatially varying AnalyticKernel
464 """
465 kWidth = 7
466 kHeight = 6
468 # create spatial model
469 sFunc = afwMath.PolynomialFunction2D(1)
471 minSigma = 1.5
472 maxSigma = 1.501
474 # spatial parameters are a list of entries, one per kernel parameter;
475 # each entry is a list of spatial parameters
476 sParams = (
477 (minSigma, (maxSigma - minSigma) / self.width, 0.0),
478 (minSigma, 0.0, (maxSigma - minSigma) / self.height),
479 (0.0, 0.0, 0.0),
480 )
482 kFunc = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
483 kernel = afwMath.AnalyticKernel(kWidth, kHeight, kFunc, sFunc)
484 kernel.setSpatialParameters(sParams)
486 for maxInterpDist, rtol, methodStr in (
487 (0, 1.0e-5, "brute force"),
488 (10, 1.0e-5, "interpolation over 10 x 10 pixels"),
489 ):
490 self.runStdTest(
491 kernel,
492 kernelDescr="Spatially Varying Gaussian Analytic Kernel using %s" % (
493 methodStr,),
494 maxInterpDist=maxInterpDist,
495 rtol=rtol)
497 @unittest.skipIf(dataDir is None, "afwdata not setup")
498 def testSpatiallyVaryingSeparableConvolve(self):
499 """Test convolution with a spatially varying SeparableKernel
500 """
501 kWidth = 7
502 kHeight = 6
504 # create spatial model
505 sFunc = afwMath.PolynomialFunction2D(1)
507 minSigma = 0.1
508 maxSigma = 3.0
510 # spatial parameters are a list of entries, one per kernel parameter;
511 # each entry is a list of spatial parameters
512 sParams = (
513 (minSigma, (maxSigma - minSigma) / self.width, 0.0),
514 (minSigma, 0.0, (maxSigma - minSigma) / self.height),
515 (0.0, 0.0, 0.0),
516 )
518 gaussFunc1 = afwMath.GaussianFunction1D(1.0)
519 gaussFunc2 = afwMath.GaussianFunction2D(1.0, 1.0, 0.0)
520 separableKernel = afwMath.SeparableKernel(
521 kWidth, kHeight, gaussFunc1, gaussFunc1, sFunc)
522 analyticKernel = afwMath.AnalyticKernel(
523 kWidth, kHeight, gaussFunc2, sFunc)
524 separableKernel.setSpatialParameters(sParams[0:2])
525 analyticKernel.setSpatialParameters(sParams)
527 self.runStdTest(separableKernel, refKernel=analyticKernel,
528 kernelDescr="Spatially Varying Gaussian Separable Kernel")
530 @unittest.skipIf(dataDir is None, "afwdata not setup")
531 def testDeltaConvolve(self):
532 """Test convolution with various delta function kernels using optimized code
533 """
534 for kWidth in range(1, 4):
535 for kHeight in range(1, 4):
536 for activeCol in range(kWidth):
537 for activeRow in range(kHeight):
538 kernel = afwMath.DeltaFunctionKernel(
539 kWidth, kHeight,
540 lsst.geom.Point2I(activeCol, activeRow))
541 if display and False:
542 kim = afwImage.ImageD(kWidth, kHeight)
543 kernel.computeImage(kim, False)
544 afwDisplay.Display(frame=1).mtv(kim, title=self._testMethodName + " image")
546 self.runStdTest(
547 kernel, kernelDescr="Delta Function Kernel")
549 @unittest.skipIf(dataDir is None, "afwdata not setup")
550 def testSpatiallyVaryingGaussianLinerCombination(self):
551 """Test convolution with a spatially varying LinearCombinationKernel of two Gaussian basis kernels.
552 """
553 kWidth = 5
554 kHeight = 5
556 # create spatial model
557 for nBasisKernels in (3, 4):
558 # at 3 the kernel will not be refactored, at 4 it will be
559 sFunc = afwMath.PolynomialFunction2D(1)
561 # spatial parameters are a list of entries, one per kernel parameter;
562 # each entry is a list of spatial parameters
563 sParams = (
564 (1.0, -0.01/self.width, -0.01/self.height),
565 (0.0, 0.01/self.width, 0.0/self.height),
566 (0.0, 0.0/self.width, 0.01/self.height),
567 (0.5, 0.005/self.width, -0.005/self.height),
568 )[:nBasisKernels]
570 gaussParamsList = (
571 (1.5, 1.5, 0.0),
572 (2.5, 1.5, 0.0),
573 (2.5, 1.5, math.pi / 2.0),
574 (2.5, 2.5, 0.0),
575 )[:nBasisKernels]
576 basisKernelList = makeGaussianKernelList(
577 kWidth, kHeight, gaussParamsList)
578 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc)
579 kernel.setSpatialParameters(sParams)
581 for maxInterpDist, rtol, methodStr in (
582 (0, 1.0e-5, "brute force"),
583 (10, 1.0e-5, "interpolation over 10 x 10 pixels"),
584 ):
585 self.runStdTest(
586 kernel,
587 kernelDescr="%s with %d basis kernels convolved using %s" %
588 ("Spatially Varying Gaussian Analytic Kernel",
589 nBasisKernels, methodStr),
590 maxInterpDist=maxInterpDist,
591 rtol=rtol)
593 @unittest.skipIf(dataDir is None, "afwdata not setup")
594 def testSpatiallyVaryingDeltaFunctionLinearCombination(self):
595 """Test convolution with a spatially varying LinearCombinationKernel of delta function basis kernels.
596 """
597 kWidth = 2
598 kHeight = 2
600 # create spatially model
601 sFunc = afwMath.PolynomialFunction2D(1)
603 # spatial parameters are a list of entries, one per kernel parameter;
604 # each entry is a list of spatial parameters
605 sParams = (
606 (1.0, -0.5/self.width, -0.5/self.height),
607 (0.0, 1.0/self.width, 0.0/self.height),
608 (0.0, 0.0/self.width, 1.0/self.height),
609 (0.5, 0.0, 0.0),
610 )
612 basisKernelList = makeDeltaFunctionKernelList(kWidth, kHeight)
613 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc)
614 kernel.setSpatialParameters(sParams)
616 for maxInterpDist, rtol, methodStr in (
617 (0, 1.0e-5, "brute force"),
618 (10, 1.0e-3, "interpolation over 10 x 10 pixels"),
619 ):
620 self.runStdTest(
621 kernel,
622 kernelDescr="Spatially varying LinearCombinationKernel of delta function kernels using %s" %
623 (methodStr,),
624 maxInterpDist=maxInterpDist,
625 rtol=rtol)
627 @unittest.skipIf(dataDir is None, "afwdata not setup")
628 def testZeroWidthKernel(self):
629 """Convolution by a 0x0 kernel should raise an exception.
631 The only way to produce a 0x0 kernel is to use the default constructor
632 (which exists only to support persistence; it does not produce a useful kernel).
633 """
634 kernelList = [
635 afwMath.FixedKernel(),
636 afwMath.AnalyticKernel(),
637 afwMath.SeparableKernel(),
638 # afwMath.DeltaFunctionKernel(), # DeltaFunctionKernel has no
639 # default constructor
640 afwMath.LinearCombinationKernel(),
641 ]
642 convolutionControl = afwMath.ConvolutionControl()
643 for kernel in kernelList:
644 with self.assertRaises(Exception):
645 afwMath.convolve(self.cnvMaskedImage,
646 self.maskedImage, kernel, convolutionControl)
648 @unittest.skipIf(dataDir is None, "afwdata not setup")
649 def testTicket873(self):
650 """Demonstrate ticket 873: convolution of a MaskedImage with a spatially varying
651 LinearCombinationKernel of basis kernels with low covariance gives incorrect variance.
652 """
653 # create spatial model
654 sFunc = afwMath.PolynomialFunction2D(1)
656 # spatial parameters are a list of entries, one per kernel parameter;
657 # each entry is a list of spatial parameters
658 sParams = (
659 (1.0, -0.5/self.width, -0.5/self.height),
660 (0.0, 1.0/self.width, 0.0/self.height),
661 (0.0, 0.0/self.width, 1.0/self.height),
662 )
664 # create three kernels with some non-overlapping pixels
665 # (non-zero pixels in one kernel vs. zero pixels in other kernels);
666 # note: the extreme example of this is delta function kernels, but this
667 # is less extreme
668 basisKernelList = []
669 kImArr = numpy.zeros([5, 5], dtype=float)
670 kImArr[1:4, 1:4] = 0.5
671 kImArr[2, 2] = 1.0
672 kImage = afwImage.makeImageFromArray(kImArr)
673 basisKernelList.append(afwMath.FixedKernel(kImage))
674 kImArr[:, :] = 0.0
675 kImArr[0:2, 0:2] = 0.125
676 kImArr[3:5, 3:5] = 0.125
677 kImage = afwImage.makeImageFromArray(kImArr)
678 basisKernelList.append(afwMath.FixedKernel(kImage))
679 kImArr[:, :] = 0.0
680 kImArr[0:2, 3:5] = 0.125
681 kImArr[3:5, 0:2] = 0.125
682 kImage = afwImage.makeImageFromArray(kImArr)
683 basisKernelList.append(afwMath.FixedKernel(kImage))
685 kernel = afwMath.LinearCombinationKernel(basisKernelList, sFunc)
686 kernel.setSpatialParameters(sParams)
688 for maxInterpDist, rtol, methodStr in (
689 (0, 1.0e-5, "brute force"),
690 (10, 3.0e-3, "interpolation over 10 x 10 pixels"),
691 ):
692 self.runStdTest(
693 kernel,
694 kernelDescr="Spatially varying LinearCombinationKernel of basis "
695 "kernels with low covariance, using %s" % (
696 methodStr,),
697 maxInterpDist=maxInterpDist,
698 rtol=rtol)
701class MemoryTester(lsst.utils.tests.MemoryTestCase):
702 pass
705def setup_module(module):
706 lsst.utils.tests.init()
709if __name__ == "__main__": 709 ↛ 710line 709 didn't jump to line 710, because the condition on line 709 was never true
710 lsst.utils.tests.init()
711 unittest.main()