Coverage for tests/test_offsetImage.py: 20%

148 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-11 02:44 -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/>. 

21 

22""" 

23Tests for offsetting images in (dx, dy) 

24 

25Run with: 

26 python test_offsetImage.py 

27or 

28 pytest test_offsetImage.py 

29""" 

30import math 

31import unittest 

32 

33import numpy as np 

34 

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 

40 

41try: 

42 type(display) 

43except NameError: 

44 display = False 

45 

46 

47class OffsetImageTestCase(unittest.TestCase): 

48 """A test case for offsetImage. 

49 """ 

50 

51 def setUp(self): 

52 self.inImage = afwImage.ImageF(200, 100) 

53 self.background = 200 

54 self.inImage.set(self.background) 

55 

56 def tearDown(self): 

57 del self.inImage 

58 

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) 

65 

66 outImage = afwMath.offsetImage(self.inImage, 0.5, 0, algorithm) 

67 self.assertAlmostEqual(outImage[50, 50, afwImage.LOCAL], self.background, 4) 

68 

69 outImage = afwMath.offsetImage(self.inImage, 0.5, 0.5, algorithm) 

70 self.assertAlmostEqual(outImage[50, 50, afwImage.LOCAL], self.background, 4) 

71 

72 def testSetIntegerOffset(self): 

73 """Test that we can offset by positive and negative amounts. 

74 """ 

75 self.inImage[50, 50, afwImage.LOCAL] = 400 

76 

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) 

83 

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) 

90 

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})") 

95 

96 disp.pan(50, 50) 

97 disp.dot("+", 50 + dx + delta - outImage.getX0(), 50 + dy + delta - outImage.getY0()) 

98 

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

104 

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 

110 

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) 

117 

118 xc, yc = size/2.0, size/2.0 

119 

120 amp, sigma1 = 1.0, 3 

121 

122 # 

123 # Calculate Gaussian directly at (xc, yc) 

124 # 

125 self.calcGaussian(refIm, xc, yc, amp, sigma1) 

126 

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) 

134 

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) 

142 

143 if display: 

144 afwDisplay.Display(frame=0).mtv(im, title=f"{algorithm}: image") 

145 

146 im -= refIm 

147 

148 if display: 

149 afwDisplay.Display(frame=1).mtv(im, title=f"{algorithm}: diff image ({dx}, {dy})") 

150 

151 imArr = im.getArray() 

152 imGoodVals = np.ma.array( 

153 imArr, copy=False, mask=np.isnan(imArr)).compressed() 

154 

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 

164 

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) 

177 

178 

179def getOrigFracShift(dx, dy): 

180 """Return the predicted integer shift to XY0 and the fractional shift that offsetImage will use 

181 

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) 

186 

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) 

192 

193 

194class TransformImageTestCase(unittest.TestCase): 

195 """A test case for rotating images. 

196 """ 

197 

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 

202 

203 def tearDown(self): 

204 del self.inImage 

205 

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

217 

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

231 

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) 

238 

239 

240class BinImageTestCase(unittest.TestCase): 

241 """A test case for binning images. 

242 """ 

243 

244 def setUp(self): 

245 pass 

246 

247 def tearDown(self): 

248 pass 

249 

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 

256 

257 outImage = afwMath.binImage(inImage, bin) 

258 

259 self.assertEqual(outImage.getWidth(), inImage.getWidth()//bin) 

260 self.assertEqual(outImage.getHeight(), inImage.getHeight()//bin) 

261 

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) 

265 

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 

273 

274 outImage = afwMath.binImage(inImage, binX, binY) 

275 

276 self.assertEqual(outImage.getWidth(), inImage.getWidth()//binX) 

277 self.assertEqual(outImage.getHeight(), inImage.getHeight()//binY) 

278 

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) 

282 

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) 

289 

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}") 

293 

294 

295class TestMemory(lsst.utils.tests.MemoryTestCase): 

296 pass 

297 

298 

299def setup_module(module): 

300 lsst.utils.tests.init() 

301 

302 

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