Coverage for tests / test_footprint2.py: 10%

457 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-14 00:45 -0700

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 Footprints, and FootprintSets 

24 

25Run with: 

26 python test_footprint2.py 

27or 

28 pytest test_footprint2.py 

29""" 

30 

31import unittest 

32 

33import lsst.utils.tests 

34import lsst.geom 

35import lsst.afw.table as afwTable 

36import lsst.afw.image as afwImage 

37import lsst.afw.geom as afwGeom 

38import lsst.afw.detection as afwDetect 

39import lsst.afw.display as afwDisplay 

40import numpy as np 

41 

42afwDisplay.setDefaultMaskTransparency(75) 

43try: 

44 type(display) 

45except NameError: 

46 display = False 

47 

48 

49def toString(*args): 

50 """toString written in python""" 

51 if len(args) == 1: 

52 args = args[0] 

53 

54 y, x0, x1 = args 

55 return f"{y}: {x0}..{x1}" 

56 

57 

58def peakFromImage(im, pos): 

59 """Function to extract the sort key of peak height. Sort by decreasing peak height.""" 

60 val = im[pos[0], pos[1], afwImage.LOCAL][0] 

61 return -1.0*val 

62 

63 

64class Object: 

65 

66 def __init__(self, val, spans, origin: lsst.geom.Point2I = None): 

67 self.val = val 

68 self.spans = spans 

69 self.origin = origin 

70 

71 def insert(self, im): 

72 """Insert self into an image""" 

73 for sp in self.spans: 

74 y, x0, x1 = sp 

75 for x in range(x0, x1 + 1): 

76 im[x, y, afwImage.LOCAL] = self.val 

77 

78 def __eq__(self, other): 

79 for osp, sp in zip(other.getSpans(), self.spans): 

80 sp_shift = sp if (self.origin is None) else ( 

81 sp[0] + self.origin[1], sp[1] + self.origin[0], sp[2] + self.origin[0] 

82 ) 

83 if osp.toString() != toString(sp_shift): 

84 return False 

85 

86 return True 

87 

88 

89class FootprintSetTestCase(unittest.TestCase): 

90 """A test case for FootprintSet""" 

91 

92 def setUp(self): 

93 self.origin = lsst.geom.Point2I(-10, 3) 

94 self.im = afwImage.ImageU(bbox=lsst.geom.Box2I(self.origin, lsst.geom.Extent2I(12, 8))) 

95 # 

96 # Objects that we should detect 

97 # 

98 self.objects = [ 

99 Object(10, [(1, 4, 4), (2, 3, 5), (3, 4, 4)], self.origin), 

100 Object(15, [(5, 7, 8), (5, 10, 10), (6, 8, 9)], self.origin), 

101 Object(20, [(6, 3, 3)], self.origin), 

102 ] 

103 

104 self.im.set(0) # clear image 

105 for obj in self.objects: 

106 obj.insert(self.im) 

107 

108 def tearDown(self): 

109 del self.im 

110 

111 def testGC(self): 

112 """Check that FootprintSets are automatically garbage collected (when MemoryTestCase runs)""" 

113 

114 afwDetect.FootprintSet(afwImage.ImageU(lsst.geom.Extent2I(10, 20)), 

115 afwDetect.Threshold(10)) 

116 

117 def testFootprints(self): 

118 """Check that we found the correct number of objects and that they are correct""" 

119 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10)) 

120 

121 objects = ds.getFootprints() 

122 

123 self.assertEqual(len(objects), len(self.objects)) 

124 for i, object in enumerate(objects): 

125 self.assertEqual(object, self.objects[i]) 

126 

127 def testFootprints2(self): 

128 """Check that we found the correct number of objects using FootprintSet""" 

129 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10)) 

130 

131 objects = ds.getFootprints() 

132 

133 self.assertEqual(len(objects), len(self.objects)) 

134 for i in range(len(objects)): 

135 self.assertEqual(objects[i], self.objects[i]) 

136 

137 def testFootprintsImageId(self): 

138 """Check that we can insert footprints into an Image""" 

139 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10)) 

140 objects = ds.getFootprints() 

141 

142 idImage = afwImage.ImageU(bbox=self.im.getBBox()) 

143 idImage.set(0) 

144 

145 for i, foot in enumerate(objects): 

146 foot.spans.setImage(idImage, i + 1) 

147 

148 for i, object_i in enumerate(objects): 

149 for sp in object_i.getSpans(): 

150 for x in range(sp.getX0(), sp.getX1() + 1): 

151 self.assertEqual( 

152 idImage[x - self.origin[0], sp.getY() - self.origin[1], afwImage.LOCAL], 

153 i + 1, 

154 ) 

155 

156 def testFootprintSetImageId(self): 

157 """Check that we can insert a FootprintSet into an Image, setting relative IDs""" 

158 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10)) 

159 objects = ds.getFootprints() 

160 

161 idImage = ds.insertIntoImage() 

162 if display: 

163 afwDisplay.Display(frame=2).mtv(idImage, title=self._testMethodName + " image") 

164 

165 for i, object_i in enumerate(objects): 

166 peakValues = object_i.getPeaks()["peakValue"] 

167 peakValue = peakValues[0] 

168 np.testing.assert_array_equal(peakValues, peakValue) 

169 for sp in object_i.getSpans(): 

170 for x in range(sp.getX0(), sp.getX1() + 1): 

171 self.assertEqual( 

172 idImage[x - self.origin[0], sp.getY() - self.origin[1], afwImage.LOCAL], 

173 i + 1, 

174 ) 

175 

176 def testFootprintSetPeaks(self): 

177 """Check that peak finding returns separate peaks with a negative 

