Coverage for tests/test_matchBackgrounds.py: 15%

199 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-03-20 01:41 -0700

1# 

2# LSST Data Management System 

3# Copyright 2008, 2009, 2010 LSST Corporation. 

4# 

5# This product includes software developed by the 

6# LSST Project (http://www.lsst.org/). 

7# 

8# This program is free software: you can redistribute it and/or modify 

9# it under the terms of the GNU General Public License as published by 

10# the Free Software Foundation, either version 3 of the License, or 

11# (at your option) any later version. 

12# 

13# This program is distributed in the hope that it will be useful, 

14# but WITHOUT ANY WARRANTY; without even the implied warranty of 

15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <http://www.lsstcorp.org/LegalNotices/>. 

21# 

22 

23import unittest 

24 

25import numpy as np 

26 

27import lsst.utils.tests 

28import lsst.afw.image as afwImage 

29import lsst.afw.math as afwMath 

30from lsst.pipe.tasks.matchBackgrounds import MatchBackgroundsTask 

31 

32 

33class MatchBackgroundsTestCase(unittest.TestCase): 

34 

35 """Background Matching""" 

36 

37 def setUp(self): 

38 np.random.seed(1) 

39 

40 # Make a few test images here 

41 # 1) full coverage (plain vanilla image) has mean = 50counts 

42 self.vanilla = afwImage.ExposureF(600, 600) 

43 im = self.vanilla.getMaskedImage().getImage() 

44 afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 1)) 

45 im += 50 

46 self.vanilla.getMaskedImage().getVariance().set(1.0) 

47 

48 # 2) has chip gap and mean = 10 counts 

49 self.chipGap = afwImage.ExposureF(600, 600) 

50 im = self.chipGap.getMaskedImage().getImage() 

51 afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 2)) 

52 im += 10 

53 im.getArray()[:, 200:300] = np.nan # simulate 100pix chip gap 

54 self.chipGap.getMaskedImage().getVariance().set(1.0) 

55 

56 # 3) has low coverage and mean = 20 counts 

57 self.lowCover = afwImage.ExposureF(600, 600) 

58 im = self.lowCover.getMaskedImage().getImage() 

59 afwMath.randomGaussianImage(im, afwMath.Random('MT19937', 3)) 

60 im += 20 

61 self.lowCover.getMaskedImage().getImage().getArray()[:, 200:] = np.nan 

62 self.lowCover.getMaskedImage().getVariance().set(1.0) 

63 

64 # make a matchBackgrounds object 

65 self.matcher = MatchBackgroundsTask() 

66 self.matcher.config.usePolynomial = True 

67 self.matcher.binSize = 64 

68 self.matcher.debugDataIdString = 'Test Visit' 

69 

70 self.sctrl = afwMath.StatisticsControl() 

71 self.sctrl.setNanSafe(True) 

72 self.sctrl.setAndMask(afwImage.Mask.getPlaneBitMask(["NO_DATA", "DETECTED", "DETECTED_NEGATIVE", 

73 "SAT", "BAD", "INTRP", "CR"])) 

74 

75 def tearDown(self): 

76 self.vanilla = None 

77 self.lowCover = None 

78 self.chipGap = None 

79 self.matcher = None 

80 self.sctrl = None 

81 

82 def checkAccuracy(self, refExp, sciExp): 

83 """Check for accurate background matching in the matchBackgrounds Method. 

84 

85 To be called by tests expecting successful matching. 

86 """ 

87 struct = self.matcher.matchBackgrounds(refExp, sciExp) 

88 resultExp = sciExp 

89 MSE = struct.matchedMSE 

90 diffImVar = struct.diffImVar 

91 

92 resultStats = afwMath.makeStatistics(resultExp.getMaskedImage(), 

93 afwMath.MEAN | afwMath.VARIANCE, 

94 self.sctrl) 

95 resultMean, _ = resultStats.getResult(afwMath.MEAN) 

96 resultVar, _ = resultStats.getResult(afwMath.VARIANCE) 

97 refStats = afwMath.makeStatistics( 

98 refExp.getMaskedImage(), afwMath.MEAN | afwMath.VARIANCE, self.sctrl) 

99 refMean, _ = refStats.getResult(afwMath.MEAN) 

100 self.assertAlmostEqual(refMean, resultMean, delta=resultVar) # very loose test. 

101 # If MSE is within 1% of the variance of the difference image: SUCCESS 

102 self.assertLess(MSE, diffImVar * 1.01) 

103 

104 # -=-=-=-=-=-=Test Polynomial Fit (Approximate class)-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 

105 

106 def testConfig(self): 

107 """Test checks on the configuration. 

108 

109 1) Should throw ValueError if binsize is > size of image 

110 2) Need Order + 1 points to fit. Should throw ValueError if Order > # of grid points 

111 """ 

112 for size in range(601, 1000, 100): 

113 self.matcher.config.binSize = size 

114 self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla) 

115 

116 # for image 600x600 and binsize 256 = 3x3 grid for fitting. order 3,4,5...should fail 

117 self.matcher.config.binSize = 256 

118 for order in range(3, 8): 

