Coverage for tests/test_footprint1.py: 9%

835 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-27 02:52 -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 

1043class FootprintSetTestCase(unittest.TestCase): 

1044 """A test case for FootprintSet""" 

1045 

1046 def setUp(self): 

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

1048 im = self.ms.getImage() 

1049 # 

1050 # Objects that we should detect 

1051 # 

1052 self.objects = [] 

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

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

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

1056 

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

1058 for obj in self.objects: 

1059 obj.insert(im) 

1060 

1061 def tearDown(self): 

1062 del self.ms 

1063 

1064 def testGC(self): 

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

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

1067 afwDetect.Threshold(10)) 

1068 

1069 def testFootprints(self): 

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

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

1072 

1073 objects = ds.getFootprints() 

1074 

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

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

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

1078 

1079 def test_str(self): 

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

1081 expect = ("3 footprints:" 

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

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

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

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

1086 

1087 def testFootprints2(self): 

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

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

1090 

1091 objects = ds.getFootprints() 

1092 

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

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

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

1096 

1097 def testFootprints3(self): 

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

1099 threshold = 4.5 # in units of sigma 

1100 

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

1102 

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

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

1105 

1106 ds = afwDetect.FootprintSet(self.ms, 

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

1108 

1109 objects = ds.getFootprints() 

1110 

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

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

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

1114 

1115 def testFootprintsMasks(self): 

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

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

1118 objects = ds.getFootprints() 

1119 

1120 if display: 

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

1122 

1123 mask = self.ms.getMask() 

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

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

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

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

1128 mask.getPlaneBitMask("OBJECT")) 

1129 

1130 def testFootprintsImageId(self): 

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

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

1133 objects = ds.getFootprints() 

1134 

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

1136 idImage.set(0) 

1137 

1138 for i, foot in enumerate(objects): 

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

1140 

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

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

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

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

1145 

1146 def testFootprintSetImageId(self): 

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

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

1149 objects = ds.getFootprints() 

1150 

1151 idImage = ds.insertIntoImage() 

1152 if display: 

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

1154 

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

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

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

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

1159 

1160 def testFootprintsImage(self): 

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

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

1163 

1164 objects = ds.getFootprints() 

1165 

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

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

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

1169 

1170 def testGrow2(self): 

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

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

1173 

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

1175 idImage.set(0) 

1176 

1177 i = 1 

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

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

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

1181 i += 1 

1182 

1183 if display: 

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

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

1186 

1187 def testFootprintPeaks(self): 

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

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

1190 

1191 foot = fs.getFootprints()[0] 

1192 

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

1194 

1195 

1196class MaskFootprintSetTestCase(unittest.TestCase): 

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

1198 

1199 def setUp(self): 

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

1201 # 

1202 # Objects that we should detect 

1203 # 

1204 self.objects = [] 

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

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

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

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

1209 

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

1211 for obj in self.objects: 

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

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

1214 

1215 if display: 

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

1217 

1218 def tearDown(self): 

1219 del self.mim 

1220 

1221 def testFootprints(self): 

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

1223 level = 0x2 

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

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

1226 

1227 objects = ds.getFootprints() 

1228 

1229 if 0 and display: 

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

1231 

1232 self.assertEqual(len(objects), 

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

1234 

1235 i = 0 

1236 for o in self.objects: 

1237 if o.val & level: 

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

1239 i += 1 

1240 

1241 

1242class NaNFootprintSetTestCase(unittest.TestCase): 

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

1244 

1245 def setUp(self): 

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

1247 im = self.ms.getImage() 

1248 # 

1249 # Objects that we should detect 

1250 # 

1251 self.objects = [] 

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

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

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

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

1256 

1257 im.set(0) # clear image 

1258 for obj in self.objects: 

1259 obj.insert(im) 

1260 

1261 self.NaN = float("NaN") 

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

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

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

1265 

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

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

1268 

1269 def tearDown(self): 

1270 del self.ms 

1271 

1272 def testFootprints(self): 

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

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

1275 

1276 objects = ds.getFootprints() 

1277 

1278 if display: 

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

1280 

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

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

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

1284 

1285 

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

1287 pass 

1288 

1289 

1290def setup_module(module): 

1291 lsst.utils.tests.init() 

1292 

1293 

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

1295 lsst.utils.tests.init() 

1296 unittest.main()