Coverage for tests/test_spanSets.py: 14%
284 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-20 02:20 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-09-20 02:20 -0700
1#
2# LSST Data Management System
3#
4# Copyright 2008-2016 AURA/LSST.
5#
6# This product includes software developed by the
7# LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20# the GNU General Public License along with this program. If not,
21# see <https://www.lsstcorp.org/LegalNotices/>.
22#
24import unittest
25import numpy as np
27import lsst.utils.tests
28import lsst.geom
29import lsst.afw.geom as afwGeom
30import lsst.afw.geom.ellipses as afwGeomEllipses
31import lsst.afw.image as afwImage
34class SpanSetTestCase(lsst.utils.tests.TestCase):
35 '''
36 This is a python level unit test of the SpanSets class. It is mean to work in conjuction
37 with the c++ unit test. The C++ test has much more coverage, and tests some features which
38 are not exposed to python. This test serves mainly as a way to test that the python bindings
39 correctly work. Many of these tests are smaller versions of the C++ tests.
40 '''
42 def testNullSpanSet(self):
43 nullSS = afwGeom.SpanSet()
44 self.assertEqual(nullSS.getArea(), 0)
45 self.assertEqual(len(nullSS), 0)
46 self.assertEqual(nullSS.getBBox().getDimensions().getX(), 0)
47 self.assertEqual(nullSS.getBBox().getDimensions().getY(), 0)
49 def testBBoxSpanSet(self):
50 boxSS = afwGeom.SpanSet(lsst.geom.Box2I(lsst.geom.Point2I(2, 2),
51 lsst.geom.Point2I(6, 6)))
52 self.assertEqual(boxSS.getArea(), 25)
53 bBox = boxSS.getBBox()
54 self.assertEqual(bBox.getMinX(), 2)
55 self.assertEqual(bBox.getMinY(), 2)
57 def testIteratorConstructor(self):
58 spans = [afwGeom.Span(0, 2, 4), afwGeom.Span(1, 2, 4),
59 afwGeom.Span(2, 2, 4)]
60 spanSetFromList = afwGeom.SpanSet(spans)
61 spanSetFromArray = afwGeom.SpanSet(np.array(spans))
63 self.assertEqual(spanSetFromList.getBBox().getMinX(), 2)
64 self.assertEqual(spanSetFromList.getBBox().getMaxX(), 4)
65 self.assertEqual(spanSetFromList.getBBox().getMinY(), 0)
67 self.assertEqual(spanSetFromArray.getBBox().getMinX(), 2)
68 self.assertEqual(spanSetFromArray.getBBox().getMaxX(), 4)
69 self.assertEqual(spanSetFromArray.getBBox().getMinY(), 0)
71 def testIsContiguous(self):
72 spanSetConList = [afwGeom.Span(0, 2, 5), afwGeom.Span(1, 5, 8)]
73 spanSetCon = afwGeom.SpanSet(spanSetConList)
74 self.assertTrue(spanSetCon.isContiguous())
76 spanSetNotConList = [afwGeom.Span(0, 2, 5), afwGeom.Span(1, 20, 25)]
77 spanSetNotCon = afwGeom.SpanSet(spanSetNotConList)
78 self.assertFalse(spanSetNotCon.isContiguous())
80 def testSplit(self):
81 spanSetOne = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(2, 2)
82 spanSetTwo = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(8, 8)
84 spanSetList = []
85 for spn in spanSetOne:
86 spanSetList.append(spn)
87 for spn in spanSetTwo:
88 spanSetList.append(spn)
89 spanSetTogether = afwGeom.SpanSet(spanSetList)
91 spanSetSplit = spanSetTogether.split()
92 self.assertEqual(len(spanSetSplit), 2)
94 for a, b in zip(spanSetOne, spanSetSplit[0]):
95 self.assertEqual(a, b)
97 for a, b in zip(spanSetTwo, spanSetSplit[1]):
98 self.assertEqual(a, b)
100 def testTransform(self):
101 transform = lsst.geom.LinearTransform(np.array([[2.0, 0.0], [0.0, 2.0]]))
102 spanSetPreScale = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.CIRCLE)
103 spanSetPostScale = spanSetPreScale.transformedBy(transform)
105 self.assertEqual(spanSetPostScale.getBBox().getMinX(), -4)
106 self.assertEqual(spanSetPostScale.getBBox().getMinY(), -4)
108 def testOverlaps(self):
109 spanSetNoShift = afwGeom.SpanSet.fromShape(4, afwGeom.Stencil.CIRCLE)
110 spanSetShift = spanSetNoShift.shiftedBy(2, 2)
112 self.assertTrue(spanSetNoShift.overlaps(spanSetShift))
114 def testContains(self):
115 spanSetLarge = afwGeom.SpanSet.fromShape(4, afwGeom.Stencil.CIRCLE)
116 spanSetSmall = afwGeom.SpanSet.fromShape(1, afwGeom.Stencil.CIRCLE)
118 self.assertTrue(spanSetLarge.contains(spanSetSmall))
119 self.assertFalse(spanSetSmall.contains(lsst.geom.Point2I(100, 100)))
121 def testComputeCentroid(self):
122 spanSetShape = afwGeom.SpanSet.fromShape(4, afwGeom.Stencil.CIRCLE).shiftedBy(2, 2)
123 center = spanSetShape.computeCentroid()
125 self.assertEqual(center.getX(), 2)
126 self.assertEqual(center.getY(), 2)
128 def testComputeShape(self):
129 spanSetShape = afwGeom.SpanSet.fromShape(1, afwGeom.Stencil.CIRCLE)
130 quad = spanSetShape.computeShape()
132 self.assertEqual(quad.getIxx(), 0.4)
133 self.assertEqual(quad.getIyy(), 0.4)
134 self.assertEqual(quad.getIxy(), 0)
136 def testdilated(self):
137 spanSetPredilated = afwGeom.SpanSet.fromShape(1, afwGeom.Stencil.CIRCLE)
138 spanSetPostdilated = spanSetPredilated.dilated(1)
140 bBox = spanSetPostdilated.getBBox()
141 self.assertEqual(bBox.getMinX(), -2)
142 self.assertEqual(bBox.getMinY(), -2)
144 def testErode(self):
145 spanSetPreErode = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.CIRCLE)
146 spanSetPostErode = spanSetPreErode.eroded(1)
148 bBox = spanSetPostErode.getBBox()
149 self.assertEqual(bBox.getMinX(), -1)
150 self.assertEqual(bBox.getMinY(), -1)
152 def testFlatten(self):
153 # Give an initial value to an input array
154 inputArray = np.ones((6, 6)) * 9
155 inputArray[1, 1] = 1
156 inputArray[1, 2] = 2
157 inputArray[2, 1] = 3
158 inputArray[2, 2] = 4
160 inputSpanSet = afwGeom.SpanSet([afwGeom.Span(0, 0, 1),
161 afwGeom.Span(1, 0, 1)])
162 flatArr = inputSpanSet.flatten(inputArray, lsst.geom.Point2I(-1, -1))
164 self.assertEqual(flatArr.size, inputSpanSet.getArea())
166 # Test flatttening a 3D array
167 spanSetArea = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX)
168 spanSetArea = spanSetArea.shiftedBy(2, 2)
170 testArray = np.arange(5*5*3).reshape(5, 5, 3)
171 flattened2DArray = spanSetArea.flatten(testArray)
173 truthArray = np.arange(5*5*3).reshape(5*5, 3)
174 self.assertFloatsAlmostEqual(flattened2DArray, truthArray)
176 def testUnflatten(self):
177 inputArray = np.ones(6) * 4
178 inputSpanSet = afwGeom.SpanSet([afwGeom.Span(9, 2, 3),
179 afwGeom.Span(10, 3, 4),
180 afwGeom.Span(11, 2, 3)])
181 outputArray = inputSpanSet.unflatten(inputArray)
183 arrayShape = outputArray.shape
184 bBox = inputSpanSet.getBBox()
185 self.assertEqual(arrayShape[0], bBox.getHeight())
186 self.assertEqual(arrayShape[1], bBox.getWidth())
188 # Test unflattening a 2D array
189 spanSetArea = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX)
190 spanSetArea = spanSetArea.shiftedBy(2, 2)
192 testArray = np.arange(5*5*3).reshape(5*5, 3)
193 unflattened3DArray = spanSetArea.unflatten(testArray)
195 truthArray = np.arange(5*5*3).reshape(5, 5, 3)
196 self.assertFloatsAlmostEqual(unflattened3DArray, truthArray)
198 def populateMask(self):
199 msk = afwImage.Mask(10, 10, 1)
200 spanSetMask = afwGeom.SpanSet.fromShape(3, afwGeom.Stencil.CIRCLE).shiftedBy(5, 5)
201 spanSetMask.setMask(msk, 2)
202 return msk, spanSetMask
204 def testSetMask(self):
205 mask, spanSetMask = self.populateMask()
206 mskArray = mask.getArray()
207 for i in range(mskArray.shape[0]):
208 for j in range(mskArray.shape[1]):
209 if lsst.geom.Point2I(i, j) in spanSetMask:
210 self.assertEqual(mskArray[i, j], 3)
211 else:
212 self.assertEqual(mskArray[i, j], 1)
214 def testClearMask(self):
215 mask, spanSetMask = self.populateMask()
216 spanSetMask.clearMask(mask, 2)
217 mskArray = mask.getArray()
218 for i in range(mskArray.shape[0]):
219 for j in range(mskArray.shape[1]):
220 self.assertEqual(mskArray[i, j], 1)
222 def makeOverlapSpanSets(self):
223 firstSpanSet = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(2, 4)
224 secondSpanSet = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(2, 2)
225 return firstSpanSet, secondSpanSet
227 def makeMaskAndSpanSetForOperationTest(self):
228 firstMaskPart = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(3, 2)
229 secondMaskPart = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(3, 8)
230 spanSetMaskOperation = afwGeom.SpanSet.fromShape(2, afwGeom.Stencil.BOX).shiftedBy(3, 5)
232 mask = afwImage.Mask(20, 20)
233 firstMaskPart.setMask(mask, 3)
234 secondMaskPart.setMask(mask, 3)
235 spanSetMaskOperation.setMask(mask, 4)
237 return mask, spanSetMaskOperation
239 def testIntersection(self):
240 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets()
242 overlap = firstSpanSet.intersect(secondSpanSet)
243 for i, span in enumerate(overlap):
244 self.assertEqual(span.getY(), i+2)
245 self.assertEqual(span.getMinX(), 0)
246 self.assertEqual(span.getMaxX(), 4)
248 mask, spanSetMaskOperation = self.makeMaskAndSpanSetForOperationTest()
249 spanSetIntersectMask = spanSetMaskOperation.intersect(mask, 2)
251 expectedYRange = [3, 4, 6, 7]
252 for expected, val in zip(expectedYRange, spanSetIntersectMask):
253 self.assertEqual(expected, val.getY())
255 def testIntersectNot(self):
256 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets()
258 overlap = firstSpanSet.intersectNot(secondSpanSet)
259 for yVal, span in enumerate(overlap):
260 self.assertEqual(span.getY(), yVal+5)
261 self.assertEqual(span.getMinX(), 0)
262 self.assertEqual(span.getMaxX(), 4)
264 mask, spanSetMaskOperation = self.makeMaskAndSpanSetForOperationTest()
266 spanSetIntersectNotMask = spanSetMaskOperation.intersectNot(mask, 2)
268 self.assertEqual(len(spanSetIntersectNotMask), 1)
269 self.assertEqual(next(iter(spanSetIntersectNotMask)).getY(), 5)
271 # More complicated intersection with disconnected SpanSets
272 spanList1 = [afwGeom.Span(0, 0, 10),
273 afwGeom.Span(1, 0, 10),
274 afwGeom.Span(2, 0, 10)]
276 spanList2 = [afwGeom.Span(1, 2, 4), afwGeom.Span(1, 7, 8)]
278 resultList = [afwGeom.Span(0, 0, 10),
279 afwGeom.Span(1, 0, 1),
280 afwGeom.Span(1, 5, 6),
281 afwGeom.Span(1, 9, 10),
282 afwGeom.Span(2, 0, 10)]
284 spanSet1 = afwGeom.SpanSet(spanList1)
285 spanSet2 = afwGeom.SpanSet(spanList2)
286 expectedSpanSet = afwGeom.SpanSet(resultList)
288 outputSpanSet = spanSet1.intersectNot(spanSet2)
290 self.assertEqual(outputSpanSet, expectedSpanSet)
292 numIntersectNotTrials = 100
293 spanRow = 5
294 # Set a seed for random functions
295 np.random.seed(400)
296 for N in range(numIntersectNotTrials):
297 # Create two random SpanSets, both with holes in them
298 listOfRandomSpanSets = []
299 for i in range(2):
300 # Make two rectangles to be turned into a SpanSet
301 rand1 = np.random.randint(0, 26, 2)
302 rand2 = np.random.randint(rand1.max(), 51, 2)
303 tempList = [afwGeom.Span(spanRow, rand1.min(), rand1.max()),
304 afwGeom.Span(spanRow, rand2.min(), rand2.max())]
305 listOfRandomSpanSets.append(afwGeom.SpanSet(tempList))
307 # IntersectNot the SpanSets, randomly choosing which one is the one
308 # to be the negated SpanSet
309 randChoice = np.random.randint(0, 2)
310 negatedRandChoice = int(not randChoice)
311 sourceSpanSet = listOfRandomSpanSets[randChoice]
312 targetSpanSet = listOfRandomSpanSets[negatedRandChoice]
313 resultSpanSet = sourceSpanSet.intersectNot(targetSpanSet)
314 for span in resultSpanSet:
315 for point in span:
316 self.assertTrue(sourceSpanSet.contains(point))
317 self.assertFalse(targetSpanSet.contains(point))
319 for x in range(51):
320 point = lsst.geom.Point2I(x, spanRow)
321 if sourceSpanSet.contains(point) and not\
322 targetSpanSet.contains(point):
323 self.assertTrue(resultSpanSet.contains(point))
325 def testUnion(self):
326 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets()
328 overlap = firstSpanSet.union(secondSpanSet)
330 for yVal, span in enumerate(overlap):
331 self.assertEqual(span.getY(), yVal)
332 self.assertEqual(span.getMinX(), 0)
333 self.assertEqual(span.getMaxX(), 4)
335 mask, spanSetMaskOperation = self.makeMaskAndSpanSetForOperationTest()
337 spanSetUnion = spanSetMaskOperation.union(mask, 2)
339 for yVal, span in enumerate(spanSetUnion):
340 self.assertEqual(span.getY(), yVal)
342 def testMaskToSpanSet(self):
343 mask, _ = self.makeMaskAndSpanSetForOperationTest()
344 spanSetFromMask = afwGeom.SpanSet.fromMask(mask)
346 for yCoord, span in enumerate(spanSetFromMask):
347 self.assertEqual(span, afwGeom.Span(yCoord, 1, 5))
349 def testFromMask(self):
350 xy0 = lsst.geom.Point2I(12345, 67890) # xy0 for image
351 dims = lsst.geom.Extent2I(123, 45) # Dimensions of image
352 box = lsst.geom.Box2I(xy0, dims) # Bounding box of image
353 value = 32
354 other = 16
355 assert value & other == 0 # Setting 'other' unsets 'value'
356 point = lsst.geom.Point2I(3 + xy0.getX(), 3 + xy0.getY()) # Point in the image
358 mask = afwImage.Mask(box)
359 mask.set(value)
361 # We can create a SpanSet from bit planes
362 spans = afwGeom.SpanSet.fromMask(mask, value)
363 self.assertEqual(spans.getArea(), box.getArea())
364 self.assertEqual(spans.getBBox(), box)
365 self.assertTrue(point in spans)
367 # Pixels not matching the desired bit plane are ignored as they should be
368 mask[point] = other # unset one pixel
369 spans = afwGeom.SpanSet.fromMask(mask, value)
370 self.assertEqual(spans.getArea(), box.getArea() - 1)
371 self.assertEqual(spans.getBBox(), box)
372 self.assertFalse(point in spans)
374 def testEquality(self):
375 firstSpanSet, secondSpanSet = self.makeOverlapSpanSets()
376 secondSpanSetShift = secondSpanSet.shiftedBy(0, 2)
378 self.assertFalse(firstSpanSet == secondSpanSet)
379 self.assertTrue(firstSpanSet != secondSpanSet)
380 self.assertTrue(firstSpanSet == secondSpanSetShift)
381 self.assertFalse(firstSpanSet != secondSpanSetShift)
383 def testSpanSetFromEllipse(self):
384 axes = afwGeomEllipses.Axes(6, 6, 0)
385 ellipse = afwGeom.Ellipse(axes, lsst.geom.Point2D(5, 6))
386 spanSet = afwGeom.SpanSet.fromShape(ellipse)
387 for ss, es in zip(spanSet, afwGeomEllipses.PixelRegion(ellipse)):
388 self.assertEqual(ss, es)
390 def testfromShapeOffset(self):
391 shift = lsst.geom.Point2I(2, 2)
392 spanSetShifted = afwGeom.SpanSet.fromShape(2, offset=shift)
393 bbox = spanSetShifted.getBBox()
394 self.assertEqual(bbox.getMinX(), 0)
395 self.assertEqual(bbox.getMinY(), 0)
397 def testFindEdgePixels(self):
398 spanSet = afwGeom.SpanSet.fromShape(6, afwGeom.Stencil.CIRCLE)
399 spanSetEdge = spanSet.findEdgePixels()
401 truthSpans = [afwGeom.Span(-6, 0, 0),
402 afwGeom.Span(-5, -3, -1),
403 afwGeom.Span(-5, 1, 3),
404 afwGeom.Span(-4, -4, -4),
405 afwGeom.Span(-4, 4, 4),
406 afwGeom.Span(-3, -5, -5),
407 afwGeom.Span(-3, 5, 5),
408 afwGeom.Span(-2, -5, -5),
409 afwGeom.Span(-2, 5, 5),
410 afwGeom.Span(-1, -5, -5),
411 afwGeom.Span(-1, 5, 5),
412 afwGeom.Span(0, -6, -6),
413 afwGeom.Span(0, 6, 6),
414 afwGeom.Span(1, -5, -5),
415 afwGeom.Span(1, 5, 5),
416 afwGeom.Span(2, -5, -5),
417 afwGeom.Span(2, 5, 5),
418 afwGeom.Span(3, -5, -5),
419 afwGeom.Span(3, 5, 5),
420 afwGeom.Span(4, -4, -4),
421 afwGeom.Span(4, 4, 4),
422 afwGeom.Span(5, -3, -1),
423 afwGeom.Span(5, 1, 3),
424 afwGeom.Span(6, 0, 0)]
425 truthSpanSet = afwGeom.SpanSet(truthSpans)
426 self.assertEqual(spanSetEdge, truthSpanSet)
428 def testIndices(self):
429 dataArray = np.zeros((5, 5))
430 spanSet = afwGeom.SpanSet.fromShape(2,
431 afwGeom.Stencil.BOX,
432 offset=(2, 2))
433 yind, xind = spanSet.indices()
434 dataArray[yind, xind] = 9
435 self.assertTrue((dataArray == 9).all())
437 def testSpanIteration(self):
438 span = afwGeom.Span(4, 3, 8)
439 points = list(span)
440 self.assertEqual(len(span), len(points))
441 self.assertEqual(points, [lsst.geom.Point2I(x, 4) for x in range(3, 9)])
444class TestMemory(lsst.utils.tests.MemoryTestCase):
445 pass
448def setup_module(module):
449 lsst.utils.tests.init()
452if __name__ == "__main__": 452 ↛ 453line 452 didn't jump to line 453, because the condition on line 452 was never true
453 lsst.utils.tests.init()
454 unittest.main()