Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

22import math 

23import os.path 

24import unittest 

25import pickle 

26from functools import reduce 

27 

28import numpy as np 

29 

30import lsst.utils 

31import lsst.utils.tests 

32import lsst.pex.exceptions 

33from lsst.daf.base import PropertySet 

34import lsst.geom 

35import lsst.afw.image as afwImage 

36import lsst.afw.math as afwMath 

37import lsst.afw.display as afwDisplay 

38import lsst.pex.exceptions as pexExcept 

39 

40# Set to True to display debug messages and images 

41debugMode = False 

42 

43afwDisplay.setDefaultMaskTransparency(75) 

44try: 

45 AfwdataDir = lsst.utils.getPackageDir("afwdata") 

46except pexExcept.NotFoundError: 

47 AfwdataDir = None 

48 

49 

50class BackgroundTestCase(lsst.utils.tests.TestCase): 

51 

52 def setUp(self): 

53 np.random.seed(1) 

54 self.val = 10 

55 self.image = afwImage.ImageF(lsst.geom.Box2I( 

56 lsst.geom.Point2I(1000, 500), lsst.geom.Extent2I(100, 200))) 

57 self.image.set(self.val) 

58 

59 def tearDown(self): 

60 del self.image 

61 

62 def testOddSize(self): 

63 """Test for ticket #1781 -- without it, in oddly-sized images 

64 there is a chunk of pixels on the right/bottom that do not go 

65 into the fit and are extrapolated. After this ticket, the 

66 subimage boundaries are spread more evenly so the last pixels 

67 get fit as well. This slightly strange test case checks that 

68 the interpolant is close to the function at the end. I could 

69 not think of an interpolant that would fit exactly, so this 

70 just puts a limit on the errors. 

71 """ 

72 W, H = 2, 99 

73 image = afwImage.ImageF(lsst.geom.Extent2I(W, H)) 

74 NY = 10 

75 bgCtrl = afwMath.BackgroundControl(2, NY) 

76 for y in range(H): 

77 for x in range(W): 

78 B = 89 

79 if y < B: 

80 image[x, y, afwImage.LOCAL] = y 

81 else: 

82 image[x, y, afwImage.LOCAL] = B + (y-B)*-1. 

83 bobj = afwMath.makeBackground(image, bgCtrl) 

84 back = bobj.getImageF(afwMath.Interpolate.LINEAR) 

85 

86 for iy, by in zip([image[0, y, afwImage.LOCAL] for y in range(H)], 

87 [back[0, y, afwImage.LOCAL] for y in range(H)]): 

88 self.assertLess(abs(iy - by), 5) 

89 

90 def testgetPixel(self): 

91 """Tests basic functionality of getPixel() method (floats)""" 

92 xcen, ycen = 50, 100 

93 bgCtrl = afwMath.BackgroundControl(10, 10) 

94 bgCtrl.setNxSample(5) 

95 bgCtrl.setNySample(5) 

96 bgCtrl.getStatisticsControl().setNumIter(3) 

97 bgCtrl.getStatisticsControl().setNumSigmaClip(3) 

98 back = afwMath.makeBackground(self.image, bgCtrl) 

99 

100 with self.assertWarns(FutureWarning): 

101 self.assertEqual(back.getPixel(xcen, ycen), self.val) 

102 

103 @unittest.skipIf(AfwdataDir is None, "afwdata not setup") 

104 def testBackgroundTestImages(self): 

105 """Tests Laher's afwdata/Statistics/*.fits images (doubles)""" 

106 imginfolist = [] 

107 # cooked to known value 

108 imginfolist.append(["v1_i1_g_m400_s20_f.fits", 399.9912966583894]) 

109 

110 for imginfo in imginfolist: 

111 imgfile, centerValue = imginfo 

112 imgPath = os.path.join(AfwdataDir, "Statistics", imgfile) 

113 

114 # get the image and header 

115 dimg = afwImage.DecoratedImageF(imgPath) 

116 img = dimg.getImage() 

117 fitsHdr = dimg.getMetadata() # the FITS header 

118 

119 # get the True values of the mean and stdev 

120 reqMean = fitsHdr.getAsDouble("MEANREQ") 

121 reqStdev = fitsHdr.getAsDouble("SIGREQ") 

122 naxis1 = img.getWidth() 

123 naxis2 = img.getHeight() 

124 

125 # create a background control object 

126 bctrl = afwMath.BackgroundControl(5, 5) 

127 

128 # run the background constructor and call the getImage() function. 

129 backobj = afwMath.makeBackground(img, bctrl) 

130 

131 pixPerSubimage = img.getWidth()*img.getHeight() / \ 

132 (bctrl.getNxSample()*bctrl.getNySample()) 

133 stdevInterp = reqStdev/math.sqrt(pixPerSubimage) 

134 

135 # test getImage() by checking the center pixel 

136 bimg = backobj.getImageF(afwMath.Interpolate.AKIMA_SPLINE) 