119 self.matcher.config.order = order 

120 self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla) 

121 

122 for size, order in [(600, 0), (300, 1), (200, 2), (100, 5)]: 

123 self.matcher.config.binSize = size 

124 self.matcher.config.order = order 

125 self.checkAccuracy(self.chipGap, self.vanilla) 

126 

127 def testInputParams(self): 

128 """Test throws RuntimeError when dimensions don't match.""" 

129 self.matcher.config.binSize = 128 

130 self.matcher.config.order = 2 

131 # make image with wronge size 

132 wrongSize = afwImage.ExposureF(500, 500) 

133 wrongSize.getMaskedImage().getImage().set(1.0) 

134 wrongSize.getMaskedImage().getVariance().set(1.0) 

135 self.assertRaises(RuntimeError, self.matcher.matchBackgrounds, self.chipGap, wrongSize) 

136 

137 def testVanillaApproximate(self): 

138 """Test basic matching scenario with .Approximate.""" 

139 self.matcher.config.binSize = 128 

140 self.matcher.config.order = 4 

141 self.checkAccuracy(self.chipGap, self.vanilla) 

142 

143 def testRampApproximate(self): 

144 """Test basic matching of a linear gradient with Approximate.""" 

145 self.matcher.config.binSize = 64 

146 testExp = afwImage.ExposureF(self.vanilla, True) 

147 testIm = testExp.getMaskedImage().getImage() 

148 afwMath.randomGaussianImage(testIm, afwMath.Random()) 

149 nx, ny = testExp.getDimensions() 

150 dzdx, dzdy, z0 = 1, 2, 0.0 

151 for x in range(nx): 

152 for y in range(ny): 

153 z = testIm[x, y, afwImage.LOCAL] 

154 testIm[x, y, afwImage.LOCAL] = z + dzdx * x + dzdy * y + z0 

155 self.checkAccuracy(testExp, self.vanilla) 

156 

157 def testLowCoverThrowExpectionApproximate(self): 

158 """Test low coverage with .config.undersampleStyle = THROW_EXCEPTION. 

159 Confirm throws ValueError with Approximate. 

160 """ 

161 self.matcher.config.binSize = 64 

162 self.matcher.config.order = 8 

163 self.matcher.config.undersampleStyle = "THROW_EXCEPTION" 

164 self.assertRaises(ValueError, self.matcher.matchBackgrounds, self.chipGap, self.lowCover) 

165 

166 def testLowCoverIncreaseSampleApproximate(self): 

167 """Test low coverage with .config.undersampleStyle = INCREASE_NXNYSAMPLE. 

168 Confirm successful matching with .Approximate. 

169 """ 

170 self.matcher.config.binSize = 128 

171 self.matcher.config.order = 4 

172 self.matcher.config.undersampleStyle = "INCREASE_NXNYSAMPLE" 

173 self.checkAccuracy(self.chipGap, self.lowCover) 

174 

175 def testLowCoverReduceInterpOrderApproximate(self): 

176 """Test low coverage with .config.undersampleStyle = REDUCE_INTERP_ORDER. 

177 Confirm successful matching with .Approximate. 

178 """ 

179 self.matcher.config.binSize = 64 

180 self.matcher.config.order = 8 

181 self.matcher.config.undersampleStyle = "REDUCE_INTERP_ORDER" 

182 self.checkAccuracy(self.chipGap, self.lowCover) 

183 

184 def testMasksApproximate(self): 

185 """Test that masks are ignored in matching backgrounds: .Approximate.""" 

186 testExp = afwImage.ExposureF(self.chipGap, True) 

187 im = testExp.getMaskedImage().getImage() 

188 im += 10 

189 mask = testExp.getMaskedImage().getMask() 

190 satbit = mask.getPlaneBitMask('SAT') 

191 for i in range(0, 200, 20): 

192 mask[5, i, afwImage.LOCAL] = satbit 

193 im[5, i, afwImage.LOCAL] = 65000 

194 self.matcher.config.binSize = 128 

195 self.matcher.config.order = 4 

196 self.checkAccuracy(self.chipGap, testExp) 

197 

198 def testWeightsByInvError(self): 

199 """Test that bins with high std.dev. and low count are weighted less in fit.""" 

200 testExp = afwImage.ExposureF(self.chipGap, True) 

201 testIm = testExp.getMaskedImage().getImage() 

202 self.matcher.config.binSize = 60 

203 self.matcher.config.order = 4 

204 for x in range(0, 50): 

205 for y in range(0, 50): 

206 if np.random.rand(1)[0] < 0.6: 

207 testIm[x, y, afwImage.LOCAL] = np.nan 

208 else: 

209 testIm[x, y, afwImage.LOCAL] = np.random.rand()*1000 

210 

211 self.matcher.matchBackgrounds(self.vanilla, testExp) 

212 resultExp = testExp 

213 resultArr = resultExp.getMaskedImage().getImage().getArray()[60:, 60:] 

214 resultMean = np.mean(resultArr[np.where(~np.isnan(resultArr))]) 

215 resultVar = np.std(resultArr[np.where(~np.isnan(resultArr))])**2 

