Skip to content

Commit adb76f1

Browse files
authored
Add PointCloudLocalTriangulation interface (#11)
* Add PointCloudLocalTriangulation interface Add class in src/point_cloud.cpp Expose class in point_cloud.py Add test based on cartwheel pointcloud * Use std::max instead of MAX
1 parent 994d2f8 commit adb76f1

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

src/cpp/point_cloud.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "geometrycentral/pointcloud/point_cloud_heat_solver.h"
44
#include "geometrycentral/pointcloud/point_cloud_io.h"
55
#include "geometrycentral/pointcloud/point_position_geometry.h"
6+
#include "geometrycentral/pointcloud/local_triangulation.h"
67
#include "geometrycentral/utilities/eigen_interop_helpers.h"
78

89
#include <pybind11/eigen.h>
@@ -11,6 +12,7 @@
1112

1213
#include "Eigen/Dense"
1314

15+
1416
namespace py = pybind11;
1517

1618
using namespace geometrycentral;
@@ -120,6 +122,59 @@ class PointCloudHeatSolverEigen {
120122
std::unique_ptr<PointCloudHeatSolver> solver;
121123
};
122124

125+
// A class that exposes the local pointcloud trinagulation to python
126+
class PointCloudLocalTriangulation {
127+
public:
128+
PointCloudLocalTriangulation(DenseMatrix<double> points, bool withDegeneracyHeuristic) : withDegeneracyHeuristic(withDegeneracyHeuristic) {
129+
130+
// Construct the internal cloud and geometry
131+
cloud.reset(new PointCloud(points.rows()));
132+
geom.reset(new PointPositionGeometry(*cloud));
133+
for (size_t i = 0; i < cloud->nPoints(); i++) {
134+
for (size_t j = 0; j < 3; j++) {
135+
geom->positions[i][j] = points(i, j);
136+
}
137+
}
138+
}
139+
140+
141+
Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic> get_local_triangulation() {
142+
PointData<std::vector<std::array<Point, 3>>> local_triangulation = buildLocalTriangulations(*cloud, *geom, withDegeneracyHeuristic);
143+
144+
int max_neigh = 0;
145+
146+
size_t idx = 0;
147+
for (Point v : cloud->points()) {
148+
max_neigh = std::max(max_neigh, static_cast<int>(local_triangulation[v].size()));
149+
if (idx != v.getIndex()) {
150+
py::print("Error. Index of points not consistent. (Idx, v.getIndex) = ", idx, v.getIndex());
151+
}
152+
idx++;
153+
}
154+
155+
Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic> out(cloud->nPoints(), 3 * max_neigh);
156+
out.setConstant(-1);
157+
158+
for (Point v : cloud->points()) {
159+
int i = 0;
160+
for (auto const &neighs : local_triangulation[v]) {
161+
out(v.getIndex(), i + 0) = (int) neighs[0].getIndex();
162+
out(v.getIndex(), i + 1) = (int) neighs[1].getIndex();
163+
out(v.getIndex(), i + 2) = (int) neighs[2].getIndex();
164+
i += 3;
165+
}
166+
}
167+
168+
return out;
169+
}
170+
171+
private:
172+
const bool withDegeneracyHeuristic;
173+
std::unique_ptr<PointCloud> cloud;
174+
std::unique_ptr<PointPositionGeometry> geom;
175+
std::unique_ptr<PointCloudHeatSolver> solver;
176+
};
177+
123178

124179
// Actual binding code
125180
// clang-format off
@@ -134,4 +189,8 @@ void bind_point_cloud(py::module& m) {
134189
.def("transport_tangent_vector", &PointCloudHeatSolverEigen::transport_tangent_vector, py::arg("source_point"), py::arg("vector"))
135190
.def("transport_tangent_vectors", &PointCloudHeatSolverEigen::transport_tangent_vectors, py::arg("source_points"), py::arg("vectors"))
136191
.def("compute_log_map", &PointCloudHeatSolverEigen::compute_log_map, py::arg("source_point"));
192+
193+
py::class_<PointCloudLocalTriangulation>(m, "PointCloudLocalTriangulation")
194+
.def(py::init<DenseMatrix<double>, bool>())
195+
.def("get_local_triangulation", &PointCloudLocalTriangulation::get_local_triangulation);
137196
}

src/potpourri3d/point_cloud.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,24 @@ def transport_tangent_vectors(self, p_inds, vectors):
3535

3636
def compute_log_map(self, p_ind):
3737
return self.bound_solver.compute_log_map(p_ind)
38+
39+
40+
class PointCloudLocalTriangulation():
41+
42+
def __init__(self, P, with_degeneracy_heuristic=True):
43+
validate_points(P)
44+
self.bound_triangulation = pp3db.PointCloudLocalTriangulation(P, with_degeneracy_heuristic)
45+
46+
def get_local_triangulation(self):
47+
"""Return the local point cloud triangulation
48+
49+
The out matrix has the following convention:
50+
size: num_points, max_neighs, 3. max_neighs is the maximum number of neighbors
51+
out[point_idx, neigh_idx, :] are the indices of the 3 neighbors
52+
-1 is used as the fill value for unused elements if num_neighs < max_neighs for a point
53+
"""
54+
out = self.bound_triangulation.get_local_triangulation()
55+
assert out.shape[-1] % 3 == 0
56+
max_neighs = out.shape[-1] // 3
57+
out = np.reshape(out, [-1, max_neighs, 3])
58+
return out

test/potpourri3d_test.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,5 +237,43 @@ def test_point_cloud_vector_heat(self):
237237
self.assertEqual(logmap.shape[0], P.shape[0])
238238
self.assertEqual(logmap.shape[1], 2)
239239

240+
def test_point_cloud_local_triangulation(self):
241+
# Test local triangulation for a "cartwheel" pointcloud
242+
243+
num = 31
244+
t = np.linspace(0, 2*np.pi, num-1, endpoint=False)
245+
points = np.concatenate([np.zeros([1, 3]), np.stack([np.cos(t), np.sin(t), 0*t], 1)], 0)
246+
247+
pcl_local_tri = pp3d.PointCloudLocalTriangulation(points)
248+
idxs = pcl_local_tri.get_local_triangulation()
249+
250+
def next_id(i):
251+
assert i != 0
252+
if i == num-1:
253+
return 1
254+
return i+1
255+
256+
def prev_id(i):
257+
assert i != 0
258+
if i == 1:
259+
return num-1
260+
return i-1
261+
262+
263+
# Explicitly check for cartwheel
264+
# Use sets for order invariance
265+
266+
res0 = set(tuple(r) for r in idxs[0])
267+
ref0 = set((0, j, next_id(j)) for j in range(1, num))
268+
self.assertEqual(res0, ref0)
269+
270+
for i in range(1, num):
271+
self.assertTrue(np.all(idxs[i, 2:] == -1))
272+
273+
res = set(tuple(r) for r in idxs[i, :2])
274+
ref = {(i, next_id(i), 0), (i, 0, prev_id(i))}
275+
self.assertEqual(res, ref)
276+
277+
240278
if __name__ == '__main__':
241279
unittest.main()

0 commit comments

Comments
 (0)