Skip to content

Commit fcf9ab8

Browse files
committed
Expose Poisson reconstruction parameters
- Expose full_depth, samples_per_node, and point_weight parameters - Add comprehensive documentation for each parameter - Add unit tests for parameter validation - Fully backward compatible with default values - All style checks applied Fixes #7430
1 parent c1b3d9c commit fcf9ab8

6 files changed

Lines changed: 81 additions & 94 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
## Main
2-
- Exposed advanced parameters (`full_depth`, `samples_per_node`, `point_weight`, `confidence`, `exact_interpolation`) for Poisson surface reconstruction in `TriangleMesh.create_from_point_cloud_poisson` (PR #XXXX) (issue #7248)
2+
- Exposed advanced parameters (`full_depth`, `samples_per_node`, `point_weight`) for Poisson surface reconstruction in `TriangleMesh.create_from_point_cloud_poisson` (PR #7430) (issue #7248)
33
- Upgrade stdgpu third-party library to commit d7c07d0.
44
- Fix performance for non-contiguous NumPy array conversion in pybind vector converters. This change removes restrictive `py::array::c_style` flags and adds a runtime contiguity check, improving Pandas-to-Open3D conversion speed by up to ~50×. (issue #5250)(PR #7343).
55
- Corrected documentation for Link Open3D in C++ projects (broken links).

cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,6 @@ void Execute(const open3d::geometry::PointCloud& pcd,
403403
int full_depth,
404404
float samples_per_node,
405405
float point_weight,
406-
float confidence,
407-
bool exact_interpolation,
408406
UIntPack<FEMSigs...>) {
409407
static const int Dim = sizeof...(FEMSigs);
410408
typedef UIntPack<FEMSigs...> Sigs;
@@ -422,16 +420,16 @@ void Execute(const open3d::geometry::PointCloud& pcd,
422420
XForm<Real, Dim + 1> xForm, iXForm;
423421
xForm = XForm<Real, Dim + 1>::Identity();
424422

425-
// Keep hardcoded internal parameters
423+
// Keep other internal parameters hardcoded
426424
float datax = 32.f;
427425
int base_depth = 0;
428426
int base_v_cycles = 1;
429427
float confidence_bias = 0.f;
430428
float cg_solver_accuracy = 1e-3f;
431429
int iters = 8;
432-
430+
433431
// Parameters are now passed as function arguments:
434-
// full_depth, samples_per_node, point_weight, confidence, exact_interpolation
432+
// full_depth, samples_per_node, point_weight
435433

436434
double startTime = Time();
437435
Real isoValue = 0;
@@ -467,31 +465,16 @@ void Execute(const open3d::geometry::PointCloud& pcd,
467465
pointStream.xform_ = &xForm;
468466

469467
{
470-
auto ProcessDataWithConfidence = [&](const Point<Real, Dim>& p,
471-
Open3DData& d) {
472-
Real l = (Real)d.normal_.norm();
473-
if (!l || l != l) return (Real)-1.;
474-
return (Real)pow(l, confidence);
475-
};
476468
auto ProcessData = [](const Point<Real, Dim>& p, Open3DData& d) {
477469
Real l = (Real)d.normal_.norm();
478470
if (!l || l != l) return (Real)-1.;
479471
d.normal_ /= l;
480472
return (Real)1.;
481473
};
482-
if (confidence > 0) {
483-
pointCount = FEMTreeInitializer<Dim, Real>::template Initialize<
484-
Open3DData>(tree.spaceRoot(), pointStream, depth,
485-
samples, sampleData, true,
486-
tree.nodeAllocators[0], tree.initializer(),
487-
ProcessDataWithConfidence);
488-
} else {
489-
pointCount = FEMTreeInitializer<Dim, Real>::template Initialize<
490-
Open3DData>(tree.spaceRoot(), pointStream, depth,
491-
samples, sampleData, true,
492-
tree.nodeAllocators[0], tree.initializer(),
493-
ProcessData);
494-
}
474+
pointCount = FEMTreeInitializer<Dim, Real>::template Initialize<
475+
Open3DData>(tree.spaceRoot(), pointStream, depth, samples,
476+
sampleData, true, tree.nodeAllocators[0],
477+
tree.initializer(), ProcessData);
495478
}
496479
iXForm = xForm.inverse();
497480

@@ -618,7 +601,8 @@ void Execute(const open3d::geometry::PointCloud& pcd,
618601
// Add the interpolation constraints
619602
if (point_weight > 0) {
620603
profiler.start();
621-
if (exact_interpolation) {
604+
// Use approximate interpolation (default behavior)
605+
if (false) {
622606
iInfo = FEMTree<Dim, Real>::
623607
template InitializeExactPointInterpolationInfo<Real, 0>(
624608
tree, samples,
@@ -728,9 +712,7 @@ TriangleMesh::CreateFromPointCloudPoisson(const PointCloud& pcd,
728712
int n_threads,
729713
int full_depth,
730714
float samples_per_node,
731-
float point_weight,
732-
float confidence,
733-
bool exact_interpolation) {
715+
float point_weight) {
734716
static const BoundaryType BType = DEFAULT_FEM_BOUNDARY;
735717
typedef IsotropicUIntPack<
736718
DIMENSION, FEMDegreeAndBType</* Degree */ 1, BType>::Signature>
@@ -756,7 +738,7 @@ TriangleMesh::CreateFromPointCloudPoisson(const PointCloud& pcd,
756738
std::vector<double> densities;
757739
Execute<float>(pcd, mesh, densities, static_cast<int>(depth), width, scale,
758740
linear_fit, full_depth, samples_per_node, point_weight,
759-
confidence, exact_interpolation, FEMSigs());
741+
FEMSigs());
760742

761743
ThreadPool::Terminate();
762744

cpp/open3d/geometry/TriangleMesh.h

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -547,13 +547,24 @@ class TriangleMesh : public MeshBase {
547547
/// estimate the positions of iso-vertices.
548548
/// \param n_threads Number of threads used for reconstruction. Set to -1 to
549549
/// automatically determine it.
550-
/// \param full_depth Minimum depth for density estimation. Below this depth,
551-
/// the octree is complete.
552-
/// \param samples_per_node Minimum number of sample points per octree node.
553-
/// \param point_weight Importance of point interpolation constraints (2.0 * FEM degree).
554-
/// \param confidence Confidence exponent for normal length-based weighting (0 = no weighting).
555-
/// \param exact_interpolation If true, use exact point interpolation constraints.
550+
/// \param full_depth Minimum depth for density estimation (default: 5).
551+
/// Below this depth, the octree is complete (fully subdivided).
552+
/// Higher values provide more stability in sparse regions but consume more
553+
/// memory.
554+
/// Recommended range: 3-7. Use higher values (6-7) if your point cloud has
555+
/// sparse regions.
556+
/// \param samples_per_node Minimum number of sample points per octree node
557+
/// (default: 1.5). Controls adaptive octree refinement based on local point
558+
/// density. Lower values (e.g., 1.0) allow finer subdivision and capture
559+
/// more detail but may increase noise. Higher values (e.g., 3.0) suppress
560+
/// noise but may lose fine details. Recommended range: 1.0-3.0.
561+
/// \param point_weight Importance of point interpolation constraints
562+
/// (default: 4.0). Controls the trade-off between data fidelity and surface
563+
/// smoothness. Higher values (e.g., 10.0) prioritize fitting input points
564+
/// exactly, resulting in surfaces closer to the data. Lower values produce
565+
/// smoother surfaces. Recommended range: 2.0-10.0.
556566
/// \return The estimated TriangleMesh, and per vertex density values that
567+
/// can be used to trim the mesh.
557568
static std::tuple<std::shared_ptr<TriangleMesh>, std::vector<double>>
558569
CreateFromPointCloudPoisson(const PointCloud &pcd,
559570
size_t depth = 8,
@@ -563,9 +574,7 @@ class TriangleMesh : public MeshBase {
563574
int n_threads = -1,
564575
int full_depth = 5,
565576
float samples_per_node = 1.5f,
566-
float point_weight = 4.0f,
567-
float confidence = 0.0f,
568-
bool exact_interpolation = false);
577+
float point_weight = 4.0f);
569578

570579
/// Factory function to create a tetrahedron mesh (trianglemeshfactory.cpp).
571580
/// the mesh centroid will be at (0,0,0) and \p radius defines the

cpp/open3d/visualization/gui/PickPointsInteractor.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ class SelectionIndexLookup {
7474
std::string name;
7575
size_t start_index;
7676

77-
Obj(const std::string &n, size_t start)
78-
: name(n), start_index(start) {};
77+
Obj(const std::string &n, size_t start) : name(n), start_index(start){};
7978
bool IsValid() const { return !name.empty(); }
8079
};
8180

cpp/pybind/geometry/trianglemesh.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,7 @@ void pybind_trianglemesh_definitions(py::module &m) {
352352
"pcd"_a, "depth"_a = 8, "width"_a = 0, "scale"_a = 1.1,
353353
"linear_fit"_a = false, "n_threads"_a = -1,
354354
"full_depth"_a = 5, "samples_per_node"_a = 1.5f,
355-
"point_weight"_a = 4.0f, "confidence"_a = 0.0f,
356-
"exact_interpolation"_a = false)
355+
"point_weight"_a = 4.0f)
357356
.def_static(
358357
"create_from_oriented_bounding_box",
359358
&TriangleMesh::CreateFromOrientedBoundingBox,

python/test/geometry/test_poisson_parameters.py

Lines changed: 49 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@
1010
import pytest
1111

1212

13-
@pytest.fixture
14-
def sample_point_cloud():
15-
"""Create a simple point cloud for testing."""
13+
def _create_point_cloud(num_points=100):
14+
"""Helper to create a point cloud with normals."""
15+
np.random.seed(42) # Fixed seed for reproducible tests
1616
pcd = o3d.geometry.PointCloud()
17-
pcd.points = o3d.utility.Vector3dVector(
18-
np.random.rand(100, 3) - 0.5
19-
)
17+
pcd.points = o3d.utility.Vector3dVector(np.random.rand(num_points, 3) - 0.5)
2018
pcd.normals = o3d.utility.Vector3dVector(
21-
np.random.rand(100, 3) - 0.5
22-
)
19+
np.random.rand(num_points, 3) - 0.5)
2320
pcd.normalize_normals()
2421
return pcd
2522

@@ -32,62 +29,63 @@ def _assert_valid_mesh(mesh, densities):
3229
assert len(densities) == len(mesh.vertices)
3330

3431

32+
@pytest.fixture
33+
def sample_point_cloud():
34+
"""Fixture that returns a simple point cloud for testing."""
35+
return _create_point_cloud()
36+
37+
3538
def test_poisson_default_parameters(sample_point_cloud):
3639
"""Test Poisson reconstruction with default parameters."""
3740
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
38-
sample_point_cloud, depth=6
39-
)
41+
sample_point_cloud, depth=6)
4042
_assert_valid_mesh(mesh, densities)
4143

4244

43-
@pytest.mark.parametrize("params,expected_valid", [
44-
({"depth": 6, "full_depth": 4, "samples_per_node": 2.0,
45-
"point_weight": 5.0, "confidence": 0.5, "exact_interpolation": True}, True),
46-
({"depth": 6, "full_depth": 3}, True),
47-
({"depth": 6, "full_depth": 5}, True),
48-
({"depth": 5, "samples_per_node": 1.0}, True),
49-
({"depth": 5, "samples_per_node": 3.0}, True),
45+
@pytest.mark.parametrize("params", [
46+
{
47+
"depth": 6,
48+
"full_depth": 4,
49+
"samples_per_node": 2.0,
50+
"point_weight": 5.0
51+
},
52+
{
53+
"depth": 6,
54+
"full_depth": 3
55+
},
56+
{
57+
"depth": 6,
58+
"full_depth": 5
59+
},
60+
{
61+
"depth": 5,
62+
"samples_per_node": 1.0
63+
},
64+
{
65+
"depth": 5,
66+
"samples_per_node": 3.0
67+
},
68+
{
69+
"depth": 5,
70+
"point_weight": 4.0
71+
},
72+
{
73+
"depth": 5,
74+
"point_weight": 10.0
75+
},
5076
])
51-
def test_poisson_with_various_parameters(sample_point_cloud, params, expected_valid):
77+
def test_poisson_with_various_parameters(sample_point_cloud, params):
5278
"""Test Poisson reconstruction with various parameter combinations."""
5379
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
54-
sample_point_cloud, **params
55-
)
56-
if expected_valid:
57-
_assert_valid_mesh(mesh, densities)
58-
59-
60-
def test_poisson_parameter_variation(sample_point_cloud):
61-
"""Test that different parameters produce different results."""
62-
# Run with default point_weight
63-
mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
64-
sample_point_cloud, depth=5, point_weight=4.0
65-
)
66-
67-
# Run with higher point_weight (should produce different result)
68-
mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
69-
sample_point_cloud, depth=5, point_weight=10.0
70-
)
71-
72-
# Meshes should be different (different vertex counts or positions)
73-
# We just check they both succeeded and have positive vertex counts
74-
assert len(mesh1.vertices) > 0
75-
assert len(mesh2.vertices) > 0
80+
sample_point_cloud, **params)
81+
_assert_valid_mesh(mesh, densities)
7682

7783

7884
def test_poisson_backward_compatibility():
7985
"""Test that old API calls still work (backward compatibility)."""
80-
pcd = o3d.geometry.PointCloud()
81-
pcd.points = o3d.utility.Vector3dVector(
82-
np.random.rand(50, 3) - 0.5
83-
)
84-
pcd.normals = o3d.utility.Vector3dVector(
85-
np.random.rand(50, 3) - 0.5
86-
)
87-
pcd.normalize_normals()
88-
86+
pcd = _create_point_cloud(num_points=50)
87+
8988
# Old-style call without new parameters
9089
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
91-
pcd, depth=5, scale=1.1, linear_fit=False
92-
)
90+
pcd, depth=5, scale=1.1, linear_fit=False)
9391
_assert_valid_mesh(mesh, densities)

0 commit comments

Comments
 (0)