From cf280f1a667eb586b74498476f02a2f18c1a2004 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Sat, 21 Dec 2024 00:18:37 +0100 Subject: [PATCH 1/2] reduce python interactions in cython part of code --- .../triangulate.pyx | 102 +++++++++++++++--- 1 file changed, 89 insertions(+), 13 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index d804520..45e6640 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -388,6 +388,46 @@ def triangulate_path_edge_py(path: Sequence[Sequence[float]], closed: bool=False triangles, ) +def triangulate_path_edge_numpy(cnp.ndarray[cnp.float32_t, ndim=2] path, bool closed=False, float limit=3.0, bool bevel=False) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """ Triangulate path""" + cdef vector[Point] path_vector + cdef PathTriangulation result + cdef Point p1, p2 + cdef cnp.ndarray[cnp.uint32_t, ndim=2] triangles + cdef cnp.ndarray[cnp.float32_t, ndim=2] offsets, centers + cdef size_t i, len_path + + len_path = path.shape[0] + + path_vector.reserve(len_path) + for i in range(len_path): + path_vector.push_back(Point(path[i, 0], path[i, 1])) + with cython.nogil: + result = triangulate_path_edge(path_vector, closed, limit, bevel) + + triangles = np.empty((result.triangles.size(), 3), dtype=np.uint32) + centers = np.empty((result.centers.size(), 2), dtype=np.float32) + offsets = np.empty((result.offsets.size(), 2), dtype=np.float32) + + for i in range(result.triangles.size()): + triangles[i, 0] = result.triangles[i].x + triangles[i, 1] = result.triangles[i].y + triangles[i, 2] = result.triangles[i].z + + for i in range(result.centers.size()): + centers[i, 0] = result.centers[i].x + centers[i, 1] = result.centers[i].y + + for i in range(result.offsets.size()): + offsets[i, 0] = result.offsets[i].x + offsets[i, 1] = result.offsets[i].y + + return ( + centers, + offsets, + triangles, + ) + def triangulate_polygon_with_edge_numpy_li(polygon_li: list[np.ndarray]) -> tuple[tuple[np.ndarray, np.ndarray], tuple[np.ndarray, np.ndarray, np.ndarray]]: """ Triangulate polygon""" @@ -396,18 +436,21 @@ def triangulate_polygon_with_edge_numpy_li(polygon_li: list[np.ndarray]) -> tupl cdef Point p1, p2 cdef pair[vector[Triangle], vector[Point]] triangulation_result cdef vector[PathTriangulation] edge_result - cdef cnp.ndarray[cnp.uint32_t, ndim=2] triangles + cdef cnp.ndarray[cnp.uint32_t, ndim=2] triangles, edge_triangles + + cdef cnp.ndarray[cnp.float32_t, ndim=2] points, edge_offsets, edges_centers, polygon + cdef size_t i, j, len_path, edge_triangle_count, edge_center_count, edge_triangle_index, edge_center_index polygon_vector_list.reserve(len(polygon_li)) - for polygon in polygon_li: - polygon_vector.clear() + for i in range(len(polygon_li)): + polygon = polygon_li[i] polygon_vector.reserve(polygon.shape[0]) polygon_vector.push_back(Point(polygon[0, 0], polygon[0, 1])) - for point in polygon[1:]: + for j in range(1, polygon.shape[0]): p1 = polygon_vector.back() - p2 = Point(point[0], point[1]) + p2 = Point(polygon[j, 0], polygon[j, 1]) if p1 != p2: # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) @@ -420,18 +463,51 @@ def triangulate_polygon_with_edge_numpy_li(polygon_li: list[np.ndarray]) -> tupl with cython.nogil: triangulation_result = triangulate_polygon_face(polygon_vector_list) - if triangulation_result.first.size() == 0: - triangles = np.zeros((0, 3), dtype=np.uint32) - else: - triangles = np.array([(triangle.x, triangle.y, triangle.z) for triangle in triangulation_result.first], dtype=np.uint32) + + triangles = np.empty((triangulation_result.first.size(), 3), dtype=np.uint32) + for i in range(triangulation_result.first.size()): + triangles[i, 0] = triangulation_result.first[i].x + triangles[i, 1] = triangulation_result.first[i].y + triangles[i, 2] = triangulation_result.first[i].z + + points =np.empty((triangulation_result.second.size(), 2), dtype=np.float32) + for i in range(triangulation_result.second.size()): + points[i, 0] = triangulation_result.second[i].x + points[i, 1] = triangulation_result.second[i].y + + edge_triangle_count = 0 + edge_center_count = 0 + for i in range(edge_result.size()): + edge_triangle_count += edge_result[i].triangles.size() + edge_center_count += edge_result[i].centers.size() + + edge_triangles = np.empty((edge_triangle_count, 3), dtype=np.uint32) + edge_offsets = np.empty((edge_center_count, 2), dtype=np.float32) + edges_centers = np.empty((edge_center_count, 2), dtype=np.float32) + + edge_triangle_index = 0 + edge_center_index = 0 + for i in range(edge_result.size()): + for j in range(edge_result[i].triangles.size()): + edge_triangles[edge_triangle_index, 0] = edge_result[i].triangles[j].x + edge_triangles[edge_triangle_index, 1] = edge_result[i].triangles[j].y + edge_triangles[edge_triangle_index, 2] = edge_result[i].triangles[j].z + edge_triangle_index += 1 + + for j in range(edge_result[i].centers.size()): + edges_centers[edge_center_index, 0] = edge_result[i].centers[j].x + edges_centers[edge_center_index, 1] = edge_result[i].centers[j].y + edge_offsets[edge_center_index, 0] = edge_result[i].offsets[j].x + edge_offsets[edge_center_index, 1] = edge_result[i].offsets[j].y + edge_center_index += 1 return (( triangles, - np.array([(point.x, point.y) for point in triangulation_result.second], dtype=np.float32) + points, ), ( - np.array([(point.x, point.y) for res in edge_result for point in res.centers], dtype=np.float32), - np.array([(offset.x, offset.y) for res in edge_result for offset in res.offsets], dtype=np.float32), - np.array([(triangle.x, triangle.y, triangle.z) for res in edge_result for triangle in res.triangles], dtype=np.uint32), + edges_centers, + edge_offsets, + edge_triangles, ) ) From 51dae011f987a9b9869efcd1bd80abe9020f0351 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Sat, 21 Dec 2024 00:25:05 +0100 Subject: [PATCH 2/2] improve tests --- src/tests/test_triangulate.py | 165 +++++++++++++++++----------------- 1 file changed, 85 insertions(+), 80 deletions(-) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index bc092bc..f436ecb 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -12,6 +12,7 @@ segment_left_to_right_comparator, triangle_convex_polygon, triangulate_monotone_polygon_py, + triangulate_path_edge_numpy, triangulate_path_edge_py, triangulate_polygon_numpy, triangulate_polygon_numpy_li, @@ -559,88 +560,92 @@ def test_triangulate_monotone_polygon_py(polygon, expected): assert triangulate_monotone_polygon_py(*polygon) == expected +PATH_DATA = [ + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + True, + False, + 10, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [5, 6, 7], [8, 7, 6], [7, 8, 9]], + ), + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + False, + False, + 8, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [5, 6, 7]], + ), + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + True, + True, + 14, + [ + [2, 1, 0], + [3, 2, 0], + [2, 3, 4], + [5, 4, 3], + [6, 5, 3], + [5, 6, 7], + [8, 7, 6], + [9, 8, 6], + [8, 9, 10], + [11, 10, 9], + [12, 11, 9], + [11, 12, 13], + ], + ), + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + False, + True, + 10, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6], [7, 6, 5], [8, 7, 5], [7, 8, 9]], + ), + ( + [[2, 10], [0, -5], [-2, 10], [-2, -10], [2, -10]], + True, + False, + 15, + [ + [2, 1, 0], + [1, 2, 3], + [1, 3, 4], + [5, 4, 3], + [6, 5, 3], + [5, 6, 7], + [8, 7, 6], + [7, 8, 9], + [7, 9, 10], + [11, 10, 9], + [10, 11, 12], + [13, 12, 11], + [12, 13, 14], + ], + ), + ([[0, 0], [0, 10]], False, False, 4, [[2, 1, 0], [1, 2, 3]]), + ([[0, 0], [0, 10], [0, 20]], False, False, 6, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5]]), + ( + [[0, 0], [0, 2], [10, 1]], + True, + False, + 9, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [7, 6, 4], [6, 7, 8]], + ), + ([[0, 0], [10, 1], [9, 1.1]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [3, 5, 6]]), + ([[9, 0.9], [10, 1], [0, 2]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [3, 5, 6]]), + ([[0, 0], [-10, 1], [-9, 1.1]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6]]), + ([[-9, 0.9], [-10, 1], [0, 2]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6]]), +] + + @pytest.mark.parametrize( ('path', 'closed', 'bevel', 'expected', 'exp_triangles'), - [ - ( - [[0, 0], [0, 10], [10, 10], [10, 0]], - True, - False, - 10, - [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [5, 6, 7], [8, 7, 6], [7, 8, 9]], - ), - ( - [[0, 0], [0, 10], [10, 10], [10, 0]], - False, - False, - 8, - [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [5, 6, 7]], - ), - ( - [[0, 0], [0, 10], [10, 10], [10, 0]], - True, - True, - 14, - [ - [2, 1, 0], - [3, 2, 0], - [2, 3, 4], - [5, 4, 3], - [6, 5, 3], - [5, 6, 7], - [8, 7, 6], - [9, 8, 6], - [8, 9, 10], - [11, 10, 9], - [12, 11, 9], - [11, 12, 13], - ], - ), - ( - [[0, 0], [0, 10], [10, 10], [10, 0]], - False, - True, - 10, - [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6], [7, 6, 5], [8, 7, 5], [7, 8, 9]], - ), - ( - [[2, 10], [0, -5], [-2, 10], [-2, -10], [2, -10]], - True, - False, - 15, - [ - [2, 1, 0], - [1, 2, 3], - [1, 3, 4], - [5, 4, 3], - [6, 5, 3], - [5, 6, 7], - [8, 7, 6], - [7, 8, 9], - [7, 9, 10], - [11, 10, 9], - [10, 11, 12], - [13, 12, 11], - [12, 13, 14], - ], - ), - ([[0, 0], [0, 10]], False, False, 4, [[2, 1, 0], [1, 2, 3]]), - ([[0, 0], [0, 10], [0, 20]], False, False, 6, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5]]), - ( - [[0, 0], [0, 2], [10, 1]], - True, - False, - 9, - [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [7, 6, 4], [6, 7, 8]], - ), - ([[0, 0], [10, 1], [9, 1.1]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [3, 5, 6]]), - ([[9, 0.9], [10, 1], [0, 2]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [3, 5, 6]]), - ([[0, 0], [-10, 1], [-9, 1.1]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6]]), - ([[-9, 0.9], [-10, 1], [0, 2]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6]]), - ], + PATH_DATA, ) -def test_triangulate_path_edge_py(path, closed, bevel, expected, exp_triangles): - centers, offsets, triangles = triangulate_path_edge_py(np.array(path, dtype='float32'), closed=closed, bevel=bevel) +@pytest.mark.parametrize('triangulate_fun', [triangulate_path_edge_py, triangulate_path_edge_numpy]) +def test_triangulate_path_edge_py(path, closed, bevel, expected, exp_triangles, triangulate_fun): + centers, offsets, triangles = triangulate_fun(np.array(path, dtype='float32'), closed=closed, bevel=bevel) assert centers.shape == offsets.shape assert centers.shape[0] == expected assert triangles.shape[0] == expected - 2 @@ -651,7 +656,7 @@ def test_triangulate_path_edge_py(path, closed, bevel, expected, exp_triangles): @pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) def test_triangulate_polygon_with_edge_numpy_li(polygon, expected): (triangles, points), (centers, offsets, edge_triangles) = triangulate_polygon_with_edge_numpy_li( - [np.array(polygon)] + [np.array(polygon, dtype=np.float32)] ) triangles_ = _renumerate_triangles(polygon, points, triangles) assert triangles_ == expected