Coverage for tests/test_footprint1.py: 9%
835 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-24 02:29 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-24 02:29 -0700
1# This file is part of afw.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""
23Tests for Footprints, and FootprintSets
24"""
26import math
27import sys
28import unittest
29import os
31import numpy as np
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
44try:
45 type(display)
46except NameError:
47 display = False
49testPath = os.path.abspath(os.path.dirname(__file__))
52def toString(*args):
53 """toString written in python"""
54 if len(args) == 1:
55 args = args[0]
57 y, x0, x1 = args
58 return f"{y}: {x0}..{x1}"
61class Object:
63 def __init__(self, val, spans):
64 self.val = val
65 self.spans = spans
67 def __str__(self):
68 return ", ".join([str(s) for s in self.spans])
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
77 def __eq__(self, other):
78 for osp, sp in zip(other.getSpans(), self.spans):
79 if osp.toString() != toString(sp):
80 return False
82 return True
85class SpanTestCase(unittest.TestCase):
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)
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)
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)
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``.
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")
128 with self.assertRaises(pexExcept.InvalidParameterError):
129 afwDetect.createThreshold(3.4, "foo bar")
131 try:
132 afwDetect.createThreshold(3.4, "variance")
133 except Exception:
134 self.fail("Failed to build Threshold with proper parameters")
136 try:
137 afwDetect.createThreshold(3.4, "stdev")
138 except Exception:
139 self.fail("Failed to build Threshold with proper parameters")
141 try:
142 afwDetect.createThreshold(3.4, "value")
143 except Exception:
144 self.fail("Failed to build Threshold with proper parameters")
146 try:
147 afwDetect.createThreshold(3.4, "value", False)
148 except Exception:
149 self.fail("Failed to build Threshold with VALUE, False parameters")
151 try:
152 afwDetect.createThreshold(0x4, "bitmask")
153 except Exception:
154 self.fail("Failed to build Threshold with BITMASK parameters")
156 try:
157 afwDetect.createThreshold(5, "pixel_stdev")
158 except Exception:
159 self.fail("Failed to build Threshold with PIXEL_STDEV parameters")
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)")
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)")
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)")
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)")
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)")
190class FootprintTestCase(lsst.utils.tests.TestCase):
191 """A test case for Footprint"""
193 def setUp(self):
194 self.foot = afwDetect.Footprint()
196 def tearDown(self):
197 del self.foot
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))
204 def testGC(self):
205 """Check that Footprints are automatically garbage collected (when MemoryTestCase runs)"""
207 afwDetect.Footprint()
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)
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())
230 self.assertEqual(fp.getArea(), maskBBox.getArea() - innerBBox.getArea())
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())
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
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)
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)
279 bbox = foot.getBBox()
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)
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()
296 dx, dy = 10, 20
297 bbox.shift(lsst.geom.Extent2I(dx, dy))
299 self.assertEqual(bbox.getMinX(), x0 + dx)
300 self.assertEqual(foot.getBBox().getMinX(), x0)
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)))
314 idImage = afwImage.ImageU(lsst.geom.Extent2I(
315 foot.getRegion().getWidth(), foot.getRegion().getHeight()))
316 idImage.set(0)
318 foot.spans.setImage(idImage, 42)
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)
328 shape = foot.getShape()
329 shape.scale(2) # <r^2> = 1/2 for a disk
330 disp.dot(shape, *cen, ctype=afwDisplay.MAGENTA)
332 axes = afwGeom.ellipses.Axes(foot.getShape())
333 axes.scale(2) # <r^2> = 1/2 for a disk
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}")
341 def testCopy(self):
342 bbox = lsst.geom.BoxI(lsst.geom.PointI(0, 2), lsst.geom.PointI(5, 6))
344 fp = afwDetect.Footprint(afwGeom.SpanSet(bbox), bbox)
346 # test copy construct
347 fp2 = afwDetect.Footprint(fp)
349 self.assertEqual(fp2.getBBox(), bbox)
350 self.assertEqual(fp2.getRegion(), bbox)
351 self.assertEqual(fp2.getArea(), bbox.getArea())
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
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())
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
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
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)
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
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())
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))
403 # Without shifting the centroid
404 self.assertEqual(shrunk.getCentroid(), foot.getCentroid())
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())
412 # Shrinking by a large amount leaves nothing.
413 shrunkToNothing = afwDetect.Footprint().assign(foot)
414 shrunkToNothing.erode(100)
415 self.assertEqual(shrunkToNothing.getArea(), 0)
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
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)
435 foot.erode(nshrink, afwGeom.Stencil.MANHATTAN)
436 footIsotropic.erode(nshrink)
437 self.assertNotEqual(foot, footIsotropic)
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
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
453 box = lsst.geom.Box2I(lsst.geom.Point2I(0, 0),
454 lsst.geom.Extent2I(imwidth, imheight))
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)
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)
468 initial = afwDetect.mergeFootprints(f1, f2)
469 initial.setRegion(f2.getRegion()) # merge does not propagate the region
470 self.assertEqual(initial_npix, initial.getArea())
472 shrunk = afwDetect.Footprint().assign(initial)
473 shrunk.erode(nshrink)
474 self.assertEqual(shrunk_npix, shrunk.getArea())
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")
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)
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)
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)))
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)
511 bbox1 = foot1.getBBox()
513 self.assertEqual(bbox1.getMinX(), x0)
514 self.assertEqual(bbox1.getMaxX(), x0 + width - 1)
515 self.assertEqual(bbox1.getWidth(), width)
517 self.assertEqual(bbox1.getMinY(), y0)
518 self.assertEqual(bbox1.getMaxY(), y0 + height - 1)
519 self.assertEqual(bbox1.getHeight(), height)
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)
528 # Check that the grown footprint is bigger than the original
529 self.assertGreater(foot2.getArea(), foot1.getArea())
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)])
537 bbox2 = foot2.getBBox()
539 # check bbox2
540 self.assertEqual(bbox2.getMinX(), x0 - ngrow)
541 self.assertEqual(bbox2.getWidth(), width + 2*ngrow)
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())
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)
558 idImage = afwImage.ImageU(region.getDimensions())
559 idImage.set(0)
561 foot.spans.setImage(idImage, 1)
562 if display:
563 disp = afwDisplay.Display(frame=1)
564 disp.mtv(idImage, title=self._testMethodName + " image")
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()
573 for y in range(y0, y1 + 1):
574 for x in range(x0, x1 + 1):
575 idImageFromBBox[x, y, afwImage.LOCAL] = 1
577 if display:
578 x0 -= 0.5
579 y0 -= 0.5
580 x1 += 0.5
581 y1 += 0.5
583 disp.line([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)], ctype=afwDisplay.RED)
585 idImageFromBBox -= idImage # should be blank
586 stats = afwMath.makeStatistics(idImageFromBBox, afwMath.MAX)
588 self.assertEqual(stats.getValue(), 0)
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)
602 openedFile = False
603 if True:
604 fd = open("/dev/null", "w")
605 openedFile = True
606 else:
607 fd = sys.stdout
609 afwDetectUtils.writeFootprintAsDefects(fd, foot)
610 if openedFile:
611 fd.close()
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)])]
625 im.set(0) # clear image
626 for obj in self.objects:
627 obj.insert(im)
629 ds = afwDetect.FootprintSet(mi, afwDetect.Threshold(15))
631 objects = ds.getFootprints()
632 afwDetect.setMaskFromFootprintList(mi.getMask(), objects, 0x1)
634 self.assertEqual(mi.getMask()[4, 2, afwImage.LOCAL], 0x0)
635 self.assertEqual(mi.getMask()[3, 6, afwImage.LOCAL], 0x1)
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)
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)))
653 self.assertTrue(objects[1].contains(lsst.geom.Point2I(3, 6)))
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)
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)
673 mi.setXY0(lsst.geom.PointI(2, 2))
674 afwDetect.FootprintSet(mi, afwDetect.Threshold(1), "DETECTED")
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)
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)
697 fpTarget = fpSource.transform(source, target, bbox)
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)
704 imTarget = afwImage.ImageU(dims)
705 fpTarget.spans.setImage(imTarget, 1)
707 subSource = imSource.Factory(imSource, fpSource.getBBox())
708 subTarget = imTarget.Factory(imTarget, fpTarget.getBBox())
709 self.assertTrue(np.all(subSource.getArray() == subTarget.getArray()))
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())
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)
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
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)
752 self.foot.spans.copyImage(source, dest)
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))
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)
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)
787 foot.spans.clippedTo(subTarget.getBBox()).clippedTo(source.getBBox()).\
788 copyImage(source, subTarget)
790 expected = np.zeros((100, 100))
791 expected[50, 50:55] = 1.0
793 self.assertTrue(np.all(target.getArray() == expected))
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
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)
814 self.foot.spans.copyMaskedImage(source, dest)
816 da = dest.getImage().getArray()
817 dv = dest.getVariance().getArray()
818 dm = dest.getMask().getArray()
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))
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))
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))
855 def testMergeFootprints(self):
856 f1 = self.foot
857 f2 = afwDetect.Footprint()
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
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
886 fA = afwDetect.mergeFootprints(f1, f2)
887 fB = afwDetect.mergeFootprints(f2, f1)
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)
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)))
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])
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)
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()))
967 def checkEdge(self, footprint):
968 """Check that Footprint::findEdgePixels() works"""
969 bbox = footprint.getBBox()
970 bbox.grow(3)
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
984 edges = self.foot.spans.findEdgePixels()
985 edgeImage = makeImage(edges)
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)
1001 self.assertTrue(np.all(trueEdges == edgeImage.getArray()))
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)
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")))
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)
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)
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)
1043class FootprintSetTestCase(unittest.TestCase):
1044 """A test case for FootprintSet"""
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)])]
1057 self.ms.set((0, 0x0, 4.0)) # clear image; set variance
1058 for obj in self.objects:
1059 obj.insert(im)
1061 def tearDown(self):
1062 del self.ms
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))
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))
1073 objects = ds.getFootprints()
1075 self.assertEqual(len(objects), len(self.objects))
1076 for i in range(len(objects)):
1077 self.assertEqual(objects[i], self.objects[i])
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)
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))
1091 objects = ds.getFootprints()
1093 self.assertEqual(len(objects), len(self.objects))
1094 for i in range(len(objects)):
1095 self.assertEqual(objects[i], self.objects[i])
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
1101 self.ms[2, 4, afwImage.LOCAL] = (10, 0x0, 36) # not detected (high variance)
1103 y, x = self.objects[2].spans[0][0:2]
1104 self.ms[x, y, afwImage.LOCAL] = (threshold, 0x0, 1.0)
1106 ds = afwDetect.FootprintSet(self.ms,
1107 afwDetect.createThreshold(threshold, "pixel_stdev"), "OBJECT")
1109 objects = ds.getFootprints()
1111 self.assertEqual(len(objects), len(self.objects))
1112 for i in range(len(objects)):
1113 self.assertEqual(objects[i], self.objects[i])
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()
1120 if display:
1121 afwDisplay.Display(frame=1).mtv(self.ms, title=self._testMethodName + " image")
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"))
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()
1135 idImage = afwImage.ImageU(self.ms.getDimensions())
1136 idImage.set(0)
1138 for i, foot in enumerate(objects):
1139 foot.spans.setImage(idImage, i + 1)
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)
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()
1151 idImage = ds.insertIntoImage()
1152 if display:
1153 afwDisplay.Display(frame=2).mtv(idImage, title=self._testMethodName + " image")
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)
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))
1164 objects = ds.getFootprints()
1166 self.assertEqual(len(objects), len(self.objects))
1167 for i in range(len(objects)):
1168 self.assertEqual(objects[i], self.objects[i])
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")
1174 idImage = afwImage.ImageU(self.ms.getDimensions())
1175 idImage.set(0)
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
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")
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")
1191 foot = fs.getFootprints()[0]
1193 self.assertEqual(len(foot.getPeaks()), 5)
1196class MaskFootprintSetTestCase(unittest.TestCase):
1197 """A test case for generating FootprintSet from Masks"""
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)])]
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())
1215 if display:
1216 afwDisplay.Display(frame=0).mtv(self.mim, title=self._testMethodName + " self.mim")
1218 def tearDown(self):
1219 del self.mim
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"))
1227 objects = ds.getFootprints()
1229 if 0 and display:
1230 afwDisplay.Display(frame=0).mtv(self.mim, title=self._testMethodName + " self.mim")
1232 self.assertEqual(len(objects),
1233 len([o for o in self.objects if (o.val & level)]))
1235 i = 0
1236 for o in self.objects:
1237 if o.val & level:
1238 self.assertEqual(o, objects[i])
1239 i += 1
1242class NaNFootprintSetTestCase(unittest.TestCase):
1243 """A test case for FootprintSet when the image contains NaNs"""
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)])]
1257 im.set(0) # clear image
1258 for obj in self.objects:
1259 obj.insert(im)
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
1266 # connects the two objects with value==20 together if NaN is detected
1267 im[9, 6, afwImage.LOCAL] = self.NaN
1269 def tearDown(self):
1270 del self.ms
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")
1276 objects = ds.getFootprints()
1278 if display:
1279 afwDisplay.Display(frame=0).mtv(self.ms, title=self._testMethodName + " self.ms")
1281 self.assertEqual(len(objects), len(self.objects))
1282 for i in range(len(objects)):
1283 self.assertEqual(objects[i], self.objects[i])
1286class MemoryTester(lsst.utils.tests.MemoryTestCase):
1287 pass
1290def setup_module(module):
1291 lsst.utils.tests.init()
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()