Coverage for tests/test_statistics.py: 14%

362 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-18 02:24 -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 Statistics 

24 

25Run with: 

26 python test_statistics.py 

27or 

28 pytest test_statistics.py 

29""" 

30 

31import math 

32import os 

33import unittest 

34 

35import numpy as np 

36 

37import lsst.utils.tests 

38import lsst.pex.exceptions 

39import lsst.geom 

40import lsst.afw.image as afwImage 

41import lsst.afw.math as afwMath 

42import lsst.afw.display as afwDisplay 

43 

44afwDisplay.setDefaultMaskTransparency(75) 

45 

46try: 

47 afwdataDir = lsst.utils.getPackageDir("afwdata") 

48except LookupError: 

49 afwdataDir = None 

50 

51try: 

52 type(display) 

53except NameError: 

54 display = False 

55 

56 

57class StatisticsTestCase(lsst.utils.tests.TestCase): 

58 """A test case for Statistics""" 

59 

60 clippedVariance3 = 0.9733369 # variance of an N(0, 1) Gaussian clipped at 3 sigma 

61 

62 def setUp(self): 

63 w, h = 900, 1500 

64 

65 mean = 10.5 # requested mean 

66 std = 1.0 # and standard deviation 

67 

68 self.images = [] 

69 # ImageI 

70 np.random.seed(666) 

71 isInt = True 

72 image = afwImage.ImageI(lsst.geom.ExtentI(w, h)) 

73 image.array[:] = np.floor(np.random.normal(mean, std, (h, w)) + 0.5).astype(int) 

74 

75 # Note that the mean/median/std may not be quite equal to the requested values 

76 self.images.append((image, isInt, np.mean(image.array), np.mean(image.array), np.std(image.array))) 

77 

78 # ImageF 

79 np.random.seed(666) 

80 isInt = False 

81 image = afwImage.ImageF(lsst.geom.ExtentI(w, h)) 

82 image.array[:] = np.random.normal(mean, std, (h, w)) 

83 

84 # Note that the mean/median/std may not be quite equal to the requested values 

85 self.images.append((image, isInt, np.mean(image.array), np.median(image.array), np.std(image.array))) 

86 

87 @staticmethod 

88 def delta(what, isInt): 

89 # Return a tolerance for a test 

90 if what == "mean": 

91 return 4e-6 

92 elif what == "meanclip": 

93 return 4e-5 

94 elif what == "median": 

95 return 0.00022 if isInt else 0.00000075 

96 

97 def tearDown(self): 

98 del self.images 

99 

100 def testDefaultGet(self): 

101 """Test that we can get a single statistic without specifying it""" 

102 for image, isInt, mean, median, std in self.images: 

103 stats = afwMath.makeStatistics(image, afwMath.MEDIAN) 

104 

105 self.assertEqual(stats.getValue(), stats.getValue(afwMath.MEDIAN)) 

106 self.assertEqual(stats.getResult()[0], stats.getResult(afwMath.MEDIAN)[0]) 

107 # 

108 stats = afwMath.makeStatistics(image, afwMath.MEDIAN | afwMath.ERRORS) 

109 

110 self.assertEqual(stats.getValue(), stats.getValue(afwMath.MEDIAN)) 

111 self.assertEqual(stats.getResult(), stats.getResult(afwMath.MEDIAN)) 

112 self.assertEqual(stats.getError(), stats.getError(afwMath.MEDIAN)) 

113 

114 def tst(): 

115 stats.getValue() 

116 stats = afwMath.makeStatistics(image, afwMath.MEDIAN | afwMath.MEAN) 

117 self.assertRaises(lsst.pex.exceptions.InvalidParameterError, tst) 

118 

119 def testStats1(self): 

120 for image, isInt, mean, median, std in self.images: 

121 stats = afwMath.makeStatistics(image, afwMath.NPOINT | afwMath.STDEV | afwMath.MEAN | afwMath.SUM) 

122 

123 self.assertEqual(stats.getValue(afwMath.NPOINT), image.getWidth()*image.getHeight()) 

124 self.assertEqual(stats.getValue(afwMath.NPOINT)*stats.getValue(afwMath.MEAN), 

125 stats.getValue(afwMath.SUM)) 

126 

127 self.assertAlmostEqual(stats.getValue(afwMath.MEAN), mean, delta=self.delta("mean", isInt)) 

128 # didn't ask for error, so it's a NaN 

129 self.assertTrue(np.isnan(stats.getError(afwMath.MEAN))) 

130 self.assertAlmostEqual(stats.getValue(afwMath.STDEV), std, delta=0.000008) 

131 

132 def testStats2(self): 

133 for image, isInt, mean, median, std in self.images: 

134 stats = afwMath.makeStatistics(image, afwMath.STDEV | afwMath.MEAN | afwMath.ERRORS) 

135 meanRes = stats.getResult(afwMath.MEAN) 

136 sd = stats.getValue(afwMath.STDEV) 

137 

138 self.assertAlmostEqual(meanRes[0], mean, delta=self.delta("mean", isInt)) 

139 self.assertAlmostEqual(meanRes[1], sd/math.sqrt(image.getWidth()*image.getHeight())) 

140 

141 def testStats3(self): 

142 for image, isInt, mean, median, std in self.images: 

143 stats = afwMath.makeStatistics(image, afwMath.NPOINT) 

144 

145 def getMean(): 

146 stats.getValue(afwMath.MEAN) 

147 

148 self.assertRaises(lsst.pex.exceptions.InvalidParameterError, getMean) 

149 

150 def testStatsZebra(self): 

151 """Add 1 to every other row""" 

152 for image, isInt, mean, median, std in self.images: 

153 image2 = image.clone() 

154 # 

155 # Add 1 to every other row, so the variance is increased by 1/4 

156 # 

157 self.assertEqual(image2.getHeight() % 2, 0) 

158 width = image2.getWidth() 

159 for y in range(1, image2.getHeight(), 2): 

160 sim = image2[lsst.geom.Box2I(lsst.geom.Point2I(0, y), lsst.geom.Extent2I(width, 1))] 

161 sim += 1 

162 

163 if display: 

164 afwDisplay.Display(frame=0).mtv(image, "Image 1") 

165 afwDisplay.Display(frame=1).mtv(image2, "Image 2 (var inc by 1/4)") 

166 

167 stats = afwMath.makeStatistics(image2, 

168 afwMath.NPOINT | afwMath.STDEV | afwMath.MEAN | afwMath.ERRORS) 

169 meanRes = stats.getResult(afwMath.MEAN) 

170 n = stats.getValue(afwMath.NPOINT) 

171 sd = stats.getValue(afwMath.STDEV) 

172 

173 self.assertAlmostEqual(meanRes[0], mean + 0.5, delta=self.delta("mean", isInt)) 

174 self.assertAlmostEqual(sd, np.hypot(std, 1/math.sqrt(4.0)*math.sqrt(n/(n - 1))), 

175 delta=0.00011) 

176 self.assertAlmostEqual(meanRes[1], sd/math.sqrt(image2.getWidth()*image2.getHeight()), 10) 

177 

178 meanSquare = afwMath.makeStatistics(image2, afwMath.MEANSQUARE).getValue() 

179 self.assertAlmostEqual(meanSquare, 0.5*(mean**2 + (mean + 1)**2) + std**2, 

180 delta=0.00025 if isInt else 0.00006) 

181 

182 def testStatsStdevclip(self): 

183 """Test STDEVCLIP; cf. #611""" 