178 origin bbox.""" 

179 

180 # Only the negative y-origin triggers the bug fixed in DM-48092, 

181 # but the x may as well also be negative to test regression. 

182 img_peaks = afwImage.ImageU( 

183 bbox=lsst.geom.Box2I(lsst.geom.Point2I(-10, -3), lsst.geom.Extent2I(5, 6)), 

184 initialValue=0, 

185 ) 

186 img_peaks.array[1, 1:4] = [10, 5, 10] 

187 

188 footprints = afwDetect.FootprintSet(img_peaks, afwDetect.Threshold(5)).getFootprints() 

189 self.assertEqual(len(footprints), 1) 

190 self.assertEqual(len(footprints[0].getPeaks()), 2) 

191 

192 def testFootprintsImage(self): 

193 """Check that we can search Images as well as MaskedImages""" 

194 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10)) 

195 

196 objects = ds.getFootprints() 

197 

198 self.assertEqual(len(objects), len(self.objects)) 

199 for i in range(len(objects)): 

200 self.assertEqual(objects[i], self.objects[i]) 

201 

202 def testGrow2(self): 

203 """Grow some more interesting shaped Footprints. Informative with display, but no numerical tests""" 

204 # Can't set mask plane as the image is not a masked image. 

205 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10)) 

206 

207 idImage = afwImage.ImageU(self.im.getDimensions()) 

208 idImage.set(0) 

209 

210 i = 1 

211 for foot in ds.getFootprints()[0:1]: 

212 foot.dilate(3, afwGeom.Stencil.MANHATTAN) 

213 foot.spans.setImage(idImage, i, doClip=True) 

214 i += 1 

215 

216 if display: 

217 afwDisplay.Display(frame=1).mtv(idImage, title=self._testMethodName + " image") 

218 

219 def testGrow(self): 

220 """Grow footprints using the FootprintSet constructor""" 

221 fs = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10)) 

222 self.assertEqual(len(fs.getFootprints()), len(self.objects)) 

223 for isotropic in (True, False, afwDetect.FootprintControl(True),): 

224 grown = afwDetect.FootprintSet(fs, 1, isotropic) 

225 self.assertEqual(len(fs.getFootprints()), len(self.objects)) 

226 

227 self.assertGreater(len(grown.getFootprints()), 0) 

228 self.assertLessEqual(len(grown.getFootprints()), 

229 len(fs.getFootprints())) 

230 

231 def testFootprintControl(self): 

232 """Test the FootprintControl constructor""" 

233 fctrl = afwDetect.FootprintControl() 

234 self.assertFalse(fctrl.isCircular()[0]) # not set 

235 self.assertFalse(fctrl.isIsotropic()[0]) # not set 

236 

237 fctrl.growIsotropic(False) 

238 self.assertTrue(fctrl.isCircular()[0]) 

239 self.assertTrue(fctrl.isIsotropic()[0]) 

240 self.assertTrue(fctrl.isCircular()[1]) 

241 self.assertFalse(fctrl.isIsotropic()[1]) 

242 

243 fctrl = afwDetect.FootprintControl() 

244 fctrl.growLeft(False) 

245 self.assertTrue(fctrl.isLeft()[0]) # it's now set 

246 self.assertFalse(fctrl.isLeft()[1]) # ... but False 

247 

248 fctrl = afwDetect.FootprintControl(True, False, False, False) 

249 self.assertTrue(fctrl.isLeft()[0]) 

250 self.assertTrue(fctrl.isRight()[0]) 

251 self.assertTrue(fctrl.isUp()[0]) 

252 self.assertTrue(fctrl.isDown()[0]) 

253 

254 self.assertTrue(fctrl.isLeft()[1]) 

255 self.assertFalse(fctrl.isRight()[1]) 

256 

257 def testGrowCircular(self): 

258 """Grow footprints in all 4 directions using the FootprintSet/FootprintControl constructor """ 

259 im = afwImage.MaskedImageF(11, 11) 

260 im[5, 5, afwImage.LOCAL] = (10, 0x0, 0.0) 

261 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10)) 

262 self.assertEqual(len(fs.getFootprints()), 1) 

263 

264 radius = 3 # How much to grow by 

265 for fctrl in (afwDetect.FootprintControl(), 

266 afwDetect.FootprintControl(True), 

267 afwDetect.FootprintControl(True, True), 

268 ): 

269 grown = afwDetect.FootprintSet(fs, radius, fctrl) 

270 afwDetect.setMaskFromFootprintList( 

271 im.getMask(), grown.getFootprints(), 0x10) 

272 

273 if display: 

274 afwDisplay.Display(frame=3).mtv(im, title=self._testMethodName + " image") 

275 

276 foot = grown.getFootprints()[0] 

277 

278 if not fctrl.isCircular()[0]: 

279 self.assertEqual(foot.getArea(), 1) 

280 elif fctrl.isCircular()[0]: 

281 assert radius == 3 

282 if fctrl.isIsotropic()[1]: 

283 self.assertEqual(foot.getArea(), 29) 

284 else: 

285 self.assertEqual(foot.getArea(), 25) 

286 

287 def testGrowLRUD(self): 

288 """Grow footprints in various directions using the FootprintSet/FootprintControl constructor """ 

289 im = afwImage.MaskedImageF(11, 11) 

290 x0, y0, ny = 5, 5, 3 

291 for y in range(y0 - ny//2, y0 + ny//2 + 1): 

292 im[x0, y, afwImage.LOCAL] = (10, 0x0, 0.0) 

293 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10)) 

294 self.assertEqual(len(fs.getFootprints()), 1) 

295 

296 ngrow = 2 # How much to grow by 

297 # 

298 # Test growing to the left and/or right 

299 # 

300 for fctrl in ( 

301 afwDetect.FootprintControl(False, True, False, False), 

302 afwDetect.FootprintControl(True, False, False, False), 

303 afwDetect.FootprintControl(True, True, False, False), 

304 ): 

305 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10)) 

306 grown = afwDetect.FootprintSet(fs, ngrow, fctrl) 

307 im.getMask().set(0) 

308 afwDetect.setMaskFromFootprintList( 

309 im.getMask(), grown.getFootprints(), 0x10) 

310 

311 if display: 

312 afwDisplay.Display(frame=3).mtv(im, title=self._testMethodName + " image") 

313 

314 foot = grown.getFootprints()[0] 

315 nextra = 0 

316 if fctrl.isLeft()[1]: 

317 nextra += ngrow 

318 for y in range(y0 - ny//2, y0 + ny//2 + 1): 

319 self.assertNotEqual(im.getMask()[x0 - 1, y, afwImage.LOCAL], 0) 

320 

321 if fctrl.isRight()[1]: 

322 nextra += ngrow 

323 for y in range(y0 - ny//2, y0 + ny//2 + 1): 

324 self.assertNotEqual(im.getMask()[x0 + 1, y, afwImage.LOCAL], 0) 

325 

326 self.assertEqual(foot.getArea(), (1 + nextra)*ny) 

327 # 

328 # Test growing to up and/or down 

329 # 

330 for fctrl in ( 

331 afwDetect.FootprintControl(False, False, True, False), 

332 afwDetect.FootprintControl(False, False, False, True), 

333 afwDetect.FootprintControl(False, False, True, True), 

334 ): 

335 grown = afwDetect.FootprintSet(fs, ngrow, fctrl) 

336 im.getMask().set(0) 

337 afwDetect.setMaskFromFootprintList( 

338 im.getMask(), grown.getFootprints(), 0x10) 

339 

340 if display: 

341 afwDisplay.Display(frame=2).mtv(im, title=self._testMethodName + " image") 

342 

343 foot = grown.getFootprints()[0] 

344 nextra = 0 

345 if fctrl.isUp()[1]: 

346 nextra += ngrow 

347 for y in range(y0 + ny//2 + 1, y0 + ny//2 + ngrow + 1): 

348 self.assertNotEqual(im.getMask()[x0, y, afwImage.LOCAL], 0) 

349 

350 if fctrl.isDown()[1]: 

351 nextra += ngrow 

352 for y in range(y0 - ny//2 - 1, y0 - ny//2 - ngrow - 1): 

353 self.assertNotEqual(im.getMask()[x0, y, afwImage.LOCAL], 0) 

354 

355 self.assertEqual(foot.getArea(), ny + nextra) 

356 

357 def testGrowLRUD2(self): 

358 """Grow footprints in various directions using the FootprintSet/FootprintControl constructor 

