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 bgCtrl = afwMath.BackgroundControl(afwMath.Interpolate.LINEAR) 

75 bgCtrl.setNxSample(2) 

76 NY = 10 

77 bgCtrl.setNySample(NY) 

78 for y in range(H): 

79 for x in range(W): 

80 B = 89 

81 if y < B: 

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

83 else: 

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

85 bobj = afwMath.makeBackground(image, bgCtrl) 

86 back = bobj.getImageF() 

87 

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

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

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

91 

92 def testgetPixel(self): 

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

94 xcen, ycen = 50, 100 

95 bgCtrl = afwMath.BackgroundControl(10, 10) 

96 bgCtrl.setNxSample(5) 

97 bgCtrl.setNySample(5) 

98 bgCtrl.getStatisticsControl().setNumIter(3) 

99 bgCtrl.getStatisticsControl().setNumSigmaClip(3) 

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

101 

102 with self.assertWarns(FutureWarning): 

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

104 

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

106 def testBackgroundTestImages(self): 

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

108 imginfolist = [] 

109 # cooked to known value 

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

111 

112 for imginfo in imginfolist: 

113 imgfile, centerValue = imginfo 

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

115 

116 # get the image and header 

117 dimg = afwImage.DecoratedImageF(imgPath) 

118 img = dimg.getImage() 

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

120 

121 # get the True values of the mean and stdev 

122 reqMean = fitsHdr.getAsDouble("MEANREQ") 

123 reqStdev = fitsHdr.getAsDouble("SIGREQ") 

124 naxis1 = img.getWidth() 

125 naxis2 = img.getHeight() 

126 

127 # create a background control object 

128 bctrl = afwMath.BackgroundControl(afwMath.Interpolate.AKIMA_SPLINE) 

129 bctrl.setNxSample(5) 

130 bctrl.setNySample(5) 

131 

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

133 backobj = afwMath.makeBackground(img, bctrl) 

134 

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

136 (bctrl.getNxSample()*bctrl.getNySample()) 

137 stdevInterp = reqStdev/math.sqrt(pixPerSubimage) 

138 

139 # test getImage() by checking the center pixel 

140 bimg = backobj.getImageF() 

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

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

143 

144 def testRamp(self): 

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

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

147 # image 

148 nx = 512 

149 ny = 512 

150 x0, y0 = 9876, 54321 

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

152 rampimg = afwImage.ImageF(box) 

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

154 for x in range(nx): 

155 for y in range(ny): 

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

157 

158 # check corner, edge, and center pixels 

159 bctrl = afwMath.BackgroundControl(10, 10) 

160 bctrl.setInterpStyle(afwMath.Interpolate.CUBIC_SPLINE) 

161 bctrl.setNxSample(6) 

162 bctrl.setNySample(6) 

163 # large enough to entirely avoid clipping 

164 bctrl.getStatisticsControl().setNumSigmaClip(20.0) 

165 bctrl.getStatisticsControl().setNumIter(1) 

166 backobj = afwMath.makeBackground(rampimg, bctrl) 

167 

168 if debugMode: 

169 print(rampimg.getArray()) 

170 

171 frame = 1 

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

173 diff = backobj.getImageF(interp) 

174 if debugMode: 

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

176 frame += 1 

177 diff -= rampimg 

178 if debugMode: 

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

180 if debugMode: 

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

182 frame += 1 

183 if debugMode: 

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

185 frame += 1 

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

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

188 frame += 1 

189 

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

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

192 for xpix in xpixels: 

193 for ypix in ypixels: 

194 testval = backobj.getImageF()[xpix, ypix, afwImage.LOCAL] 

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

196 

197 # Test pickle 

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

199 self.assertBackgroundEqual(backobj, new) 

200 

201 # Check creation of sub-image 

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

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

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

205 bgImage = backobj.getImageF("AKIMA_SPLINE") 

206 bgSubImage = afwImage.ImageF(bgImage, box) 

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

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

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

210 self.assertImagesEqual(testImage, bgSubImage) 

211 

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

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