184 for image, isInt, mean, median, std in self.images: 

185 image2 = image.clone() 

186 

187 stats = afwMath.makeStatistics(image2, afwMath.STDEVCLIP | afwMath.NPOINT | afwMath.SUM) 

188 self.assertAlmostEqual(stats.getValue(afwMath.STDEVCLIP), math.sqrt(self.clippedVariance3)*std, 

189 delta=0.0015) 

190 # 

191 # Check we get the correct sum even when clipping 

192 # 

193 self.assertEqual( 

194 stats.getValue(afwMath.NPOINT)*afwMath.makeStatistics(image2, afwMath.MEAN).getValue(), 

195 stats.getValue(afwMath.SUM)) 

196 

197 def testMedian(self): 

198 """Test the median code""" 

199 for image, isInt, mean, median, std in self.images: 

200 med = afwMath.makeStatistics(image, afwMath.MEDIAN).getValue() 

201 self.assertAlmostEqual(med, median, delta=self.delta("median", isInt)) 

202 

203 values = [1.0, 2.0, 3.0, 2.0] 

204 self.assertEqual(afwMath.makeStatistics(values, afwMath.MEDIAN).getValue(), 2.0) 

205 

206 def testIqrange(self): 

207 """Test the inter-quartile range""" 

208 for image, isInt, mean, median, std in self.images: 

