Coverage for tests / test_footprint1.py: 9%

850 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-21 01:29 -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""" 

25 

26import math 

27import sys 

28import unittest 

29import os 

30 

31import numpy as np 

32 

33import lsst.utils.tests 

34import lsst.geom 

35import lsst.afw.geom as afwGeom 

36import lsst.afw.geom.ellipses as afwGeomEllipses 

37import lsst.afw.image as afwImage 

38import lsst.afw.math as afwMath 

39import lsst.afw.detection as afwDetect 

40import lsst.afw.detection.utils as afwDetectUtils 

41import lsst.afw.display as afwDisplay 

42import lsst.pex.exceptions as pexExcept 

43 

44try: 

45 type(display) 

46except NameError: 

47 display = False 

48 

49testPath = os.path.abspath(os.path.dirname(__file__)) 

50 

51 

52def toString(*args): 

53 """toString written in python""" 

54 if len(args) == 1: 

55 args = args[0] 

56 

57 y, x0, x1 = args 

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

59 

60 

61class Object: 

62 

63 def __init__(self, val, spans): 

64 self.val = val 

65 self.spans = spans 

66 

67 def __str__(self): 

68 return ", ".join([str(s) for s in self.spans]) 

69 

70 def insert(self, im): 

71 """Insert self into an image""" 

72 for sp in self.spans: 

73 y, x0, x1 = sp 

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

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

76 

77 def __eq__(self, other): 

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

79 if osp.toString() != toString(sp): 

80 return False 

81 

82 return True 

83 

84 

85class SpanTestCase(unittest.TestCase): 

86 

87 def testLessThan(self): 

88 span1 = afwGeom.Span(42, 0, 100) 

89 span2 = afwGeom.Span(41, 0, 100) 

90 span3 = afwGeom.Span(43, 0, 100) 

91 span4 = afwGeom.Span(42, -100, 100) 

92 span5 = afwGeom.Span(42, 100, 200) 

93 span6 = afwGeom.Span(42, 0, 10) 

94 span7 = afwGeom.Span(42, 0, 200) 

95 span8 = afwGeom.Span(42, 0, 100) 

96 

97 # Cannot use assertLess and friends here 

98 # because Span only has operator < 

99 def assertOrder(x1, x2): 

100 self.assertTrue(x1 < x2) 

101 self.assertFalse(x2 < x1) 

102 

103 assertOrder(span2, span1) 

104 assertOrder(span1, span3) 

105 assertOrder(span4, span1) 

106 assertOrder(span1, span5) 

107 assertOrder(span6, span1) 

108 assertOrder(span1, span7) 

109 self.assertFalse(span1 < span8) 

110 self.assertFalse(span8 < span1) 

111 

112 

113class ThresholdTestCase(unittest.TestCase): 

114 """Tests of the Threshold class.""" 

115 def testThresholdFactory(self): 

116 """ 

117 Test the creation of a Threshold object with ``createThreshold``. 

118 

119 This is a white-box test. 

120 -tests missing parameters 

121 -tests mal-formed parameters 

122 """ 

123 try: 

124 afwDetect.createThreshold(3.4) 

125 except Exception: 

126 self.fail("Failed to build Threshold with proper parameters") 

127 

128 with self.assertRaises(pexExcept.InvalidParameterError): 

129 afwDetect.createThreshold(3.4, "foo bar") 

130 

131 try: 

132 afwDetect.createThreshold(3.4, "variance") 

133 except Exception: 

134 self.fail("Failed to build Threshold with proper parameters") 

135 

136 try: 

137 afwDetect.createThreshold(3.4, "stdev") 

138 except Exception: 

139 self.fail("Failed to build Threshold with proper parameters") 

140 

141 try: 

142 afwDetect.createThreshold(3.4, "value") 

143 except Exception: 

144 self.fail("Failed to build Threshold with proper parameters") 

145 

146 try: 

147 afwDetect.createThreshold(3.4, "value", False) 

148 except Exception: 

149 self.fail("Failed to build Threshold with VALUE, False parameters") 

150 

151 try: 

152 afwDetect.createThreshold(0x4, "bitmask") 

153 except Exception: 

154 self.fail("Failed to build Threshold with BITMASK parameters") 

155 

156 try: 

157 afwDetect.createThreshold(5, "pixel_stdev") 

158 except Exception: 

159 self.fail("Failed to build Threshold with PIXEL_STDEV parameters") 

160 

161 def test_str(self): 

162 """Test that str/repr provide useful information. 