216 self.assertAlmostEqual(50, resultMean, delta=resultVar) # very loose test. 

217 

218 def testSameImageApproximate(self): 

219 """Test able to match identical images: .Approximate.""" 

220 vanillaTwin = afwImage.ExposureF(self.vanilla, True) 

221 self.matcher.config.binSize = 128 

222 self.matcher.config.order = 4 

223 self.checkAccuracy(self.vanilla, vanillaTwin) 

224 

225 # -=-=-=-=-=-=-=-=-=Background Interp (Splines) -=-=-=-=-=-=-=-=- 

226 

227 def testVanillaBackground(self): 

228 """Test basic matching scenario with .Background.""" 

229 self.matcher.config.usePolynomial = False 

230 self.matcher.config.binSize = 128 

231 self.checkAccuracy(self.chipGap, self.vanilla) 

232 

233 def testRampBackground(self): 

234 """Test basic matching of a linear gradient with .Background.""" 

235 self.matcher.config.usePolynomial = False 

236 self.matcher.config.binSize = 64 

237 testExp = afwImage.ExposureF(self.vanilla, True) 

238 testIm = testExp.getMaskedImage().getImage() 

239 afwMath.randomGaussianImage(testIm, afwMath.Random()) 

240 nx, ny = testExp.getDimensions() 

241 dzdx, dzdy, z0 = 1, 2, 0.0 

242 for x in range(nx): 

243 for y in range(ny): 

244 z = testIm[x, y, afwImage.LOCAL] 

245 testIm[x, y, afwImage.LOCAL] = z + dzdx * x + dzdy * y + z0 

246 self.checkAccuracy(testExp, self.vanilla) 

247 

248 def testUndersampleBackgroundPasses(self): 

249 """Test undersample style (REDUCE_INTERP_ORDER): .Background. 

250 

251 INCREASE_NXNYSAMPLE no longer supported by .Background because .Backgrounds's are 

252 defined by their nx and ny grid. 

253 """ 

254 self.matcher.config.usePolynomial = False 

255 self.matcher.config.binSize = 256 

256 self.matcher.config.undersampleStyle = "REDUCE_INTERP_ORDER" 

257 self.checkAccuracy(self.chipGap, self.vanilla) 

258 

259 self.matcher.config.undersampleStyle = "INCREASE_NXNYSAMPLE" 

260 self.assertRaises(RuntimeError, self.matcher.matchBackgrounds, self.chipGap, self.vanilla) 

261 

262 def testSameImageBackground(self): 

263 """Test able to match identical images with .Background.""" 

264 self.matcher.config.usePolynomial = False 

265 self.matcher.config.binSize = 256 

266 self.checkAccuracy(self.vanilla, self.vanilla) 

267 

268 def testMasksBackground(self): 

269 """Test masks ignored in matching backgrounds with .Background.""" 

270 self.matcher.config.usePolynomial = False 

271 self.matcher.config.binSize = 256 

272 testExp = afwImage.ExposureF(self.chipGap, True) 

273 im = testExp.getMaskedImage().getImage() 

274 im += 10 

275 mask = testExp.getMaskedImage().getMask() 

276 satbit = mask.getPlaneBitMask('SAT') 

277 for i in range(0, 200, 20): 

278 mask[5, i, afwImage.LOCAL] = satbit 

279 im[5, i, afwImage.LOCAL] = 65000 

280 self.checkAccuracy(self.chipGap, testExp) 

281 

282 def testChipGapHorizontalBackground(self): 

283 """ Test able to match image with horizontal chip gap (row of nans) with .Background""" 

284 self.matcher.config.usePolynomial = False 

285 self.matcher.config.binSize = 64 

286 chipGapHorizontal = afwImage.ExposureF(600, 600) 

287 im = chipGapHorizontal.getMaskedImage().getImage() 

288 afwMath.randomGaussianImage(im, afwMath.Random()) 

289 im += 10 

290 im.getArray()[200:300, :] = np.nan # simulate 100pix chip gap horizontal 

291 chipGapHorizontal.getMaskedImage().getVariance().set(1.0) 

292 self.checkAccuracy(self.vanilla, chipGapHorizontal) 

293 

294 def testChipGapVerticalBackground(self): 

295 """ Test able to match images with vertical chip gaps (column of nans) wider than bin size""" 

296 self.matcher.config.usePolynomial = False 

297 self.matcher.config.binSize = 64 

298 self.checkAccuracy(self.chipGap, self.vanilla) 

299 

300 def testLowCoverBackground(self): 

301 """ Test able to match images that do not cover the whole patch""" 

302 self.matcher.config.usePolynomial = False 

303 self.matcher.config.binSize = 64 

304 self.checkAccuracy(self.vanilla, self.lowCover) 

305 

306 

307def setup_module(module): 

308 lsst.utils.tests.init() 

309 

310 

311class MatchMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

312 pass 

313 

314 

315if __name__ == "__main__": 315 ↛ 316line 315 didn't jump to line 316, because the condition on line 315 was never true

316 lsst.utils.tests.init() 

317 unittest.main()