137 testImgval = bimg[naxis1//2, naxis2//2, afwImage.LOCAL] 

138 self.assertLess(abs(testImgval - reqMean), 2*stdevInterp) 

139 

140 def testRamp(self): 

141 """tests Laher's afwdata/Statistics/*.fits images (doubles)""" 

142 # make a ramping image (spline should be exact for linear increasing 

143 # image 

144 nx = 512 

145 ny = 512 

146 x0, y0 = 9876, 54321 

147 box = lsst.geom.Box2I(lsst.geom.Point2I(x0, y0), lsst.geom.Extent2I(nx, ny)) 

148 rampimg = afwImage.ImageF(box) 

149 dzdx, dzdy, z0 = 0.1, 0.2, 10000.0 

150 for x in range(nx): 

151 for y in range(ny): 

152 rampimg[x, y, afwImage.LOCAL] = dzdx*x + dzdy*y + z0 

153 

154 # check corner, edge, and center pixels 

155 bctrl = afwMath.BackgroundControl(10, 10) 

156 bctrl.setNxSample(6) 

157 bctrl.setNySample(6) 

158 # large enough to entirely avoid clipping 

159 bctrl.getStatisticsControl().setNumSigmaClip(20.0) 

160 bctrl.getStatisticsControl().setNumIter(1) 

161 backobj = afwMath.makeBackground(rampimg, bctrl) 

162 

163 if debugMode: 

164 print(rampimg.getArray()) 

165 

166 frame = 1 

167 for interp in ("CONSTANT", "LINEAR", "NATURAL_SPLINE", "AKIMA_SPLINE"): 

168 diff = backobj.getImageF(interp) 

169 if debugMode: 

170 afwDisplay.Display(frame=frame).mtv(diff, title=self._testMethodName + " diff") 

171 frame += 1 

172 diff -= rampimg 

173 if debugMode: 

174 print(interp, diff.getArray().mean(), diff.getArray().std()) 

175 if debugMode: 

176 afwDisplay.Display(frame=frame).mtv(diff, title=self._testMethodName + " diff-ramping") 

177 frame += 1 

178 if debugMode: 

179 afwDisplay.Display(frame=frame).mtv(rampimg, title=self._testMethodName + " ramping") 

180 frame += 1 

181 afwDisplay.Display(frame=frame).mtv(backobj.getStatsImage(), 

182 title=self._testMethodName + " bkgd StatsImage") 

183 frame += 1 

184 

185 xpixels = [0, nx//2, nx - 1] 

186 ypixels = [0, ny//2, ny - 1] 

187 for xpix in xpixels: 

188 for ypix in ypixels: 

189 testval = backobj.getImageF(afwMath.Interpolate.CUBIC_SPLINE)[xpix, ypix, afwImage.LOCAL] 

190 self.assertAlmostEqual(testval/rampimg[xpix, ypix, afwImage.LOCAL], 1, 6) 

191 

192 # Test pickle 

193 new = pickle.loads(pickle.dumps(backobj)) 

194 self.assertBackgroundEqual(backobj, new) 

195 

196 # Check creation of sub-image 

197 box = lsst.geom.Box2I(lsst.geom.Point2I(123, 45), 

198 lsst.geom.Extent2I(45, 123)) 

199 box.shift(lsst.geom.Extent2I(x0, y0)) 

200 bgImage = backobj.getImageF("AKIMA_SPLINE") 

201 bgSubImage = afwImage.ImageF(bgImage, box) 

202 testImage = backobj.getImageF(box, "AKIMA_SPLINE") 

203 self.assertEqual(testImage.getXY0(), bgSubImage.getXY0()) 

204 self.assertEqual(testImage.getDimensions(), bgSubImage.getDimensions()) 

205 self.assertImagesEqual(testImage, bgSubImage) 

206 

207 def getParabolaImage(self, nx, ny, pars=(1.0e-4, 1.0e-4, 0.1, 0.2, 10.0)): 

208 """Make sure a quadratic map is *well* reproduced by the spline model 

209 """ 

210 parabimg = afwImage.ImageF(lsst.geom.Extent2I(nx, ny)) 

211 d2zdx2, d2zdy2, dzdx, dzdy, z0 = pars # no cross-terms 

212 x, y = np.meshgrid(np.arange(nx, dtype=int), np.arange(ny, dtype=int)) 

213 parabimg.array[:, :] = d2zdx2*x**2 + d2zdy2*y**2 + dzdx*x + dzdy*y + z0 

214 return parabimg 

215 

216 @unittest.skipIf(AfwdataDir is None, "afwdata not setup") 

217 def testTicket987(self): 

218 """This code used to abort; so the test is that it doesn't""" 

219 imagePath = os.path.join( 

220 AfwdataDir, "DC3a-Sim", "sci", "v5-e0", "v5-e0-c011-a00.sci.fits") 

221 mimg = afwImage.MaskedImageF(imagePath) 

222 binsize = 512 

223 nx = int(mimg.getWidth()/binsize) + 1 

224 ny = int(mimg.getHeight()/binsize) + 1 

225 bctrl = afwMath.BackgroundControl(nx, ny) 

226 

227 image = mimg.getImage() 

228 backobj = afwMath.makeBackground(image, bctrl) 

229 # note: by default undersampleStyle is THROW_EXCEPTION 

230 image -= backobj.getImageF("NATURAL_SPLINE", "REDUCE_INTERP_ORDER") 

231 

232 def testTicket1781(self): 

233 """Test an unusual-sized image""" 

234 nx = 526 

235 ny = 154 

236 

237 parabimg = self.getParabolaImage(nx, ny) 

238 

239 bctrl = afwMath.BackgroundControl(16, 4) 

240 bctrl.getStatisticsControl().setNumSigmaClip(10.0) 

241 bctrl.getStatisticsControl().setNumIter(1) 

242 afwMath.makeBackground(parabimg, bctrl) 

243 

244 def testParabola(self): 

245 """Test an image which varies parabolicly (spline should be exact for 2rd order polynomial)""" 

246 nx = 512 

247 ny = 512 

248 

249 parabimg = self.getParabolaImage(nx, ny) 

250 

251 # check corner, edge, and center pixels 

252 bctrl = afwMath.BackgroundControl(24, 24) 

253 bctrl.getStatisticsControl().setNumSigmaClip(10.0) 

254 bctrl.getStatisticsControl().setNumIter(1) 

255 backobj = afwMath.makeBackground(parabimg, bctrl) 

256 

257 segmentCenter = int(0.5*nx/bctrl.getNxSample()) 

258 xpixels = [segmentCenter, nx//2, nx - segmentCenter] 

259 ypixels = [segmentCenter, ny//2, ny - segmentCenter] 

260 for xpix in xpixels: 

261 for ypix in ypixels: 

262 testval = backobj.getImageF(afwMath.Interpolate.CUBIC_SPLINE)[xpix, ypix] 

263 realval = parabimg[xpix, ypix, afwImage.LOCAL] 

264 # quadratic terms skew the averages of the subimages and the clipped mean for 

265 # a subimage != value of center pixel. 1/20 counts on a 10000 count sky 

266 # is a fair (if arbitrary) test. 

267 self.assertLess(abs(testval - realval), 0.5) 

268 

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

270 def testCFHT_oldAPI(self): 

271 """Test background subtraction on some real CFHT data""" 

272 mi = afwImage.MaskedImageF(os.path.join( 

273 AfwdataDir, "CFHT", "D4", "cal-53535-i-797722_1.fits")) 

274 mi = mi.Factory(mi, 

275 lsst.geom.Box2I(lsst.geom.Point2I(32, 2), 

276 lsst.geom.Point2I(2079, 4609)), 

277 afwImage.LOCAL) 

278 

279 bctrl = afwMath.BackgroundControl(16, 16) 

280 bctrl.getStatisticsControl().setNumSigmaClip(3.0) 

281 bctrl.getStatisticsControl().setNumIter(2) 

282 backobj = afwMath.makeBackground(mi.getImage(), bctrl) 

283 

284 if debugMode: 

285 afwDisplay.Display(frame=0).mtv(mi, title=self._testMethodName + " image") 

286 

287 im = mi.getImage() 

288 im -= backobj.getImageF(afwMath.Interpolate.AKIMA_SPLINE) 

289 

290 if debugMode: 

291 afwDisplay.Display(frame=1).mtv(mi, title=self._testMethodName + " image-back") 

292 

293 def getCfhtImage(self): 

294 """Get a portion of a CFHT image as a MaskedImageF""" 

295 bbox = lsst.geom.Box2I(lsst.geom.Point2I(500, 2000), 

296 lsst.geom.Point2I(2079, 4609)) 

297 imagePath = os.path.join( 

298 AfwdataDir, "CFHT", "D4", "cal-53535-i-797722_1.fits") 

299 return afwImage.MaskedImageF(imagePath, PropertySet(), bbox) 

300 

301 @unittest.skipIf(AfwdataDir is None, "afwdata not setup") 

302 def testXY0(self): 

303 """Test fitting the background to an image with nonzero xy0 

304 

305 The statsImage and background image should not vary with xy0 

306 """ 

307 bgImageList = [] # list of background images, one per xy0 

308 statsImageList = [] # list of stats images, one per xy0 

309 for xy0 in (lsst.geom.Point2I(0, 0), 

310 lsst.geom.Point2I(-100, -999), 

311 lsst.geom.Point2I(1000, 500)): 

312 mi = self.getCfhtImage() 

313 mi.setXY0(xy0) 

314 

315 bctrl = afwMath.BackgroundControl( 

316 mi.getWidth()//128, mi.getHeight()//128) 

317 backobj = afwMath.makeBackground(mi.getImage(), bctrl) 

318 bgImage = backobj.getImageF(afwMath.Interpolate.AKIMA_SPLINE) 

319 self.assertEqual(bgImage.getBBox(), mi.getBBox()) 

320 bgImageList.append(bgImage) 

321 

322 statsImage = backobj.getStatsImage() 

323 statsImageList.append(statsImage) 

324 

325 # changing the bounding box should make no difference to the pixel values, 

326 # so compare pixels using exact equality 

327 for bgImage in bgImageList[1:]: 

328 self.assertImagesEqual(bgImage, bgImageList[0]) 

329 for statsImage in statsImageList[1:]: 

330 self.assertMaskedImagesEqual(statsImage, statsImageList[0]) 

331 

332 @unittest.skipIf(AfwdataDir is None, "afwdata not setup") 

333 def testSubImage(self): 

334 """Test getImage on a subregion of the full background image 

335 

336 Using real image data is a cheap way to get a variable background 

337 """ 

338 mi = self.getCfhtImage() 

339 

340 bctrl = afwMath.BackgroundControl( 

341 mi.getWidth()//128, mi.getHeight()//128) 

342 backobj = afwMath.makeBackground(mi.getImage(), bctrl) 

343 subBBox = lsst.geom.Box2I(lsst.geom.Point2I(1000, 3000), 

344 lsst.geom.Extent2I(100, 100)) 

345 

346 bgFullImage = backobj.getImageF(afwMath.Interpolate.AKIMA_SPLINE) 

347 self.assertEqual(bgFullImage.getBBox(), mi.getBBox()) 

348 

349 subFullArr = afwImage.ImageF(bgFullImage, subBBox).getArray() 

350 

351 bgSubImage = backobj.getImageF(subBBox, afwMath.Interpolate.AKIMA_SPLINE) 

352 subArr = bgSubImage.getArray() 

353 

354 # the pixels happen to be identical but it is safer not to rely on 

355 # that; close is good enough 

356 self.assertFloatsEqual(subArr, subFullArr) 

357 

358 @unittest.skipIf(AfwdataDir is None, "afwdata not setup") 

359 def testCFHT(self): 

360 """Test background subtraction on some real CFHT data""" 

361 mi = self.getCfhtImage() 

362 

363 bctrl = afwMath.BackgroundControl( 

364 mi.getWidth()//128, mi.getHeight()//128) 

365 bctrl.getStatisticsControl().setNumSigmaClip(3.0) 

366 bctrl.getStatisticsControl().setNumIter(2) 

367 backobj = afwMath.makeBackground(mi.getImage(), bctrl) 

368 

369 if debugMode: 

370 afwDisplay.Display(frame=0).mtv(mi, title=self._testMethodName + " image") 

371 

372 im = mi.getImage() 

373 im -= backobj.getImageF("AKIMA_SPLINE") 

374 

375 if debugMode: 

376 afwDisplay.Display(frame=1).mtv(mi, title=self._testMethodName + " image-back") 

377 

378 statsImage = backobj.getStatsImage() 

379 

380 if debugMode: 

381 afwDisplay.Display(frame=2).mtv(statsImage, title=self._testMethodName + " bkgd StatsImage") 

382 afwDisplay.Display(frame=3).mtv(statsImage.getVariance(), 

383 title=self._testMethodName + " bkgd Variance") 

384 

385 def testUndersample(self): 

386 """Test how the program handles nx,ny being too small for requested interp style.""" 

387 nx = 64 

388 ny = 64 

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

390 

391 # make a background control object 

392 bctrl = afwMath.BackgroundControl(10, 10) 

393 bctrl.setNxSample(3) 

394 bctrl.setNySample(3) 

395 

396 # put nx,ny back to 2 and see if it adjusts the interp style down to 

397 # linear 

398 bctrl.setNxSample(2) 

399 bctrl.setNySample(2) 

400 backobj = afwMath.makeBackground(img, bctrl) 

401 # Need to interpolate background to discover what we actually needed 

402 backobj.getImageF(afwMath.Interpolate.CUBIC_SPLINE, afwMath.UndersampleStyle.REDUCE_INTERP_ORDER) 

403 self.assertEqual(backobj.getAsUsedInterpStyle(), 

404 afwMath.Interpolate.LINEAR) 

405 

406 def tst(img, bctrl): 

407 backobj = afwMath.makeBackground(img, bctrl) 

408 # only now do we see that we have too few points 

409 backobj.getImageF("CUBIC_SPLINE") 

410 self.assertRaises(lsst.pex.exceptions.InvalidParameterError, 

411 tst, img, bctrl) 

412 

413 def testOnlyOneGridCell(self): 

414 """Test how the program handles nxSample,nySample being 1x1.""" 

415 # try a ramping image ... has an easy analytic solution 

416 nx = 64 

417 ny = 64 

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

419 

420 dzdx, dzdy, z0 = 0.1, 0.2, 10000.0 

421 mean = z0 + dzdx*(nx - 1)/2 + dzdy*(ny - 1)/2 # the analytic solution 

422 for x in range(nx): 

423 for y in range(ny): 

424 img[x, y, afwImage.LOCAL] = dzdx*x + dzdy*y + z0 

425 

426 # make a background control object 

427 bctrl = afwMath.BackgroundControl(10, 10) 

428 bctrl.setNxSample(1) 

429 bctrl.setNySample(1) 

430 bctrl.setUndersampleStyle(afwMath.THROW_EXCEPTION) 

431 backobj = afwMath.makeBackground(img, bctrl) 

432 

433 xpixels = [0, nx//2, nx - 1] 

434 ypixels = [0, ny//2, ny - 1] 

435 for xpix in xpixels: 

436 for ypix in ypixels: 

437 testval = backobj.getImageF(afwMath.Interpolate.CONSTANT)[xpix, ypix] 

438 self.assertAlmostEqual(testval/mean, 1) 

439 

440 def testAdjustLevel(self): 

441 """Test that we can adjust a background level""" 

442 sky = 100 

443 im = afwImage.ImageF(40, 40) 

444 im.set(sky) 

445 nx, ny = im.getWidth()//2, im.getHeight()//2 

446 bctrl = afwMath.BackgroundControl(nx, ny) 

447 bkd = afwMath.makeBackground(im, bctrl) 

448 

449 self.assertEqual( 

450 afwMath.makeStatistics(bkd.getImageF("LINEAR"), afwMath.MEAN).getValue(), 

451 sky) 

452 

453 delta = 123 

454 bkd += delta 

455 self.assertEqual( 

456 afwMath.makeStatistics(bkd.getImageF("LINEAR"), afwMath.MEAN).getValue(), 

457 sky + delta) 

458 bkd -= delta 

459 self.assertEqual(afwMath.makeStatistics(bkd.getImageF("LINEAR"), afwMath.MEAN).getValue(), 

460 sky) 

461 

462 def testNaNFromMaskedImage(self): 

463 """Check that an extensively masked image doesn't lead to NaNs in the background estimation""" 

464 image = afwImage.MaskedImageF(800, 800) 

465 msk = image.getMask() 

466 bbox = lsst.geom.BoxI(lsst.geom.PointI(560, 0), lsst.geom.PointI(799, 335)) 

467 smsk = msk.Factory(msk, bbox) 

468 smsk.set(msk.getPlaneBitMask("DETECTED")) 

469 

470 binSize = 256 

471 nx = image.getWidth()//binSize + 1 

472 ny = image.getHeight()//binSize + 1 

473 

474 sctrl = afwMath.StatisticsControl() 

475 sctrl.setAndMask(reduce(lambda x, y: x | image.getMask().getPlaneBitMask(y), 

476 ['EDGE', 'DETECTED', 'DETECTED_NEGATIVE'], 0x0)) 

477 

478 bctrl = afwMath.BackgroundControl(nx, ny, sctrl, "MEANCLIP") 

479 

480 bkgd = afwMath.makeBackground(image, bctrl) 

481 bkgdImage = bkgd.getImageF("NATURAL_SPLINE", "THROW_EXCEPTION") 

482 if debugMode: 

483 afwDisplay.Display(frame=0).mtv(image, title=self._testMethodName + " image") 

484 afwDisplay.Display(frame=1).mtv(bkgdImage, title=self._testMethodName + " bkgdImage") 

485 

486 self.assertFalse(np.isnan(bkgdImage[0, 0, afwImage.LOCAL])) 

487 

488 # Check that the non-string API works too 

489 bkgdImage = bkgd.getImageF( 

490 afwMath.Interpolate.NATURAL_SPLINE, afwMath.THROW_EXCEPTION) 

491 

492 def testBadAreaFailsSpline(self): 

493 """Check that a NaN in the stats image doesn't cause spline interpolation to fail (#2734)""" 

494 image = afwImage.ImageF(15, 9) 

495 for y in range(image.getHeight()): 

496 for x in range(image.getWidth()): 

497 # n.b. linear, which is what the interpolation will fall back 

498 # to 

499 image[x, y, afwImage.LOCAL] = 1 + 2*y 

500 

501 # Set the right corner to NaN. This will mean that we have too few 

502 # points for a spline interpolator 

503 binSize = 3 

504 image[-binSize:, -binSize:, afwImage.LOCAL] = np.nan 

505 

506 nx = image.getWidth()//binSize 

507 ny = image.getHeight()//binSize 

508 

509 sctrl = afwMath.StatisticsControl() 

510 bctrl = afwMath.BackgroundControl(nx, ny, sctrl, afwMath.MEANCLIP) 

511 

512 bkgd = afwMath.makeBackground(image, bctrl) 

513 if debugMode: 

514 afwDisplay.Display(frame=0).mtv(image, title=self._testMethodName + " image") 

515 afwDisplay.Display(frame=1).mtv(bkgd.getStatsImage(), 

516 title=self._testMethodName + " bkgd StatsImage") 

517 # Should throw if we don't permit REDUCE_INTERP_ORDER 

518 self.assertRaises(lsst.pex.exceptions.OutOfRangeError, 

519 bkgd.getImageF, afwMath.Interpolate.NATURAL_SPLINE) 

520 # The interpolation should fall back to linear for the right part of the image 

521 # where the NaNs don't permit spline interpolation (n.b. this happens 

522 # to be exact) 

523 bkgdImage = bkgd.getImageF( 

524 afwMath.Interpolate.NATURAL_SPLINE, afwMath.REDUCE_INTERP_ORDER) 

525 

526 if debugMode: 

527 afwDisplay.Display(frame=2).mtv(bkgdImage, title=self._testMethodName + " bkgdImage") 

528 

529 image -= bkgdImage 

530 self.assertEqual(afwMath.makeStatistics(image, afwMath.MEAN).getValue(), 

531 0.0) 

532 

533 def testBadPatch(self): 

534 """Test that a large bad patch of an image doesn't cause an absolute failure""" 

535 initialValue = 20 

536 mi = afwImage.MaskedImageF(500, 200) 

537 mi.set((initialValue, 0x0, 1.0)) 

538 mi.image[0:200, :] = np.nan 

539 badBits = mi.mask.getPlaneBitMask( 

540 ['EDGE', 'DETECTED', 'DETECTED_NEGATIVE']) 

541 mi.mask[0:400, :] |= badBits 

542 

543 if debugMode: 

544 afwDisplay.Display(frame=0).mtv(mi, title=self._testMethodName + " image") 

545 

546 sctrl = afwMath.StatisticsControl() 

547 sctrl.setAndMask(badBits) 

548 nx, ny = 17, 17 

549 bctrl = afwMath.BackgroundControl(nx, ny, sctrl, afwMath.MEANCLIP) 

550 

551 bkgd = afwMath.makeBackground(mi, bctrl) 

552 statsImage = bkgd.getStatsImage() 

553 if debugMode: 

554 afwDisplay.Display(frame=1).mtv(statsImage, title=self._testMethodName + " bkgd StatsImage") 

555 

556 # the test is that this doesn't fail if the bug (#2297) is fixed 

557 bkgdImage = bkgd.getImageF( 

558 afwMath.Interpolate.NATURAL_SPLINE, afwMath.REDUCE_INTERP_ORDER) 

559 self.assertEqual( 

560 np.mean(bkgdImage[0:100, 0:100].array), initialValue) 

561 if debugMode: 

562 afwDisplay.Display(frame=2).mtv(bkgdImage, title=self._testMethodName + " bkgdImage") 

563 # Check that we can fix the NaNs in the statsImage 

564 sim = statsImage.getImage().getArray() 

565 sim[np.isnan(sim)] = initialValue # replace NaN by initialValue 

566 bkgdImage = bkgd.getImageF( 

567 afwMath.Interpolate.NATURAL_SPLINE, afwMath.REDUCE_INTERP_ORDER) 

568 

569 self.assertAlmostEqual( 

570 np.mean(bkgdImage[0:100, 0:100].array, dtype=np.float64), 

571 initialValue) 

572 

573 def testBadRows(self): 

574 """Test that a bad set of rows in an image doesn't cause a failure""" 

575 initialValue = 20 

576 mi = afwImage.MaskedImageF(500, 200) 

577 mi.set((initialValue, 0x0, 1.0)) 

578 mi.image[:, 0:100] = np.nan 

579 badBits = mi.mask.getPlaneBitMask( 

580 ['EDGE', 'DETECTED', 'DETECTED_NEGATIVE']) 

581 mi.mask[0:400, :] |= badBits 

582 

583 if debugMode: 

584 afwDisplay.Display(frame=0).mtv(mi, title=self._testMethodName + " image") 

585 

586 sctrl = afwMath.StatisticsControl() 

587 sctrl.setAndMask(badBits) 

588 nx, ny = 17, 17 

589 bctrl = afwMath.BackgroundControl(nx, ny, sctrl, afwMath.MEANCLIP) 

590 

591 bkgd = afwMath.makeBackground(mi, bctrl) 

592 statsImage = bkgd.getStatsImage() 

593 if debugMode: 

594 afwDisplay.Display(frame=1).mtv(statsImage, title=self._testMethodName + " bkgd StatsImage") 

595 

596 # the test is that this doesn't fail if the bug (#2297) is fixed 

597 for frame, interpStyle in enumerate([afwMath.Interpolate.CONSTANT, afwMath.Interpolate.LINEAR, 

598 afwMath.Interpolate.NATURAL_SPLINE, 

599 afwMath.Interpolate.AKIMA_SPLINE], 2): 

600 bkgdImage = bkgd.getImageF( 

601 interpStyle, afwMath.REDUCE_INTERP_ORDER) 

602 self.assertEqual( 

603 np.mean(bkgdImage[0:100, 0:100].array), initialValue) 

604 if debugMode: 

605 afwDisplay.Display(frame=frame).mtv(bkgdImage, 

606 title=f"{self._testMethodName} bkgdImage: {interpStyle}") 

607 

608 def testBadImage(self): 

609 """Test that an entirely bad image doesn't cause an absolute failure""" 

610 initialValue = 20 

611 mi = afwImage.MaskedImageF(500, 200) 

612 # Check that no good values don't crash (they return NaN), and that a single good value 

613 # is enough to redeem the entire image 

614 for pix00 in [np.nan, initialValue]: 

615 mi.image[:] = np.nan 

616 mi.image[0, 0] = pix00 

617 

618 sctrl = afwMath.StatisticsControl() 

619 nx, ny = 17, 17 

620 bctrl = afwMath.BackgroundControl(nx, ny, sctrl, afwMath.MEANCLIP) 

621 

622 bkgd = afwMath.makeBackground(mi, bctrl) 

623 

624 for interpStyle in [afwMath.Interpolate.CONSTANT, afwMath.Interpolate.LINEAR, 

625 afwMath.Interpolate.NATURAL_SPLINE, afwMath.Interpolate.AKIMA_SPLINE]: 

626 # the test is that this doesn't fail if the bug (#2297) is 

627 # fixed 

628 bkgdImage = bkgd.getImageF( 

629 interpStyle, afwMath.REDUCE_INTERP_ORDER) 

630 val = np.mean(bkgdImage[0:100, 0:100].array) 

631 

632 if np.isfinite(pix00): 

633 self.assertEqual(val, pix00) 

634 else: 

635 self.assertTrue(np.isnan(val)) 

636 

637 def testBackgroundFromStatsImage(self): 

638 """Check that we can rebuild a Background from a BackgroundMI.getStatsImage()""" 

639 bgCtrl = afwMath.BackgroundControl(10, 10) 

640 bkgd = afwMath.makeBackground(self.image, bgCtrl) 

641 

642 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

643 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

644 bkgdImage = bkgd.getImageF(interpStyle, undersampleStyle) 

645 self.assertEqual(np.mean(bkgdImage.getArray()), self.val) 

646 self.assertEqual(interpStyle, bkgd.getAsUsedInterpStyle()) 

647 self.assertEqual(undersampleStyle, bkgd.getAsUsedUndersampleStyle()) 

648 

649 # OK, we have our background. Make a copy 

650 bkgd2 = afwMath.BackgroundMI( 

651 self.image.getBBox(), bkgd.getStatsImage()) 

652 del bkgd # we should be handling the memory correctly, but let's check 

653 bkgdImage2 = bkgd2.getImageF(interpStyle) 

654 

655 self.assertEqual(np.mean(bkgdImage2.getArray()), self.val) 

656 

657 def testBackgroundList(self): 

658 """Test that a BackgroundLists behaves like a list""" 

659 bgCtrl = afwMath.BackgroundControl(10, 10) 

660 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

661 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

662 approxStyle = afwMath.ApproximateControl.UNKNOWN 

663 approxOrderX = 0 

664 approxOrderY = 0 

665 approxWeighting = False 

666 

667 backgroundList = afwMath.BackgroundList() 

668 

669 for i in range(2): 

670 bkgd = afwMath.makeBackground(self.image, bgCtrl) 

671 if i == 0: 

672 # no need to call getImage 

673 backgroundList.append((bkgd, interpStyle, undersampleStyle, 

674 approxStyle, approxOrderX, approxOrderY, approxWeighting)) 

675 else: 

676 # Relies on having called getImage; deprecated 

677 with self.assertWarns(FutureWarning): 

678 backgroundList.append(bkgd) 

679 

680 def assertBackgroundList(bgl): 

681 self.assertEqual(len(bgl), 2) # check that len() works 

682 for a in bgl: # check that we can iterate 

683 pass 

684 self.assertEqual(len(bgl[0]), 7) # check that we can index 

685 # check that we always have a tuple (bkgd, interp, under, 

686 # approxStyle, orderX, orderY, weighting) 

687 self.assertEqual(len(bgl[1]), 7) 

688 

689 assertBackgroundList(backgroundList) 

690 

691 # Check pickling 

692 new = pickle.loads(pickle.dumps(backgroundList)) 

693 assertBackgroundList(new) 

694 self.assertEqual(len(new), len(backgroundList)) 

695 for i, j in zip(new, backgroundList): 

696 self.assertBackgroundEqual(i[0], j[0]) 

697 self.assertEqual(i[1:], j[1:]) 

698 

699 def assertBackgroundEqual(self, lhs, rhs): 

700 lhsStats, rhsStats = lhs.getStatsImage(), rhs.getStatsImage() 

701 self.assertEqual(lhs.getImageBBox(), rhs.getImageBBox()) 

702 self.assertMaskedImagesEqual(lhsStats, rhsStats) 

703 lhsImage, rhsImage = lhs.getImageF("LINEAR"), rhs.getImageF("LINEAR") 

704 self.assertImagesEqual(lhsImage, rhsImage) 

705 

706 def testApproximate(self): 

707 """Test I/O for BackgroundLists with Approximate""" 

708 # approx and interp should be very close, but not the same 

709 img = self.getParabolaImage(256, 256) 

710 

711 # try regular interpolated image (the default) 

712 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

713 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

714 bgCtrl = afwMath.BackgroundControl(6, 6) 

715 bgCtrl.setUndersampleStyle(undersampleStyle) 

716 bkgd = afwMath.makeBackground(img, bgCtrl) 

717 interpImage = bkgd.getImageF(interpStyle) 

718 

719 with lsst.utils.tests.getTempFilePath("_bgi.fits") as bgiFile, \ 

720 lsst.utils.tests.getTempFilePath("_bga.fits") as bgaFile: 

721 bglInterp = afwMath.BackgroundList() 

722 bglInterp.append((bkgd, interpStyle, undersampleStyle, 

723 afwMath.ApproximateControl.UNKNOWN, 0, 0, True)) 

724 bglInterp.writeFits(bgiFile) 

725 

726 # try an approx background 

727 approxStyle = afwMath.ApproximateControl.CHEBYSHEV 

728 approxOrder = 2 

729 actrl = afwMath.ApproximateControl(approxStyle, approxOrder) 

730 bkgd.getBackgroundControl().setApproximateControl(actrl) 

731 approxImage = bkgd.getImageF(interpStyle) 

732 bglApprox = afwMath.BackgroundList() 

733 bglApprox.append((bkgd, interpStyle, undersampleStyle, 

734 approxStyle, approxOrder, approxOrder, True)) 

735 bglApprox.writeFits(bgaFile) 

736 

737 # take a difference and make sure the two are very similar 

738 interpNp = interpImage.getArray() 

739 diff = np.abs(interpNp - approxImage.getArray())/interpNp 

740 

741 # the image and interp/approx parameters are chosen so these limits 

742 # will be greater than machine precision for float. The two methods 

743 # should be measurably different (so we know we're not just getting the 

744 # same thing from the getImage() method. But they should be very close 

745 # since they're both doing the same sort of thing. 

746 tolSame = 1.0e-3 # should be the same to this order 

747 tolDiff = 1.0e-4 # should be different here 

748 self.assertLess(diff.max(), tolSame) 

749 self.assertGreater(diff.max(), tolDiff) 

750 

751 # now see if we can reload them from files and get the same images 

752 # we wrote 

753 interpImage2 = afwMath.BackgroundList().readFits(bgiFile).getImage() 

754 approxImage2 = afwMath.BackgroundList().readFits(bgaFile).getImage() 

755 

756 idiff = interpImage.getArray() - interpImage2.getArray() 

757 adiff = approxImage.getArray() - approxImage2.getArray() 

758 self.assertEqual(idiff.max(), 0.0) 

759 self.assertEqual(adiff.max(), 0.0) 

760 

761 def testBackgroundListIO(self): 

762 """Test I/O for BackgroundLists""" 

763 bgCtrl = afwMath.BackgroundControl(10, 10) 

764 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

765 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

766 approxOrderX = 6 

767 approxOrderY = 6 

768 approxWeighting = True 

769 

770 im = self.image.Factory( 

771 self.image, self.image.getBBox()) 

772 arr = im.getArray() 

773 arr += np.random.normal(size=(im.getHeight(), im.getWidth())) 

774 

775 for astyle in afwMath.ApproximateControl.UNKNOWN, afwMath.ApproximateControl.CHEBYSHEV: 

776 actrl = afwMath.ApproximateControl(astyle, approxOrderX) 

777 bgCtrl.setApproximateControl(actrl) 

778 

779 backgroundList = afwMath.BackgroundList() 

780 backImage = afwImage.ImageF(im.getDimensions()) 

781 for i in range(2): 

782 bkgd = afwMath.makeBackground(im, bgCtrl) 

783 if i == 0: 

784 # no need to call getImage 

785 backgroundList.append((bkgd, interpStyle, undersampleStyle, 

786 astyle, approxOrderX, approxOrderY, approxWeighting)) 

787 else: 

788 # Relies on having called getImage; deprecated 

789 with self.assertWarns(FutureWarning): 

790 backgroundList.append(bkgd) 

791 

792 backImage += bkgd.getImageF(interpStyle, undersampleStyle) 

793 

794 with lsst.utils.tests.getTempFilePath(".fits") as fileName: 

795 backgroundList.writeFits(fileName) 

796 

797 backgrounds = afwMath.BackgroundList.readFits(fileName) 

798 

799 img = backgrounds.getImage() 

800 # Check that the read-back image is identical to that generated from the backgroundList 

801 # round-tripped to disk 

802 backImage -= img 

803 

804 self.assertEqual(np.min(backImage.getArray()), 0.0) 

805 self.assertEqual(np.max(backImage.getArray()), 0.0) 

806 

807 

808class MemoryTester(lsst.utils.tests.MemoryTestCase): 

809 pass 

810 

811 

812def setup_module(module): 

813 lsst.utils.tests.init() 

814 

815 

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

817 lsst.utils.tests.init() 

818 unittest.main()