359 

360 Check that overlapping grown Footprints give the expected answers 

361 """ 

362 ngrow = 3 # How much to grow by 

363 for fctrl, xy in [ 

364 (afwDetect.FootprintControl(True, True, 

365 False, False), [(4, 5), (5, 6), (6, 5)]), 

366 (afwDetect.FootprintControl(False, False, 

367 True, True), [(5, 4), (6, 5), (5, 6)]), 

368 ]: 

369 im = afwImage.MaskedImageF(11, 11) 

370 for x, y in xy: 

371 im[x, y, afwImage.LOCAL] = (10, 0x0, 0.0) 

372 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10)) 

373 self.assertEqual(len(fs.getFootprints()), 1) 

374 

375 grown = afwDetect.FootprintSet(fs, ngrow, fctrl) 

376 im.getMask().set(0) 

377 afwDetect.setMaskFromFootprintList( 

378 im.getMask(), grown.getFootprints(), 0x10) 

379 

380 if display: 

381 afwDisplay.Display(frame=1).mtv(im, title=self._testMethodName + " image") 

382 

383 self.assertEqual(len(grown.getFootprints()), 1) 

384 foot = grown.getFootprints()[0] 

385 

386 npix = 1 + 2*ngrow 

387 npix += 3 + 2*ngrow # 3: distance between pair of set pixels 000X0X000 

388 self.assertEqual(foot.getArea(), npix) 

389 

390 def testInf(self): 

391 """Test detection for images with Infs""" 

392 

393 im = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 20)) 

394 im.set(0) 

395 

396 import numpy 

397 for x in range(im.getWidth()): 

398 im[x, -1, afwImage.LOCAL] = (numpy.inf, 0x0, 0) 

399 

400 ds = afwDetect.FootprintSet(im, afwDetect.createThreshold(100)) 

401 

402 objects = ds.getFootprints() 

403 afwDetect.setMaskFromFootprintList(im.getMask(), objects, 0x10) 

404 

405 if display: 

406 afwDisplay.Display(frame=2).mtv(im, title=self._testMethodName + " image") 

407 

408 self.assertEqual(len(objects), 1) 

409 

410 

411class PeaksInFootprintsTestCase(unittest.TestCase): 

412 """A test case for detecting Peaks within Footprints""" 

413 

414 def doSetUp(self, dwidth=0, dheight=0, x0=0, y0=0): 

415 width, height = 14 + x0 + dwidth, 10 + y0 + dheight 

416 self.im = afwImage.MaskedImageF(lsst.geom.Extent2I(width, height)) 

417 # 

418 # Objects that we should detect 

419 # 

420 self.objects, self.peaks = [], [] 

421 self.objects.append( 

422 [[4, 1, 10], [3, 2, 10], [4, 2, 20], [5, 2, 10], [4, 3, 10], ]) 

423 self.peaks.append([[4, 2]]) 

424 self.objects.append( 

425 [[9, 7, 30], [10, 7, 29], [12, 7, 25], [10, 8, 27], [11, 8, 26], ]) 

426 self.peaks.append([[9, 7]]) 

427 self.objects.append([[3, 8, 10], [4, 8, 10], ]) 

428 self.peaks.append([[3, 8], [4, 8], ]) 

429 

430 for pp in self.peaks: # allow for x0, y0 

431 for p in pp: 

432 p[0] += x0 

433 p[1] += y0 

434 for oo in self.objects: 

435 for o in oo: 

436 o[0] += x0 

437 o[1] += y0 

438 

439 self.im.set((0, 0x0, 0)) # clear image 

440 for obj in self.objects: 

441 for x, y, I in obj: 

442 self.im.getImage()[x, y, afwImage.LOCAL] = I 

443 

444 def setUp(self): 

445 self.im, self.fs = None, None 

446 

447 def tearDown(self): 

448 del self.im 

449 del self.fs 

450 

451 def doTestPeaks(self, dwidth=0, dheight=0, x0=0, y0=0, threshold=10, callback=None, polarity=True, 

452 grow=0): 

453 """Worker routine for tests 

