Skip to content

Commit 193cd06

Browse files
authored
Merge pull request #60 from compas-dev/copilot/add-additional-parameter-inputs
Add surface meshing parameters to Poisson surface reconstruction
2 parents 46db245 + bd87901 commit 193cd06

File tree

7 files changed

+117
-9
lines changed

7 files changed

+117
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919

2020

2121
### Changed
22+
* Added optional surface meshing parameters (`sm_angle`, `sm_radius`, `sm_distance`) to `compas_cgal.reconstruction.poisson_surface_reconstruction` for controlling mesh quality and density.
2223

2324
### Removed
2425

_codeql_detected_source_root

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.

docs/examples/example_reconstruction_poisson_surface_reconstruction.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,16 @@
2323
# Reconstruction
2424
# =============================================================================
2525

26-
V, F = poisson_surface_reconstruction(points, normals)
26+
# Reconstruct surface with custom parameters
27+
# Using larger sm_radius and sm_distance values reduces mesh complexity
28+
# and helps filter out vertices that don't belong to the original point cloud
29+
V, F = poisson_surface_reconstruction(
30+
points,
31+
normals,
32+
sm_angle=20.0, # Surface meshing angle bound (degrees)
33+
sm_radius=30.0, # Surface meshing radius bound (factor of avg spacing)
34+
sm_distance=0.375, # Surface meshing distance bound (factor of avg spacing)
35+
)
2736
mesh = Mesh.from_vertices_and_faces(V, F)
2837

2938
# ==============================================================================

src/compas_cgal/reconstruction.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
def poisson_surface_reconstruction(
1515
points: Union[list[Point], FloatNx3],
1616
normals: Union[list[Vector], FloatNx3],
17+
sm_angle: float = 20.0,
18+
sm_radius: float = 30.0,
19+
sm_distance: float = 0.375,
1720
) -> Tuple[FloatNx3, IntNx3]:
1821
"""Reconstruct a surface from a point cloud using the Poisson surface reconstruction algorithm.
1922
@@ -23,6 +26,20 @@ def poisson_surface_reconstruction(
2326
The points of the point cloud.
2427
normals
2528
The normals of the point cloud.
29+
sm_angle : float, optional
30+
Surface meshing angle bound in degrees.
31+
Controls the minimum angle of triangles in the output mesh.
32+
Default is 20.0.
33+
sm_radius : float, optional
34+
Surface meshing radius bound as a factor of average spacing.
35+
Controls the size of triangles relative to the point cloud density.
36+
Larger values result in coarser meshes with fewer vertices.
37+
Default is 30.0.
38+
sm_distance : float, optional
39+
Surface meshing approximation error bound as a factor of average spacing.
40+
Controls how closely the mesh approximates the implicit surface.
41+
Larger values result in coarser meshes with fewer vertices that may deviate more from the original point cloud.
42+
Default is 0.375.
2643
2744
Returns
2845
-------
@@ -46,6 +63,18 @@ def poisson_surface_reconstruction(
4663
2. Well-oriented normals
4764
3. Points distributed across a meaningful surface
4865
66+
The surface meshing parameters (sm_angle, sm_radius, sm_distance) control the quality and
67+
density of the output mesh. Increasing sm_radius and sm_distance will typically result in
68+
fewer mesh vertices, which can help filter out vertices that don't belong to the original
69+
point cloud, but may also reduce detail.
70+
71+
Examples
72+
--------
73+
>>> points = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]
74+
>>> normals = [[0, 0, 1], [0, 0, 1], [0, 0, 1], [0, 0, 1]]
75+
>>> V, F = poisson_surface_reconstruction(points, normals)
76+
>>> # Use larger sm_radius and sm_distance to reduce mesh complexity
77+
>>> V, F = poisson_surface_reconstruction(points, normals, sm_radius=50.0, sm_distance=0.5)
4978
"""
5079
# Convert input to numpy arrays with proper type and memory layout
5180
P = np.asarray(points, dtype=np.float64, order="C")
@@ -67,7 +96,7 @@ def poisson_surface_reconstruction(
6796
N = N / norms[:, np.newaxis]
6897

6998
try:
70-
return _reconstruction.poisson_surface_reconstruction(P, N)
99+
return _reconstruction.poisson_surface_reconstruction(P, N, sm_angle, sm_radius, sm_distance)
71100
except RuntimeError as e:
72101
raise RuntimeError(f"Poisson surface reconstruction failed: {str(e)}")
73102

src/reconstruction.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ typedef CGAL::Parallel_if_available_tag ConcurrencyTag;
77
std::tuple<compas::RowMatrixXd, compas::RowMatrixXi>
88
poisson_surface_reconstruction(
99
Eigen::Ref<const compas::RowMatrixXd> points,
10-
Eigen::Ref<const compas::RowMatrixXd> normals)
10+
Eigen::Ref<const compas::RowMatrixXd> normals,
11+
double sm_angle,
12+
double sm_radius,
13+
double sm_distance)
1114
{
1215
compas::Polyhedron mesh;
1316
std::vector<PointVectorPair> points_with_normals;
@@ -30,7 +33,10 @@ poisson_surface_reconstruction(
3033
CGAL::First_of_pair_property_map<PointVectorPair>(),
3134
CGAL::Second_of_pair_property_map<PointVectorPair>(),
3235
mesh,
33-
average_spacing);
36+
average_spacing,
37+
sm_angle,
38+
sm_radius,
39+
sm_distance);
3440