209 iqr = afwMath.makeStatistics(image, afwMath.IQRANGE).getValue() 

210 # pretty loose constraint for isInt; probably because the distribution 

211 # isn't very Gaussian with the added rounding to integer values 

212 self.assertAlmostEqual(iqr, std/0.741301109252802, delta=0.063 if isInt else 0.00011) 

213 

214 def testMeanClip(self): 

215 """Test the clipped mean""" 

216 

217 sctrl = afwMath.StatisticsControl() 

218 sctrl.setNumSigmaClip(6) 

219 

220 for image, isInt, mean, median, std in self.images: 

221 stats = afwMath.makeStatistics(image, afwMath.MEANCLIP | afwMath.NCLIPPED, sctrl) 

222 self.assertAlmostEqual(stats.getValue(afwMath.MEANCLIP), mean, delta=self.delta("mean", isInt)) 

223 self.assertEqual(stats.getValue(afwMath.NCLIPPED), 0) 

224 

225 def testVarianceClip(self): 

226 """Test the 3-sigma clipped standard deviation and variance""" 

227 for image, isInt, mean, median, std in self.images: 

228 delta = 0.0006 if isInt else 0.0014 

229 stdevClip = afwMath.makeStatistics(image, afwMath.STDEVCLIP).getValue() 

230 self.assertAlmostEqual(stdevClip, math.sqrt(self.clippedVariance3)*std, delta=delta) 

231 

232 varianceClip = afwMath.makeStatistics(image, afwMath.VARIANCECLIP).getValue() 

233 self.assertAlmostEqual(varianceClip, self.clippedVariance3*std**2, delta=2*delta) 

234 

235 def _testBadValue(self, badVal): 

236 """Test that we can handle an instance of `badVal` in the data correctly 

237 

238 Note that we only test ImageF here (as ImageI can't contain a NaN) 

239 """ 

240 mean = self.images[0][1] 

241 x, y = 10, 10 

242 for useImage in [True, False]: 

243 if useImage: 

244 image = afwImage.ImageF(100, 100) 

245 image.set(mean) 

246 image[x, y] = badVal 

247 else: 

248 image = afwImage.MaskedImageF(100, 100) 

249 image.set(mean, 0x0, 1.0) 

250 image[x, y] = (badVal, 0x0, 1.0) 

251 

252 self.assertEqual(afwMath.makeStatistics(image, afwMath.MAX).getValue(), mean) 

253 self.assertEqual(afwMath.makeStatistics(image, afwMath.MEAN).getValue(), mean) 

254 

255 sctrl = afwMath.StatisticsControl() 

256 

257 sctrl.setNanSafe(False) 

258 self.assertFalse(np.isfinite(afwMath.makeStatistics(image, afwMath.MAX, sctrl).getValue())) 

259 self.assertFalse(np.isfinite(afwMath.makeStatistics(image, afwMath.MEAN, sctrl).getValue())) 

260 

261 def testMaxWithNan(self): 

262 """Test that we can handle NaNs correctly""" 

263 self._testBadValue(np.nan) 

264 

265 def testMaxWithInf(self): 

266 """Test that we can handle infinities correctly""" 

267 self._testBadValue(np.inf) 

268 

269 @unittest.skipIf(afwdataDir is None, "afwdata not setup") 

270 def testSampleImageStats(self): 

271 """ Compare our results to known values in test data """ 

272 

273 imgfiles = [] 

274 imgfiles.append("v1_i1_g_m400_s20_f.fits") 

275 imgfiles.append("v1_i1_g_m400_s20_u16.fits") 

276 imgfiles.append("v1_i2_g_m400_s20_f.fits") 

277 imgfiles.append("v1_i2_g_m400_s20_u16.fits") 

278 imgfiles.append("v2_i1_p_m9_f.fits") 

