Coverage for tests / test_footprint2.py: 10%
457 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-23 01:26 -0700
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-23 01:26 -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/>.
22"""
23Tests for Footprints, and FootprintSets
25Run with:
26 python test_footprint2.py
27or
28 pytest test_footprint2.py
29"""
31import unittest
33import lsst.utils.tests
34import lsst.geom
35import lsst.afw.table as afwTable
36import lsst.afw.image as afwImage
37import lsst.afw.geom as afwGeom
38import lsst.afw.detection as afwDetect
39import lsst.afw.display as afwDisplay
40import numpy as np
42afwDisplay.setDefaultMaskTransparency(75)
43try:
44 type(display)
45except NameError:
46 display = False
49def toString(*args):
50 """toString written in python"""
51 if len(args) == 1:
52 args = args[0]
54 y, x0, x1 = args
55 return f"{y}: {x0}..{x1}"
58def peakFromImage(im, pos):
59 """Function to extract the sort key of peak height. Sort by decreasing peak height."""
60 val = im[pos[0], pos[1], afwImage.LOCAL][0]
61 return -1.0*val
64class Object:
66 def __init__(self, val, spans, origin: lsst.geom.Point2I = None):
67 self.val = val
68 self.spans = spans
69 self.origin = origin
71 def insert(self, im):
72 """Insert self into an image"""
73 for sp in self.spans:
74 y, x0, x1 = sp
75 for x in range(x0, x1 + 1):
76 im[x, y, afwImage.LOCAL] = self.val
78 def __eq__(self, other):
79 for osp, sp in zip(other.getSpans(), self.spans):
80 sp_shift = sp if (self.origin is None) else (
81 sp[0] + self.origin[1], sp[1] + self.origin[0], sp[2] + self.origin[0]
82 )
83 if osp.toString() != toString(sp_shift):
84 return False
86 return True
89class FootprintSetTestCase(unittest.TestCase):
90 """A test case for FootprintSet"""
92 def setUp(self):
93 self.origin = lsst.geom.Point2I(-10, 3)
94 self.im = afwImage.ImageU(bbox=lsst.geom.Box2I(self.origin, lsst.geom.Extent2I(12, 8)))
95 #
96 # Objects that we should detect
97 #
98 self.objects = [
99 Object(10, [(1, 4, 4), (2, 3, 5), (3, 4, 4)], self.origin),
100 Object(15, [(5, 7, 8), (5, 10, 10), (6, 8, 9)], self.origin),
101 Object(20, [(6, 3, 3)], self.origin),
102 ]
104 self.im.set(0) # clear image
105 for obj in self.objects:
106 obj.insert(self.im)
108 def tearDown(self):
109 del self.im
111 def testGC(self):
112 """Check that FootprintSets are automatically garbage collected (when MemoryTestCase runs)"""
114 afwDetect.FootprintSet(afwImage.ImageU(lsst.geom.Extent2I(10, 20)),
115 afwDetect.Threshold(10))
117 def testFootprints(self):
118 """Check that we found the correct number of objects and that they are correct"""
119 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
121 objects = ds.getFootprints()
123 self.assertEqual(len(objects), len(self.objects))
124 for i, object in enumerate(objects):
125 self.assertEqual(object, self.objects[i])
127 def testFootprints2(self):
128 """Check that we found the correct number of objects using FootprintSet"""
129 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
131 objects = ds.getFootprints()
133 self.assertEqual(len(objects), len(self.objects))
134 for i in range(len(objects)):
135 self.assertEqual(objects[i], self.objects[i])
137 def testFootprintsImageId(self):
138 """Check that we can insert footprints into an Image"""
139 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
140 objects = ds.getFootprints()
142 idImage = afwImage.ImageU(bbox=self.im.getBBox())
143 idImage.set(0)
145 for i, foot in enumerate(objects):
146 foot.spans.setImage(idImage, i + 1)
148 for i, object_i in enumerate(objects):
149 for sp in object_i.getSpans():
150 for x in range(sp.getX0(), sp.getX1() + 1):
151 self.assertEqual(
152 idImage[x - self.origin[0], sp.getY() - self.origin[1], afwImage.LOCAL],
153 i + 1,
154 )
156 def testFootprintSetImageId(self):
157 """Check that we can insert a FootprintSet into an Image, setting relative IDs"""
158 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
159 objects = ds.getFootprints()
161 idImage = ds.insertIntoImage()
162 if display:
163 afwDisplay.Display(frame=2).mtv(idImage, title=self._testMethodName + " image")
165 for i, object_i in enumerate(objects):
166 peakValues = object_i.getPeaks()["peakValue"]
167 peakValue = peakValues[0]
168 np.testing.assert_array_equal(peakValues, peakValue)
169 for sp in object_i.getSpans():
170 for x in range(sp.getX0(), sp.getX1() + 1):
171 self.assertEqual(
172 idImage[x - self.origin[0], sp.getY() - self.origin[1], afwImage.LOCAL],
173 i + 1,
174 )
176 def testFootprintSetPeaks(self):
177 """Check that peak finding returns separate peaks with a negative
178 origin bbox."""
180 # Only the negative y-origin triggers the bug fixed in DM-48092,
181 # but the x may as well also be negative to test regression.
182 img_peaks = afwImage.ImageU(
183 bbox=lsst.geom.Box2I(lsst.geom.Point2I(-10, -3), lsst.geom.Extent2I(5, 6)),
184 initialValue=0,
185 )
186 img_peaks.array[1, 1:4] = [10, 5, 10]
188 footprints = afwDetect.FootprintSet(img_peaks, afwDetect.Threshold(5)).getFootprints()
189 self.assertEqual(len(footprints), 1)
190 self.assertEqual(len(footprints[0].getPeaks()), 2)
192 def testFootprintsImage(self):
193 """Check that we can search Images as well as MaskedImages"""
194 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
196 objects = ds.getFootprints()
198 self.assertEqual(len(objects), len(self.objects))
199 for i in range(len(objects)):
200 self.assertEqual(objects[i], self.objects[i])
202 def testGrow2(self):
203 """Grow some more interesting shaped Footprints. Informative with display, but no numerical tests"""
204 # Can't set mask plane as the image is not a masked image.
205 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
207 idImage = afwImage.ImageU(self.im.getDimensions())
208 idImage.set(0)
210 i = 1
211 for foot in ds.getFootprints()[0:1]:
212 foot.dilate(3, afwGeom.Stencil.MANHATTAN)
213 foot.spans.setImage(idImage, i, doClip=True)
214 i += 1
216 if display:
217 afwDisplay.Display(frame=1).mtv(idImage, title=self._testMethodName + " image")
219 def testGrow(self):
220 """Grow footprints using the FootprintSet constructor"""
221 fs = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
222 self.assertEqual(len(fs.getFootprints()), len(self.objects))
223 for isotropic in (True, False, afwDetect.FootprintControl(True),):
224 grown = afwDetect.FootprintSet(fs, 1, isotropic)
225 self.assertEqual(len(fs.getFootprints()), len(self.objects))
227 self.assertGreater(len(grown.getFootprints()), 0)
228 self.assertLessEqual(len(grown.getFootprints()),
229 len(fs.getFootprints()))
231 def testFootprintControl(self):
232 """Test the FootprintControl constructor"""
233 fctrl = afwDetect.FootprintControl()
234 self.assertFalse(fctrl.isCircular()[0]) # not set
235 self.assertFalse(fctrl.isIsotropic()[0]) # not set
237 fctrl.growIsotropic(False)
238 self.assertTrue(fctrl.isCircular()[0])
239 self.assertTrue(fctrl.isIsotropic()[0])
240 self.assertTrue(fctrl.isCircular()[1])
241 self.assertFalse(fctrl.isIsotropic()[1])
243 fctrl = afwDetect.FootprintControl()
244 fctrl.growLeft(False)
245 self.assertTrue(fctrl.isLeft()[0]) # it's now set
246 self.assertFalse(fctrl.isLeft()[1]) # ... but False
248 fctrl = afwDetect.FootprintControl(True, False, False, False)
249 self.assertTrue(fctrl.isLeft()[0])
250 self.assertTrue(fctrl.isRight()[0])
251 self.assertTrue(fctrl.isUp()[0])
252 self.assertTrue(fctrl.isDown()[0])
254 self.assertTrue(fctrl.isLeft()[1])
255 self.assertFalse(fctrl.isRight()[1])
257 def testGrowCircular(self):
258 """Grow footprints in all 4 directions using the FootprintSet/FootprintControl constructor """
259 im = afwImage.MaskedImageF(11, 11)
260 im[5, 5, afwImage.LOCAL] = (10, 0x0, 0.0)
261 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
262 self.assertEqual(len(fs.getFootprints()), 1)
264 radius = 3 # How much to grow by
265 for fctrl in (afwDetect.FootprintControl(),
266 afwDetect.FootprintControl(True),
267 afwDetect.FootprintControl(True, True),
268 ):
269 grown = afwDetect.FootprintSet(fs, radius, fctrl)
270 afwDetect.setMaskFromFootprintList(
271 im.getMask(), grown.getFootprints(), 0x10)
273 if display:
274 afwDisplay.Display(frame=3).mtv(im, title=self._testMethodName + " image")
276 foot = grown.getFootprints()[0]
278 if not fctrl.isCircular()[0]:
279 self.assertEqual(foot.getArea(), 1)
280 elif fctrl.isCircular()[0]:
281 assert radius == 3
282 if fctrl.isIsotropic()[1]:
283 self.assertEqual(foot.getArea(), 29)
284 else:
285 self.assertEqual(foot.getArea(), 25)
287 def testGrowLRUD(self):
288 """Grow footprints in various directions using the FootprintSet/FootprintControl constructor """
289 im = afwImage.MaskedImageF(11, 11)
290 x0, y0, ny = 5, 5, 3
291 for y in range(y0 - ny//2, y0 + ny//2 + 1):
292 im[x0, y, afwImage.LOCAL] = (10, 0x0, 0.0)
293 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
294 self.assertEqual(len(fs.getFootprints()), 1)
296 ngrow = 2 # How much to grow by
297 #
298 # Test growing to the left and/or right
299 #
300 for fctrl in (
301 afwDetect.FootprintControl(False, True, False, False),
302 afwDetect.FootprintControl(True, False, False, False),
303 afwDetect.FootprintControl(True, True, False, False),
304 ):
305 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
306 grown = afwDetect.FootprintSet(fs, ngrow, fctrl)
307 im.getMask().set(0)
308 afwDetect.setMaskFromFootprintList(
309 im.getMask(), grown.getFootprints(), 0x10)
311 if display:
312 afwDisplay.Display(frame=3).mtv(im, title=self._testMethodName + " image")
314 foot = grown.getFootprints()[0]
315 nextra = 0
316 if fctrl.isLeft()[1]:
317 nextra += ngrow
318 for y in range(y0 - ny//2, y0 + ny//2 + 1):
319 self.assertNotEqual(im.getMask()[x0 - 1, y, afwImage.LOCAL], 0)
321 if fctrl.isRight()[1]:
322 nextra += ngrow
323 for y in range(y0 - ny//2, y0 + ny//2 + 1):
324 self.assertNotEqual(im.getMask()[x0 + 1, y, afwImage.LOCAL], 0)
326 self.assertEqual(foot.getArea(), (1 + nextra)*ny)
327 #
328 # Test growing to up and/or down
329 #
330 for fctrl in (
331 afwDetect.FootprintControl(False, False, True, False),
332 afwDetect.FootprintControl(False, False, False, True),
333 afwDetect.FootprintControl(False, False, True, True),
334 ):
335 grown = afwDetect.FootprintSet(fs, ngrow, fctrl)
336 im.getMask().set(0)
337 afwDetect.setMaskFromFootprintList(
338 im.getMask(), grown.getFootprints(), 0x10)
340 if display:
341 afwDisplay.Display(frame=2).mtv(im, title=self._testMethodName + " image")
343 foot = grown.getFootprints()[0]
344 nextra = 0
345 if fctrl.isUp()[1]:
346 nextra += ngrow
347 for y in range(y0 + ny//2 + 1, y0 + ny//2 + ngrow + 1):
348 self.assertNotEqual(im.getMask()[x0, y, afwImage.LOCAL], 0)
350 if fctrl.isDown()[1]:
351 nextra += ngrow
352 for y in range(y0 - ny//2 - 1, y0 - ny//2 - ngrow - 1):
353 self.assertNotEqual(im.getMask()[x0, y, afwImage.LOCAL], 0)
355 self.assertEqual(foot.getArea(), ny + nextra)
357 def testGrowLRUD2(self):
358 """Grow footprints in various directions using the FootprintSet/FootprintControl constructor
360 Check that overlapping grown Footprints give the expected answers
361 """
362 ngrow = 3 # How much to grow by
363 for fctrl, xy in [
364 (afwDetect.FootprintControl(True, True,
365 False, False), [(4, 5), (5, 6), (6, 5)]),
366 (afwDetect.FootprintControl(False, False,
367 True, True), [(5, 4), (6, 5), (5, 6)]),
368 ]:
369 im = afwImage.MaskedImageF(11, 11)
370 for x, y in xy:
371 im[x, y, afwImage.LOCAL] = (10, 0x0, 0.0)
372 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
373 self.assertEqual(len(fs.getFootprints()), 1)
375 grown = afwDetect.FootprintSet(fs, ngrow, fctrl)
376 im.getMask().set(0)
377 afwDetect.setMaskFromFootprintList(
378 im.getMask(), grown.getFootprints(), 0x10)
380 if display:
381 afwDisplay.Display(frame=1).mtv(im, title=self._testMethodName + " image")
383 self.assertEqual(len(grown.getFootprints()), 1)
384 foot = grown.getFootprints()[0]
386 npix = 1 + 2*ngrow
387 npix += 3 + 2*ngrow # 3: distance between pair of set pixels 000X0X000
388 self.assertEqual(foot.getArea(), npix)
390 def testInf(self):
391 """Test detection for images with Infs"""
393 im = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 20))
394 im.set(0)
396 import numpy
397 for x in range(im.getWidth()):
398 im[x, -1, afwImage.LOCAL] = (numpy.inf, 0x0, 0)
400 ds = afwDetect.FootprintSet(im, afwDetect.createThreshold(100))
402 objects = ds.getFootprints()
403 afwDetect.setMaskFromFootprintList(im.getMask(), objects, 0x10)
405 if display:
406 afwDisplay.Display(frame=2).mtv(im, title=self._testMethodName + " image")
408 self.assertEqual(len(objects), 1)
411class PeaksInFootprintsTestCase(unittest.TestCase):
412 """A test case for detecting Peaks within Footprints"""
414 def doSetUp(self, dwidth=0, dheight=0, x0=0, y0=0):
415 width, height = 14 + x0 + dwidth, 10 + y0 + dheight
416 self.im = afwImage.MaskedImageF(lsst.geom.Extent2I(width, height))
417 #
418 # Objects that we should detect
419 #
420 self.objects, self.peaks = [], []
421 self.objects.append(
422 [[4, 1, 10], [3, 2, 10], [4, 2, 20], [5, 2, 10], [4, 3, 10], ])
423 self.peaks.append([[4, 2]])
424 self.objects.append(
425 [[9, 7, 30], [10, 7, 29], [12, 7, 25], [10, 8, 27], [11, 8, 26], ])
426 self.peaks.append([[9, 7]])
427 self.objects.append([[3, 8, 10], [4, 8, 10], ])
428 self.peaks.append([[3, 8], [4, 8], ])
430 for pp in self.peaks: # allow for x0, y0
431 for p in pp:
432 p[0] += x0
433 p[1] += y0
434 for oo in self.objects:
435 for o in oo:
436 o[0] += x0
437 o[1] += y0
439 self.im.set((0, 0x0, 0)) # clear image
440 for obj in self.objects:
441 for x, y, I in obj:
442 self.im.getImage()[x, y, afwImage.LOCAL] = I
444 def setUp(self):
445 self.im, self.fs = None, None
447 def tearDown(self):
448 del self.im
449 del self.fs
451 def doTestPeaks(self, dwidth=0, dheight=0, x0=0, y0=0, threshold=10, callback=None, polarity=True,
452 grow=0):
453 """Worker routine for tests
454 polarity: True if should search for +ve pixels"""
456 self.doSetUp(dwidth, dheight, x0, y0)
457 if not polarity:
458 self.im *= -1
460 if callback:
461 callback()
463 def peakDescending(p):
464 """Sort self.peaks in decreasing peak height to match Footprint.getPeaks()"""
465 return p[2]*-1.0
466 for i, peaks in enumerate(self.peaks):
467 self.peaks[i] = sorted([(x, y, self.im.getImage()[x, y, afwImage.LOCAL]) for x, y in peaks],
468 key=peakDescending)
470 threshold = afwDetect.Threshold(
471 threshold, afwDetect.Threshold.VALUE, polarity)
472 fs = afwDetect.FootprintSet(self.im, threshold, "BINNED1")
474 if grow:
475 fs = afwDetect.FootprintSet(fs, grow, True)
476 msk = self.im.getMask()
477 afwDetect.setMaskFromFootprintList(
478 msk, fs.getFootprints(), msk.getPlaneBitMask("DETECTED"))
479 del msk
481 self.fs = fs
482 self.checkPeaks(dwidth, dheight, frame=3)
484 def checkPeaks(self, dwidth=0, dheight=0, frame=3):
485 """Check that we got the peaks right"""
486 feet = self.fs.getFootprints()
487 #
488 # Check that we found all the peaks
489 #
490 self.assertEqual(sum([len(f.getPeaks()) for f in feet]),
491 sum([len(f.getPeaks()) for f in feet]))
493 if display:
494 disp = afwDisplay.Display(frame=frame)
495 disp.mtv(self.im, title=self._testMethodName + " image")
497 with disp.Buffering():
498 for i, foot in enumerate(feet):
499 for p in foot.getPeaks():
500 disp.dot("+", p.getIx(), p.getIy(), size=0.4)
502 if i < len(self.peaks):
503 for trueX, trueY, peakVal in self.peaks[i]:
504 disp.dot("x", trueX, trueY, size=0.4, ctype=afwDisplay.RED)
506 for i, foot in enumerate(feet):
507 npeak = None
508 #
509 # Peaks that touch the edge are handled differently, as only the single highest/lowest pixel
510 # is treated as a Peak
511 #
512 if (dwidth != 0 or dheight != 0):
513 if (foot.getBBox().getMinX() == 0
514 or foot.getBBox().getMaxX() == self.im.getWidth() - 1
515 or foot.getBBox().getMinY() == 0
516 or foot.getBBox().getMaxY() == self.im.getHeight() - 1):
517 npeak = 1
519 if npeak is None:
520 npeak = len(self.peaks[i])
522 self.assertEqual(len(foot.getPeaks()), npeak)
524 for j, p in enumerate(foot.getPeaks()):
525 trueX, trueY, peakVal = self.peaks[i][j]
526 self.assertEqual((p.getIx(), p.getIy()), (trueX, trueY))
528 def testSinglePeak(self):
529 """Test that we can find single Peaks in Footprints"""
531 self.doTestPeaks()
533 def testSingleNegativePeak(self):
534 """Test that we can find single Peaks in Footprints when looking for -ve detections"""
536 self.doTestPeaks(polarity=False)
538 def testSinglePeakAtEdge(self):
539 """Test that we handle Peaks correctly at the edge"""
541 self.doTestPeaks(dheight=-1)
543 def testSingleNegativePeakAtEdge(self):
544 """Test that we handle -ve Peaks correctly at the edge"""
546 self.doTestPeaks(dheight=-1, polarity=False)
548 def testMultiPeak(self):
549 """Test that multiple peaks are handled correctly"""
550 def callback():
551 x, y = 12, 7
552 self.im.getImage()[x, y, afwImage.LOCAL] = 100
553 self.peaks[1].append((x, y))
555 self.doTestPeaks(callback=callback)
557 def testMultiNegativePeak(self):
558 """Test that multiple negative peaks are handled correctly"""
559 def callback():
560 x, y = 12, 7
561 self.im.getImage()[x, y, afwImage.LOCAL] = -100
562 self.peaks[1].append((x, y))
564 self.doTestPeaks(polarity=False, callback=callback)
566 def testGrowFootprints(self):
567 """Test that we can grow footprints, correctly merging those that now touch"""
568 def callback():
569 self.im.getImage()[10, 4, afwImage.LOCAL] = 20
570 self.peaks[-2].append((10, 4,))
572 self.doTestPeaks(dwidth=1, dheight=1, callback=callback, grow=1)
574 def testGrowFootprints2(self):
575 """Test that we can grow footprints, correctly merging those that now overlap
576 N.b. this caused RHL's initial implementation to crash
577 """
578 def callback():
579 self.im.getImage()[10, 4, afwImage.LOCAL] = 20
580 self.peaks[-2].append((10, 4, ))
582 def peaksSortKey(p):
583 return peakFromImage(self.im, p)
584 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey)
586 self.doTestPeaks(x0=0, y0=2, dwidth=2, dheight=2,
587 callback=callback, grow=2)
589 def testGrowFootprints3(self):
590 """Test that we can grow footprints, correctly merging those that now totally overwritten"""
592 self.im = afwImage.MaskedImageF(14, 11)
594 self.im.getImage().set(0)
595 self.peaks = []
597 value = 11
598 for x, y in [(4, 7), (5, 7), (6, 7), (7, 7), (8, 7),
599 (4, 6), (8, 6),
600 (4, 5), (8, 5),
601 (4, 4), (8, 4),
602 (4, 3), (8, 3),
603 ]:
604 self.im.getImage()[x, y, afwImage.LOCAL] = value
605 value -= 1e-3
607 self.im.getImage()[4, 7, afwImage.LOCAL] = 15
608 self.peaks.append([(4, 7,), ])
610 self.im.getImage()[6, 5, afwImage.LOCAL] = 30
611 self.peaks[0].append((6, 5,))
613 self.fs = afwDetect.FootprintSet(
614 self.im, afwDetect.Threshold(10), "BINNED1")
615 #
616 # The disappearing Footprint special case only shows up if the outer Footprint is grown
617 # _after_ the inner one. So arrange the order properly
618 feet = self.fs.getFootprints()
619 feet[0], feet[1] = feet[1], feet[0]
621 msk = self.im.getMask()
623 grow = 2
624 self.fs = afwDetect.FootprintSet(self.fs, grow, False)
625 afwDetect.setMaskFromFootprintList(msk, self.fs.getFootprints(),
626 msk.getPlaneBitMask("DETECTED_NEGATIVE"))
628 if display:
629 frame = 0
631 disp = afwDisplay.Display(frame=frame)
632 disp.mtv(self.im, title=self._testMethodName + " image")
634 with disp.Buffering():
635 for i, foot in enumerate(self.fs.getFootprints()):
636 for p in foot.getPeaks():
637 disp.dot("+", p.getIx(), p.getIy(), size=0.4)
639 if i < len(self.peaks):
640 for trueX, trueY in self.peaks[i]:
641 disp.dot("x", trueX, trueY, size=0.4, ctype=afwDisplay.RED)
643 self.assertEqual(len(self.fs.getFootprints()), 1)
644 self.assertEqual(len(self.fs.getFootprints()[
645 0].getPeaks()), len(self.peaks[0]))
647 def testMergeFootprints(self): # YYYY
648 """Merge positive and negative Footprints"""
649 x0, y0 = 5, 6
650 dwidth, dheight = 6, 7
652 def callback():
653 x, y, value = x0 + 10, y0 + 4, -20
654 self.im.getImage()[x, y, afwImage.LOCAL] = value
655 peaks2.append((x, y, value))
657 for grow1, grow2 in [(1, 1), (3, 3), (6, 6), ]:
658 peaks2 = []
659 self.doTestPeaks(threshold=10, callback=callback, grow=0,
660 x0=x0, y0=y0, dwidth=dwidth, dheight=dheight)
662 threshold = afwDetect.Threshold(
663 10, afwDetect.Threshold.VALUE, False)
664 fs2 = afwDetect.FootprintSet(self.im, threshold)
666 msk = self.im.getMask()
667 afwDetect.setMaskFromFootprintList(
668 msk, fs2.getFootprints(), msk.getPlaneBitMask("DETECTED_NEGATIVE"))
670 self.fs.merge(fs2, grow1, grow2)
671 self.peaks[-2] += peaks2
673 if grow1 + grow2 > 2: # grow merged all peaks
674 def peaksSortKey(p):
675 return peakFromImage(self.im, p)
676 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey)
678 afwDetect.setMaskFromFootprintList(
679 msk, self.fs.getFootprints(), msk.getPlaneBitMask("EDGE"))
681 self.checkPeaks(frame=3)
683 def testMergeFootprintPeakSchemas(self):
684 """Test that merging footprints preserves fields in the peak schemas.
685 """
686 self.doSetUp()
687 self.im.getImage()[3, 4, afwImage.LOCAL] = 100
688 self.im.getImage()[3, 5, afwImage.LOCAL] = 200
689 self.im.getImage()[6, 7, afwImage.LOCAL] = 400
691 threshold = afwDetect.Threshold(10, afwDetect.Threshold.VALUE, True)
692 fs1 = afwDetect.FootprintSet(self.im, threshold)
693 threshold = afwDetect.Threshold(150, afwDetect.Threshold.VALUE, True)
694 fs2 = afwDetect.FootprintSet(self.im, threshold)
696 def addSignificance(footprints):
697 """Return a new FootprintSet with a significance field added."""
698 mapper = afwTable.SchemaMapper(footprints.getFootprints()[0].peaks.schema)
699 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
700 mapper.addOutputField("significance", type=float,
701 doc="Ratio of peak value to configured standard deviation.")
703 newFootprints = afwDetect.FootprintSet(footprints)
704 for old, new in zip(footprints.getFootprints(), newFootprints.getFootprints()):
705 newPeaks = afwDetect.PeakCatalog(mapper.getOutputSchema())
706 newPeaks.extend(old.peaks, mapper=mapper)
707 new.getPeaks().clear()
708 new.setPeakCatalog(newPeaks)
710 for footprint in newFootprints.getFootprints():
711 for peak in footprint.peaks:
712 peak['significance'] = 10
714 return newFootprints
716 def checkPeakSignificance(footprints):
717 """Check that all peaks have significance=10."""
718 for footprint in footprints.getFootprints():
719 self.assertIn("significance", footprint.peaks.schema)
720 for peak in footprint.peaks:
721 self.assertEqual(peak["significance"], 10)
723 newFs1 = addSignificance(fs1)
725 # Check that mismatched peak schemas raise an exception in merge().
726 with self.assertRaisesRegex(RuntimeError,
727 "FootprintSets to be merged must have identical peak schemas"):
728 fs1.merge(newFs1)
730 with self.assertRaisesRegex(RuntimeError,
731 "FootprintSets to be merged must have identical peak schemas"):
732 newFs1.merge(fs2)
734 newFs2 = addSignificance(fs2)
736 # Check that the field was added correctly.
737 checkPeakSignificance(newFs1)
738 checkPeakSignificance(newFs2)
740 # Does merging footprints preserve the added field?
741 newFs1.merge(newFs2)
742 checkPeakSignificance(newFs1)
743 checkPeakSignificance(newFs2)
745 def testMergeFootprintsEngulf(self):
746 """Merge two Footprints when growing one Footprint totally replaces the other"""
747 def callback():
748 self.im.set(0)
749 self.peaks, self.objects = [], []
751 for x, y, I in [[6, 4, 20], [6, 5, 10]]:
752 self.im.getImage()[x, y, afwImage.LOCAL] = I
753 self.peaks.append([[6, 4]])
755 x, y, value = 8, 4, -20
756 self.im.getImage()[x, y, afwImage.LOCAL] = value
757 peaks2.append((x, y, value))
759 grow1, grow2 = 0, 3
760 peaks2 = []
761 self.doTestPeaks(threshold=10, callback=callback, grow=0)
763 threshold = afwDetect.Threshold(10, afwDetect.Threshold.VALUE, False)
764 fs2 = afwDetect.FootprintSet(self.im, threshold)
766 msk = self.im.getMask()
767 afwDetect.setMaskFromFootprintList(
768 msk, fs2.getFootprints(), msk.getPlaneBitMask("DETECTED_NEGATIVE"))
770 self.fs.merge(fs2, grow1, grow2)
771 self.peaks[0] += peaks2
773 def peaksSortKey(p):
774 return peakFromImage(self.im, p)
775 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey)
777 afwDetect.setMaskFromFootprintList(
778 msk, self.fs.getFootprints(), msk.getPlaneBitMask("EDGE"))
780 self.checkPeaks(frame=3)
783class MemoryTester(lsst.utils.tests.MemoryTestCase):
784 pass
787def setup_module(module):
788 lsst.utils.tests.init()
791if __name__ == "__main__": 791 ↛ 792line 791 didn't jump to line 792 because the condition on line 791 was never true
792 lsst.utils.tests.init()
793 unittest.main()