454 polarity: True if should search for +ve pixels""" 

455 

456 self.doSetUp(dwidth, dheight, x0, y0) 

457 if not polarity: 

458 self.im *= -1 

459 

460 if callback: 

461 callback() 

462 

463 def peakDescending(p): 

464 """Sort self.peaks in decreasing peak height to match Footprint.getPeaks()""" 

465 return p[2]*-1.0 

466 for i, peaks in enumerate(self.peaks): 

467 self.peaks[i] = sorted([(x, y, self.im.getImage()[x, y, afwImage.LOCAL]) for x, y in peaks], 

468 key=peakDescending) 

469 

470 threshold = afwDetect.Threshold( 

471 threshold, afwDetect.Threshold.VALUE, polarity) 

472 fs = afwDetect.FootprintSet(self.im, threshold, "BINNED1") 

473 

474 if grow: 

475 fs = afwDetect.FootprintSet(fs, grow, True) 

476 msk = self.im.getMask() 

477 afwDetect.setMaskFromFootprintList( 

478 msk, fs.getFootprints(), msk.getPlaneBitMask("DETECTED")) 

479 del msk 

480 

481 self.fs = fs 

482 self.checkPeaks(dwidth, dheight, frame=3) 

483 

484 def checkPeaks(self, dwidth=0, dheight=0, frame=3): 

485 """Check that we got the peaks right""" 

486 feet = self.fs.getFootprints() 

487 # 

488 # Check that we found all the peaks 

489 # 

490 self.assertEqual(sum([len(f.getPeaks()) for f in feet]), 

491 sum([len(f.getPeaks()) for f in feet])) 

492 

493 if display: 

494 disp = afwDisplay.Display(frame=frame) 

495 disp.mtv(self.im, title=self._testMethodName + " image") 

496 

497 with disp.Buffering(): 

498 for i, foot in enumerate(feet): 

499 for p in foot.getPeaks(): 

500 disp.dot("+", p.getIx(), p.getIy(), size=0.4) 

501 

502 if i < len(self.peaks): 

503 for trueX, trueY, peakVal in self.peaks[i]: 

504 disp.dot("x", trueX, trueY, size=0.4, ctype=afwDisplay.RED) 

505 

506 for i, foot in enumerate(feet): 

507 npeak = None 

508 # 

509 # Peaks that touch the edge are handled differently, as only the single highest/lowest pixel 

510 # is treated as a Peak 

511 # 

512 if (dwidth != 0 or dheight != 0): 

513 if (foot.getBBox().getMinX() == 0 

514 or foot.getBBox().getMaxX() == self.im.getWidth() - 1 

515 or foot.getBBox().getMinY() == 0 

516 or foot.getBBox().getMaxY() == self.im.getHeight() - 1): 

517 npeak = 1 

518 

519 if npeak is None: 

520 npeak = len(self.peaks[i]) 

521 

522 self.assertEqual(len(foot.getPeaks()), npeak) 

523 

524 for j, p in enumerate(foot.getPeaks()): 

525 trueX, trueY, peakVal = self.peaks[i][j] 

526 self.assertEqual((p.getIx(), p.getIy()), (trueX, trueY)) 

527 

528 def testSinglePeak(self): 

529 """Test that we can find single Peaks in Footprints""" 

530 

531 self.doTestPeaks() 

532 

533 def testSingleNegativePeak(self): 

534 """Test that we can find single Peaks in Footprints when looking for -ve detections""" 

535 

536 self.doTestPeaks(polarity=False) 

537 

538 def testSinglePeakAtEdge(self): 

539 """Test that we handle Peaks correctly at the edge""" 

540 

541 self.doTestPeaks(dheight=-1) 

542 

543 def testSingleNegativePeakAtEdge(self): 

544 """Test that we handle -ve Peaks correctly at the edge""" 

545 

546 self.doTestPeaks(dheight=-1, polarity=False) 

547 

548 def testMultiPeak(self): 

549 """Test that multiple peaks are handled correctly""" 

550 def callback(): 

551 x, y = 12, 7 

552 self.im.getImage()[x, y, afwImage.LOCAL] = 100 

553 self.peaks[1].append((x, y)) 

554 

555 self.doTestPeaks(callback=callback) 

556 

557 def testMultiNegativePeak(self): 

558 """Test that multiple negative peaks are handled correctly""" 

559 def callback(): 

560 x, y = 12, 7 

561 self.im.getImage()[x, y, afwImage.LOCAL] = -100 

562 self.peaks[1].append((x, y)) 

563 

564 self.doTestPeaks(polarity=False, callback=callback) 

565 

566 def testGrowFootprints(self): 

567 """Test that we can grow footprints, correctly merging those that now touch""" 

568 def callback(): 

569 self.im.getImage()[10, 4, afwImage.LOCAL] = 20 

570 self.peaks[-2].append((10, 4,)) 

571 

572 self.doTestPeaks(dwidth=1, dheight=1, callback=callback, grow=1) 

573 

574 def testGrowFootprints2(self): 

575 """Test that we can grow footprints, correctly merging those that now overlap 

