Coverage for tests/test_footprint2.py: 10%
448 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-18 02:24 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-18 02:24 -0800
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
41afwDisplay.setDefaultMaskTransparency(75)
42try:
43 type(display)
44except NameError:
45 display = False
48def toString(*args):
49 """toString written in python"""
50 if len(args) == 1:
51 args = args[0]
53 y, x0, x1 = args
54 return f"{y}: {x0}..{x1}"
57def peakFromImage(im, pos):
58 """Function to extract the sort key of peak height. Sort by decreasing peak height."""
59 val = im[pos[0], pos[1], afwImage.LOCAL][0]
60 return -1.0*val
63class Object:
65 def __init__(self, val, spans):
66 self.val = val
67 self.spans = spans
69 def insert(self, im):
70 """Insert self into an image"""
71 for sp in self.spans:
72 y, x0, x1 = sp
73 for x in range(x0, x1 + 1):
74 im[x, y, afwImage.LOCAL] = self.val
76 def __eq__(self, other):
77 for osp, sp in zip(other.getSpans(), self.spans):
78 if osp.toString() != toString(sp):
79 return False
81 return True
84class FootprintSetTestCase(unittest.TestCase):
85 """A test case for FootprintSet"""
87 def setUp(self):
88 self.im = afwImage.ImageU(lsst.geom.Extent2I(12, 8))
89 #
90 # Objects that we should detect
91 #
92 self.objects = []
93 self.objects += [Object(10, [(1, 4, 4), (2, 3, 5), (3, 4, 4)])]
94 self.objects += [Object(20, [(5, 7, 8), (5, 10, 10), (6, 8, 9)])]
95 self.objects += [Object(20, [(6, 3, 3)])]
97 self.im.set(0) # clear image
98 for obj in self.objects:
99 obj.insert(self.im)
101 def tearDown(self):
102 del self.im
104 def testGC(self):
105 """Check that FootprintSets are automatically garbage collected (when MemoryTestCase runs)"""
107 afwDetect.FootprintSet(afwImage.ImageU(lsst.geom.Extent2I(10, 20)),
108 afwDetect.Threshold(10))
110 def testFootprints(self):
111 """Check that we found the correct number of objects and that they are correct"""
112 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
114 objects = ds.getFootprints()
116 self.assertEqual(len(objects), len(self.objects))
117 for i in range(len(objects)):
118 self.assertEqual(objects[i], self.objects[i])
120 def testFootprints2(self):
121 """Check that we found the correct number of objects using FootprintSet"""
122 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
124 objects = ds.getFootprints()
126 self.assertEqual(len(objects), len(self.objects))
127 for i in range(len(objects)):
128 self.assertEqual(objects[i], self.objects[i])
130 def testFootprintsImageId(self):
131 """Check that we can insert footprints into an Image"""
132 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
133 objects = ds.getFootprints()
135 idImage = afwImage.ImageU(self.im.getDimensions())
136 idImage.set(0)
138 for i, foot in enumerate(objects):
139 foot.spans.setImage(idImage, i + 1)
141 for i in range(len(objects)):
142 for sp in objects[i].getSpans():
143 for x in range(sp.getX0(), sp.getX1() + 1):
144 self.assertEqual(idImage[x, sp.getY(), afwImage.LOCAL], i + 1)
146 def testFootprintSetImageId(self):
147 """Check that we can insert a FootprintSet into an Image, setting relative IDs"""
148 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
149 objects = ds.getFootprints()
151 idImage = ds.insertIntoImage()
152 if display:
153 afwDisplay.Display(frame=2).mtv(idImage, title=self._testMethodName + " image")
155 for i in range(len(objects)):
156 for sp in objects[i].getSpans():
157 for x in range(sp.getX0(), sp.getX1() + 1):
158 self.assertEqual(idImage[x, sp.getY(), afwImage.LOCAL], i + 1)
160 def testFootprintsImage(self):
161 """Check that we can search Images as well as MaskedImages"""
162 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
164 objects = ds.getFootprints()
166 self.assertEqual(len(objects), len(self.objects))
167 for i in range(len(objects)):
168 self.assertEqual(objects[i], self.objects[i])
170 def testGrow2(self):
171 """Grow some more interesting shaped Footprints. Informative with display, but no numerical tests"""
172 # Can't set mask plane as the image is not a masked image.
173 ds = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
175 idImage = afwImage.ImageU(self.im.getDimensions())
176 idImage.set(0)
178 i = 1
179 for foot in ds.getFootprints()[0:1]:
180 foot.dilate(3, afwGeom.Stencil.MANHATTAN)
181 foot.spans.setImage(idImage, i, doClip=True)
182 i += 1
184 if display:
185 afwDisplay.Display(frame=1).mtv(idImage, title=self._testMethodName + " image")
187 def testGrow(self):
188 """Grow footprints using the FootprintSet constructor"""
189 fs = afwDetect.FootprintSet(self.im, afwDetect.Threshold(10))
190 self.assertEqual(len(fs.getFootprints()), len(self.objects))
191 for isotropic in (True, False, afwDetect.FootprintControl(True),):
192 grown = afwDetect.FootprintSet(fs, 1, isotropic)
193 self.assertEqual(len(fs.getFootprints()), len(self.objects))
195 self.assertGreater(len(grown.getFootprints()), 0)
196 self.assertLessEqual(len(grown.getFootprints()),
197 len(fs.getFootprints()))
199 def testFootprintControl(self):
200 """Test the FootprintControl constructor"""
201 fctrl = afwDetect.FootprintControl()
202 self.assertFalse(fctrl.isCircular()[0]) # not set
203 self.assertFalse(fctrl.isIsotropic()[0]) # not set
205 fctrl.growIsotropic(False)
206 self.assertTrue(fctrl.isCircular()[0])
207 self.assertTrue(fctrl.isIsotropic()[0])
208 self.assertTrue(fctrl.isCircular()[1])
209 self.assertFalse(fctrl.isIsotropic()[1])
211 fctrl = afwDetect.FootprintControl()
212 fctrl.growLeft(False)
213 self.assertTrue(fctrl.isLeft()[0]) # it's now set
214 self.assertFalse(fctrl.isLeft()[1]) # ... but False
216 fctrl = afwDetect.FootprintControl(True, False, False, False)
217 self.assertTrue(fctrl.isLeft()[0])
218 self.assertTrue(fctrl.isRight()[0])
219 self.assertTrue(fctrl.isUp()[0])
220 self.assertTrue(fctrl.isDown()[0])
222 self.assertTrue(fctrl.isLeft()[1])
223 self.assertFalse(fctrl.isRight()[1])
225 def testGrowCircular(self):
226 """Grow footprints in all 4 directions using the FootprintSet/FootprintControl constructor """
227 im = afwImage.MaskedImageF(11, 11)
228 im[5, 5, afwImage.LOCAL] = (10, 0x0, 0.0)
229 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
230 self.assertEqual(len(fs.getFootprints()), 1)
232 radius = 3 # How much to grow by
233 for fctrl in (afwDetect.FootprintControl(),
234 afwDetect.FootprintControl(True),
235 afwDetect.FootprintControl(True, True),
236 ):
237 grown = afwDetect.FootprintSet(fs, radius, fctrl)
238 afwDetect.setMaskFromFootprintList(
239 im.getMask(), grown.getFootprints(), 0x10)
241 if display:
242 afwDisplay.Display(frame=3).mtv(im, title=self._testMethodName + " image")
244 foot = grown.getFootprints()[0]
246 if not fctrl.isCircular()[0]:
247 self.assertEqual(foot.getArea(), 1)
248 elif fctrl.isCircular()[0]:
249 assert radius == 3
250 if fctrl.isIsotropic()[1]:
251 self.assertEqual(foot.getArea(), 29)
252 else:
253 self.assertEqual(foot.getArea(), 25)
255 def testGrowLRUD(self):
256 """Grow footprints in various directions using the FootprintSet/FootprintControl constructor """
257 im = afwImage.MaskedImageF(11, 11)
258 x0, y0, ny = 5, 5, 3
259 for y in range(y0 - ny//2, y0 + ny//2 + 1):
260 im[x0, y, afwImage.LOCAL] = (10, 0x0, 0.0)
261 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
262 self.assertEqual(len(fs.getFootprints()), 1)
264 ngrow = 2 # How much to grow by
265 #
266 # Test growing to the left and/or right
267 #
268 for fctrl in (
269 afwDetect.FootprintControl(False, True, False, False),
270 afwDetect.FootprintControl(True, False, False, False),
271 afwDetect.FootprintControl(True, True, False, False),
272 ):
273 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
274 grown = afwDetect.FootprintSet(fs, ngrow, fctrl)
275 im.getMask().set(0)
276 afwDetect.setMaskFromFootprintList(
277 im.getMask(), grown.getFootprints(), 0x10)
279 if display:
280 afwDisplay.Display(frame=3).mtv(im, title=self._testMethodName + " image")
282 foot = grown.getFootprints()[0]
283 nextra = 0
284 if fctrl.isLeft()[1]:
285 nextra += ngrow
286 for y in range(y0 - ny//2, y0 + ny//2 + 1):
287 self.assertNotEqual(im.getMask()[x0 - 1, y, afwImage.LOCAL], 0)
289 if fctrl.isRight()[1]:
290 nextra += ngrow
291 for y in range(y0 - ny//2, y0 + ny//2 + 1):
292 self.assertNotEqual(im.getMask()[x0 + 1, y, afwImage.LOCAL], 0)
294 self.assertEqual(foot.getArea(), (1 + nextra)*ny)
295 #
296 # Test growing to up and/or down
297 #
298 for fctrl in (
299 afwDetect.FootprintControl(False, False, True, False),
300 afwDetect.FootprintControl(False, False, False, True),
301 afwDetect.FootprintControl(False, False, True, True),
302 ):
303 grown = afwDetect.FootprintSet(fs, ngrow, fctrl)
304 im.getMask().set(0)
305 afwDetect.setMaskFromFootprintList(
306 im.getMask(), grown.getFootprints(), 0x10)
308 if display:
309 afwDisplay.Display(frame=2).mtv(im, title=self._testMethodName + " image")
311 foot = grown.getFootprints()[0]
312 nextra = 0
313 if fctrl.isUp()[1]:
314 nextra += ngrow
315 for y in range(y0 + ny//2 + 1, y0 + ny//2 + ngrow + 1):
316 self.assertNotEqual(im.getMask()[x0, y, afwImage.LOCAL], 0)
318 if fctrl.isDown()[1]:
319 nextra += ngrow
320 for y in range(y0 - ny//2 - 1, y0 - ny//2 - ngrow - 1):
321 self.assertNotEqual(im.getMask()[x0, y, afwImage.LOCAL], 0)
323 self.assertEqual(foot.getArea(), ny + nextra)
325 def testGrowLRUD2(self):
326 """Grow footprints in various directions using the FootprintSet/FootprintControl constructor
328 Check that overlapping grown Footprints give the expected answers
329 """
330 ngrow = 3 # How much to grow by
331 for fctrl, xy in [
332 (afwDetect.FootprintControl(True, True,
333 False, False), [(4, 5), (5, 6), (6, 5)]),
334 (afwDetect.FootprintControl(False, False,
335 True, True), [(5, 4), (6, 5), (5, 6)]),
336 ]:
337 im = afwImage.MaskedImageF(11, 11)
338 for x, y in xy:
339 im[x, y, afwImage.LOCAL] = (10, 0x0, 0.0)
340 fs = afwDetect.FootprintSet(im, afwDetect.Threshold(10))
341 self.assertEqual(len(fs.getFootprints()), 1)
343 grown = afwDetect.FootprintSet(fs, ngrow, fctrl)
344 im.getMask().set(0)
345 afwDetect.setMaskFromFootprintList(
346 im.getMask(), grown.getFootprints(), 0x10)
348 if display:
349 afwDisplay.Display(frame=1).mtv(im, title=self._testMethodName + " image")
351 self.assertEqual(len(grown.getFootprints()), 1)
352 foot = grown.getFootprints()[0]
354 npix = 1 + 2*ngrow
355 npix += 3 + 2*ngrow # 3: distance between pair of set pixels 000X0X000
356 self.assertEqual(foot.getArea(), npix)
358 def testInf(self):
359 """Test detection for images with Infs"""
361 im = afwImage.MaskedImageF(lsst.geom.Extent2I(10, 20))
362 im.set(0)
364 import numpy
365 for x in range(im.getWidth()):
366 im[x, -1, afwImage.LOCAL] = (numpy.Inf, 0x0, 0)
368 ds = afwDetect.FootprintSet(im, afwDetect.createThreshold(100))
370 objects = ds.getFootprints()
371 afwDetect.setMaskFromFootprintList(im.getMask(), objects, 0x10)
373 if display:
374 afwDisplay.Display(frame=2).mtv(im, title=self._testMethodName + " image")
376 self.assertEqual(len(objects), 1)
379class PeaksInFootprintsTestCase(unittest.TestCase):
380 """A test case for detecting Peaks within Footprints"""
382 def doSetUp(self, dwidth=0, dheight=0, x0=0, y0=0):
383 width, height = 14 + x0 + dwidth, 10 + y0 + dheight
384 self.im = afwImage.MaskedImageF(lsst.geom.Extent2I(width, height))
385 #
386 # Objects that we should detect
387 #
388 self.objects, self.peaks = [], []
389 self.objects.append(
390 [[4, 1, 10], [3, 2, 10], [4, 2, 20], [5, 2, 10], [4, 3, 10], ])
391 self.peaks.append([[4, 2]])
392 self.objects.append(
393 [[9, 7, 30], [10, 7, 29], [12, 7, 25], [10, 8, 27], [11, 8, 26], ])
394 self.peaks.append([[9, 7]])
395 self.objects.append([[3, 8, 10], [4, 8, 10], ])
396 self.peaks.append([[3, 8], [4, 8], ])
398 for pp in self.peaks: # allow for x0, y0
399 for p in pp:
400 p[0] += x0
401 p[1] += y0
402 for oo in self.objects:
403 for o in oo:
404 o[0] += x0
405 o[1] += y0
407 self.im.set((0, 0x0, 0)) # clear image
408 for obj in self.objects:
409 for x, y, I in obj:
410 self.im.getImage()[x, y, afwImage.LOCAL] = I
412 def setUp(self):
413 self.im, self.fs = None, None
415 def tearDown(self):
416 del self.im
417 del self.fs
419 def doTestPeaks(self, dwidth=0, dheight=0, x0=0, y0=0, threshold=10, callback=None, polarity=True,
420 grow=0):
421 """Worker routine for tests
422 polarity: True if should search for +ve pixels"""
424 self.doSetUp(dwidth, dheight, x0, y0)
425 if not polarity:
426 self.im *= -1
428 if callback:
429 callback()
431 def peakDescending(p):
432 """Sort self.peaks in decreasing peak height to match Footprint.getPeaks()"""
433 return p[2]*-1.0
434 for i, peaks in enumerate(self.peaks):
435 self.peaks[i] = sorted([(x, y, self.im.getImage()[x, y, afwImage.LOCAL]) for x, y in peaks],
436 key=peakDescending)
438 threshold = afwDetect.Threshold(
439 threshold, afwDetect.Threshold.VALUE, polarity)
440 fs = afwDetect.FootprintSet(self.im, threshold, "BINNED1")
442 if grow:
443 fs = afwDetect.FootprintSet(fs, grow, True)
444 msk = self.im.getMask()
445 afwDetect.setMaskFromFootprintList(
446 msk, fs.getFootprints(), msk.getPlaneBitMask("DETECTED"))
447 del msk
449 self.fs = fs
450 self.checkPeaks(dwidth, dheight, frame=3)
452 def checkPeaks(self, dwidth=0, dheight=0, frame=3):
453 """Check that we got the peaks right"""
454 feet = self.fs.getFootprints()
455 #
456 # Check that we found all the peaks
457 #
458 self.assertEqual(sum([len(f.getPeaks()) for f in feet]),
459 sum([len(f.getPeaks()) for f in feet]))
461 if display:
462 disp = afwDisplay.Display(frame=frame)
463 disp.mtv(self.im, title=self._testMethodName + " image")
465 with disp.Buffering():
466 for i, foot in enumerate(feet):
467 for p in foot.getPeaks():
468 disp.dot("+", p.getIx(), p.getIy(), size=0.4)
470 if i < len(self.peaks):
471 for trueX, trueY, peakVal in self.peaks[i]:
472 disp.dot("x", trueX, trueY, size=0.4, ctype=afwDisplay.RED)
474 for i, foot in enumerate(feet):
475 npeak = None
476 #
477 # Peaks that touch the edge are handled differently, as only the single highest/lowest pixel
478 # is treated as a Peak
479 #
480 if (dwidth != 0 or dheight != 0):
481 if (foot.getBBox().getMinX() == 0
482 or foot.getBBox().getMaxX() == self.im.getWidth() - 1
483 or foot.getBBox().getMinY() == 0
484 or foot.getBBox().getMaxY() == self.im.getHeight() - 1):
485 npeak = 1
487 if npeak is None:
488 npeak = len(self.peaks[i])
490 self.assertEqual(len(foot.getPeaks()), npeak)
492 for j, p in enumerate(foot.getPeaks()):
493 trueX, trueY, peakVal = self.peaks[i][j]
494 self.assertEqual((p.getIx(), p.getIy()), (trueX, trueY))
496 def testSinglePeak(self):
497 """Test that we can find single Peaks in Footprints"""
499 self.doTestPeaks()
501 def testSingleNegativePeak(self):
502 """Test that we can find single Peaks in Footprints when looking for -ve detections"""
504 self.doTestPeaks(polarity=False)
506 def testSinglePeakAtEdge(self):
507 """Test that we handle Peaks correctly at the edge"""
509 self.doTestPeaks(dheight=-1)
511 def testSingleNegativePeakAtEdge(self):
512 """Test that we handle -ve Peaks correctly at the edge"""
514 self.doTestPeaks(dheight=-1, polarity=False)
516 def testMultiPeak(self):
517 """Test that multiple peaks are handled correctly"""
518 def callback():
519 x, y = 12, 7
520 self.im.getImage()[x, y, afwImage.LOCAL] = 100
521 self.peaks[1].append((x, y))
523 self.doTestPeaks(callback=callback)
525 def testMultiNegativePeak(self):
526 """Test that multiple negative peaks are handled correctly"""
527 def callback():
528 x, y = 12, 7
529 self.im.getImage()[x, y, afwImage.LOCAL] = -100
530 self.peaks[1].append((x, y))
532 self.doTestPeaks(polarity=False, callback=callback)
534 def testGrowFootprints(self):
535 """Test that we can grow footprints, correctly merging those that now touch"""
536 def callback():
537 self.im.getImage()[10, 4, afwImage.LOCAL] = 20
538 self.peaks[-2].append((10, 4,))
540 self.doTestPeaks(dwidth=1, dheight=1, callback=callback, grow=1)
542 def testGrowFootprints2(self):
543 """Test that we can grow footprints, correctly merging those that now overlap
544 N.b. this caused RHL's initial implementation to crash
545 """
546 def callback():
547 self.im.getImage()[10, 4, afwImage.LOCAL] = 20
548 self.peaks[-2].append((10, 4, ))
550 def peaksSortKey(p):
551 return peakFromImage(self.im, p)
552 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey)
554 self.doTestPeaks(x0=0, y0=2, dwidth=2, dheight=2,
555 callback=callback, grow=2)
557 def testGrowFootprints3(self):
558 """Test that we can grow footprints, correctly merging those that now totally overwritten"""
560 self.im = afwImage.MaskedImageF(14, 11)
562 self.im.getImage().set(0)
563 self.peaks = []
565 value = 11
566 for x, y in [(4, 7), (5, 7), (6, 7), (7, 7), (8, 7),
567 (4, 6), (8, 6),
568 (4, 5), (8, 5),
569 (4, 4), (8, 4),
570 (4, 3), (8, 3),
571 ]:
572 self.im.getImage()[x, y, afwImage.LOCAL] = value
573 value -= 1e-3
575 self.im.getImage()[4, 7, afwImage.LOCAL] = 15
576 self.peaks.append([(4, 7,), ])
578 self.im.getImage()[6, 5, afwImage.LOCAL] = 30
579 self.peaks[0].append((6, 5,))
581 self.fs = afwDetect.FootprintSet(
582 self.im, afwDetect.Threshold(10), "BINNED1")
583 #
584 # The disappearing Footprint special case only shows up if the outer Footprint is grown
585 # _after_ the inner one. So arrange the order properly
586 feet = self.fs.getFootprints()
587 feet[0], feet[1] = feet[1], feet[0]
589 msk = self.im.getMask()
591 grow = 2
592 self.fs = afwDetect.FootprintSet(self.fs, grow, False)
593 afwDetect.setMaskFromFootprintList(msk, self.fs.getFootprints(),
594 msk.getPlaneBitMask("DETECTED_NEGATIVE"))
596 if display:
597 frame = 0
599 disp = afwDisplay.Display(frame=frame)
600 disp.mtv(self.im, title=self._testMethodName + " image")
602 with disp.Buffering():
603 for i, foot in enumerate(self.fs.getFootprints()):
604 for p in foot.getPeaks():
605 disp.dot("+", p.getIx(), p.getIy(), size=0.4)
607 if i < len(self.peaks):
608 for trueX, trueY in self.peaks[i]:
609 disp.dot("x", trueX, trueY, size=0.4, ctype=afwDisplay.RED)
611 self.assertEqual(len(self.fs.getFootprints()), 1)
612 self.assertEqual(len(self.fs.getFootprints()[
613 0].getPeaks()), len(self.peaks[0]))
615 def testMergeFootprints(self): # YYYY
616 """Merge positive and negative Footprints"""
617 x0, y0 = 5, 6
618 dwidth, dheight = 6, 7
620 def callback():
621 x, y, value = x0 + 10, y0 + 4, -20
622 self.im.getImage()[x, y, afwImage.LOCAL] = value
623 peaks2.append((x, y, value))
625 for grow1, grow2 in [(1, 1), (3, 3), (6, 6), ]:
626 peaks2 = []
627 self.doTestPeaks(threshold=10, callback=callback, grow=0,
628 x0=x0, y0=y0, dwidth=dwidth, dheight=dheight)
630 threshold = afwDetect.Threshold(
631 10, afwDetect.Threshold.VALUE, False)
632 fs2 = afwDetect.FootprintSet(self.im, threshold)
634 msk = self.im.getMask()
635 afwDetect.setMaskFromFootprintList(
636 msk, fs2.getFootprints(), msk.getPlaneBitMask("DETECTED_NEGATIVE"))
638 self.fs.merge(fs2, grow1, grow2)
639 self.peaks[-2] += peaks2
641 if grow1 + grow2 > 2: # grow merged all peaks
642 def peaksSortKey(p):
643 return peakFromImage(self.im, p)
644 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey)
646 afwDetect.setMaskFromFootprintList(
647 msk, self.fs.getFootprints(), msk.getPlaneBitMask("EDGE"))
649 self.checkPeaks(frame=3)
651 def testMergeFootprintPeakSchemas(self):
652 """Test that merging footprints preserves fields in the peak schemas.
653 """
654 self.doSetUp()
655 self.im.getImage()[3, 4, afwImage.LOCAL] = 100
656 self.im.getImage()[3, 5, afwImage.LOCAL] = 200
657 self.im.getImage()[6, 7, afwImage.LOCAL] = 400
659 threshold = afwDetect.Threshold(10, afwDetect.Threshold.VALUE, True)
660 fs1 = afwDetect.FootprintSet(self.im, threshold)
661 threshold = afwDetect.Threshold(150, afwDetect.Threshold.VALUE, True)
662 fs2 = afwDetect.FootprintSet(self.im, threshold)
664 def addSignificance(footprints):
665 """Return a new FootprintSet with a significance field added."""
666 mapper = afwTable.SchemaMapper(footprints.getFootprints()[0].peaks.schema)
667 mapper.addMinimalSchema(footprints.getFootprints()[0].peaks.schema)
668 mapper.addOutputField("significance", type=float,
669 doc="Ratio of peak value to configured standard deviation.")
671 newFootprints = afwDetect.FootprintSet(footprints)
672 for old, new in zip(footprints.getFootprints(), newFootprints.getFootprints()):
673 newPeaks = afwDetect.PeakCatalog(mapper.getOutputSchema())
674 newPeaks.extend(old.peaks, mapper=mapper)
675 new.getPeaks().clear()
676 new.setPeakCatalog(newPeaks)
678 for footprint in newFootprints.getFootprints():
679 for peak in footprint.peaks:
680 peak['significance'] = 10
682 return newFootprints
684 def checkPeakSignificance(footprints):
685 """Check that all peaks have significance=10."""
686 for footprint in footprints.getFootprints():
687 self.assertIn("significance", footprint.peaks.schema)
688 for peak in footprint.peaks:
689 self.assertEqual(peak["significance"], 10)
691 newFs1 = addSignificance(fs1)
693 # Check that mismatched peak schemas raise an exception in merge().
694 with self.assertRaisesRegex(RuntimeError,
695 "FootprintSets to be merged must have identical peak schemas"):
696 fs1.merge(newFs1)
698 with self.assertRaisesRegex(RuntimeError,
699 "FootprintSets to be merged must have identical peak schemas"):
700 newFs1.merge(fs2)
702 newFs2 = addSignificance(fs2)
704 # Check that the field was added correctly.
705 checkPeakSignificance(newFs1)
706 checkPeakSignificance(newFs2)
708 # Does merging footprints preserve the added field?
709 newFs1.merge(newFs2)
710 checkPeakSignificance(newFs1)
711 checkPeakSignificance(newFs2)
713 def testMergeFootprintsEngulf(self):
714 """Merge two Footprints when growing one Footprint totally replaces the other"""
715 def callback():
716 self.im.set(0)
717 self.peaks, self.objects = [], []
719 for x, y, I in [[6, 4, 20], [6, 5, 10]]:
720 self.im.getImage()[x, y, afwImage.LOCAL] = I
721 self.peaks.append([[6, 4]])
723 x, y, value = 8, 4, -20
724 self.im.getImage()[x, y, afwImage.LOCAL] = value
725 peaks2.append((x, y, value))
727 grow1, grow2 = 0, 3
728 peaks2 = []
729 self.doTestPeaks(threshold=10, callback=callback, grow=0)
731 threshold = afwDetect.Threshold(10, afwDetect.Threshold.VALUE, False)
732 fs2 = afwDetect.FootprintSet(self.im, threshold)
734 msk = self.im.getMask()
735 afwDetect.setMaskFromFootprintList(
736 msk, fs2.getFootprints(), msk.getPlaneBitMask("DETECTED_NEGATIVE"))
738 self.fs.merge(fs2, grow1, grow2)
739 self.peaks[0] += peaks2
741 def peaksSortKey(p):
742 return peakFromImage(self.im, p)
743 self.peaks[0] = sorted(sum(self.peaks, []), key=peaksSortKey)
745 afwDetect.setMaskFromFootprintList(
746 msk, self.fs.getFootprints(), msk.getPlaneBitMask("EDGE"))
748 self.checkPeaks(frame=3)
751class MemoryTester(lsst.utils.tests.MemoryTestCase):
752 pass
755def setup_module(module):
756 lsst.utils.tests.init()
759if __name__ == "__main__": 759 ↛ 760line 759 didn't jump to line 760, because the condition on line 759 was never true
760 lsst.utils.tests.init()
761 unittest.main()