214 """ 

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

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

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

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

219 return parabimg 

220 

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

222 def testTicket987(self): 

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

224 imagePath = os.path.join( 

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

226 mimg = afwImage.MaskedImageF(imagePath) 

227 binsize = 512 

228 bctrl = afwMath.BackgroundControl("NATURAL_SPLINE") 

229 

230 # note: by default undersampleStyle is THROW_EXCEPTION 

231 bctrl.setUndersampleStyle(afwMath.REDUCE_INTERP_ORDER) 

232 

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

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

235 

236 bctrl.setNxSample(nx) 

237 bctrl.setNySample(ny) 

238 image = mimg.getImage() 

239 backobj = afwMath.makeBackground(image, bctrl) 

240 image -= backobj.getImageF() 

241 

242 def testTicket1781(self): 

243 """Test an unusual-sized image""" 

244 nx = 526 

245 ny = 154 

246 

247 parabimg = self.getParabolaImage(nx, ny) 

248 

249 bctrl = afwMath.BackgroundControl(afwMath.Interpolate.CUBIC_SPLINE) 

250 bctrl.setNxSample(16) 

251 bctrl.setNySample(4) 

252 bctrl.getStatisticsControl().setNumSigmaClip(10.0) 

253 bctrl.getStatisticsControl().setNumIter(1) 

254 afwMath.makeBackground(parabimg, bctrl) 

255 

256 def testParabola(self): 

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

258 nx = 512 

259 ny = 512 

260 

261 parabimg = self.getParabolaImage(nx, ny) 

262 

263 # check corner, edge, and center pixels 

264 bctrl = afwMath.BackgroundControl(afwMath.Interpolate.CUBIC_SPLINE) 

265 bctrl.setNxSample(24) 

266 bctrl.setNySample(24) 

267 bctrl.getStatisticsControl().setNumSigmaClip(10.0) 

268 bctrl.getStatisticsControl().setNumIter(1) 

269 backobj = afwMath.makeBackground(parabimg, bctrl) 

270 

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

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

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

274 for xpix in xpixels: 

275 for ypix in ypixels: 

276 testval = backobj.getImageF(bctrl.getInterpStyle())[xpix, ypix, afwImage.LOCAL] 

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

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

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

280 # is a fair (if arbitrary) test. 

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

282 

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

284 def testCFHT_oldAPI(self): 

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

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

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

288 mi = mi.Factory(mi, 

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

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

291 afwImage.LOCAL) 

292 

293 bctrl = afwMath.BackgroundControl(afwMath.Interpolate.AKIMA_SPLINE) 

294 bctrl.setNxSample(16) 

295 bctrl.setNySample(16) 

296 bctrl.getStatisticsControl().setNumSigmaClip(3.0) 

297 bctrl.getStatisticsControl().setNumIter(2) 

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

299 

300 if debugMode: 

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

302 

303 im = mi.getImage() 

304 im -= backobj.getImageF() 

305 

306 if debugMode: 

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

308 

309 def getCfhtImage(self): 

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

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

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

313 imagePath = os.path.join( 

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

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

316 

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

318 def testXY0(self): 

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

320 

321 The statsImage and background image should not vary with xy0 

322 """ 

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

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

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

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

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

328 mi = self.getCfhtImage() 

329 mi.setXY0(xy0) 

330 

