Coverage for tests/test_footprint1.py: 9%

826 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-03-30 02:46 -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 

1020class FootprintSetTestCase(unittest.TestCase): 

1021 """A test case for FootprintSet""" 

1022 

1023 def setUp(self): 

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

1025 im = self.ms.getImage() 

1026 # 

1027 # Objects that we should detect 

1028 # 

1029 self.objects = [] 

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

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

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

1033 

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

1035 for obj in self.objects: 

1036 obj.insert(im) 

1037 

1038 def tearDown(self): 

1039 del self.ms 

1040 

1041 def testGC(self): 

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

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

1044 afwDetect.Threshold(10)) 

1045 

1046 def testFootprints(self): 

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

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

1049 

1050 objects = ds.getFootprints() 

1051 

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

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

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

1055 

1056 def test_str(self): 

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

1058 expect = ("3 footprints:" 

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

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

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

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

1063 

1064 def testFootprints2(self): 

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

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

1067 

1068 objects = ds.getFootprints() 

1069 

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

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

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

1073 

1074 def testFootprints3(self): 

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

1076 threshold = 4.5 # in units of sigma 

1077 

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

1079 

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

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

1082 

1083 ds = afwDetect.FootprintSet(self.ms, 

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

1085 

1086 objects = ds.getFootprints() 

1087 

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

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

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

1091 

1092 def testFootprintsMasks(self): 

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

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

1095 objects = ds.getFootprints() 

1096 

1097 if display: 

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

1099 

1100 mask = self.ms.getMask() 

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

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

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

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

1105 mask.getPlaneBitMask("OBJECT")) 

1106 

1107 def testFootprintsImageId(self): 

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

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

1110 objects = ds.getFootprints() 

1111 

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

1113 idImage.set(0) 

1114 

1115 for i, foot in enumerate(objects): 

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

1117 

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

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

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

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

1122 

1123 def testFootprintSetImageId(self): 

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

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

1126 objects = ds.getFootprints() 

1127 

1128 idImage = ds.insertIntoImage() 

1129 if display: 

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

1131 

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

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

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

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

1136 

1137 def testFootprintsImage(self): 

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

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

1140 

1141 objects = ds.getFootprints() 

1142 

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

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

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

1146 

1147 def testGrow2(self): 

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

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

1150 

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

1152 idImage.set(0) 

1153 

1154 i = 1 

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

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

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

1158 i += 1 

1159 

1160 if display: 

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

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

1163 

1164 def testFootprintPeaks(self): 

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

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

1167 

1168 foot = fs.getFootprints()[0] 

1169 

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

1171 

1172 

1173class MaskFootprintSetTestCase(unittest.TestCase): 

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

1175 

1176 def setUp(self): 

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

1178 # 

1179 # Objects that we should detect 

1180 # 

1181 self.objects = [] 

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

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

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

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

1186 

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

1188 for obj in self.objects: 

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

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

1191 

1192 if display: 

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

1194 

1195 def tearDown(self): 

1196 del self.mim 

1197 

1198 def testFootprints(self): 

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

1200 level = 0x2 

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

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

1203 

1204 objects = ds.getFootprints() 

1205 

1206 if 0 and display: 

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

1208 

1209 self.assertEqual(len(objects), 

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

1211 

1212 i = 0 

1213 for o in self.objects: 

1214 if o.val & level: 

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

1216 i += 1 

1217 

1218 

1219class NaNFootprintSetTestCase(unittest.TestCase): 

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

1221 

1222 def setUp(self): 

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

1224 im = self.ms.getImage() 

1225 # 

1226 # Objects that we should detect 

1227 # 

1228 self.objects = [] 

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

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

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

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

1233 

1234 im.set(0) # clear image 

1235 for obj in self.objects: 

1236 obj.insert(im) 

1237 

1238 self.NaN = float("NaN") 

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

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

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

1242 

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

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

1245 

1246 def tearDown(self): 

1247 del self.ms 

1248 

1249 def testFootprints(self): 

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

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

1252 

1253 objects = ds.getFootprints() 

1254 

1255 if display: 

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

1257 

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

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

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

1261 

1262 

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

1264 pass 

1265 

1266 

1267def setup_module(module): 

1268 lsst.utils.tests.init() 

1269 

1270 

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

1272 lsst.utils.tests.init() 

1273 unittest.main()