279 imgfiles.append("v2_i1_p_m9_u16.fits") 

280 imgfiles.append("v2_i2_p_m9_f.fits") 

281 imgfiles.append("v2_i2_p_m9_u16.fits") 

282 

283 afwdataDir = os.getenv("AFWDATA_DIR") 

284 

285 for imgfile in imgfiles: 

286 

287 imgPath = os.path.join(afwdataDir, "Statistics", imgfile) 

288 

289 # get the image and header 

290 dimg = afwImage.DecoratedImageF(imgPath) 

291 fitsHdr = dimg.getMetadata() 

292 

293 # get the true values of the mean and stdev 

294 trueMean = fitsHdr.getAsDouble("MEANCOMP") 

295 trueStdev = fitsHdr.getAsDouble("SIGCOMP") 

296 

297 # measure the mean and stdev with the Statistics class 

298 img = dimg.getImage() 

299 statobj = afwMath.makeStatistics(img, afwMath.MEAN | afwMath.STDEV) 

300 mean = statobj.getValue(afwMath.MEAN) 

301 stdev = statobj.getValue(afwMath.STDEV) 

302 

303 # print trueMean, mean, trueStdev, stdev 

304 self.assertAlmostEqual(mean, trueMean, 8) 

305 self.assertAlmostEqual(stdev, trueStdev, 8) 

306 

307 def testStatisticsRamp(self): 

308 """ Tests Statistics on a 'ramp' (image with constant gradient) """ 

309 

310 nx = 101 

311 ny = 64 

312 img = afwImage.ImageF(lsst.geom.Extent2I(nx, ny)) 

313 

314 z0 = 10.0 

315 dzdx = 1.0 

