Coverage for tests/test_polygon.py: 10%
281 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/>.
22import pickle
23import unittest
25import numpy as np
27import lsst.utils.tests
28import lsst.geom
29import lsst.afw.geom as afwGeom
30import lsst.afw.image # noqa: F401 required by Polygon.createImage
32display = False
33doPause = False # If False, autoscan plots with 2 sec delay. If True, impose manual closing of plots.
36def circle(radius, num, x0=0.0, y0=0.0):
37 """Generate points on a circle
39 @param radius: radius of circle
40 @param num: number of points
41 @param x0,y0: Offset in x,y
42 @return x,y coordinates as numpy array
43 """
44 theta = np.linspace(0, 2*np.pi, num=num, endpoint=False)
45 x = radius*np.cos(theta) + x0
46 y = radius*np.sin(theta) + y0
47 return np.array([x, y]).transpose()
50class PolygonTest(lsst.utils.tests.TestCase):
52 def setUp(self):
53 self.x0 = 0.0
54 self.y0 = 0.0
56 def polygon(self, num, radius=1.0, x0=None, y0=None):
57 """Generate a polygon
59 @param num: Number of points
60 @param radius: Radius of polygon
61 @param x0,y0: Offset of center
62 @return polygon
63 """
64 if x0 is None:
65 x0 = self.x0
66 if y0 is None:
67 y0 = self.y0
68 points = circle(radius, num, x0=x0, y0=y0)
69 return afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in reversed(points)])
71 def square(self, size=1.0, x0=0, y0=0):
72 """Generate a square
74 @param size: Half-length of the sides
75 @param x0,y0: Offset of center
76 """
77 return afwGeom.Polygon([lsst.geom.Point2D(size*x + x0, size*y + y0) for
78 x, y in ((-1, -1), (-1, 1), (1, 1), (1, -1))])
80 def testGetters(self):
81 """Test Polygon getters"""
82 for num in range(3, 30):
83 poly = self.polygon(num)
84 self.assertEqual(poly, poly)
85 self.assertNotEqual(poly, self.square(1.0, 2.0, 3.0))
86 self.assertEqual(poly.getNumEdges(), num)
87 # One extra for the closing point
88 self.assertEqual(len(poly.getVertices()), num + 1)
89 self.assertEqual(len(poly.getEdges()), num)
90 perimeter = 0.0
91 for p1, p2 in poly.getEdges():
92 perimeter += np.hypot(p1.getX() - p2.getX(),
93 p1.getY() - p2.getY())
94 self.assertAlmostEqual(poly.calculatePerimeter(), perimeter)
96 size = 3.0
97 poly = self.square(size=size)
98 self.assertEqual(poly.calculateArea(), (2*size)**2)
99 self.assertEqual(poly.calculatePerimeter(), 2*size*4)
100 edges = poly.getEdges()
101 self.assertEqual(len(edges), 4)
102 perimeter = 0.0
103 for p1, p2 in edges:
104 self.assertEqual(abs(p1.getX()), size)
105 self.assertEqual(abs(p1.getY()), size)
106 self.assertEqual(abs(p2.getX()), size)
107 self.assertEqual(abs(p2.getY()), size)
108 self.assertNotEqual(p1, p2)
110 def testPickle(self):
111 for num in range(3, 30):
112 poly = self.polygon(num)
113 self.assertEqual(pickle.loads(pickle.dumps(poly)), poly)
115 def testFromBox(self):
116 size = 1.0
117 poly1 = self.square(size=size)
118 box = lsst.geom.Box2D(lsst.geom.Point2D(-1.0, -1.0),
119 lsst.geom.Point2D(1.0, 1.0))
120 poly2 = afwGeom.Polygon(box)
121 self.assertEqual(poly1, poly2)
123 def testBBox(self):
124 """Test Polygon.getBBox"""
125 size = 3.0
126 poly = self.square(size=size)
127 box = poly.getBBox()
128 self.assertEqual(box.getMinX(), -size)
129 self.assertEqual(box.getMinY(), -size)
130 self.assertEqual(box.getMaxX(), size)
131 self.assertEqual(box.getMaxY(), size)
133 def testCenter(self):
134 """Test Polygon.calculateCenter"""
135 for num in range(3, 30):
136 poly = self.polygon(num)
137 center = poly.calculateCenter()
138 self.assertAlmostEqual(center.getX(), self.x0)
139 self.assertAlmostEqual(center.getY(), self.y0)
141 def testContains(self):
142 """Test Polygon.contains"""
143 radius = 1.0
144 for num in range(3, 30):
145 poly = self.polygon(num, radius=radius)
146 self.assertTrue(poly.contains(lsst.geom.Point2D(self.x0, self.y0)))
147 self.assertFalse(poly.contains(
148 lsst.geom.Point2D(self.x0 + radius, self.y0 + radius)))
149 # validate checking an array
150 poly = self.polygon(50, radius=10)
151 # check for inclusion
152 yind, xind = np.mgrid[-4:5, -4:5]
153 self.assertTrue(np.all(poly.contains(xind, yind)))
154 # check for exclusion
155 yind, xind = np.mgrid[30:35, 30:35]
156 self.assertTrue(np.all(~poly.contains(xind, yind)))
158 # check various box types
159 box = lsst.geom.Box2I(lsst.geom.Point2I(-1, -1), lsst.geom.Point2I(1, 1))
160 self.assertTrue(all(poly.contains(box.getCorners())))
162 box = lsst.geom.Box2I(lsst.geom.Point2I(-100, -100), lsst.geom.Point2I(100, 100))
163 self.assertFalse(all(poly.contains(box.getCorners())))
165 box = lsst.geom.Box2D(lsst.geom.Point2D(-1, -1), lsst.geom.Point2D(1, 1))
166 self.assertTrue(all(poly.contains(box.getCorners())))
168 box = lsst.geom.Box2D(lsst.geom.Point2D(-100, -100), lsst.geom.Point2D(100, 100))
169 self.assertFalse(all(poly.contains(box.getCorners())))
171 def testOverlaps(self):
172 """Test Polygon.overlaps"""
173 radius = 1.0
174 for num in range(3, 30):
175 poly1 = self.polygon(num, radius=radius)
176 poly2 = self.polygon(num, radius=radius, x0=radius, y0=radius)
177 poly3 = self.polygon(num, radius=2*radius)
178 poly4 = self.polygon(num, radius=radius, x0=3*radius, y0=3*radius)
179 self.assertTrue(poly1.overlaps(poly2))
180 self.assertTrue(poly2.overlaps(poly1))
181 self.assertTrue(poly1.overlaps(poly3))
182 self.assertTrue(poly3.overlaps(poly1))
183 self.assertFalse(poly1.overlaps(poly4))
184 self.assertFalse(poly4.overlaps(poly1))
186 def testIntersection(self):
187 """Test Polygon.intersection"""
188 poly1 = self.square(2.0, -1.0, -1.0)
189 poly2 = self.square(2.0, +1.0, +1.0)
190 poly3 = self.square(1.0, 0.0, 0.0)
191 poly4 = self.square(1.0, +5.0, +5.0)
193 # intersectionSingle: assumes there's a single intersection (convex
194 # polygons)
195 self.assertEqual(poly1.intersectionSingle(poly2), poly3)
196 self.assertEqual(poly2.intersectionSingle(poly1), poly3)
197 self.assertRaises(afwGeom.SinglePolygonException,
198 poly1.intersectionSingle, poly4)
199 self.assertRaises(afwGeom.SinglePolygonException,
200 poly4.intersectionSingle, poly1)
202 # intersection: no assumptions
203 polyList1 = poly1.intersection(poly2)
204 polyList2 = poly2.intersection(poly1)
205 self.assertEqual(polyList1, polyList2)
206 self.assertEqual(len(polyList1), 1)
207 self.assertEqual(polyList1[0], poly3)
208 polyList3 = poly1.intersection(poly4)
209 polyList4 = poly4.intersection(poly1)
210 self.assertEqual(polyList3, polyList4)
211 self.assertEqual(len(polyList3), 0)
213 def testUnion(self):
214 """Test Polygon.union"""
215 poly1 = self.square(2.0, -1.0, -1.0)
216 poly2 = self.square(2.0, +1.0, +1.0)
217 poly3 = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
218 ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +1.0), (-1.0, +3.0),
219 (+3.0, +3.0), (+3.0, -1.0), (+1.0, -1.0), (+1.0, -3.0))])
220 poly4 = self.square(1.0, +5.0, +5.0)
222 # unionSingle: assumes there's a single union (intersecting polygons)
223 self.assertEqual(poly1.unionSingle(poly2), poly3)
224 self.assertEqual(poly2.unionSingle(poly1), poly3)
225 self.assertRaises(afwGeom.SinglePolygonException, poly1.unionSingle, poly4)
226 self.assertRaises(afwGeom.SinglePolygonException, poly4.unionSingle, poly1)
228 # union: no assumptions
229 polyList1 = poly1.union(poly2)
230 polyList2 = poly2.union(poly1)
231 self.assertEqual(polyList1, polyList2)
232 self.assertEqual(len(polyList1), 1)
233 self.assertEqual(polyList1[0], poly3)
234 polyList3 = poly1.union(poly4)
235 polyList4 = poly4.union(poly1)
236 self.assertEqual(len(polyList3), 2)
237 self.assertEqual(len(polyList3), len(polyList4))
238 self.assertTrue((polyList3[0] == polyList4[0] and polyList3[1] == polyList4[1])
239 or (polyList3[0] == polyList4[1] and polyList3[1] == polyList4[0]))
240 self.assertTrue((polyList3[0] == poly1 and polyList3[1] == poly4)
241 or (polyList3[0] == poly4 and polyList3[1] == poly1))
243 def testSymDifference(self):
244 """Test Polygon.symDifference"""
245 poly1 = self.square(2.0, -1.0, -1.0)
246 poly2 = self.square(2.0, +1.0, +1.0)
248 poly3 = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
249 ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +1.0),
250 (-1.0, -1.0), (+1.0, -1.0), (1.0, -3.0))])
251 poly4 = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
252 ((-1.0, +1.0), (-1.0, +3.0), (+3.0, +3.0),
253 (+3.0, -1.0), (+1.0, -1.0), (1.0, +1.0))])
255 diff1 = poly1.symDifference(poly2)
256 diff2 = poly2.symDifference(poly1)
258 self.assertEqual(len(diff1), 2)
259 self.assertEqual(len(diff2), 2)
260 self.assertTrue((diff1[0] == diff2[0] and diff1[1] == diff2[1])
261 or (diff1[1] == diff2[0] and diff1[0] == diff2[1]))
262 self.assertTrue((diff1[0] == poly3 and diff1[1] == poly4)
263 or (diff1[1] == poly3 and diff1[0] == poly4))
265 def testConvexHull(self):
266 """Test Polygon.convexHull"""
267 poly1 = self.square(2.0, -1.0, -1.0)
268 poly2 = self.square(2.0, +1.0, +1.0)
269 poly = poly1.unionSingle(poly2)
270 expected = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
271 ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +3.0),
272 (+3.0, +3.0), (+3.0, -1.0), (+1.0, -3.0))])
273 self.assertEqual(poly.convexHull(), expected)
275 def testImage(self):
276 """Test Polygon.createImage"""
277 if display:
278 import lsst.afw.display as afwDisplay
280 for i, num in enumerate(range(3, 30)):
281 # We need to test at different centers, because the
282 # boost::intersection algorithm depends sensitively on
283 # input floating-point values, and you will get different
284 # aliasing depending on the central pixel value when
285 # generating the polygon from numpy values.
286 for cent in [-75, -50, -25, 0, 25, 50, 75]:
287 poly = self.polygon(num, 25, cent, cent)
288 box = lsst.geom.Box2I(lsst.geom.Point2I(cent - 60, cent - 60),
289 lsst.geom.Extent2I(115, 115))
290 image = poly.createImage(box)
291 if display:
292 disp = afwDisplay.Display(frame=i + 1)
293 disp.mtv(image, title=f"Polygon nside={num}")
294 for p1, p2 in poly.getEdges():
295 disp.line((p1, p2))
296 # Some computations of the image area have such large aliasing
297 # in boost::intersection that the precision required here is 0.025.
298 self.assertFloatsAlmostEqual(
299 image.getArray().sum(), poly.calculateArea(), rtol=0.025)
301 def testTransform(self):
302 """Test constructor for Polygon involving transforms"""
303 box = lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
304 lsst.geom.Point2D(123.4, 567.8))
305 poly1 = afwGeom.Polygon(box)
306 scale = 1.5
307 shift = lsst.geom.Extent2D(3.0, 4.0)
308 affineTransform = lsst.geom.AffineTransform.makeTranslation(shift) * \
309 lsst.geom.AffineTransform.makeScaling(scale)
310 transform22 = afwGeom.makeTransform(affineTransform)
311 transformedVertices = transform22.applyForward(box.getCorners())
312 expect = afwGeom.Polygon(transformedVertices)
313 poly1 = afwGeom.Polygon(box, affineTransform)
314 poly2 = afwGeom.Polygon(box, transform22)
315 self.assertEqual(poly1, expect)
316 self.assertEqual(poly2, expect)
318 def testIteration(self):
319 """Test iteration over polygon"""
320 for num in range(3, 30):
321 poly = self.polygon(num)
322 self.assertEqual(len(poly), num)
323 points1 = [p for p in poly]
324 points2 = poly.getVertices()
325 self.assertEqual(len(points1), num + 1)
326 self.assertEqual(len(points2), num + 1)
327 self.assertEqual(points2[0], points2[-1]) # Closed representation
328 for p1, p2 in zip(points1, points2):
329 self.assertEqual(p1, p2)
330 for i, p1 in enumerate(points1):
331 self.assertEqual(poly[i], p1)
333 def testSubSample(self):
334 """Test Polygon.subSample"""
335 if display:
336 import matplotlib.pyplot as plt
337 for num in range(3, 30):
338 poly = self.polygon(num)
339 sub = poly.subSample(2)
341 if display:
342 axes = poly.plot(c='b')
343 axes.set_aspect("equal")
344 axes.set_title(f"Polygon nside={num}")
345 sub.plot(axes, c='r')
346 if not doPause:
347 try:
348 plt.pause(2)
349 plt.close()
350 except Exception:
351 print(f"{str(self)}: plt.pause() failed. Please close plots when done.")
352 plt.show()
353 else:
354 print(f"{str(self)}: Please close plots when done.")
355 plt.show()
357 self.assertEqual(len(sub), 2*num)
358 self.assertAlmostEqual(sub.calculateArea(), poly.calculateArea())
359 self.assertAlmostEqual(
360 sub.calculatePerimeter(), poly.calculatePerimeter())
361 polyCenter = poly.calculateCenter()
362 subCenter = sub.calculateCenter()
363 self.assertAlmostEqual(polyCenter[0], subCenter[0])
364 self.assertAlmostEqual(polyCenter[1], subCenter[1])
365 for i in range(num):
366 self.assertEqual(sub[2*i], poly[i])
368 sub = poly.subSample(0.1)
369 self.assertAlmostEqual(sub.calculateArea(), poly.calculateArea())
370 self.assertAlmostEqual(
371 sub.calculatePerimeter(), poly.calculatePerimeter())
372 polyCenter = poly.calculateCenter()
373 subCenter = sub.calculateCenter()
374 self.assertAlmostEqual(polyCenter[0], subCenter[0])
375 self.assertAlmostEqual(polyCenter[1], subCenter[1])
377 def testTransform2(self):
378 if display:
379 import matplotlib.pyplot as plt
380 scale = 2.0
381 shift = lsst.geom.Extent2D(3.0, 4.0)
382 affineTransform = lsst.geom.AffineTransform.makeTranslation(shift) * \
383 lsst.geom.AffineTransform.makeScaling(scale)
384 transform22 = afwGeom.makeTransform(affineTransform)
385 for num in range(3, 30):
386 small = self.polygon(num, 1.0, 0.0, 0.0)
387 large1 = small.transform(affineTransform)
388 large2 = small.transform(transform22)
389 expect = self.polygon(num, scale, shift[0], shift[1])
390 self.assertEqual(large1, expect)
391 self.assertEqual(large2, expect)
393 if display:
394 axes = small.plot(c='k')
395 axes.set_aspect("equal")
396 axes.set_title(f"AffineTransform: Polygon nside={num}")
397 large1.plot(axes, c='b')
398 if not doPause:
399 try:
400 plt.pause(2)
401 plt.close()
402 except Exception:
403 print(f"{str(self)}: plt.pause() failed. Please close plots when done.")
404 plt.show()
405 else:
406 print(f"{str(self)}: Please close plots when done.")
407 plt.show()
409 def testReadWrite(self):
410 """Test that polygons can be read and written to fits files"""
411 for num in range(3, 30):
412 poly = self.polygon(num)
413 with lsst.utils.tests.getTempFilePath(".fits") as filename:
414 poly.writeFits(filename)
415 poly2 = afwGeom.Polygon.readFits(filename)
416 self.assertEqual(poly, poly2)
419class TestMemory(lsst.utils.tests.MemoryTestCase):
420 pass
423def setup_module(module):
424 lsst.utils.tests.init()
427if __name__ == "__main__": 427 ↛ 428line 427 didn't jump to line 428, because the condition on line 427 was never true
428 lsst.utils.tests.init()
429 unittest.main()