331 bctrl = afwMath.BackgroundControl( 

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

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

334 bgImage = backobj.getImageF() 

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

336 bgImageList.append(bgImage) 

337 

338 statsImage = backobj.getStatsImage() 

339 statsImageList.append(statsImage) 

340 

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

342 # so compare pixels using exact equality 

343 for bgImage in bgImageList[1:]: 

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

345 for statsImage in statsImageList[1:]: 

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

347 

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

349 def testSubImage(self): 

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

351 

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

353 """ 

354 mi = self.getCfhtImage() 

355 

356 bctrl = afwMath.BackgroundControl( 

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

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

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

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

361 

362 bgFullImage = backobj.getImageF() 

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

364 

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

366 

367 bgSubImage = backobj.getImageF(subBBox, bctrl.getInterpStyle()) 

368 subArr = bgSubImage.getArray() 

369 

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

371 # that; close is good enough 

372 self.assertFloatsEqual(subArr, subFullArr) 

373 

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

375 def testCFHT(self): 

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

377 mi = self.getCfhtImage() 

378 

379 bctrl = afwMath.BackgroundControl( 

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

381 bctrl.getStatisticsControl().setNumSigmaClip(3.0) 

382 bctrl.getStatisticsControl().setNumIter(2) 

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

384 

385 if debugMode: 

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

387 

388 im = mi.getImage() 

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

390 

391 if debugMode: 

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

393 

394 statsImage = backobj.getStatsImage() 

395 

396 if debugMode: 

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

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

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

400 

401 def testUndersample(self): 

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

403 nx = 64 

404 ny = 64 

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

406 

407 # make a background control object 

408 bctrl = afwMath.BackgroundControl(10, 10) 

409 bctrl.setInterpStyle(afwMath.Interpolate.CUBIC_SPLINE) 

410 bctrl.setNxSample(3) 

411 bctrl.setNySample(3) 

412 

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

414 # linear 

415 bctrl.setNxSample(2) 

416 bctrl.setNySample(2) 

417 bctrl.setUndersampleStyle("REDUCE_INTERP_ORDER") 

418 backobj = afwMath.makeBackground(img, bctrl) 

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

420 backobj.getImageF() 

421 self.assertEqual(backobj.getAsUsedInterpStyle(), 

422 afwMath.Interpolate.LINEAR) 

423 

424 # put interp style back up to cspline and see if it throws an exception 

425 bctrl.setUndersampleStyle("THROW_EXCEPTION") 

426 

427 def tst(img, bctrl): 

428 backobj = afwMath.makeBackground(img, bctrl) 

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

430 backobj.getImageF("CUBIC_SPLINE") 

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

432 tst, img, bctrl) 

433 

434 def testOnlyOneGridCell(self): 

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

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

437 nx = 64 

438 ny = 64 

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

440 

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

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

443 for x in range(nx): 

444 for y in range(ny): 

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

446 

447 # make a background control object 

448 bctrl = afwMath.BackgroundControl(10, 10) 

449 bctrl.setInterpStyle(afwMath.Interpolate.CONSTANT) 

450 bctrl.setNxSample(1) 

451 bctrl.setNySample(1) 

452 bctrl.setUndersampleStyle(afwMath.THROW_EXCEPTION) 

453 backobj = afwMath.makeBackground(img, bctrl) 

454 

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

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

457 for xpix in xpixels: 

458 for ypix in ypixels: 

459 testval = backobj.getImageF(bctrl.getInterpStyle())[xpix, ypix] 

460 self.assertAlmostEqual(testval/mean, 1) 

461 

462 def testAdjustLevel(self): 

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

464 sky = 100 

465 im = afwImage.ImageF(40, 40) 

466 im.set(sky) 

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

468 bctrl = afwMath.BackgroundControl("LINEAR", nx, ny) 

469 bkd = afwMath.makeBackground(im, bctrl) 

470 

471 self.assertEqual( 

472 afwMath.makeStatistics(bkd.getImageF(), afwMath.MEAN).getValue(), 

473 sky) 

474 

475 delta = 123 

476 bkd += delta 

477 self.assertEqual( 

478 afwMath.makeStatistics(bkd.getImageF(), afwMath.MEAN).getValue(), 

479 sky + delta) 

480 bkd -= delta 

481 self.assertEqual(afwMath.makeStatistics(bkd.getImageF(), afwMath.MEAN).getValue(), 

482 sky) 

483 

484 def testNaNFromMaskedImage(self): 

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

486 image = afwImage.MaskedImageF(800, 800) 

487 msk = image.getMask() 

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

489 smsk = msk.Factory(msk, bbox) 

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

491 

492 binSize = 256 

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

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

495 

496 sctrl = afwMath.StatisticsControl() 

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

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

499 

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

501 

502 bkgd = afwMath.makeBackground(image, bctrl) 

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

504 if debugMode: 

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

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

507 

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

509 

510 # Check that the non-string API works too 

511 bkgdImage = bkgd.getImageF( 

512 afwMath.Interpolate.NATURAL_SPLINE, afwMath.THROW_EXCEPTION) 

513 

514 def testBadAreaFailsSpline(self): 

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

516 image = afwImage.ImageF(15, 9) 

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

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

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

520 # to 

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

522 

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

524 # points for a spline interpolator 

525 binSize = 3 

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

527 

528 nx = image.getWidth()//binSize 

529 ny = image.getHeight()//binSize 

530 

531 sctrl = afwMath.StatisticsControl() 

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

533 

534 bkgd = afwMath.makeBackground(image, bctrl) 

535 if debugMode: 

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

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

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

539 # Should throw if we don't permit REDUCE_INTERP_ORDER 

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

541 bkgd.getImageF, afwMath.Interpolate.NATURAL_SPLINE) 

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

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

544 # to be exact) 

545 bkgdImage = bkgd.getImageF( 

546 afwMath.Interpolate.NATURAL_SPLINE, afwMath.REDUCE_INTERP_ORDER) 

547 

548 if debugMode: 

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

550 

551 image -= bkgdImage 

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

553 0.0) 

554 

555 def testBadPatch(self): 

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

557 initialValue = 20 

558 mi = afwImage.MaskedImageF(500, 200) 

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

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

561 badBits = mi.mask.getPlaneBitMask( 

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

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

564 

565 if debugMode: 

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

567 

568 sctrl = afwMath.StatisticsControl() 

569 sctrl.setAndMask(badBits) 

570 nx, ny = 17, 17 

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

572 

573 bkgd = afwMath.makeBackground(mi, bctrl) 

574 statsImage = bkgd.getStatsImage() 

575 if debugMode: 

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

577 

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

579 bkgdImage = bkgd.getImageF( 

580 afwMath.Interpolate.NATURAL_SPLINE, afwMath.REDUCE_INTERP_ORDER) 

581 self.assertEqual( 

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

583 if debugMode: 

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

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

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

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

588 bkgdImage = bkgd.getImageF( 

589 afwMath.Interpolate.NATURAL_SPLINE, afwMath.REDUCE_INTERP_ORDER) 

590 

591 self.assertAlmostEqual( 

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

593 initialValue) 

594 

595 def testBadRows(self): 

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

597 initialValue = 20 

598 mi = afwImage.MaskedImageF(500, 200) 

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

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

601 badBits = mi.mask.getPlaneBitMask( 

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

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

604 

605 if debugMode: 

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

607 

608 sctrl = afwMath.StatisticsControl() 

609 sctrl.setAndMask(badBits) 

610 nx, ny = 17, 17 

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

612 

613 bkgd = afwMath.makeBackground(mi, bctrl) 

614 statsImage = bkgd.getStatsImage() 

615 if debugMode: 

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

617 

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

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

620 afwMath.Interpolate.NATURAL_SPLINE, 

621 afwMath.Interpolate.AKIMA_SPLINE], 2): 

622 bkgdImage = bkgd.getImageF( 

623 interpStyle, afwMath.REDUCE_INTERP_ORDER) 

624 self.assertEqual( 

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

626 if debugMode: 

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

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

629 

630 def testBadImage(self): 

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

632 initialValue = 20 

633 mi = afwImage.MaskedImageF(500, 200) 

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

635 # is enough to redeem the entire image 

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

637 mi.image[:] = np.nan 

638 mi.image[0, 0] = pix00 

639 

640 sctrl = afwMath.StatisticsControl() 

641 nx, ny = 17, 17 

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

643 

644 bkgd = afwMath.makeBackground(mi, bctrl) 

645 

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

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

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

649 # fixed 

650 bkgdImage = bkgd.getImageF( 

651 interpStyle, afwMath.REDUCE_INTERP_ORDER) 

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

653 

654 if np.isfinite(pix00): 

655 self.assertEqual(val, pix00) 

656 else: 

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

658 

659 def testBackgroundFromStatsImage(self): 

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

661 bgCtrl = afwMath.BackgroundControl(10, 10) 

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

663 

664 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

665 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

666 bkgdImage = bkgd.getImageF(interpStyle, undersampleStyle) 

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

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

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

670 

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

672 bkgd2 = afwMath.BackgroundMI( 

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

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

675 bkgdImage2 = bkgd2.getImageF(interpStyle) 

676 

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

678 

679 def testBackgroundList(self): 

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

681 bgCtrl = afwMath.BackgroundControl(10, 10) 

682 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

683 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

684 approxStyle = afwMath.ApproximateControl.UNKNOWN 

685 approxOrderX = 0 

686 approxOrderY = 0 

687 approxWeighting = False 

688 

689 backgroundList = afwMath.BackgroundList() 

690 

691 for i in range(2): 

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

693 if i == 0: 

694 # no need to call getImage 

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

696 approxStyle, approxOrderX, approxOrderY, approxWeighting)) 

697 else: 

698 backgroundList.append(bkgd) 

699 

700 def assertBackgroundList(bgl): 

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

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

703 pass 

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

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

706 # approxStyle, orderX, orderY, weighting) 

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

708 

709 assertBackgroundList(backgroundList) 

710 

711 # Check pickling 

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

713 assertBackgroundList(new) 

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

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

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

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

718 

719 def assertBackgroundEqual(self, lhs, rhs): 

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

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

722 self.assertMaskedImagesEqual(lhsStats, rhsStats) 

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

724 self.assertImagesEqual(lhsImage, rhsImage) 

725 

726 def testApproximate(self): 

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

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

729 img = self.getParabolaImage(256, 256) 

730 

731 # try regular interpolated image (the default) 

732 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

733 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

734 bgCtrl = afwMath.BackgroundControl(6, 6) 

735 bgCtrl.setInterpStyle(interpStyle) 

736 bgCtrl.setUndersampleStyle(undersampleStyle) 

737 bkgd = afwMath.makeBackground(img, bgCtrl) 

738 interpImage = bkgd.getImageF() 

739 

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

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

742 bglInterp = afwMath.BackgroundList() 

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

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

745 bglInterp.writeFits(bgiFile) 

746 

747 # try an approx background 

748 approxStyle = afwMath.ApproximateControl.CHEBYSHEV 

749 approxOrder = 2 

750 actrl = afwMath.ApproximateControl(approxStyle, approxOrder) 

751 bkgd.getBackgroundControl().setApproximateControl(actrl) 

752 approxImage = bkgd.getImageF() 

753 bglApprox = afwMath.BackgroundList() 

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

755 approxStyle, approxOrder, approxOrder, True)) 

756 bglApprox.writeFits(bgaFile) 

757 

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

759 interpNp = interpImage.getArray() 

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

761 

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

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

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

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

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

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

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

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

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

771 

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

773 # we wrote 

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

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

776 

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

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

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

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

781 

782 def testBackgroundListIO(self): 

783 """Test I/O for BackgroundLists""" 

784 bgCtrl = afwMath.BackgroundControl(10, 10) 

785 interpStyle = afwMath.Interpolate.AKIMA_SPLINE 

786 undersampleStyle = afwMath.REDUCE_INTERP_ORDER 

787 approxOrderX = 6 

788 approxOrderY = 6 

789 approxWeighting = True 

790 

791 im = self.image.Factory( 

792 self.image, self.image.getBBox()) 

793 arr = im.getArray() 

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

795 

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

797 actrl = afwMath.ApproximateControl(astyle, approxOrderX) 

798 bgCtrl.setApproximateControl(actrl) 

799 

800 backgroundList = afwMath.BackgroundList() 

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

802 for i in range(2): 

803 bkgd = afwMath.makeBackground(im, bgCtrl) 

804 if i == 0: 

805 # no need to call getImage 

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

807 astyle, approxOrderX, approxOrderY, approxWeighting)) 

808 else: 

809 backgroundList.append(bkgd) 

810 

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

812 

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

814 backgroundList.writeFits(fileName) 

815 

816 backgrounds = afwMath.BackgroundList.readFits(fileName) 

817 

818 img = backgrounds.getImage() 

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

820 # round-tripped to disk 

821 backImage -= img 

822 

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

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

825 

826 

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

828 pass 

829 

830 

831def setup_module(module): 

832 lsst.utils.tests.init() 

833 

834 

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

836 lsst.utils.tests.init() 

837 unittest.main()