163 """ 

164 threshold = afwDetect.createThreshold(10, "pixel_stdev") 

165 self.assertEqual(str(threshold), "PIXEL_STDEV value=10 (positive)") 

166 self.assertEqual(repr(threshold), 

167 "Threshold(value=10, type=PIXEL_STDEV, polarity=1, includeMultiplier=1)") 

168 

169 threshold = afwDetect.createThreshold(3.123456789, "value", polarity=False) 

170 self.assertEqual(str(threshold), "VALUE value=3.1234568 (negative)") 

171 self.assertEqual(repr(threshold), 

172 "Threshold(value=3.123456789, type=VALUE, polarity=0, includeMultiplier=1)") 

173 

174 threshold = afwDetect.Threshold(2, afwDetect.Threshold.VALUE, includeMultiplier=4) 

175 self.assertEqual(str(threshold), "VALUE value=2 (positive) multiplier=4") 

176 self.assertEqual(repr(threshold), 

177 "Threshold(value=2, type=VALUE, polarity=1, includeMultiplier=4)") 

178 

179 threshold = afwDetect.createThreshold(5, "stdev") 

180 self.assertEqual(str(threshold), "STDEV value=5 (positive)") 

181 self.assertEqual(repr(threshold), 

182 "Threshold(value=5, type=STDEV, polarity=1, includeMultiplier=1)") 

183 

184 threshold = afwDetect.createThreshold(4, "variance") 

185 self.assertEqual(str(threshold), "VARIANCE value=4 (positive)") 

186 self.assertEqual(repr(threshold), 

187 "Threshold(value=4, type=VARIANCE, polarity=1, includeMultiplier=1)") 

188 

189 

190class FootprintTestCase(lsst.utils.tests.TestCase): 

191 """A test case for Footprint""" 

192 

193 def setUp(self): 

194 self.foot = afwDetect.Footprint() 

195 

196 def tearDown(self): 

197 del self.foot 

198 

199 def testToString(self): 

200 y, x0, x1 = 10, 100, 101 

201 s = afwGeom.Span(y, x0, x1) 

202 self.assertEqual(s.toString(), toString(y, x0, x1)) 

203 

204 def testGC(self): 

205 """Check that Footprints are automatically garbage collected (when MemoryTestCase runs)""" 

206 

207 afwDetect.Footprint() 

208 

209 def testIntersectMask(self): 

210 bbox = lsst.geom.BoxI(lsst.geom.PointI(0, 0), lsst.geom.ExtentI(10)) 

211 fp = afwDetect.Footprint(afwGeom.SpanSet(bbox)) 

212 maskBBox = lsst.geom.BoxI(bbox) 

213 maskBBox.grow(-2) 

214 mask = afwImage.Mask(maskBBox) 

215 innerBBox = lsst.geom.BoxI(maskBBox) 

216 innerBBox.grow(-2) 

217 subMask = mask.Factory(mask, innerBBox) 

218 subMask.set(1) 

219 

220 # We only want the pixels that are unmasked, and lie in the bounding box 

221 # of the mask, so not the mask (selecting only zero values) and clipped 

222 fp.spans = fp.spans.intersectNot(mask).clippedTo(mask.getBBox()) 

223 fp.removeOrphanPeaks() 

224 fpBBox = fp.getBBox() 

225 self.assertEqual(fpBBox.getMinX(), maskBBox.getMinX()) 

226 self.assertEqual(fpBBox.getMinY(), maskBBox.getMinY()) 

227 self.assertEqual(fpBBox.getMaxX(), maskBBox.getMaxX()) 

228 self.assertEqual(fpBBox.getMaxY(), maskBBox.getMaxY()) 

229 

230 self.assertEqual(fp.getArea(), maskBBox.getArea() - innerBBox.getArea()) 

231 

232 def testTablePersistence(self): 

233 ellipse = afwGeom.Ellipse(afwGeomEllipses.Axes(8, 6, 0.25), 

234 lsst.geom.Point2D(9, 15)) 

235 fp1 = afwDetect.Footprint(afwGeom.SpanSet.fromShape(ellipse)) 

236 fp1.addPeak(6, 7, 2) 

237 fp1.addPeak(8, 9, 3) 

238 with lsst.utils.tests.getTempFilePath(".fits") as tmpFile: 

239 fp1.writeFits(tmpFile) 

240 fp2 = afwDetect.Footprint.readFits(tmpFile) 

241 self.assertEqual(fp1.getArea(), fp2.getArea()) 

242 self.assertEqual(list(fp1.getSpans()), list(fp2.getSpans())) 

243 # can't use Peak operator== for comparison because it compares IDs, not positions/values 

244 self.assertEqual(len(fp1.getPeaks()), len(fp2.getPeaks())) 

245 for peak1, peak2 in zip(fp1.getPeaks(), fp2.getPeaks()): 

246 self.assertEqual(peak1.getIx(), peak2.getIx()) 

247 self.assertEqual(peak1.getIy(), peak2.getIy()) 

248 self.assertEqual(peak1.getFx(), peak2.getFx()) 

249 self.assertEqual(peak1.getFy(), peak2.getFy()) 

250 self.assertEqual(peak1.getPeakValue(), peak2.getPeakValue()) 

251 

252 def testBbox(self): 

253 """Add Spans and check bounding box""" 

254 foot = afwDetect.Footprint() 

255 spanLists = [afwGeom.Span(10, 100, 105), afwGeom.Span(11, 99, 104)] 

256 spanSet = afwGeom.SpanSet(spanLists) 

257 foot.spans = spanSet 

258 

259 bbox = foot.getBBox() 

260 self.assertEqual(bbox.getWidth(), 7) 

261 self.assertEqual(bbox.getHeight(), 2) 

262 self.assertEqual(bbox.getMinX(), 99) 

263 self.assertEqual(bbox.getMinY(), 10) 

264 self.assertEqual(bbox.getMaxX(), 105) 

265 self.assertEqual(bbox.getMaxY(), 11) 

266 # clip with a bbox that doesn't overlap at all 

267 bbox2 = lsst.geom.Box2I(lsst.geom.Point2I(5, 90), lsst.geom.Extent2I(1, 2)) 

268 foot.clipTo(bbox2) 

269 self.assertTrue(foot.getBBox().isEmpty()) 

270 self.assertEqual(foot.getArea(), 0) 

271 

272 def testFootprintFromBBox1(self): 

273 """Create a rectangular Footprint""" 

274 x0, y0, w, h = 9, 10, 7, 4 

275 spanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(x0, y0), 

276 lsst.geom.Extent2I(w, h))) 

277 foot = afwDetect.Footprint(spanSet) 

278 

279 bbox = foot.getBBox() 

280 

281 self.assertEqual(bbox.getWidth(), w) 

282 self.assertEqual(bbox.getHeight(), h) 

283 self.assertEqual(bbox.getMinX(), x0) 

284 self.assertEqual(bbox.getMinY(), y0) 

285 self.assertEqual(bbox.getMaxX(), x0 + w - 1) 

286 self.assertEqual(bbox.getMaxY(), y0 + h - 1) 

287 

288 def testGetBBox(self): 

289 """Check that Footprint.getBBox() returns a copy""" 

290 x0, y0, w, h = 9, 10, 7, 4 

291 spanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(x0, y0), 

292 lsst.geom.Extent2I(w, h))) 

293 foot = afwDetect.Footprint(spanSet) 

294 bbox = foot.getBBox() 

295 

296 dx, dy = 10, 20 

297 bbox.shift(lsst.geom.Extent2I(dx, dy)) 

298 

299 self.assertEqual(bbox.getMinX(), x0 + dx) 

300 self.assertEqual(foot.getBBox().getMinX(), x0) 

301 

302 def testFootprintFromEllipse(self): 

303 """Create an elliptical Footprint""" 

304 cen = lsst.geom.Point2D(23, 25) 

305 a, b, theta = 25, 15, 30 

306 ellipse = afwGeom.Ellipse( 

307 afwGeomEllipses.Axes(a, b, math.radians(theta)), 

308 cen) 

309 spanSet = afwGeom.SpanSet.fromShape(ellipse) 

310 foot = afwDetect.Footprint(spanSet, 

311 lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

312 lsst.geom.Extent2I(50, 60))) 

313 

314 idImage = afwImage.ImageU(lsst.geom.Extent2I( 

315 foot.getRegion().getWidth(), foot.getRegion().getHeight())) 

316 idImage.set(0) 

317 

318 foot.spans.setImage(idImage, 42) 

319 

320 if display: 

321 disp = afwDisplay.Display(frame=2) 

322 disp.mtv(idImage, title=self._testMethodName + " image") 

323 afwDisplay.utils.drawFootprint(foot, frame=2) 

324 shape = foot.getShape() 

325 shape.scale(2) # <r^2> = 1/2 for a disk 

326 disp.dot(shape, *cen, ctype=afwDisplay.RED) 

327 

328 shape = foot.getShape() 

329 shape.scale(2) # <r^2> = 1/2 for a disk 

330 disp.dot(shape, *cen, ctype=afwDisplay.MAGENTA) 

331 

332 axes = afwGeom.ellipses.Axes(foot.getShape()) 

333 axes.scale(2) # <r^2> = 1/2 for a disk 

334 

335 self.assertEqual(foot.getCentroid(), cen) 

336 self.assertLess(abs(a - axes.getA()), 0.15, f"a: {a:g} vs. {axes.getA():g}") 

337 self.assertLess(abs(b - axes.getB()), 0.02, f"b: {b:g} va. {axes.getB():g}") 

338 self.assertLess(abs(theta - math.degrees(axes.getTheta())), 0.2, 

339 f"theta: {theta:g} vs. {math.degrees(axes.getTheta()):g}") 

340 

341 def testCopy(self): 

342 bbox = lsst.geom.BoxI(lsst.geom.PointI(0, 2), lsst.geom.PointI(5, 6)) 

343 

344 fp = afwDetect.Footprint(afwGeom.SpanSet(bbox), bbox) 

345 

346 # test copy construct 

347 fp2 = afwDetect.Footprint(fp) 

348 

349 self.assertEqual(fp2.getBBox(), bbox) 

350 self.assertEqual(fp2.getRegion(), bbox) 

351 self.assertEqual(fp2.getArea(), bbox.getArea()) 

352 

353 y = bbox.getMinY() 

354 for s in fp2.getSpans(): 

355 self.assertEqual(s.getY(), y) 

356 self.assertEqual(s.getX0(), bbox.getMinX()) 

357 self.assertEqual(s.getX1(), bbox.getMaxX()) 

358 y += 1 

359 

360 # test assignment 

361 fp3 = afwDetect.Footprint() 

362 fp3.assign(fp) 

363 self.assertEqual(fp3.getBBox(), bbox) 

364 self.assertEqual(fp3.getRegion(), bbox) 

365 self.assertEqual(fp3.getArea(), bbox.getArea()) 

366 

367 y = bbox.getMinY() 

368 for s in fp3.getSpans(): 

369 self.assertEqual(s.getY(), y) 

370 self.assertEqual(s.getX0(), bbox.getMinX()) 

371 self.assertEqual(s.getX1(), bbox.getMaxX()) 

372 y += 1 

373 

374 def testShrink(self): 

375 width, height = 5, 10 # Size of footprint 

376 x0, y0 = 50, 50 # Position of footprint 

377 imwidth, imheight = 100, 100 # Size of image 

378 

379 spanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(x0, y0), 

380 lsst.geom.Extent2I(width, height))) 

381 region = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

382 lsst.geom.Extent2I(imwidth, imheight)) 

383 foot = afwDetect.Footprint(spanSet, region) 

384 self.assertEqual(foot.getArea(), width*height) 

385 

386 # Add some peaks to the original footprint and check that those lying outside 

387 # the shrunken footprint are omitted from the returned shrunken footprint. 

388 foot.addPeak(50, 50, 1) # should be omitted in shrunken footprint 

389 foot.addPeak(52, 52, 2) # should be kept in shrunken footprint 

390 foot.addPeak(50, 59, 3) # should be omitted in shrunken footprint 

391 self.assertEqual(len(foot.getPeaks()), 3) # check that all three peaks were added 

392 

393 # Shrinking by one pixel makes each dimension *two* pixels shorter. 

394 shrunk = afwDetect.Footprint().assign(foot) 

395 shrunk.erode(1) 

396 self.assertEqual(3*8, shrunk.getArea()) 

397 

398 # Shrunken footprint should now only contain one peak at (52, 52) 

399 self.assertEqual(len(shrunk.getPeaks()), 1) 

400 peak = shrunk.getPeaks()[0] 

401 self.assertEqual((peak.getIx(), peak.getIy()), (52, 52)) 

402 

403 # Without shifting the centroid 

404 self.assertEqual(shrunk.getCentroid(), foot.getCentroid()) 

405 

406 # Get the same result from a Manhattan shrink 

407 shrunk = afwDetect.Footprint().assign(foot) 

408 shrunk.erode(1, afwGeom.Stencil.MANHATTAN) 

409 self.assertEqual(3*8, shrunk.getArea()) 

410 self.assertEqual(shrunk.getCentroid(), foot.getCentroid()) 

411 

412 # Shrinking by a large amount leaves nothing. 

413 shrunkToNothing = afwDetect.Footprint().assign(foot) 

414 shrunkToNothing.erode(100) 

415 self.assertEqual(shrunkToNothing.getArea(), 0) 

416 

417 def testShrinkIsoVsManhattan(self): 

418 # Demonstrate that isotropic and Manhattan shrinks are different. 

419 radius = 8 

420 imwidth, imheight = 100, 100 

421 x0, y0 = imwidth//2, imheight//2 

422 nshrink = 4 

423 

424 ellipse = afwGeom.Ellipse( 

425 afwGeomEllipses.Axes(1.5*radius, 2*radius, 0), 

426 lsst.geom.Point2D(x0, y0)) 

427 spanSet = afwGeom.SpanSet.fromShape(ellipse) 

428 foot = afwDetect.Footprint( 

429 spanSet, 

430 lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

431 lsst.geom.Extent2I(imwidth, imheight))) 

432 footIsotropic = afwDetect.Footprint() 

433 footIsotropic.assign(foot) 

434 

435 foot.erode(nshrink, afwGeom.Stencil.MANHATTAN) 

436 footIsotropic.erode(nshrink) 

437 self.assertNotEqual(foot, footIsotropic) 

438 

439 def _fig8Test(self, x1, y1, x2, y2): 

440 # Construct a "figure of 8" consisting of two circles touching at the 

441 # centre of an image, then demonstrate that it shrinks correctly. 

442 # (Helper method for tests below.) 

443 radius = 3 

444 imwidth, imheight = 100, 100 

445 nshrink = 1 

446 

447 # These are the correct values for footprint sizes given the paramters 

448 # above. 

449 circle_npix = 29 

450 initial_npix = circle_npix*2 - 1 # touch at one pixel 

451 shrunk_npix = 26 

452 

453 box = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

454 lsst.geom.Extent2I(imwidth, imheight)) 

455 

456 e1 = afwGeom.Ellipse(afwGeomEllipses.Axes(radius, radius, 0), 

457 lsst.geom.Point2D(x1, y1)) 

458 spanSet1 = afwGeom.SpanSet.fromShape(e1) 

459 f1 = afwDetect.Footprint(spanSet1, box) 

460 self.assertEqual(f1.getArea(), circle_npix) 

461 

462 e2 = afwGeom.Ellipse(afwGeomEllipses.Axes(radius, radius, 0), 

463 lsst.geom.Point2D(x2, y2)) 

464 spanSet2 = afwGeom.SpanSet.fromShape(e2) 

465 f2 = afwDetect.Footprint(spanSet2, box) 

466 self.assertEqual(f2.getArea(), circle_npix) 

467 

468 initial = afwDetect.mergeFootprints(f1, f2) 

469 initial.setRegion(f2.getRegion()) # merge does not propagate the region 

470 self.assertEqual(initial_npix, initial.getArea()) 

471 

472 shrunk = afwDetect.Footprint().assign(initial) 

473 shrunk.erode(nshrink) 

474 self.assertEqual(shrunk_npix, shrunk.getArea()) 

475 

476 if display: 

477 idImage = afwImage.ImageU(imwidth, imheight) 

478 for i, foot in enumerate([initial, shrunk]): 

479 print(foot.getArea()) 

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

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

482 

483 def testShrinkEightVertical(self): 

484 # Test a "vertical" figure of 8. 

485 radius = 3 

486 imwidth, imheight = 100, 100 

487 self._fig8Test(imwidth//2, imheight//2 - radius, imwidth//2, imheight//2 + radius) 

488 

489 def testShrinkEightHorizontal(self): 

490 # Test a "horizontal" figure of 8. 

491 radius = 3 

492 imwidth, imheight = 100, 100 

493 self._fig8Test(imwidth//2 - radius, imheight//2, imwidth//2 + radius, imheight//2) 

494 

495 def testGrow(self): 

496 """Test growing a footprint""" 

497 x0, y0 = 20, 20 

498 width, height = 20, 30 

499 spanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(x0, y0), 

500 lsst.geom.Extent2I(width, height))) 

501 foot1 = afwDetect.Footprint(spanSet, 

502 lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

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

504 

505 # Add some peaks and check that they get copied into the new grown footprint 

506 foot1.addPeak(20, 20, 1) 

507 foot1.addPeak(30, 35, 2) 

508 foot1.addPeak(25, 45, 3) 

509 self.assertEqual(len(foot1.getPeaks()), 3) 

510 

511 bbox1 = foot1.getBBox() 

512 

513 self.assertEqual(bbox1.getMinX(), x0) 

514 self.assertEqual(bbox1.getMaxX(), x0 + width - 1) 

515 self.assertEqual(bbox1.getWidth(), width) 

516 

517 self.assertEqual(bbox1.getMinY(), y0) 

518 self.assertEqual(bbox1.getMaxY(), y0 + height - 1) 

519 self.assertEqual(bbox1.getHeight(), height) 

520 

521 ngrow = 5 

522 for isotropic in (True, False): 

523 foot2 = afwDetect.Footprint().assign(foot1) 

524 stencil = afwGeom.Stencil.CIRCLE if isotropic else \ 

525 afwGeom.Stencil.MANHATTAN 

526 foot2.dilate(ngrow, stencil) 

527 

528 # Check that the grown footprint is bigger than the original 

529 self.assertGreater(foot2.getArea(), foot1.getArea()) 

530 

531 # Check that peaks got copied into grown footprint 

532 self.assertEqual(len(foot2.getPeaks()), 3) 

533 for peak in foot2.getPeaks(): 

534 self.assertIn((peak.getIx(), peak.getIy()), 

535 [(20, 20), (30, 35), (25, 45)]) 

536 

537 bbox2 = foot2.getBBox() 

538 

539 # check bbox2 

540 self.assertEqual(bbox2.getMinX(), x0 - ngrow) 

541 self.assertEqual(bbox2.getWidth(), width + 2*ngrow) 

542 

543 self.assertEqual(bbox2.getMinY(), y0 - ngrow) 

544 self.assertEqual(bbox2.getHeight(), height + 2*ngrow) 

545 # Check that region was preserved 

546 self.assertEqual(foot1.getRegion(), foot2.getRegion()) 

547 

548 def testFootprintToBBoxList(self): 

549 """Test footprintToBBoxList""" 

550 region = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(12, 10)) 

551 foot = afwDetect.Footprint(afwGeom.SpanSet(), region) 

552 spanList = [afwGeom.Span(*span) for span in ((3, 3, 5), (3, 7, 7), 

553 (4, 2, 3), (4, 5, 7), 

554 (5, 2, 3), (5, 5, 8), 

555 (6, 3, 5))] 

556 foot.spans = afwGeom.SpanSet(spanList) 

557 

558 idImage = afwImage.ImageU(region.getDimensions()) 

559 idImage.set(0) 

560 

561 foot.spans.setImage(idImage, 1) 

562 if display: 

563 disp = afwDisplay.Display(frame=1) 

564 disp.mtv(idImage, title=self._testMethodName + " image") 

565 

566 idImageFromBBox = idImage.Factory(idImage, True) 

567 idImageFromBBox.set(0) 

568 bboxes = afwDetect.footprintToBBoxList(foot) 

569 for bbox in bboxes: 

570 x0, y0, x1, y1 = bbox.getMinX(), bbox.getMinY(), \ 

571 bbox.getMaxX(), bbox.getMaxY() 

572 

573 for y in range(y0, y1 + 1): 

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

575 idImageFromBBox[x, y, afwImage.LOCAL] = 1 

576 

577 if display: 

578 x0 -= 0.5 

579 y0 -= 0.5 

580 x1 += 0.5 

581 y1 += 0.5 

582 

583 disp.line([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], ctype=afwDisplay.RED) 

584 

585 idImageFromBBox -= idImage # should be blank 

586 stats = afwMath.makeStatistics(idImageFromBBox, afwMath.MAX) 

587 

588 self.assertEqual(stats.getValue(), 0) 

589 

590 def testWriteDefect(self): 

591 """Write a Footprint as a set of Defects""" 

592 region = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(12, 10)) 

593 spanSet = afwGeom.SpanSet([afwGeom.Span(*span) for span in [(3, 3, 5), 

594 (3, 7, 7), 

595 (4, 2, 3), 

596 (4, 5, 7), 

597 (5, 2, 3), 

598 (5, 5, 8), 

599 (6, 3, 5)]]) 

600 foot = afwDetect.Footprint(spanSet, region) 

601 

602 openedFile = False 

603 if True: 

604 fd = open("/dev/null", "w") 

605 openedFile = True 

606 else: 

607 fd = sys.stdout 

608 

609 afwDetectUtils.writeFootprintAsDefects(fd, foot) 

610 if openedFile: 

611 fd.close() 

612 

613 def testSetFromFootprint(self): 

614 """Test setting mask/image pixels from a Footprint list""" 

615 mi = afwImage.MaskedImageF(lsst.geom.Extent2I(12, 8)) 

616 im = mi.getImage() 

617 # 

618 # Objects that we should detect 

619 # 

620 self.objects = [] 

621 self.objects += [Object(10, [(1, 4, 4), (2, 3, 5), (3, 4, 4)])] 

622 self.objects += [Object(20, [(5, 7, 8), (5, 10, 10), (6, 8, 9)])] 

623 self.objects += [Object(20, [(6, 3, 3)])] 

624 

625 im.set(0) # clear image 

626 for obj in self.objects: 

627 obj.insert(im) 

628 

629 ds = afwDetect.FootprintSet(mi, afwDetect.Threshold(15)) 

630 

631 objects = ds.getFootprints() 

632 afwDetect.setMaskFromFootprintList(mi.getMask(), objects, 0x1) 

633 

634 self.assertEqual(mi.getMask()[4, 2, afwImage.LOCAL], 0x0) 

635 self.assertEqual(mi.getMask()[3, 6, afwImage.LOCAL], 0x1) 

636 

637 self.assertEqual(mi.getImage()[3, 6, afwImage.LOCAL], 20) 

638 for ft in objects: 

639 ft.spans.setImage(mi.getImage(), 5.0) 

640 self.assertEqual(mi.getImage()[4, 2, afwImage.LOCAL], 10) 

641 self.assertEqual(mi.getImage()[3, 6, afwImage.LOCAL], 5) 

642 

643 if display: 

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

645 # 

646 # Check Footprint.contains() while we are about it 

647 # 

648 self.assertTrue(objects[0].contains(lsst.geom.Point2I(7, 5))) 

649 self.assertFalse(objects[0].contains(lsst.geom.Point2I(10, 6))) 

650 self.assertFalse(objects[0].contains(lsst.geom.Point2I(7, 6))) 

651 self.assertFalse(objects[0].contains(lsst.geom.Point2I(4, 2))) 

652 

653 self.assertTrue(objects[1].contains(lsst.geom.Point2I(3, 6))) 

654 

655 # Verify the FootprintSet footprint list setter can accept inputs from 

656 # the footprint list getter 

657 # Create a copy of the ds' FootprintList 

658 dsFpList = ds.getFootprints() 

659 footprintListCopy = [afwDetect.Footprint().assign(f) for f in dsFpList] 

660 # Use the FootprintList setter with the output from the getter 

661 ds.setFootprints(ds.getFootprints()[:-1]) 

662 dsFpListNew = ds.getFootprints() 

663 self.assertTrue(len(dsFpListNew) == len(footprintListCopy)-1) 

664 for new, old in zip(dsFpListNew, footprintListCopy[:-1]): 

665 self.assertEqual(new, old) 

666 

667 def testMakeFootprintSetXY0(self): 

668 """Test setting mask/image pixels from a Footprint list""" 

669 mi = afwImage.MaskedImageF(lsst.geom.Extent2I(12, 8)) 

670 im = mi.getImage() 

671 im.set(100) 

672 

673 mi.setXY0(lsst.geom.PointI(2, 2)) 

674 afwDetect.FootprintSet(mi, afwDetect.Threshold(1), "DETECTED") 

675 

676 bitmask = mi.getMask().getPlaneBitMask("DETECTED") 

677 for y in range(im.getHeight()): 

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

679 self.assertEqual(mi.getMask()[x, y, afwImage.LOCAL], bitmask) 

680 

681 def testTransform(self): 

682 dims = lsst.geom.Extent2I(512, 512) 

683 bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), dims) 

684 radius = 5 

685 offset = lsst.geom.Extent2D(123, 456) 

686 crval = lsst.geom.SpherePoint(0, 0, lsst.geom.degrees) 

687 crpix = lsst.geom.Point2D(0, 0) 

688 cdMatrix = np.array([1.0e-5, 0.0, 0.0, 1.0e-5]) 

689 cdMatrix.shape = (2, 2) 

690 source = afwGeom.makeSkyWcs(crval=crval, crpix=crpix, cdMatrix=cdMatrix) 

691 target = afwGeom.makeSkyWcs(crval=crval, crpix=crpix + offset, cdMatrix=cdMatrix) 

692 sourceSpanSet = afwGeom.SpanSet.fromShape(radius, 

693 afwGeom.Stencil.CIRCLE) 

694 sourceSpanSet = sourceSpanSet.shiftedBy(12, 34) 

695 fpSource = afwDetect.Footprint(sourceSpanSet, bbox) 

696 

697 fpTarget = fpSource.transform(source, target, bbox) 

698 

699 self.assertEqual(len(fpSource.getSpans()), len(fpTarget.getSpans())) 

700 self.assertEqual(fpSource.getArea(), fpTarget.getArea()) 

701 imSource = afwImage.ImageU(dims) 

702 fpSource.spans.setImage(imSource, 1) 

703 

704 imTarget = afwImage.ImageU(dims) 

705 fpTarget.spans.setImage(imTarget, 1) 

706 

707 subSource = imSource.Factory(imSource, fpSource.getBBox()) 

708 subTarget = imTarget.Factory(imTarget, fpTarget.getBBox()) 

709 self.assertTrue(np.all(subSource.getArray() == subTarget.getArray())) 

710 

711 # make a bbox smaller than the target footprint 

712 bbox2 = lsst.geom.Box2I(fpTarget.getBBox()) 

713 bbox2.grow(-1) 

714 fpTarget2 = fpSource.transform(source, target, bbox2) # this one clips 

715 fpTarget3 = fpSource.transform(source, target, bbox2, False) # this one doesn't 

716 self.assertTrue(bbox2.contains(fpTarget2.getBBox())) 

717 self.assertFalse(bbox2.contains(fpTarget3.getBBox())) 

718 self.assertNotEqual(fpTarget.getArea(), fpTarget2.getArea()) 

719 self.assertEqual(fpTarget.getArea(), fpTarget3.getArea()) 

720 

721 # Test that peakCatalogs get Transformed correctly 

722 truthList = [(x, y, 10) for x, y in zip(range(-2, 2), range(-1, 3))] 

723 for value in truthList: 

724 fpSource.addPeak(*value) 

725 scaleFactor = 2 

726 linTrans = lsst.geom.LinearTransform(np.array([[scaleFactor, 0], 

727 [0, scaleFactor]], 

728 dtype=float)) 

729 linTransFootprint = fpSource.transform(linTrans, fpSource.getBBox(), 

730 False) 

731 for peak, truth in zip(linTransFootprint.peaks, truthList): 

732 # Multiplied by two because that is the linear transform scaling 

733 # factor 

734 self.assertEqual(peak.getIx(), truth[0]*scaleFactor) 

735 self.assertEqual(peak.getIy(), truth[1]*scaleFactor) 

736 

737 def testCopyWithinFootprintImage(self): 

738 W, H = 10, 10 

739 dims = lsst.geom.Extent2I(W, H) 

740 source = afwImage.ImageF(dims) 

741 dest = afwImage.ImageF(dims) 

742 sa = source.getArray() 

743 for i in range(H): 

744 for j in range(W): 

745 sa[i, j] = 100*i + j 

746 

747 footSpans = [s for s in self.foot.spans] 

748 footSpans.append(afwGeom.Span(4, 3, 6)) 

749 footSpans.append(afwGeom.Span(5, 2, 4)) 

750 self.foot.spans = afwGeom.SpanSet(footSpans) 

751 

752 self.foot.spans.copyImage(source, dest) 

753 

754 da = dest.getArray() 

755 self.assertEqual(da[4, 2], 0) 

756 self.assertEqual(da[4, 3], 403) 

757 self.assertEqual(da[4, 4], 404) 

758 self.assertEqual(da[4, 5], 405) 

759 self.assertEqual(da[4, 6], 406) 

760 self.assertEqual(da[4, 7], 0) 

761 self.assertEqual(da[5, 1], 0) 

762 self.assertEqual(da[5, 2], 502) 

763 self.assertEqual(da[5, 3], 503) 

764 self.assertEqual(da[5, 4], 504) 

765 self.assertEqual(da[5, 5], 0) 

766 self.assertTrue(np.all(da[:4, :] == 0)) 

767 self.assertTrue(np.all(da[6:, :] == 0)) 

768 

769 def testCopyWithinFootprintOutside(self): 

770 """Copy a footprint that is larger than the image""" 

771 target = afwImage.ImageF(100, 100) 

772 target.set(0) 

773 subTarget = afwImage.ImageF(target, lsst.geom.Box2I(lsst.geom.Point2I(40, 40), 

774 lsst.geom.Extent2I(20, 20))) 

775 source = afwImage.ImageF(10, 30) 

776 source.setXY0(45, 45) 

777 source.set(1.0) 

778 

779 foot = afwDetect.Footprint() 

780 spanList = [afwGeom.Span(*s) for s in ( 

781 (50, 50, 60), # Oversized on the source image, right; only some pixels overlap 

782 (60, 0, 100), # Oversized on the source, left and right; and on sub-target image, top 

783 (99, 0, 1000), # Oversized on the source image, top, left and right; aiming for segfault 

784 )] 

785 foot.spans = afwGeom.SpanSet(spanList) 

786 

787 foot.spans.clippedTo(subTarget.getBBox()).clippedTo(source.getBBox()).\ 

788 copyImage(source, subTarget) 

789 

790 expected = np.zeros((100, 100)) 

791 expected[50, 50:55] = 1.0 

792 

793 self.assertTrue(np.all(target.getArray() == expected)) 

794 

795 def testCopyWithinFootprintMaskedImage(self): 

796 W, H = 10, 10 

797 dims = lsst.geom.Extent2I(W, H) 

798 source = afwImage.MaskedImageF(dims) 

799 dest = afwImage.MaskedImageF(dims) 

800 sa = source.getImage().getArray() 

801 sv = source.getVariance().getArray() 

802 sm = source.getMask().getArray() 

803 for i in range(H): 

804 for j in range(W): 

805 sa[i, j] = 100*i + j 

806 sv[i, j] = 100*j + i 

807 sm[i, j] = 1 

808 

809 footSpans = [s for s in self.foot.spans] 

810 footSpans.append(afwGeom.Span(4, 3, 6)) 

811 footSpans.append(afwGeom.Span(5, 2, 4)) 

812 self.foot.spans = afwGeom.SpanSet(footSpans) 

813 

814 self.foot.spans.copyMaskedImage(source, dest) 

815 

816 da = dest.getImage().getArray() 

817 dv = dest.getVariance().getArray() 

818 dm = dest.getMask().getArray() 

819 

820 self.assertEqual(da[4, 2], 0) 

821 self.assertEqual(da[4, 3], 403) 

822 self.assertEqual(da[4, 4], 404) 

823 self.assertEqual(da[4, 5], 405) 

824 self.assertEqual(da[4, 6], 406) 

825 self.assertEqual(da[4, 7], 0) 

826 self.assertEqual(da[5, 1], 0) 

827 self.assertEqual(da[5, 2], 502) 

828 self.assertEqual(da[5, 3], 503) 

829 self.assertEqual(da[5, 4], 504) 

830 self.assertEqual(da[5, 5], 0) 

831 self.assertTrue(np.all(da[:4, :] == 0)) 

832 self.assertTrue(np.all(da[6:, :] == 0)) 

833 

834 self.assertEqual(dv[4, 2], 0) 

835 self.assertEqual(dv[4, 3], 304) 

836 self.assertEqual(dv[4, 4], 404) 

837 self.assertEqual(dv[4, 5], 504) 

838 self.assertEqual(dv[4, 6], 604) 

839 self.assertEqual(dv[4, 7], 0) 

840 self.assertEqual(dv[5, 1], 0) 

841 self.assertEqual(dv[5, 2], 205) 

842 self.assertEqual(dv[5, 3], 305) 

843 self.assertEqual(dv[5, 4], 405) 

844 self.assertEqual(dv[5, 5], 0) 

845 self.assertTrue(np.all(dv[:4, :] == 0)) 

846 self.assertTrue(np.all(dv[6:, :] == 0)) 

847 

848 self.assertTrue(np.all(dm[4, 3:7] == 1)) 

849 self.assertTrue(np.all(dm[5, 2:5] == 1)) 

850 self.assertTrue(np.all(dm[:4, :] == 0)) 

851 self.assertTrue(np.all(dm[6:, :] == 0)) 

852 self.assertTrue(np.all(dm[4, :3] == 0)) 

853 self.assertTrue(np.all(dm[4, 7:] == 0)) 

854 

855 def testMergeFootprints(self): 

856 f1 = self.foot 

857 f2 = afwDetect.Footprint() 

858 

859 spanList1 = [(10, 10, 20), 

860 (10, 30, 40), 

861 (10, 50, 60), 

862 (11, 30, 50), 

863 (12, 30, 50), 

864 (13, 10, 20), 

865 (13, 30, 40), 

866 (13, 50, 60), 

867 (15, 10, 20), 

868 (15, 31, 40), 

869 (15, 51, 60)] 

870 spanSet1 = afwGeom.SpanSet([afwGeom.Span(*span) for span in spanList1]) 

871 f1.spans = spanSet1 

872 

873 spanList2 = [(8, 10, 20), 

874 (9, 20, 30), 

875 (10, 0, 9), 

876 (10, 35, 65), 

877 (10, 70, 80), 

878 (13, 49, 54), 

879 (14, 10, 30), 

880 (15, 21, 30), 

881 (15, 41, 50), 

882 (15, 61, 70)] 

883 spanSet2 = afwGeom.SpanSet([afwGeom.Span(*span) for span in spanList2]) 

884 f2.spans = spanSet2 

885 

886 fA = afwDetect.mergeFootprints(f1, f2) 

887 fB = afwDetect.mergeFootprints(f2, f1) 

888 

889 ims = [] 

890 for i, f in enumerate([f1, f2, fA, fB]): 

891 im1 = afwImage.ImageU(100, 100) 

892 im1.set(0) 

893 imbb = im1.getBBox() 

894 f.setRegion(imbb) 

895 f.spans.setImage(im1, 1) 

896 ims.append(im1) 

897 

898 for i, merged in enumerate([ims[2], ims[3]]): 

899 m = merged.getArray() 

900 a1 = ims[0].getArray() 

901 a2 = ims[1].getArray() 

902 # Slightly looser tests to start... 

903 # Every pixel in f1 is in f[AB] 

904 self.assertTrue(np.all(m.flat[np.flatnonzero(a1)] == 1)) 

905 # Every pixel in f2 is in f[AB] 

906 self.assertTrue(np.all(m.flat[np.flatnonzero(a2)] == 1)) 

907 # merged == a1 | a2. 

908 self.assertTrue(np.all(m == np.maximum(a1, a2))) 

909 

910 def testPeakSort(self): 

911 spanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(0, 0), 

912 lsst.geom.Point2I(10, 10))) 

913 footprint = afwDetect.Footprint(spanSet) 

914 footprint.addPeak(4, 5, 1) 

915 footprint.addPeak(3, 2, 5) 

916 footprint.addPeak(7, 8, -2) 

917 footprint.addPeak(5, 7, 4) 

918 footprint.sortPeaks() 

919 self.assertEqual([peak.getIx() for peak in footprint.getPeaks()], 

920 [3, 5, 4, 7]) 

921 

922 def testInclude(self): 

923 """Test that we can expand a Footprint to include the union of itself and all others provided.""" 

924 region = lsst.geom.Box2I(lsst.geom.Point2I(-6, -6), lsst.geom.Point2I(6, 6)) 

925 parentSpanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(-2, -2), 

926 lsst.geom.Point2I(2, 2))) 

927 parent = afwDetect.Footprint(parentSpanSet, region) 

928 parent.addPeak(0, 0, float("NaN")) 

929 child1SpanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(-3, 0), 

930 lsst.geom.Point2I(0, 3))) 

931 child1 = afwDetect.Footprint(child1SpanSet, region) 

932 child1.addPeak(-1, 1, float("NaN")) 

933 child2SpanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(-4, -3), 

934 lsst.geom.Point2I(-1, 0))) 

935 child2 = afwDetect.Footprint(child2SpanSet, region) 

936 child3SpanSet = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(4, -1), 

937 lsst.geom.Point2I(6, 1))) 

938 child3 = afwDetect.Footprint(child3SpanSet) 

939 merge123 = afwDetect.Footprint(parent) 

940 merge123.spans = merge123.spans.union(child1.spans).union(child2.spans).union(child3.spans) 

941 self.assertTrue(merge123.getBBox().contains(parent.getBBox())) 

942 self.assertTrue(merge123.getBBox().contains(child1.getBBox())) 

943 self.assertTrue(merge123.getBBox().contains(child2.getBBox())) 

944 self.assertTrue(merge123.getBBox().contains(child3.getBBox())) 

945 mask123a = afwImage.Mask(region) 

946 mask123b = afwImage.Mask(region) 

947 parent.spans.setMask(mask123a, 1) 

948 child1.spans.setMask(mask123a, 1) 

949 child2.spans.setMask(mask123a, 1) 

950 child3.spans.setMask(mask123a, 1) 

951 merge123.spans.setMask(mask123b, 1) 

952 self.assertEqual(mask123a.getArray().sum(), merge123.getArea()) 

953 self.assertFloatsAlmostEqual(mask123a.getArray(), mask123b.getArray(), 

954 rtol=0, atol=0) 

955 

956 # Test that ignoreSelf=True works for include 

957 childOnly = afwDetect.Footprint() 

958 childOnly.spans = childOnly.spans.union(child1.spans).union(child2.spans).union(child3.spans) 

959 merge123 = afwDetect.Footprint(parent) 

960 merge123.spans = child1.spans.union(child2.spans).union(child3.spans) 

961 maskChildren = afwImage.Mask(region) 

962 mask123 = afwImage.Mask(region) 

963 childOnly.spans.setMask(maskChildren, 1) 

964 merge123.spans.setMask(mask123, 1) 

965 self.assertTrue(np.all(maskChildren.getArray() == mask123.getArray())) 

966 

967 def checkEdge(self, footprint): 

968 """Check that Footprint::findEdgePixels() works""" 

969 bbox = footprint.getBBox() 

970 bbox.grow(3) 

971 

972 def makeImage(area): 

973 """Make an ImageF with 1 in the footprint, and 0 elsewhere""" 

974 ones = afwImage.ImageI(bbox) 

975 ones.set(1) 

976 image = afwImage.ImageI(bbox) 

977 image.set(0) 

978 if isinstance(area, afwDetect.Footprint): 

979 area.spans.copyImage(ones, image) 

980 if isinstance(area, afwGeom.SpanSet): 

981 area.copyImage(ones, image) 

982 return image 

983 

984 edges = self.foot.spans.findEdgePixels() 

985 edgeImage = makeImage(edges) 

986 

987 # Find edges with an edge-detection kernel 

988 image = makeImage(self.foot) 

989 kernel = afwImage.ImageD(3, 3) 

990 kernel[1, 1, afwImage.LOCAL] = 4 

991 for x, y in [(1, 2), (0, 1), (1, 0), (2, 1)]: 

992 kernel[x, y, afwImage.LOCAL] = -1 

993 kernel.setXY0(1, 1) 

994 result = afwImage.ImageI(bbox) 

995 result.set(0) 

996 afwMath.convolve(result, image, afwMath.FixedKernel(kernel), 

997 afwMath.ConvolutionControl(False)) 

998 result.getArray().__imul__(image.getArray()) 

999 trueEdges = np.where(result.getArray() > 0, 1, 0) 

1000 

1001 self.assertTrue(np.all(trueEdges == edgeImage.getArray())) 

1002 

1003 def testEdge(self): 

1004 """Test for Footprint::findEdgePixels()""" 

1005 foot = afwDetect.Footprint() 

1006 spanList = [afwGeom.Span(*span) for span in ((3, 3, 9), 

1007 (4, 2, 4), 

1008 (4, 6, 7), 

1009 (4, 9, 11), 

1010 (5, 3, 9), 

1011 (6, 6, 7))] 

1012 foot.spans = afwGeom.SpanSet(spanList) 

1013 self.checkEdge(foot) 

1014 

1015 # This footprint came from a very large Footprint in a deep HSC coadd patch 

1016 self.checkEdge(afwDetect.Footprint.readFits( 

1017 os.path.join(testPath, "testFootprintEdge.fits"))) 

1018 

1019 def testExtractImage(self): 

1020 """Test image extraction from a Footprint""" 

1021 image = afwImage.Image( 

1022 np.arange(100, dtype=np.int32).reshape(10, 10), 

1023 xy0=lsst.geom.Point2I(23, 25), 

1024 dtype="I" 

1025 ) 

1026 radius = 3 

1027 spans = afwGeom.SpanSet.fromShape(radius, afwGeom.Stencil.CIRCLE, offset=(27, 30)) 

1028 footprint = afwDetect.Footprint(spans) 

1029 

1030 # The extracted footprint should be the same as the product of the 

1031 # spans and the overlapped box with the image 

1032 truth = spans.asArray() * image.array[2:9, 1:8] 

1033 # Test hard coded value 

1034 self.assertEqual(np.sum(truth), 1566) 

1035 self.assertEqual(footprint.computeFluxFromImage(image), 1566) 

1036 

1037 # Test the array method with an offset 

1038 # Since 3 is subtracted from all of the pixels, and there are 29 pixls in the image, 

1039 # we subtract 29*3 from the total. 

1040 self.assertEqual(footprint.computeFluxFromArray(image.array-3, image.getBBox().getMin()), 1566-29*3) 

1041 

1042 def testFootprintIntersect(self): 

1043 f1 = self.foot 

1044 f2 = afwDetect.Footprint() 

1045 spanSet1 = afwGeom.SpanSet.fromShape(5, afwGeom.Stencil.BOX).shiftedBy(15, 15) 

1046 f1.spans = spanSet1 

1047 # Intersecting peaks 

1048 intersectingPeaks = [ 

1049 f1.addPeak(12, 11, 20), 

1050 f1.addPeak(12, 12, 20), 

1051 f1.addPeak(12, 13, 30), 

1052 ] 

1053 # Non-intersecting peaks 

1054 outerPeaks = [ 

1055 f1.addPeak(11, 10, 10), 

1056 f1.addPeak(18, 18, 10), 

1057 ] 

1058 

1059 spanSet2 = afwGeom.SpanSet.fromShape(3, afwGeom.Stencil.BOX).shiftedBy(15, 11) 

1060 f2.spans = spanSet2 

1061 outerPeaks.append(f2.addPeak(13, 10, 10)) 

1062 

1063 intersection = f1.intersect(f2) 

1064 self.assertEqual(intersection.spans, spanSet1.intersect(spanSet2)) 

1065 

1066 for peak in intersectingPeaks: 

1067 self.assertIn(peak, intersection.getPeaks()) 

1068 for peak in outerPeaks: 

1069 self.assertNotIn(peak, intersection.getPeaks()) 

1070 

1071 

1072class FootprintSetTestCase(unittest.TestCase): 

1073 """A test case for FootprintSet""" 

1074 

1075 def setUp(self): 

1076 self.ms = afwImage.MaskedImageF(lsst.geom.Extent2I(12, 8)) 

1077 im = self.ms.getImage() 

1078 # 

1079 # Objects that we should detect 

1080 # 

1081 self.objects = [] 

1082 self.objects += [Object(10, [(1, 4, 4), (2, 3, 5), (3, 4, 4)])] 

1083 self.objects += [Object(20, [(5, 7, 8), (5, 10, 10), (6, 8, 9)])] 

1084 self.objects += [Object(20, [(6, 3, 3)])] 

1085 

1086 self.ms.set((0, 0x0, 4.0)) # clear image; set variance 

1087 for obj in self.objects: 

1088 obj.insert(im) 

1089 

1090 def tearDown(self): 

1091 del self.ms 

1092 

1093 def testGC(self): 

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

1095 afwDetect.FootprintSet(afwImage.MaskedImageF(lsst.geom.Extent2I(10, 20)), 

1096 afwDetect.Threshold(10)) 

1097 

1098 def testFootprints(self): 

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

1100 ds = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10)) 

1101 

1102 objects = ds.getFootprints() 

1103 

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

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

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

1107 

1108 def test_str(self): 

1109 footprints = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10)) 

1110 expect = ("3 footprints:" 

1111 "\n5 peaks, area=5, centroid=(4, 2)" 

1112 "\n5 peaks, area=5, centroid=(8.4, 5.4)" 

1113 "\n1 peaks, area=1, centroid=(3, 6)") 

1114 self.assertEqual(str(footprints), expect) 

1115 

1116 def testFootprints2(self): 

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

1118 ds = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10)) 

1119 

1120 objects = ds.getFootprints() 

1121 

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

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

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

1125 

1126 def testFootprints3(self): 

1127 """Check that we found the correct number of objects using FootprintSet and PIXEL_STDEV""" 

1128 threshold = 4.5 # in units of sigma 

1129 

1130 self.ms[2, 4, afwImage.LOCAL] = (10, 0x0, 36) # not detected (high variance) 

1131 

1132 y, x = self.objects[2].spans[0][0:2] 

1133 self.ms[x, y, afwImage.LOCAL] = (threshold, 0x0, 1.0) 

1134 

1135 ds = afwDetect.FootprintSet(self.ms, 

1136 afwDetect.createThreshold(threshold, "pixel_stdev"), "OBJECT") 

1137 

1138 objects = ds.getFootprints() 

1139 

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

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

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

1143 

1144 def testFootprintsMasks(self): 

1145 """Check that detectionSets have the proper mask bits set""" 

1146 ds = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10), "OBJECT") 

1147 objects = ds.getFootprints() 

1148 

1149 if display: 

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

1151 

1152 mask = self.ms.getMask() 

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

1154 for sp in objects[i].getSpans(): 

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

1156 self.assertEqual(mask[x, sp.getY(), afwImage.LOCAL], 

1157 mask.getPlaneBitMask("OBJECT")) 

1158 

1159 def testFootprintsImageId(self): 

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

1161 ds = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10)) 

1162 objects = ds.getFootprints() 

1163 

1164 idImage = afwImage.ImageU(self.ms.getDimensions()) 

1165 idImage.set(0) 

1166 

1167 for i, foot in enumerate(objects): 

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

1169 

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

1171 for sp in objects[i].getSpans(): 

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

1173 self.assertEqual(idImage[x, sp.getY(), afwImage.LOCAL], i + 1) 

1174 

1175 def testFootprintSetImageId(self): 

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

1177 ds = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10)) 

1178 objects = ds.getFootprints() 

1179 

1180 idImage = ds.insertIntoImage() 

1181 if display: 

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

1183 

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

1185 for sp in objects[i].getSpans(): 

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

1187 self.assertEqual(idImage[x, sp.getY(), afwImage.LOCAL], i + 1) 

1188 

1189 def testFootprintsImage(self): 

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

1191 ds = afwDetect.FootprintSet(self.ms.getImage(), afwDetect.Threshold(10)) 

1192 

1193 objects = ds.getFootprints() 

1194 

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

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

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

1198 

1199 def testGrow2(self): 

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

1201 ds = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10), "OBJECT") 

1202 

1203 idImage = afwImage.ImageU(self.ms.getDimensions()) 

1204 idImage.set(0) 

1205 

1206 i = 1 

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

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

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

1210 i += 1 

1211 

1212 if display: 

1213 afwDisplay.Display(frame=0).mtv(self.ms, title=self._testMethodName + " self.ms") 

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

1215 

1216 def testFootprintPeaks(self): 

1217 """Test that we can extract the peaks from a Footprint""" 

1218 fs = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10), "OBJECT") 

1219 

1220 foot = fs.getFootprints()[0] 

1221 

1222 self.assertEqual(len(foot.getPeaks()), 5) 

1223 

1224 

1225class MaskFootprintSetTestCase(unittest.TestCase): 

1226 """A test case for generating FootprintSet from Masks""" 

1227 

1228 def setUp(self): 

1229 self.mim = afwImage.MaskedImageF(lsst.geom.ExtentI(12, 8)) 

1230 # 

1231 # Objects that we should detect 

1232 # 

1233 self.objects = [] 

1234 self.objects += [Object(0x2, [(1, 4, 4), (2, 3, 5), (3, 4, 4)])] 

1235 self.objects += [Object(0x41, [(5, 7, 8), (6, 8, 8)])] 

1236 self.objects += [Object(0x42, [(5, 10, 10)])] 

1237 self.objects += [Object(0x82, [(6, 3, 3)])] 

1238 

1239 self.mim.set((0, 0, 0)) # clear image 

1240 for obj in self.objects: 

1241 obj.insert(self.mim.getImage()) 

1242 obj.insert(self.mim.getMask()) 

1243 

1244 if display: 

1245 afwDisplay.Display(frame=0).mtv(self.mim, title=self._testMethodName + " self.mim") 

1246 

1247 def tearDown(self): 

1248 del self.mim 

1249 

1250 def testFootprints(self): 

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

1252 level = 0x2 

1253 ds = afwDetect.FootprintSet(self.mim.getMask(), 

1254 afwDetect.createThreshold(level, "bitmask")) 

1255 

1256 objects = ds.getFootprints() 

1257 

1258 if 0 and display: 

1259 afwDisplay.Display(frame=0).mtv(self.mim, title=self._testMethodName + " self.mim") 

1260 

1261 self.assertEqual(len(objects), 

1262 len([o for o in self.objects if (o.val & level)])) 

1263 

1264 i = 0 

1265 for o in self.objects: 

1266 if o.val & level: 

1267 self.assertEqual(o, objects[i]) 

1268 i += 1 

1269 

1270 

1271class NaNFootprintSetTestCase(unittest.TestCase): 

1272 """A test case for FootprintSet when the image contains NaNs""" 

1273 

1274 def setUp(self): 

1275 self.ms = afwImage.MaskedImageF(lsst.geom.Extent2I(12, 8)) 

1276 im = self.ms.getImage() 

1277 # 

1278 # Objects that we should detect 

1279 # 

1280 self.objects = [] 

1281 self.objects += [Object(10, [(1, 4, 4), (2, 3, 5), (3, 4, 4)])] 

1282 self.objects += [Object(20, [(5, 7, 8), (6, 8, 8)])] 

1283 self.objects += [Object(20, [(5, 10, 10)])] 

1284 self.objects += [Object(30, [(6, 3, 3)])] 

1285 

1286 im.set(0) # clear image 

1287 for obj in self.objects: 

1288 obj.insert(im) 

1289 

1290 self.NaN = float("NaN") 

1291 im[3, 7, afwImage.LOCAL] = self.NaN 

1292 im[0, 0, afwImage.LOCAL] = self.NaN 

1293 im[8, 2, afwImage.LOCAL] = self.NaN 

1294 

1295 # connects the two objects with value==20 together if NaN is detected 

1296 im[9, 6, afwImage.LOCAL] = self.NaN 

1297 

1298 def tearDown(self): 

1299 del self.ms 

1300 

1301 def testFootprints(self): 

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

1303 ds = afwDetect.FootprintSet(self.ms, afwDetect.Threshold(10), "DETECTED") 

1304 

1305 objects = ds.getFootprints() 

1306 

1307 if display: 

1308 afwDisplay.Display(frame=0).mtv(self.ms, title=self._testMethodName + " self.ms") 

1309 

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

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

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

1313 

1314 

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

1316 pass 

1317 

1318 

1319def setup_module(module): 

1320 lsst.utils.tests.init() 

1321 

1322 

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

1324 lsst.utils.tests.init() 

1325 unittest.main()