576 N.b. this caused RHL's initial implementation to crash 

577 """ 

578 def callback(): 

579 self.im.getImage()[10, 4, afwImage.LOCAL] = 20 

580 self.peaks[-2].append((10, 4, )) 

581 

582 def peaksSortKey(p): 

583 return peakFromImage(self.im, p) 

584 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey) 

585 

586 self.doTestPeaks(x0=0, y0=2, dwidth=2, dheight=2, 

587 callback=callback, grow=2) 

588 

589 def testGrowFootprints3(self): 

590 """Test that we can grow footprints, correctly merging those that now totally overwritten""" 

591 

592 self.im = afwImage.MaskedImageF(14, 11) 

593 

594 self.im.getImage().set(0) 

595 self.peaks = [] 

596 

597 value = 11 

598 for x, y in [(4, 7), (5, 7), (6, 7), (7, 7), (8, 7), 

599 (4, 6), (8, 6), 

600 (4, 5), (8, 5), 

601 (4, 4), (8, 4), 

602 (4, 3), (8, 3), 

603 ]: 

604 self.im.getImage()[x, y, afwImage.LOCAL] = value 

605 value -= 1e-3 

606 

607 self.im.getImage()[4, 7, afwImage.LOCAL] = 15 

608 self.peaks.append([(4, 7,), ]) 

609 

610 self.im.getImage()[6, 5, afwImage.LOCAL] = 30 

611 self.peaks[0].append((6, 5,)) 

612 

613 self.fs = afwDetect.FootprintSet( 

614 self.im, afwDetect.Threshold(10), "BINNED1") 

615 # 

616 # The disappearing Footprint special case only shows up if the outer Footprint is grown 

617 # _after_ the inner one. So arrange the order properly 

618 feet = self.fs.getFootprints() 

619 feet[0], feet[1] = feet[1], feet[0] 

620 

621 msk = self.im.getMask() 

622 

623 grow = 2 

624 self.fs = afwDetect.FootprintSet(self.fs, grow, False) 

625 afwDetect.setMaskFromFootprintList(msk, self.fs.getFootprints(), 

626 msk.getPlaneBitMask("DETECTED_NEGATIVE")) 

627 

628 if display: 

629 frame = 0 

630 

631 disp = afwDisplay.Display(frame=frame) 

632 disp.mtv(self.im, title=self._testMethodName + " image") 

633 

634 with disp.Buffering(): 

635 for i, foot in enumerate(self.fs.getFootprints()): 

636 for p in foot.getPeaks(): 

637 disp.dot("+", p.getIx(), p.getIy(), size=0.4) 

638 

639 if i < len(self.peaks): 

640 for trueX, trueY in self.peaks[i]: 

641 disp.dot("x", trueX, trueY, size=0.4, ctype=afwDisplay.RED) 

642 

643 self.assertEqual(len(self.fs.getFootprints()), 1) 

644 self.assertEqual(len(self.fs.getFootprints()[ 

645 0].getPeaks()), len(self.peaks[0])) 

646 

647 def testMergeFootprints(self): # YYYY 

648 """Merge positive and negative Footprints""" 

649 x0, y0 = 5, 6 

650 dwidth, dheight = 6, 7 

651 

652 def callback(): 

653 x, y, value = x0 + 10, y0 + 4, -20 

654 self.im.getImage()[x, y, afwImage.LOCAL] = value 

655 peaks2.append((x, y, value)) 

656 

657 for grow1, grow2 in [(1, 1), (3, 3), (6, 6), ]: 

658 peaks2 = [] 

659 self.doTestPeaks(threshold=10, callback=callback, grow=0, 

660 x0=x0, y0=y0, dwidth=dwidth, dheight=dheight) 

661 

662 threshold = afwDetect.Threshold( 

663 10, afwDetect.Threshold.VALUE, False) 

664 fs2 = afwDetect.FootprintSet(self.im, threshold) 

665 

666 msk = self.im.getMask() 

667 afwDetect.setMaskFromFootprintList( 

668 msk, fs2.getFootprints(), msk.getPlaneBitMask("DETECTED_NEGATIVE")) 

669 

670 self.fs.merge(fs2, grow1, grow2) 

671 self.peaks[-2] += peaks2 

672 

673 if grow1 + grow2 > 2: # grow merged all peaks 

674 def peaksSortKey(p): 

675 return peakFromImage(self.im, p) 

676 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey) 

677 

678 afwDetect.setMaskFromFootprintList( 

679 msk, self.fs.getFootprints(), msk.getPlaneBitMask("EDGE")) 

680 

681 self.checkPeaks(frame=3) 

682 

683 def testMergeFootprintPeakSchemas(self): 

684 """Test that merging footprints preserves fields in the peak schemas. 