316 mean = z0 + (nx//2)*dzdx 

317 stdev = 0.0 

318 for y in range(ny): 

319 for x in range(nx): 

320 z = z0 + dzdx*x 

321 img[x, y] = z 

322 stdev += (z - mean)*(z - mean) 

323 

324 stdev = math.sqrt(stdev/(nx*ny - 1)) 

325 

326 stats = afwMath.makeStatistics( 

327 img, afwMath.NPOINT | afwMath.STDEV | afwMath.MEAN) 

328 testmean = stats.getValue(afwMath.MEAN) 

329 teststdev = stats.getValue(afwMath.STDEV) 

330 

331 self.assertEqual(stats.getValue(afwMath.NPOINT), nx*ny) 

332 self.assertEqual(testmean, mean) 

333 self.assertAlmostEqual(teststdev, stdev) 

334 

335 stats = afwMath.makeStatistics( 

336 img, afwMath.STDEV | afwMath.MEAN | afwMath.ERRORS) 

337 mean, meanErr = stats.getResult(afwMath.MEAN) 

338 sd = stats.getValue(afwMath.STDEV) 

339 

340 self.assertEqual(mean, img[nx//2, ny//2]) 

341 self.assertEqual(meanErr, sd/math.sqrt(img.getWidth()*img.getHeight())) 

342 

343 # =============================================================================== 

344 # sjb code for percentiles and clipped stats 

345 

346 stats = afwMath.makeStatistics(img, afwMath.MEDIAN) 

347 self.assertEqual(z0 + dzdx*(nx - 1)/2.0, stats.getValue(afwMath.MEDIAN)) 

348 

349 stats = afwMath.makeStatistics(img, afwMath.IQRANGE) 

350 self.assertEqual(dzdx*(nx - 1)/2.0, stats.getValue(afwMath.IQRANGE)) 

351 

352 stats = afwMath.makeStatistics(img, afwMath.MEANCLIP) 

353 self.assertEqual(z0 + dzdx*(nx - 1)/2.0, stats.getValue(afwMath.MEANCLIP)) 

354 

355 def testMask(self): 

356 mask = afwImage.Mask(lsst.geom.Extent2I(10, 10)) 

357 mask.set(0x0) 

358 

359 mask[1, 1] = 0x10 

360 mask[3, 1] = 0x08 

361 mask[5, 4] = 0x08 

362 mask[4, 5] = 0x02 

363 

364 stats = afwMath.makeStatistics(mask, afwMath.SUM | afwMath.NPOINT) 

365 self.assertEqual(mask.getWidth()*mask.getHeight(), 

366 stats.getValue(afwMath.NPOINT)) 

367 self.assertEqual(0x1a, stats.getValue(afwMath.SUM)) 

368 

369 def tst(): 

370 afwMath.makeStatistics(mask, afwMath.MEAN) 

371 self.assertRaises(lsst.pex.exceptions.InvalidParameterError, tst) 

372 

373 def testTicket1025(self): 

374 """ 

375 Ticket #1025 reported that the Statistics median was getting '3' as the median of [1,2,3,2] 

376 it was caused by an off-by-one error in the implementation 

377 """ 

378 

379 # check the exact example in the ticket 

380 values = [1.0, 2.0, 3.0, 2.0] 

381 self.assertEqual(afwMath.makeStatistics(values, afwMath.MEDIAN).getValue(), 2) 

382 self.assertEqual(afwMath.makeStatistics(sorted(values), afwMath.MEDIAN).getValue(), 2) 

383 

384 # check some other possible ways it could show up 

385 values = list(range(10)) 

386 self.assertEqual(afwMath.makeStatistics(values, afwMath.MEDIAN).getValue(), 4.5) 

387 values = list(range(11)) 

388 self.assertEqual(afwMath.makeStatistics(values, afwMath.MEDIAN).getValue(), 5.0) 

389 

390 def testTicket1123(self): 

391 """ 

392 Ticket #1123 reported that the Statistics stack routine throws an exception 

393 when all pixels in a stack are masked. Returning a NaN pixel in the stack is preferred 

394 """ 

395 

396 mean = self.images[0][1] 

397 

398 ctrl = afwMath.StatisticsControl() 

399 ctrl.setAndMask(~0x0) 

400 

401 mimg = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 10)) 

402 mimg.set([mean, 0x1, mean]) 

403 

404 # test the case with no valid pixels ... both mean and stdev should be 

405 # nan 

406 stat = afwMath.makeStatistics(mimg, afwMath.MEAN | afwMath.STDEV, ctrl) 

407 mean = stat.getValue(afwMath.MEAN) 

408 stdev = stat.getValue(afwMath.STDEV) 

409 self.assertNotEqual(mean, mean) # NaN does not equal itself 

410 self.assertNotEqual(stdev, stdev) # NaN does not equal itself 

411 

412 # test the case with one valid pixel ... mean is ok, but stdev should 

413 # still be nan 

414 mimg.getMask()[1, 1] = 0x0 

415 stat = afwMath.makeStatistics(mimg, afwMath.MEAN | afwMath.STDEV, ctrl) 

416 mean = stat.getValue(afwMath.MEAN) 

417 stdev = stat.getValue(afwMath.STDEV) 

418 self.assertEqual(mean, mean) 

419 self.assertNotEqual(stdev, stdev) # NaN does not equal itself 

420 

421 # test the case with two valid pixels ... both mean and stdev are ok 

422 mimg.getMask()[1, 2] = 0x0 

423 stat = afwMath.makeStatistics(mimg, afwMath.MEAN | afwMath.STDEV, ctrl) 

424 mean = stat.getValue(afwMath.MEAN) 

425 stdev = stat.getValue(afwMath.STDEV) 

426 self.assertEqual(mean, mean) 

427 self.assertEqual(stdev, 0.0) 

428 

429 def testTicket1125(self): 

430 """Ticket 1125 reported that the clipped routines were aborting when called with no valid pixels. """ 

431 

432 mean = self.images[0][1] 

433 

434 mimg = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 10)) 

435 mimg.set([mean, 0x1, mean]) 

436 

437 ctrl = afwMath.StatisticsControl() 

438 ctrl.setAndMask(~0x0) 

439 

440 # test the case with no valid pixels ... try MEANCLIP and STDEVCLIP 

441 stat = afwMath.makeStatistics( 

442 mimg, afwMath.MEANCLIP | afwMath.STDEVCLIP, ctrl) 

443 mean = stat.getValue(afwMath.MEANCLIP) 

444 stdev = stat.getValue(afwMath.STDEVCLIP) 

445 self.assertNotEqual(mean, mean) # NaN does not equal itself 

