Coverage for tests/test_polygon.py: 12%

268 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-06-02 03:42 -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/>. 

21 

22import pickle 

23import unittest 

24 

25import numpy as np 

26 

27import lsst.utils.tests 

28import lsst.geom 

29import lsst.afw.geom as afwGeom 

30import lsst.afw.image # noqa: F401 required by Polygon.createImage 

31 

32display = False 

33doPause = False # If False, autoscan plots with 2 sec delay. If True, impose manual closing of plots. 

34 

35 

36def circle(radius, num, x0=0.0, y0=0.0): 

37 """Generate points on a circle 

38 

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() 

48 

49 

50class PolygonTest(lsst.utils.tests.TestCase): 

51 

52 def setUp(self): 

53 self.x0 = 0.0 

54 self.y0 = 0.0 

55 

56 def polygon(self, num, radius=1.0, x0=None, y0=None): 

57 """Generate a polygon 

58 

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)]) 

70 

71 def square(self, size=1.0, x0=0, y0=0): 

72 """Generate a square 

73 

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))]) 

79 

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) 

95 

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) 

109 

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) 

114 

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) 

122 

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) 

132 

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) 

140 

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 

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)) 

164 

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) 

171 

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) 

180 

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) 

191 

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) 

200 

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) 

206 

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)) 

221 

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) 

226 

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))]) 

233 

234 diff1 = poly1.symDifference(poly2) 

235 diff2 = poly2.symDifference(poly1) 

236 

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)) 

243 

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) 

253 

254 def testImage(self): 

255 """Test Polygon.createImage""" 

256 if display: 

257 import lsst.afw.display as afwDisplay 

258 

259 for i, num in enumerate(range(3, 30)): 

260 # We need to test at different centers, because the 

261 # boost::intersection algorithm depends sensitively on 

262 # input floating-point values, and you will get different 

263 # aliasing depending on the central pixel value when 

264 # generating the polygon from numpy values. 

265 for cent in [-75, -50, -25, 0, 25, 50, 75]: 

266 poly = self.polygon(num, 25, cent, cent) 

267 box = lsst.geom.Box2I(lsst.geom.Point2I(cent - 60, cent - 60), 

268 lsst.geom.Extent2I(115, 115)) 

269 image = poly.createImage(box) 

270 if display: 

271 disp = afwDisplay.Display(frame=i + 1) 

272 disp.mtv(image, title=f"Polygon nside={num}") 

273 for p1, p2 in poly.getEdges(): 

274 disp.line((p1, p2)) 

275 # Some computations of the image area have such large aliasing 

276 # in boost::intersection that the precision required here is 0.025. 

277 self.assertFloatsAlmostEqual( 

278 image.getArray().sum(), poly.calculateArea(), rtol=0.025) 

279 

280 def testTransform(self): 

281 """Test constructor for Polygon involving transforms""" 

282 box = lsst.geom.Box2D(lsst.geom.Point2D(0.0, 0.0), 

283 lsst.geom.Point2D(123.4, 567.8)) 

284 poly1 = afwGeom.Polygon(box) 

285 scale = 1.5 

286 shift = lsst.geom.Extent2D(3.0, 4.0) 

287 affineTransform = lsst.geom.AffineTransform.makeTranslation(shift) * \ 

288 lsst.geom.AffineTransform.makeScaling(scale) 

289 transform22 = afwGeom.makeTransform(affineTransform) 

290 transformedVertices = transform22.applyForward(box.getCorners()) 

291 expect = afwGeom.Polygon(transformedVertices) 

292 poly1 = afwGeom.Polygon(box, affineTransform) 

293 poly2 = afwGeom.Polygon(box, transform22) 

294 self.assertEqual(poly1, expect) 

295 self.assertEqual(poly2, expect) 

296 

297 def testIteration(self): 

298 """Test iteration over polygon""" 

299 for num in range(3, 30): 

300 poly = self.polygon(num) 

301 self.assertEqual(len(poly), num) 

302 points1 = [p for p in poly] 

303 points2 = poly.getVertices() 

304 self.assertEqual(len(points1), num + 1) 

305 self.assertEqual(len(points2), num + 1) 

306 self.assertEqual(points2[0], points2[-1]) # Closed representation 

307 for p1, p2 in zip(points1, points2): 

308 self.assertEqual(p1, p2) 

309 for i, p1 in enumerate(points1): 

310 self.assertEqual(poly[i], p1) 

311 

312 def testSubSample(self): 

313 """Test Polygon.subSample""" 

314 if display: 

315 import matplotlib.pyplot as plt 

316 for num in range(3, 30): 

317 poly = self.polygon(num) 

318 sub = poly.subSample(2) 

319 

320 if display: 

321 axes = poly.plot(c='b') 

322 axes.set_aspect("equal") 

323 axes.set_title(f"Polygon nside={num}") 

324 sub.plot(axes, c='r') 

325 if not doPause: 

326 try: 

327 plt.pause(2) 

328 plt.close() 

329 except Exception: 

330 print(f"{str(self)}: plt.pause() failed. Please close plots when done.") 

331 plt.show() 

332 else: 

333 print(f"{str(self)}: Please close plots when done.") 

334 plt.show() 

335 

336 self.assertEqual(len(sub), 2*num) 

337 self.assertAlmostEqual(sub.calculateArea(), poly.calculateArea()) 

338 self.assertAlmostEqual( 

339 sub.calculatePerimeter(), poly.calculatePerimeter()) 

340 polyCenter = poly.calculateCenter() 

341 subCenter = sub.calculateCenter() 

342 self.assertAlmostEqual(polyCenter[0], subCenter[0]) 

343 self.assertAlmostEqual(polyCenter[1], subCenter[1]) 

344 for i in range(num): 

345 self.assertEqual(sub[2*i], poly[i]) 

346 

347 sub = poly.subSample(0.1) 

348 self.assertAlmostEqual(sub.calculateArea(), poly.calculateArea()) 

349 self.assertAlmostEqual( 

350 sub.calculatePerimeter(), poly.calculatePerimeter()) 

351 polyCenter = poly.calculateCenter() 

352 subCenter = sub.calculateCenter() 

353 self.assertAlmostEqual(polyCenter[0], subCenter[0]) 

354 self.assertAlmostEqual(polyCenter[1], subCenter[1]) 

355 

356 def testTransform2(self): 

357 if display: 

358 import matplotlib.pyplot as plt 

359 scale = 2.0 

360 shift = lsst.geom.Extent2D(3.0, 4.0) 

361 affineTransform = lsst.geom.AffineTransform.makeTranslation(shift) * \ 

362 lsst.geom.AffineTransform.makeScaling(scale) 

363 transform22 = afwGeom.makeTransform(affineTransform) 

364 for num in range(3, 30): 

365 small = self.polygon(num, 1.0, 0.0, 0.0) 

366 large1 = small.transform(affineTransform) 

367 large2 = small.transform(transform22) 

368 expect = self.polygon(num, scale, shift[0], shift[1]) 

369 self.assertEqual(large1, expect) 

370 self.assertEqual(large2, expect) 

371 

372 if display: 

373 axes = small.plot(c='k') 

374 axes.set_aspect("equal") 

375 axes.set_title(f"AffineTransform: Polygon nside={num}") 

376 large1.plot(axes, c='b') 

377 if not doPause: 

378 try: 

379 plt.pause(2) 

380 plt.close() 

381 except Exception: 

382 print(f"{str(self)}: plt.pause() failed. Please close plots when done.") 

383 plt.show() 

384 else: 

385 print(f"{str(self)}: Please close plots when done.") 

386 plt.show() 

387 

388 def testReadWrite(self): 

389 """Test that polygons can be read and written to fits files""" 

390 for num in range(3, 30): 

391 poly = self.polygon(num) 

392 with lsst.utils.tests.getTempFilePath(".fits") as filename: 

393 poly.writeFits(filename) 

394 poly2 = afwGeom.Polygon.readFits(filename) 

395 self.assertEqual(poly, poly2) 

396 

397 

398class TestMemory(lsst.utils.tests.MemoryTestCase): 

399 pass 

400 

401 

402def setup_module(module): 

403 lsst.utils.tests.init() 

404 

405 

406if __name__ == "__main__": 406 ↛ 407line 406 didn't jump to line 407, because the condition on line 406 was never true

407 lsst.utils.tests.init() 

408 unittest.main()