3541
return compas::polyhedron_to_vertices_and_faces(mesh);
3642
}
@@ -238,7 +244,10 @@ NB_MODULE(_reconstruction, m) {
238244
&poisson_surface_reconstruction,
239245
"Perform Poisson surface reconstruction on an oriented pointcloud with normals",
240246
"points"_a,
241-
"normals"_a
247+
"normals"_a,
248+
"sm_angle"_a = 20.0,
249+
"sm_radius"_a = 30.0,
250+
"sm_distance"_a = 0.375
242251
);
243252

244253
m.def(

src/reconstruction.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@
1919
*
2020
* @param points Matrix of point positions as Nx3 matrix in row-major order (float64)
2121
* @param normals Matrix of point normals as Nx3 matrix in row-major order (float64)
22+
* @param sm_angle Surface meshing angle bound in degrees (default: 20.0)
23+
* @param sm_radius Surface meshing radius bound as a factor of average spacing (default: 30.0)
24+
* @param sm_distance Surface meshing approximation error bound as a factor of average spacing (default: 0.375)
2225
* @return std::tuple<RowMatrixXd, RowMatrixXi> containing:
2326
* - vertices as Rx3 matrix (float64)
2427
* - faces as Sx3 matrix (int32)
2528
*/
2629
std::tuple<compas::RowMatrixXd, compas::RowMatrixXi>
2730
poisson_surface_reconstruction(
2831
Eigen::Ref<const compas::RowMatrixXd> points,
29-
Eigen::Ref<const compas::RowMatrixXd> normals);
32+
Eigen::Ref<const compas::RowMatrixXd> normals,
33+
double sm_angle = 20.0,
34+
double sm_radius = 30.0,
35+
double sm_distance = 0.375);
3036

3137
/**
3238
* @brief Remove outliers from a pointcloud.

tests/test_reconstruction_poisson_surface_reconstruction.py

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,35 @@
77
from compas.geometry import Scale
88
from compas_cgal.reconstruction import poisson_surface_reconstruction
99

10+
# Test data file path
11+
TEST_DATA_FILE = Path(__file__).parent.parent / "data" / "oni.xyz"
1012

11-
def test_reconstruction_poisson_surface_reconstruction():
12-
FILE = Path(__file__).parent.parent / "data" / "oni.xyz"
1313

14+
def load_test_data(file_path):
15+
"""Load point cloud data from file.
16+
17+
Parameters
18+
----------
19+
file_path : Path
20+
Path to the xyz file containing points and normals.
21+
22+
Returns
23+
-------
24+
tuple
25+
(points, normals) where each is a list of 3D coordinates.
26+
"""
1427
points = []
1528
normals = []
16-
with open(FILE, "r") as f:
29+
with open(file_path, "r") as f:
1730
for line in f:
1831
x, y, z, nx, ny, nz = line.strip().split()
1932
points.append([float(x), float(y), float(z)])
2033
normals.append([float(nx), float(ny), float(nz)])
34+
return points, normals
35+
36+
37+
def test_reconstruction_poisson_surface_reconstruction():
38+
points, normals = load_test_data(TEST_DATA_FILE)
2139

2240
V, F = poisson_surface_reconstruction(points, normals)
2341
mesh = Mesh.from_vertices_and_faces(V, F)
@@ -33,3 +51,38 @@ def test_reconstruction_poisson_surface_reconstruction():
3351
assert mesh.is_manifold()
3452
assert mesh.number_of_vertices() > 0
3553
assert mesh.number_of_faces() > 0
54+
55+
56+
def test_reconstruction_poisson_surface_reconstruction_with_parameters():
57+
"""Test Poisson surface reconstruction with custom parameters to reduce mesh complexity."""
58+
points, normals = load_test_data(TEST_DATA_FILE)
59+
60+
# Test with larger sm_radius and sm_distance to reduce mesh complexity
61+
V1, F1 = poisson_surface_reconstruction(points, normals, sm_radius=50.0, sm_distance=0.5)
62+
mesh1 = Mesh.from_vertices_and_faces(V1, F1)
63+
64+
# Test with default parameters
65+
V2, F2 = poisson_surface_reconstruction(points, normals)
66+
mesh2 = Mesh.from_vertices_and_faces(V2, F2)
67+
68+
# Mesh with larger parameters should have fewer or equal vertices
69+
# (though this isn't guaranteed in all cases due to CGAL's internal logic)
70+
assert mesh1.is_manifold()
71+
assert mesh1.number_of_vertices() > 0
72+
assert mesh1.number_of_faces() > 0
73+
assert mesh2.is_manifold()
74+
assert mesh2.number_of_vertices() > 0
75+
assert mesh2.number_of_faces() > 0
76+
77+
78+
def test_reconstruction_poisson_surface_reconstruction_angle_parameter():
79+
"""Test Poisson surface reconstruction with custom angle parameter."""
80+
points, normals = load_test_data(TEST_DATA_FILE)
81+
82+
# Test with custom angle parameter
83+
V, F = poisson_surface_reconstruction(points, normals, sm_angle=25.0)
84+
mesh = Mesh.from_vertices_and_faces(V, F)
85+
86+
assert mesh.is_manifold()
87+
assert mesh.number_of_vertices() > 0
88+
assert mesh.number_of_faces() > 0

0 commit comments

Comments
 (0)