446 self.assertNotEqual(stdev, stdev) # NaN does not equal itself 

447 

448 def testWeightedSum(self): 

449 ctrl = afwMath.StatisticsControl() 

450 mi = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 10)) 

451 mi.getImage().set(1.0) 

452 mi.getVariance().set(0.1) 

453 

454 stats = afwMath.makeStatistics(mi, afwMath.SUM, ctrl) 

455 self.assertEqual(stats.getValue(afwMath.SUM), 100.0) 

456 

457 ctrl.setWeighted(True) 

458 weighted = afwMath.makeStatistics(mi, afwMath.SUM, ctrl) 

459 # precision at "4 places" as images are floats 

460 # ... variance = 0.1 is stored as 0.100000001 

461 self.assertAlmostEqual(weighted.getValue(afwMath.SUM), 1000.0, 4) 

462 

463 def testWeightedSum2(self): 

464 """Test using a weight image separate from the variance plane""" 

465 weight, mean = 0.1, 1.0 

466 

467 ctrl = afwMath.StatisticsControl() 

468 mi = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 10)) 

469 npix = 10*10 

470 mi.getImage().set(mean) 

471 mi.getVariance().set(np.nan) 

472 

473 weights = afwImage.ImageF(mi.getDimensions()) 

474 weights.set(weight) 

475 

476 stats = afwMath.makeStatistics(mi, afwMath.SUM, ctrl) 

477 self.assertEqual(stats.getValue(afwMath.SUM), mean*npix) 

478 

479 weighted = afwMath.makeStatistics(mi, weights, afwMath.SUM, ctrl) 

480 # precision at "4 places" as images are floats 

481 # ... variance = 0.1 is stored as 0.100000001 

482 self.assertAlmostEqual(weighted.getValue(afwMath.SUM), mean*npix*weight, 4) 

483 

484 def testErrorsFromVariance(self): 

485 """Test that we can estimate the errors from the incoming variances""" 

486 weight, mean, variance = 0.1, 1.0, 10.0 

487 

488 mi = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 10)) 

489 npix = 10*10 

490 mi.getImage().set(mean) 

491 mi.getVariance().set(variance) 

492 

493 weights = afwImage.ImageF(mi.getDimensions()) 

494 weights.set(weight) 

495 

496 ctrl = afwMath.StatisticsControl() 

497 ctrl.setCalcErrorFromInputVariance(True) 

498 weighted = afwMath.makeStatistics(mi, weights, 

499 afwMath.MEAN | afwMath.MEANCLIP | afwMath.SUM | afwMath.ERRORS, 

500 ctrl) 

501 

502 self.assertAlmostEqual(weighted.getValue(afwMath.SUM)/(npix*mean*weight), 1) 

503 self.assertAlmostEqual(weighted.getValue(afwMath.MEAN), mean) 

504 self.assertAlmostEqual(weighted.getError(afwMath.MEAN)**2, variance/npix) 

505 self.assertAlmostEqual(weighted.getError(afwMath.MEANCLIP)**2, variance/npix) 

506 

507 # calcErrorMosaicMode and calcErrorFromInputVariance cannot both be selected 

508 ctrl.setCalcErrorMosaicMode(True) 

509 with self.assertRaises(lsst.pex.exceptions.InvalidParameterError): 

510 afwMath.makeStatistics(mi, weights, afwMath.MEAN, ctrl) 

511 

512 # Test only mosaic mode wherein output variance is the mean of the input variance 

513 ctrl.setCalcErrorFromInputVariance(False) 

514 statsMosaic = afwMath.makeStatistics(mi, weights, 

515 afwMath.MEAN | afwMath.MEANCLIP | afwMath.SUM | afwMath.ERRORS, 

516 ctrl) 

517 

518 self.assertAlmostEqual(statsMosaic.getValue(afwMath.SUM)/(npix*mean*weight), 1) 

519 self.assertAlmostEqual(statsMosaic.getValue(afwMath.MEAN), mean) 

520 self.assertAlmostEqual(statsMosaic.getError(afwMath.MEAN)**2, variance) 

521 self.assertAlmostEqual(statsMosaic.getError(afwMath.MEANCLIP)**2, variance) 

522 

523 def testMeanClipSingleValue(self): 

