Coverage for tests/test_polygon.py: 12%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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)))
150 def testOverlaps(self):
151 """Test Polygon.overlaps"""
152 radius = 1.0
153 for num in range(3, 30):
154 poly1 = self.polygon(num, radius=radius)
155 poly2 = self.polygon(num, radius=radius, x0=radius, y0=radius)
156 poly3 = self.polygon(num, radius=2*radius)
157 poly4 = self.polygon(num, radius=radius, x0=3*radius, y0=3*radius)
158 self.assertTrue(poly1.overlaps(poly2))
159 self.assertTrue(poly2.overlaps(poly1))
160 self.assertTrue(poly1.overlaps(poly3))
161 self.assertTrue(poly3.overlaps(poly1))
162 self.assertFalse(poly1.overlaps(poly4))
163 self.assertFalse(poly4.overlaps(poly1))
165 def testIntersection(self):
166 """Test Polygon.intersection"""
167 poly1 = self.square(2.0, -1.0, -1.0)
168 poly2 = self.square(2.0, +1.0, +1.0)
169 poly3 = self.square(1.0, 0.0, 0.0)
170 poly4 = self.square(1.0, +5.0, +5.0)
172 # intersectionSingle: assumes there's a single intersection (convex
173 # polygons)
174 self.assertEqual(poly1.intersectionSingle(poly2), poly3)
175 self.assertEqual(poly2.intersectionSingle(poly1), poly3)
176 self.assertRaises(afwGeom.SinglePolygonException,
177 poly1.intersectionSingle, poly4)
178 self.assertRaises(afwGeom.SinglePolygonException,
179 poly4.intersectionSingle, poly1)
181 # intersection: no assumptions
182 polyList1 = poly1.intersection(poly2)
183 polyList2 = poly2.intersection(poly1)
184 self.assertEqual(polyList1, polyList2)
185 self.assertEqual(len(polyList1), 1)
186 self.assertEqual(polyList1[0], poly3)
187 polyList3 = poly1.intersection(poly4)
188 polyList4 = poly4.intersection(poly1)
189 self.assertEqual(polyList3, polyList4)
190 self.assertEqual(len(polyList3), 0)
192 def testUnion(self):
193 """Test Polygon.union"""
194 poly1 = self.square(2.0, -1.0, -1.0)
195 poly2 = self.square(2.0, +1.0, +1.0)
196 poly3 = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
197 ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +1.0), (-1.0, +3.0),
198 (+3.0, +3.0), (+3.0, -1.0), (+1.0, -1.0), (+1.0, -3.0))])
199 poly4 = self.square(1.0, +5.0, +5.0)
201 # unionSingle: assumes there's a single union (intersecting polygons)
202 self.assertEqual(poly1.unionSingle(poly2), poly3)
203 self.assertEqual(poly2.unionSingle(poly1), poly3)
204 self.assertRaises(afwGeom.SinglePolygonException, poly1.unionSingle, poly4)
205 self.assertRaises(afwGeom.SinglePolygonException, poly4.unionSingle, poly1)
207 # union: no assumptions
208 polyList1 = poly1.union(poly2)
209 polyList2 = poly2.union(poly1)
210 self.assertEqual(polyList1, polyList2)
211 self.assertEqual(len(polyList1), 1)
212 self.assertEqual(polyList1[0], poly3)
213 polyList3 = poly1.union(poly4)
214 polyList4 = poly4.union(poly1)
215 self.assertEqual(len(polyList3), 2)
216 self.assertEqual(len(polyList3), len(polyList4))
217 self.assertTrue((polyList3[0] == polyList4[0] and polyList3[1] == polyList4[1])
218 or (polyList3[0] == polyList4[1] and polyList3[1] == polyList4[0]))
219 self.assertTrue((polyList3[0] == poly1 and polyList3[1] == poly4)
220 or (polyList3[0] == poly4 and polyList3[1] == poly1))
222 def testSymDifference(self):
223 """Test Polygon.symDifference"""
224 poly1 = self.square(2.0, -1.0, -1.0)
225 poly2 = self.square(2.0, +1.0, +1.0)
227 poly3 = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
228 ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +1.0),
229 (-1.0, -1.0), (+1.0, -1.0), (1.0, -3.0))])
230 poly4 = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
231 ((-1.0, +1.0), (-1.0, +3.0), (+3.0, +3.0),
232 (+3.0, -1.0), (+1.0, -1.0), (1.0, +1.0))])
234 diff1 = poly1.symDifference(poly2)
235 diff2 = poly2.symDifference(poly1)
237 self.assertEqual(len(diff1), 2)
238 self.assertEqual(len(diff2), 2)
239 self.assertTrue((diff1[0] == diff2[0] and diff1[1] == diff2[1])
240 or (diff1[1] == diff2[0] and diff1[0] == diff2[1]))
241 self.assertTrue((diff1[0] == poly3 and diff1[1] == poly4)
242 or (diff1[1] == poly3 and diff1[0] == poly4))
244 def testConvexHull(self):
245 """Test Polygon.convexHull"""
246 poly1 = self.square(2.0, -1.0, -1.0)
247 poly2 = self.square(2.0, +1.0, +1.0)
248 poly = poly1.unionSingle(poly2)
249 expected = afwGeom.Polygon([lsst.geom.Point2D(x, y) for x, y in
250 ((-3.0, -3.0), (-3.0, +1.0), (-1.0, +3.0),
251 (+3.0, +3.0), (+3.0, -1.0), (+1.0, -3.0))])
252 self.assertEqual(poly.convexHull(), expected)
254 def testImage(self):
255 """Test Polygon.createImage"""
256 if display:
257 import lsst.afw.display as afwDisplay
258 for i, num in enumerate(range(3, 30)):
259 poly = self.polygon(num, 25, 75, 75)
260 box = lsst.geom.Box2I(lsst.geom.Point2I(15, 15),
261 lsst.geom.Extent2I(115, 115))
262 image = poly.createImage(box)
263 if display:
264 disp = afwDisplay.Display(frame=i + 1)
265 disp.mtv(image, title=f"Polygon nside={num}")
266 for p1, p2 in poly.getEdges():
267 disp.line((p1, p2))
268 self.assertAlmostEqual(
269 image.getArray().sum()/poly.calculateArea(), 1.0, 6)
271 def testTransform(self):
272 """Test constructor for Polygon involving transforms"""
273 box = lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0),
274 lsst.geom.Point2D(123.4, 567.8))
275 poly1 = afwGeom.Polygon(box)
276 scale = 1.5
277 shift = lsst.geom.Extent2D(3.0, 4.0)
278 affineTransform = lsst.geom.AffineTransform.makeTranslation(shift) * \
279 lsst.geom.AffineTransform.makeScaling(scale)
280 transform22 = afwGeom.makeTransform(affineTransform)
281 transformedVertices = transform22.applyForward(box.getCorners())
282 expect = afwGeom.Polygon(transformedVertices)
283 poly1 = afwGeom.Polygon(box, affineTransform)
284 poly2 = afwGeom.Polygon(box, transform22)
285 self.assertEqual(poly1, expect)
286 self.assertEqual(poly2, expect)
288 def testIteration(self):
289 """Test iteration over polygon"""
290 for num in range(3, 30):
291 poly = self.polygon(num)
292 self.assertEqual(len(poly), num)
293 points1 = [p for p in poly]
294 points2 = poly.getVertices()
295 self.assertEqual(len(points1), num + 1)
296 self.assertEqual(len(points2), num + 1)
297 self.assertEqual(points2[0], points2[-1]) # Closed representation
298 for p1, p2 in zip(points1, points2):
299 self.assertEqual(p1, p2)
300 for i, p1 in enumerate(points1):
301 self.assertEqual(poly[i], p1)
303 def testSubSample(self):
304 """Test Polygon.subSample"""
305 if display:
306 import matplotlib.pyplot as plt
307 for num in range(3, 30):
308 poly = self.polygon(num)
309 sub = poly.subSample(2)
311 if display:
312 axes = poly.plot(c='b')
313 axes.set_aspect("equal")
314 axes.set_title(f"Polygon nside={num}")
315 sub.plot(axes, c='r')
316 if not doPause:
317 try:
318 plt.pause(2)
319 plt.close()
320 except Exception:
321 print(f"{str(self)}: plt.pause() failed. Please close plots when done.")
322 plt.show()
323 else:
324 print(f"{str(self)}: Please close plots when done.")
325 plt.show()
327 self.assertEqual(len(sub), 2*num)
328 self.assertAlmostEqual(sub.calculateArea(), poly.calculateArea())
329 self.assertAlmostEqual(
330 sub.calculatePerimeter(), poly.calculatePerimeter())
331 polyCenter = poly.calculateCenter()
332 subCenter = sub.calculateCenter()
333 self.assertAlmostEqual(polyCenter[0], subCenter[0])
334 self.assertAlmostEqual(polyCenter[1], subCenter[1])
335 for i in range(num):
336 self.assertEqual(sub[2*i], poly[i])
338 sub = poly.subSample(0.1)
339 self.assertAlmostEqual(sub.calculateArea(), poly.calculateArea())
340 self.assertAlmostEqual(
341 sub.calculatePerimeter(), poly.calculatePerimeter())
342 polyCenter = poly.calculateCenter()
343 subCenter = sub.calculateCenter()
344 self.assertAlmostEqual(polyCenter[0], subCenter[0])
345 self.assertAlmostEqual(polyCenter[1], subCenter[1])
347 def testTransform2(self):
348 if display:
349 import matplotlib.pyplot as plt
350 scale = 2.0
351 shift = lsst.geom.Extent2D(3.0, 4.0)
352 affineTransform = lsst.geom.AffineTransform.makeTranslation(shift) * \
353 lsst.geom.AffineTransform.makeScaling(scale)
354 transform22 = afwGeom.makeTransform(affineTransform)
355 for num in range(3, 30):
356 small = self.polygon(num, 1.0, 0.0, 0.0)
357 large1 = small.transform(affineTransform)
358 large2 = small.transform(transform22)
359 expect = self.polygon(num, scale, shift[0], shift[1])
360 self.assertEqual(large1, expect)
361 self.assertEqual(large2, expect)
363 if display:
364 axes = small.plot(c='k')
365 axes.set_aspect("equal")
366 axes.set_title(f"AffineTransform: Polygon nside={num}")
367 large1.plot(axes, c='b')
368 if not doPause:
369 try:
370 plt.pause(2)
371 plt.close()
372 except Exception:
373 print(f"{str(self)}: plt.pause() failed. Please close plots when done.")
374 plt.show()
375 else:
376 print(f"{str(self)}: Please close plots when done.")
377 plt.show()
379 def testReadWrite(self):
380 """Test that polygons can be read and written to fits files"""
381 for num in range(3, 30):
382 poly = self.polygon(num)
383 with lsst.utils.tests.getTempFilePath(".fits") as filename:
384 poly.writeFits(filename)
385 poly2 = afwGeom.Polygon.readFits(filename)
386 self.assertEqual(poly, poly2)
389class TestMemory(lsst.utils.tests.MemoryTestCase):
390 pass
393def setup_module(module):
394 lsst.utils.tests.init()
397if __name__ == "__main__": 397 ↛ 398line 397 didn't jump to line 398, because the condition on line 397 was never true
398 lsst.utils.tests.init()
399 unittest.main()