Coverage for tests/test_offsetImage.py: 20%
148 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"""
23Tests for offsetting images in (dx, dy)
25Run with:
26 python test_offsetImage.py
27or
28 pytest test_offsetImage.py
29"""
30import math
31import unittest
33import numpy as np
35import lsst.utils.tests
36import lsst.geom
37import lsst.afw.image as afwImage
38import lsst.afw.math as afwMath
39import lsst.afw.display as afwDisplay
41try:
42 type(display)
43except NameError:
44 display = False
47class OffsetImageTestCase(unittest.TestCase):
48 """A test case for offsetImage.
49 """
51 def setUp(self):
52 self.inImage = afwImage.ImageF(200, 100)
53 self.background = 200
54 self.inImage.set(self.background)
56 def tearDown(self):
57 del self.inImage
59 def testSetFluxConvervation(self):
60 """Test that flux is preserved.
61 """
62 for algorithm in ("lanczos5", "bilinear", "nearest"):
63 outImage = afwMath.offsetImage(self.inImage, 0, 0, algorithm)
64 self.assertEqual(outImage[50, 50, afwImage.LOCAL], self.background)
66 outImage = afwMath.offsetImage(self.inImage, 0.5, 0, algorithm)
67 self.assertAlmostEqual(outImage[50, 50, afwImage.LOCAL], self.background, 4)
69 outImage = afwMath.offsetImage(self.inImage, 0.5, 0.5, algorithm)
70 self.assertAlmostEqual(outImage[50, 50, afwImage.LOCAL], self.background, 4)
72 def testSetIntegerOffset(self):
73 """Test that we can offset by positive and negative amounts.
74 """
75 self.inImage[50, 50, afwImage.LOCAL] = 400
77 if False and display:
78 frame = 0
79 disp = afwDisplay.Display(frame=frame)
80 disp.mtv(self.inImage, title="Image for Integer Offset Test")
81 disp.pan(50, 50)
82 disp.dot("+", 50, 50)
84 for algorithm in ("lanczos5", "bilinear", "nearest"):
85 frame = 1
86 for delta in [-0.49, 0.51]:
87 for dx, dy in [(2, 3), (-2, 3), (-2, -3), (2, -3)]:
88 outImage = afwMath.offsetImage(
89 self.inImage, dx + delta, dy + delta, algorithm)
91 if False and display:
92 frame += 1
93 disp = afwDisplay.Display(frame=frame)
94 disp.mtv(outImage, title=f"{algorithm}: offset image (dx, dy) = ({dx}, {dy})")
96 disp.pan(50, 50)
97 disp.dot("+", 50 + dx + delta - outImage.getX0(), 50 + dy + delta - outImage.getY0())
99 def calcGaussian(self, im, x, y, amp, sigma1):
100 """Insert a Gaussian into the image centered at (x, y).
101 """
102 x = x - im.getX0()
103 y = y - im.getY0()
105 for ix in range(im.getWidth()):
106 for iy in range(im.getHeight()):
107 r2 = math.pow(x - ix, 2) + math.pow(y - iy, 2)
108 val = math.exp(-r2/(2.0*pow(sigma1, 2)))
109 im[ix, iy, afwImage.LOCAL] = amp*val
111 def testOffsetGaussian(self):
112 """Insert a Gaussian, offset, and check the residuals.
113 """
114 size = 50
115 refIm = afwImage.ImageF(size, size)
116 unshiftedIm = afwImage.ImageF(size, size)
118 xc, yc = size/2.0, size/2.0
120 amp, sigma1 = 1.0, 3
122 #
123 # Calculate Gaussian directly at (xc, yc)
124 #
125 self.calcGaussian(refIm, xc, yc, amp, sigma1)
127 for dx in (-55.5, -1.500001, -1.5, -1.499999, -1.00001, -1.0, -0.99999, -0.5,
128 0.0, 0.5, 0.99999, 1.0, 1.00001, 1.499999, 1.5, 1.500001, 99.3):
129 for dy in (-3.7, -1.500001, -1.5, -1.499999, -1.00001, -1.0, -0.99999, -0.5,
130 0.0, 0.5, 0.99999, 1.0, 1.00001, 1.499999, 1.5, 1.500001, 2.99999):
131 dOrigX, dOrigY, dFracX, dFracY = getOrigFracShift(dx, dy)
132 self.calcGaussian(unshiftedIm, xc - dFracX,
133 yc - dFracY, amp, sigma1)
135 for algorithm, maxMean, maxLim in (
136 ("lanczos5", 1e-8, 0.0015),
137 ("bilinear", 1e-8, 0.03),
138 ("nearest", 1e-8, 0.2),
139 ):
140 im = afwImage.ImageF(size, size)
141 im = afwMath.offsetImage(unshiftedIm, dx, dy, algorithm)
143 if display:
144 afwDisplay.Display(frame=0).mtv(im, title=f"{algorithm}: image")
146 im -= refIm
148 if display:
149 afwDisplay.Display(frame=1).mtv(im, title=f"{algorithm}: diff image ({dx}, {dy})")
151 imArr = im.getArray()
152 imGoodVals = np.ma.array(
153 imArr, copy=False, mask=np.isnan(imArr)).compressed()
155 try:
156 imXY0 = tuple(im.getXY0())
157 self.assertEqual(imXY0, (dOrigX, dOrigY))
158 self.assertLess(abs(imGoodVals.mean()), maxMean*amp)
159 self.assertLess(abs(imGoodVals.max()), maxLim*amp)
160 self.assertLess(abs(imGoodVals.min()), maxLim*amp)
161 except Exception:
162 print(f"failed on algorithm={algorithm}; dx = {dx}; dy = {dy}")
163 raise
165# the following would be preferable if there was an easy way to NaN pixels
166#
167# stats = afwMath.makeStatistics(im, afwMath.MEAN | afwMath.MAX | afwMath.MIN)
168#
169# if not False:
170# print "mean = %g, min = %g, max = %g" % (stats.getValue(afwMath.MEAN),
171# stats.getValue(afwMath.MIN),
172# stats.getValue(afwMath.MAX))
173#
174# self.assertTrue(abs(stats.getValue(afwMath.MEAN)) < 1e-7)
175# self.assertTrue(abs(stats.getValue(afwMath.MIN)) < 1.2e-3*amp)
176# self.assertTrue(abs(stats.getValue(afwMath.MAX)) < 1.2e-3*amp)
179def getOrigFracShift(dx, dy):
180 """Return the predicted integer shift to XY0 and the fractional shift that offsetImage will use
182 offsetImage preserves the origin if dx and dy both < 1 pixel; larger shifts are to the nearest pixel.
183 """
184 if (abs(dx) < 1) and (abs(dy) < 1):
185 return (0, 0, dx, dy)
187 dOrigX = math.floor(dx + 0.5)
188 dOrigY = math.floor(dy + 0.5)
189 dFracX = dx - dOrigX
190 dFracY = dy - dOrigY
191 return (int(dOrigX), int(dOrigY), dFracX, dFracY)
194class TransformImageTestCase(unittest.TestCase):
195 """A test case for rotating images.
196 """
198 def setUp(self):
199 self.inImage = afwImage.ImageF(20, 10)
200 self.inImage[0, 0, afwImage.LOCAL] = 100
201 self.inImage[10, 0, afwImage.LOCAL] = 50
203 def tearDown(self):
204 del self.inImage
206 def testRotate(self):
207 """Test that we end up with the correct image after rotating by 90 degrees.
208 """
209 for nQuarter, x, y in [(0, 0, 0),
210 (1, 9, 0),
211 (2, 19, 9),
212 (3, 0, 19)]:
213 outImage = afwMath.rotateImageBy90(self.inImage, nQuarter)
214 if display:
215 afwDisplay.Display(frame=nQuarter).mtv(outImage, title=f"out {nQuarter}")
216 self.assertEqual(self.inImage[0, 0, afwImage.LOCAL], outImage[x, y, afwImage.LOCAL])
218 def testFlip(self):
219 """Test that we end up with the correct image after flipping it.
220 """
221 frame = 2
222 for flipLR, flipTB, x, y in [(True, False, 19, 0),
223 (True, True, 19, 9),
224 (False, True, 0, 9),
225 (False, False, 0, 0)]:
226 outImage = afwMath.flipImage(self.inImage, flipLR, flipTB)
227 if display:
228 afwDisplay.Display(frame=frame).mtv(outImage, title=f"{flipLR} {flipTB}")
229 frame += 1
230 self.assertEqual(self.inImage[0, 0, afwImage.LOCAL], outImage[x, y, afwImage.LOCAL])
232 def testMask(self):
233 """Test that we can flip a Mask.
234 """
235 mask = afwImage.Mask(10, 20)
236 # for a while, swig couldn't handle the resulting std::shared_ptr<Mask>
237 afwMath.flipImage(mask, True, False)
240class BinImageTestCase(unittest.TestCase):
241 """A test case for binning images.
242 """
244 def setUp(self):
245 pass
247 def tearDown(self):
248 pass
250 def testBin(self):
251 """Test that we can bin images.
252 """
253 inImage = afwImage.ImageF(203, 131)
254 inImage.set(1)
255 bin = 4
257 outImage = afwMath.binImage(inImage, bin)
259 self.assertEqual(outImage.getWidth(), inImage.getWidth()//bin)
260 self.assertEqual(outImage.getHeight(), inImage.getHeight()//bin)
262 stats = afwMath.makeStatistics(outImage, afwMath.MAX | afwMath.MIN)
263 self.assertEqual(stats.getValue(afwMath.MIN), 1)
264 self.assertEqual(stats.getValue(afwMath.MAX), 1)
266 def testBin2(self):
267 """Test that we can bin images anisotropically.
268 """
269 inImage = afwImage.ImageF(203, 131)
270 val = 1
271 inImage.set(val)
272 binX, binY = 2, 4
274 outImage = afwMath.binImage(inImage, binX, binY)
276 self.assertEqual(outImage.getWidth(), inImage.getWidth()//binX)
277 self.assertEqual(outImage.getHeight(), inImage.getHeight()//binY)
279 stats = afwMath.makeStatistics(outImage, afwMath.MAX | afwMath.MIN)
280 self.assertEqual(stats.getValue(afwMath.MIN), val)
281 self.assertEqual(stats.getValue(afwMath.MAX), val)
283 inImage.set(0)
284 subImg = inImage.Factory(inImage, lsst.geom.BoxI(lsst.geom.PointI(4, 4), lsst.geom.ExtentI(4, 8)),
285 afwImage.LOCAL)
286 subImg.set(100)
287 del subImg
288 outImage = afwMath.binImage(inImage, binX, binY)
290 if display:
291 afwDisplay.Display(frame=2).mtv(inImage, title="unbinned")
292 afwDisplay.Display(frame=3).mtv(outImage, title=f"binned {binX}x{binY}")
295class TestMemory(lsst.utils.tests.MemoryTestCase):
296 pass
299def setup_module(module):
300 lsst.utils.tests.init()
303if __name__ == "__main__": 303 ↛ 304line 303 didn't jump to line 304, because the condition on line 303 was never true
304 lsst.utils.tests.init()
305 unittest.main()