685 """ 

686 self.doSetUp() 

687 self.im.getImage()[3, 4, afwImage.LOCAL] = 100 

688 self.im.getImage()[3, 5, afwImage.LOCAL] = 200 

689 self.im.getImage()[6, 7, afwImage.LOCAL] = 400 

690 

691 threshold = afwDetect.Threshold(10, afwDetect.Threshold.VALUE, True) 

692 fs1 = afwDetect.FootprintSet(self.im, threshold) 

693 threshold = afwDetect.Threshold(150, afwDetect.Threshold.VALUE, True) 

694 fs2 = afwDetect.FootprintSet(self.im, threshold) 

695 

696 def addSignificance(footprints): 

697 """Return a new FootprintSet with a significance field added.""" 

698 mapper = afwTable.SchemaMapper(footprints.getFootprints()[0].peaks.schema) 

699 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema) 

700 mapper.addOutputField("significance", type=float, 

701 doc="Ratio of peak value to configured standard deviation.") 

702 

703 newFootprints = afwDetect.FootprintSet(footprints) 

704 for old, new in zip(footprints.getFootprints(), newFootprints.getFootprints()): 

705 newPeaks = afwDetect.PeakCatalog(mapper.getOutputSchema()) 

706 newPeaks.extend(old.peaks, mapper=mapper) 

707 new.getPeaks().clear() 

708 new.setPeakCatalog(newPeaks) 

709 

710 for footprint in newFootprints.getFootprints(): 

711 for peak in footprint.peaks: 

712 peak['significance'] = 10 

713 

714 return newFootprints 

715 

716 def checkPeakSignificance(footprints): 

717 """Check that all peaks have significance=10.""" 

718 for footprint in footprints.getFootprints(): 

719 self.assertIn("significance", footprint.peaks.schema) 

720 for peak in footprint.peaks: 

721 self.assertEqual(peak["significance"], 10) 

722 

723 newFs1 = addSignificance(fs1) 

724 

725 # Check that mismatched peak schemas raise an exception in merge(). 

726 with self.assertRaisesRegex(RuntimeError, 

727 "FootprintSets to be merged must have identical peak schemas"): 

728 fs1.merge(newFs1) 

729 

730 with self.assertRaisesRegex(RuntimeError, 

731 "FootprintSets to be merged must have identical peak schemas"): 

732 newFs1.merge(fs2) 

733 

734 newFs2 = addSignificance(fs2) 

735 

736 # Check that the field was added correctly. 

737 checkPeakSignificance(newFs1) 

738 checkPeakSignificance(newFs2) 

739 

740 # Does merging footprints preserve the added field? 

741 newFs1.merge(newFs2) 

742 checkPeakSignificance(newFs1) 

743 checkPeakSignificance(newFs2) 

744 

745 def testMergeFootprintsEngulf(self): 

746 """Merge two Footprints when growing one Footprint totally replaces the other""" 

747 def callback(): 

748 self.im.set(0) 

749 self.peaks, self.objects = [], [] 

750 

751 for x, y, I in [[6, 4, 20], [6, 5, 10]]: 

752 self.im.getImage()[x, y, afwImage.LOCAL] = I 

753 self.peaks.append([[6, 4]]) 

754 

755 x, y, value = 8, 4, -20 

756 self.im.getImage()[x, y, afwImage.LOCAL] = value 

757 peaks2.append((x, y, value)) 

758 

759 grow1, grow2 = 0, 3 

760 peaks2 = [] 

761 self.doTestPeaks(threshold=10, callback=callback, grow=0) 

762 

763 threshold = afwDetect.Threshold(10, afwDetect.Threshold.VALUE, False) 

764 fs2 = afwDetect.FootprintSet(self.im, threshold) 

765 

766 msk = self.im.getMask() 

767 afwDetect.setMaskFromFootprintList( 

768 msk, fs2.getFootprints(), msk.getPlaneBitMask("DETECTED_NEGATIVE")) 

769 

770 self.fs.merge(fs2, grow1, grow2) 

771 self.peaks[0] += peaks2 

772 

773 def peaksSortKey(p): 

774 return peakFromImage(self.im, p) 

775 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey) 

776 

777 afwDetect.setMaskFromFootprintList( 

778 msk, self.fs.getFootprints(), msk.getPlaneBitMask("EDGE")) 

779 

780 self.checkPeaks(frame=3) 

781 

782 

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

784 pass 

785 

786 

787def setup_module(module): 

788 lsst.utils.tests.init() 

789 

790 

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

792 lsst.utils.tests.init() 

793 unittest.main()