524 """Verify that the clipped mean doesn't not return NaN for a single value.""" 

525 sctrl = afwMath.StatisticsControl() 

526 sctrl.setNumSigmaClip(6) 

527 

528 for image, isInt, mean, median, std in self.images: 

529 stats = afwMath.makeStatistics(image, afwMath.MEANCLIP | afwMath.NCLIPPED, sctrl) 

530 self.assertAlmostEqual(stats.getValue(afwMath.MEANCLIP), mean, 

531 delta=self.delta("meanclip", isInt)) 

532 self.assertEqual(stats.getValue(afwMath.NCLIPPED), 0) 

533 

534 # this bug was caused by the iterative nature of the MEANCLIP. 

535 # With only one point, the sample variance returns NaN to avoid a divide by zero error 

536 # Thus, on the second iteration, the clip width (based on _variance) is NaN and corrupts 

537 # all further calculations. 

538 img = afwImage.ImageF(lsst.geom.Extent2I(1, 1)) 

539 img.set(0) 

540 stats = afwMath.makeStatistics(img, afwMath.MEANCLIP | afwMath.NCLIPPED) 

541 self.assertEqual(stats.getValue(afwMath.MEANCLIP), 0) 

542 self.assertEqual(stats.getValue(afwMath.NCLIPPED), 0) 

543 

544 def testMismatch(self): 

545 """Test that we get an exception when there's a size mismatch""" 

546 scale = 5 

547 for image, isInt, mean, median, std in self.images: 

548 dims = image.getDimensions() 

549 mask = afwImage.Mask(dims*scale) 

550 mask.set(0xFF) 

551 ctrl = afwMath.StatisticsControl() 

552 ctrl.setAndMask(0xFF) 

553 # If it didn't raise, this would result in a NaN (the image data is 

554 # completely masked). 

555 self.assertRaises(lsst.pex.exceptions.InvalidParameterError, afwMath.makeStatistics, 

556 image, mask, afwMath.MEDIAN, ctrl) 

557 subMask = afwImage.Mask(mask, lsst.geom.Box2I(lsst.geom.Point2I(dims*(scale - 1)), dims)) 

558 subMask.set(0) 

559 # Using subMask is successful. 

560 self.assertAlmostEqual(afwMath.makeStatistics(image, subMask, afwMath.MEDIAN, ctrl).getValue(), 

561 median, delta=self.delta("median", isInt)) 

562 

563 def testClipping(self): 

564 """Test that clipping statistics work 

565 

566 Insert a single bad pixel; it should be clipped. 

567 """ 

568 sctrl = afwMath.StatisticsControl() 

569 sctrl.setNumSigmaClip(10) 

570 

571 for image, isInt, mean, median, std in self.images: 

572 nval = 1000*mean 

573 if isInt: 

574 nval = int(nval) 

575 image[0, 0] = nval 

576 

577 stats = afwMath.makeStatistics(image, afwMath.MEANCLIP | afwMath.NCLIPPED | afwMath.NPOINT, sctrl) 

578 self.assertAlmostEqual(stats.getValue(afwMath.MEANCLIP), mean, 

579 delta=self.delta("meanclip", isInt)) 

580 self.assertEqual(stats.getValue(afwMath.NCLIPPED), 1) 

581 self.assertEqual(stats.getValue(afwMath.NPOINT), image.getBBox().getArea()) 

582 

583 def testNMasked(self): 

584 """Test that NMASKED works""" 

585 maskVal = 0xBE 

586 ctrl = afwMath.StatisticsControl() 

587 ctrl.setAndMask(maskVal) 

588 for image, isInt, mean, median, std in self.images: 

589 mask = afwImage.Mask(image.getBBox()) 

590 mask.set(0) 

591 self.assertEqual(afwMath.makeStatistics(image, mask, afwMath.NMASKED, ctrl).getValue(), 0) 

592 mask[1, 1] = maskVal 

593 self.assertEqual(afwMath.makeStatistics(image, mask, afwMath.NMASKED, ctrl).getValue(), 1) 

594 

595 

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

597 pass 

598 

599 

600def setup_module(module): 

601 lsst.utils.tests.init() 

602 

603 

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

605 lsst.utils.tests.init() 

606 unittest.main()