From e8337ab95718ae544d2a8bccfc2e3f92bba8a90b Mon Sep 17 00:00:00 2001 From: Chevi Koren Date: Sat, 7 Feb 2026 22:06:29 +0200 Subject: [PATCH 1/6] Expose Poisson reconstruction parameters Expose 5 important parameters in CreateFromPointCloudPoisson that were previously hardcoded: - full_depth: Minimum depth for density estimation (default: 5) - samples_per_node: Minimum sample points per octree node (default: 1.5) - point_weight: Point interpolation weight (default: 4.0) - confidence: Normal confidence weighting exponent (default: 0.0) - exact_interpolation: Use exact constraints (default: false) This allows fine-tuning of surface reconstruction quality and performance. All parameters have sensible defaults matching previous behavior. Fully backward compatible. Fixes #7248 --- .../geometry/SurfaceReconstructionPoisson.cpp | 24 ++- cpp/open3d/geometry/TriangleMesh.h | 14 +- cpp/pybind/geometry/trianglemesh.cpp | 5 +- .../test/geometry/test_poisson_parameters.py | 161 ++++++++++++++++++ 4 files changed, 194 insertions(+), 10 deletions(-) create mode 100644 python/test/geometry/test_poisson_parameters.py diff --git a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp index 7195166701f..f3db42126ec 100644 --- a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp +++ b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp @@ -400,6 +400,11 @@ void Execute(const open3d::geometry::PointCloud& pcd, float width, float scale, bool linear_fit, + int full_depth, + float samples_per_node, + float point_weight, + float confidence, + bool exact_interpolation, UIntPack) { static const int Dim = sizeof...(FEMSigs); typedef UIntPack Sigs; @@ -417,17 +422,16 @@ void Execute(const open3d::geometry::PointCloud& pcd, XForm xForm, iXForm; xForm = XForm::Identity(); + // Keep hardcoded internal parameters float datax = 32.f; int base_depth = 0; int base_v_cycles = 1; - float confidence = 0.f; - float point_weight = 2.f * DEFAULT_FEM_DEGREE; float confidence_bias = 0.f; - float samples_per_node = 1.5f; float cg_solver_accuracy = 1e-3f; - int full_depth = 5; int iters = 8; - bool exact_interpolation = false; + + // Parameters are now passed as function arguments: + // full_depth, samples_per_node, point_weight, confidence, exact_interpolation double startTime = Time(); Real isoValue = 0; @@ -721,7 +725,12 @@ TriangleMesh::CreateFromPointCloudPoisson(const PointCloud& pcd, float width, float scale, bool linear_fit, - int n_threads) { + int n_threads, + int full_depth, + float samples_per_node, + float point_weight, + float confidence, + bool exact_interpolation) { static const BoundaryType BType = DEFAULT_FEM_BOUNDARY; typedef IsotropicUIntPack< DIMENSION, FEMDegreeAndBType::Signature> @@ -746,7 +755,8 @@ TriangleMesh::CreateFromPointCloudPoisson(const PointCloud& pcd, auto mesh = std::make_shared(); std::vector densities; Execute(pcd, mesh, densities, static_cast(depth), width, scale, - linear_fit, FEMSigs()); + linear_fit, full_depth, samples_per_node, point_weight, + confidence, exact_interpolation, FEMSigs()); ThreadPool::Terminate(); diff --git a/cpp/open3d/geometry/TriangleMesh.h b/cpp/open3d/geometry/TriangleMesh.h index 7f0b05c6ca5..74edf172e2b 100644 --- a/cpp/open3d/geometry/TriangleMesh.h +++ b/cpp/open3d/geometry/TriangleMesh.h @@ -547,15 +547,25 @@ class TriangleMesh : public MeshBase { /// estimate the positions of iso-vertices. /// \param n_threads Number of threads used for reconstruction. Set to -1 to /// automatically determine it. + /// \param full_depth Minimum depth for density estimation. Below this depth, + /// the octree is complete. + /// \param samples_per_node Minimum number of sample points per octree node. + /// \param point_weight Importance of point interpolation constraints (2.0 * FEM degree). + /// \param confidence Confidence exponent for normal length-based weighting (0 = no weighting). + /// \param exact_interpolation If true, use exact point interpolation constraints. /// \return The estimated TriangleMesh, and per vertex density values that - /// can be used to to trim the mesh. static std::tuple, std::vector> CreateFromPointCloudPoisson(const PointCloud &pcd, size_t depth = 8, float width = 0.0f, float scale = 1.1f, bool linear_fit = false, - int n_threads = -1); + int n_threads = -1, + int full_depth = 5, + float samples_per_node = 1.5f, + float point_weight = 4.0f, + float confidence = 0.0f, + bool exact_interpolation = false); /// Factory function to create a tetrahedron mesh (trianglemeshfactory.cpp). /// the mesh centroid will be at (0,0,0) and \p radius defines the diff --git a/cpp/pybind/geometry/trianglemesh.cpp b/cpp/pybind/geometry/trianglemesh.cpp index 818630e9542..ea4d8b46be0 100644 --- a/cpp/pybind/geometry/trianglemesh.cpp +++ b/cpp/pybind/geometry/trianglemesh.cpp @@ -350,7 +350,10 @@ void pybind_trianglemesh_definitions(py::module &m) { "This function uses the original implementation by " "Kazhdan. See https://github.com/mkazhdan/PoissonRecon", "pcd"_a, "depth"_a = 8, "width"_a = 0, "scale"_a = 1.1, - "linear_fit"_a = false, "n_threads"_a = -1) + "linear_fit"_a = false, "n_threads"_a = -1, + "full_depth"_a = 5, "samples_per_node"_a = 1.5f, + "point_weight"_a = 4.0f, "confidence"_a = 0.0f, + "exact_interpolation"_a = false) .def_static( "create_from_oriented_bounding_box", &TriangleMesh::CreateFromOrientedBoundingBox, diff --git a/python/test/geometry/test_poisson_parameters.py b/python/test/geometry/test_poisson_parameters.py new file mode 100644 index 00000000000..2d84cb024ce --- /dev/null +++ b/python/test/geometry/test_poisson_parameters.py @@ -0,0 +1,161 @@ +# ---------------------------------------------------------------------------- +# - Open3D: www.open3d.org - +# ---------------------------------------------------------------------------- +# Copyright (c) 2018-2024 www.open3d.org +# SPDX-License-Identifier: MIT +# ---------------------------------------------------------------------------- + +import open3d as o3d +import numpy as np +import pytest + + +def test_poisson_default_parameters(): + """Test Poisson reconstruction with default parameters.""" + # Create a simple point cloud (sphere) + pcd = o3d.geometry.PointCloud() + pcd.points = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + # Add normals pointing outward + pcd.normals = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normalize_normals() + + # Run with default parameters + mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=6 + ) + + assert mesh is not None + assert len(mesh.vertices) > 0 + assert len(mesh.triangles) > 0 + assert len(densities) == len(mesh.vertices) + + +def test_poisson_custom_parameters(): + """Test Poisson reconstruction with custom parameters.""" + # Create a simple point cloud + pcd = o3d.geometry.PointCloud() + pcd.points = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normals = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normalize_normals() + + # Run with custom parameters + mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, + depth=6, + full_depth=4, + samples_per_node=2.0, + point_weight=5.0, + confidence=0.5, + exact_interpolation=True + ) + + assert mesh is not None + assert len(mesh.vertices) > 0 + assert len(mesh.triangles) > 0 + assert len(densities) == len(mesh.vertices) + + +def test_poisson_parameter_variation(): + """Test that different parameters produce different results.""" + # Create a simple point cloud + np.random.seed(42) + pcd = o3d.geometry.PointCloud() + pcd.points = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normals = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normalize_normals() + + # Run with default point_weight + mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=5, point_weight=4.0 + ) + + # Run with higher point_weight (should produce different result) + mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=5, point_weight=10.0 + ) + + # Meshes should be different (different vertex counts or positions) + # We just check they both succeeded and have positive vertex counts + assert len(mesh1.vertices) > 0 + assert len(mesh2.vertices) > 0 + + +def test_poisson_backward_compatibility(): + """Test that old API calls still work (backward compatibility).""" + pcd = o3d.geometry.PointCloud() + pcd.points = o3d.utility.Vector3dVector( + np.random.rand(50, 3) - 0.5 + ) + pcd.normals = o3d.utility.Vector3dVector( + np.random.rand(50, 3) - 0.5 + ) + pcd.normalize_normals() + + # Old-style call without new parameters + mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=5, scale=1.1, linear_fit=False + ) + + assert mesh is not None + assert len(mesh.vertices) > 0 + assert len(densities) == len(mesh.vertices) + + +def test_poisson_full_depth_parameter(): + """Test full_depth parameter specifically.""" + pcd = o3d.geometry.PointCloud() + pcd.points = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normals = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normalize_normals() + + # Test with different full_depth values + mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=6, full_depth=3 + ) + + mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=6, full_depth=5 + ) + + assert len(mesh1.vertices) > 0 + assert len(mesh2.vertices) > 0 + + +def test_poisson_samples_per_node_parameter(): + """Test samples_per_node parameter specifically.""" + pcd = o3d.geometry.PointCloud() + pcd.points = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normals = o3d.utility.Vector3dVector( + np.random.rand(100, 3) - 0.5 + ) + pcd.normalize_normals() + + # Test with different samples_per_node values + mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=5, samples_per_node=1.0 + ) + + mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + pcd, depth=5, samples_per_node=3.0 + ) + + assert len(mesh1.vertices) > 0 + assert len(mesh2.vertices) > 0 From 2576ca20f767f9a2d199187a345790b29af143af Mon Sep 17 00:00:00 2001 From: Chevi Koren Date: Sat, 7 Feb 2026 22:30:28 +0200 Subject: [PATCH 2/6] Update CHANGELOG for exposed Poisson parameters --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce980c4b417..29c885183e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## Main +- 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) - Upgrade stdgpu third-party library to commit d7c07d0. - 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). - Corrected documentation for Link Open3D in C++ projects (broken links). From c1b3d9c58f1e3751003eb259246ace3a56ce310c Mon Sep 17 00:00:00 2001 From: Chevi Koren Date: Sun, 8 Feb 2026 10:33:18 +0200 Subject: [PATCH 3/6] refactor: reduce test complexity and duplication --- .../test/geometry/test_poisson_parameters.py | 132 +++++------------- 1 file changed, 32 insertions(+), 100 deletions(-) diff --git a/python/test/geometry/test_poisson_parameters.py b/python/test/geometry/test_poisson_parameters.py index 2d84cb024ce..3950ad32057 100644 --- a/python/test/geometry/test_poisson_parameters.py +++ b/python/test/geometry/test_poisson_parameters.py @@ -10,80 +10,63 @@ import pytest -def test_poisson_default_parameters(): - """Test Poisson reconstruction with default parameters.""" - # Create a simple point cloud (sphere) +@pytest.fixture +def sample_point_cloud(): + """Create a simple point cloud for testing.""" pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector( np.random.rand(100, 3) - 0.5 ) - # Add normals pointing outward pcd.normals = o3d.utility.Vector3dVector( np.random.rand(100, 3) - 0.5 ) pcd.normalize_normals() - - # Run with default parameters - mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=6 - ) - + return pcd + + +def _assert_valid_mesh(mesh, densities): + """Helper to validate mesh and densities output.""" assert mesh is not None assert len(mesh.vertices) > 0 assert len(mesh.triangles) > 0 assert len(densities) == len(mesh.vertices) -def test_poisson_custom_parameters(): - """Test Poisson reconstruction with custom parameters.""" - # Create a simple point cloud - pcd = o3d.geometry.PointCloud() - pcd.points = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) - pcd.normals = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 +def test_poisson_default_parameters(sample_point_cloud): + """Test Poisson reconstruction with default parameters.""" + mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( + sample_point_cloud, depth=6 ) - pcd.normalize_normals() - - # Run with custom parameters + _assert_valid_mesh(mesh, densities) + + +@pytest.mark.parametrize("params,expected_valid", [ + ({"depth": 6, "full_depth": 4, "samples_per_node": 2.0, + "point_weight": 5.0, "confidence": 0.5, "exact_interpolation": True}, True), + ({"depth": 6, "full_depth": 3}, True), + ({"depth": 6, "full_depth": 5}, True), + ({"depth": 5, "samples_per_node": 1.0}, True), + ({"depth": 5, "samples_per_node": 3.0}, True), +]) +def test_poisson_with_various_parameters(sample_point_cloud, params, expected_valid): + """Test Poisson reconstruction with various parameter combinations.""" mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, - depth=6, - full_depth=4, - samples_per_node=2.0, - point_weight=5.0, - confidence=0.5, - exact_interpolation=True + sample_point_cloud, **params ) - - assert mesh is not None - assert len(mesh.vertices) > 0 - assert len(mesh.triangles) > 0 - assert len(densities) == len(mesh.vertices) + if expected_valid: + _assert_valid_mesh(mesh, densities) -def test_poisson_parameter_variation(): +def test_poisson_parameter_variation(sample_point_cloud): """Test that different parameters produce different results.""" - # Create a simple point cloud - np.random.seed(42) - pcd = o3d.geometry.PointCloud() - pcd.points = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) - pcd.normals = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) - pcd.normalize_normals() - # Run with default point_weight mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=5, point_weight=4.0 + sample_point_cloud, depth=5, point_weight=4.0 ) # Run with higher point_weight (should produce different result) mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=5, point_weight=10.0 + sample_point_cloud, depth=5, point_weight=10.0 ) # Meshes should be different (different vertex counts or positions) @@ -107,55 +90,4 @@ def test_poisson_backward_compatibility(): mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( pcd, depth=5, scale=1.1, linear_fit=False ) - - assert mesh is not None - assert len(mesh.vertices) > 0 - assert len(densities) == len(mesh.vertices) - - -def test_poisson_full_depth_parameter(): - """Test full_depth parameter specifically.""" - pcd = o3d.geometry.PointCloud() - pcd.points = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) - pcd.normals = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) - pcd.normalize_normals() - - # Test with different full_depth values - mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=6, full_depth=3 - ) - - mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=6, full_depth=5 - ) - - assert len(mesh1.vertices) > 0 - assert len(mesh2.vertices) > 0 - - -def test_poisson_samples_per_node_parameter(): - """Test samples_per_node parameter specifically.""" - pcd = o3d.geometry.PointCloud() - pcd.points = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) - pcd.normals = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) - pcd.normalize_normals() - - # Test with different samples_per_node values - mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=5, samples_per_node=1.0 - ) - - mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=5, samples_per_node=3.0 - ) - - assert len(mesh1.vertices) > 0 - assert len(mesh2.vertices) > 0 + _assert_valid_mesh(mesh, densities) From fcf9ab8dc56ef9e2c75f17f8aaae9de1210a2840 Mon Sep 17 00:00:00 2001 From: Chevi Koren Date: Sun, 22 Feb 2026 14:25:53 +0200 Subject: [PATCH 4/6] 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 --- CHANGELOG.md | 2 +- .../geometry/SurfaceReconstructionPoisson.cpp | 40 ++----- cpp/open3d/geometry/TriangleMesh.h | 27 +++-- .../gui/PickPointsInteractor.cpp | 3 +- cpp/pybind/geometry/trianglemesh.cpp | 3 +- .../test/geometry/test_poisson_parameters.py | 100 +++++++++--------- 6 files changed, 81 insertions(+), 94 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c885183e2..8405fcb2696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## Main -- 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) +- 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) - Upgrade stdgpu third-party library to commit d7c07d0. - 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). - Corrected documentation for Link Open3D in C++ projects (broken links). diff --git a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp index f3db42126ec..fafe5ec638a 100644 --- a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp +++ b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp @@ -403,8 +403,6 @@ void Execute(const open3d::geometry::PointCloud& pcd, int full_depth, float samples_per_node, float point_weight, - float confidence, - bool exact_interpolation, UIntPack) { static const int Dim = sizeof...(FEMSigs); typedef UIntPack Sigs; @@ -422,16 +420,16 @@ void Execute(const open3d::geometry::PointCloud& pcd, XForm xForm, iXForm; xForm = XForm::Identity(); - // Keep hardcoded internal parameters + // Keep other internal parameters hardcoded float datax = 32.f; int base_depth = 0; int base_v_cycles = 1; float confidence_bias = 0.f; float cg_solver_accuracy = 1e-3f; int iters = 8; - + // Parameters are now passed as function arguments: - // full_depth, samples_per_node, point_weight, confidence, exact_interpolation + // full_depth, samples_per_node, point_weight double startTime = Time(); Real isoValue = 0; @@ -467,31 +465,16 @@ void Execute(const open3d::geometry::PointCloud& pcd, pointStream.xform_ = &xForm; { - auto ProcessDataWithConfidence = [&](const Point& p, - Open3DData& d) { - Real l = (Real)d.normal_.norm(); - if (!l || l != l) return (Real)-1.; - return (Real)pow(l, confidence); - }; auto ProcessData = [](const Point& p, Open3DData& d) { Real l = (Real)d.normal_.norm(); if (!l || l != l) return (Real)-1.; d.normal_ /= l; return (Real)1.; }; - if (confidence > 0) { - pointCount = FEMTreeInitializer::template Initialize< - Open3DData>(tree.spaceRoot(), pointStream, depth, - samples, sampleData, true, - tree.nodeAllocators[0], tree.initializer(), - ProcessDataWithConfidence); - } else { - pointCount = FEMTreeInitializer::template Initialize< - Open3DData>(tree.spaceRoot(), pointStream, depth, - samples, sampleData, true, - tree.nodeAllocators[0], tree.initializer(), - ProcessData); - } + pointCount = FEMTreeInitializer::template Initialize< + Open3DData>(tree.spaceRoot(), pointStream, depth, samples, + sampleData, true, tree.nodeAllocators[0], + tree.initializer(), ProcessData); } iXForm = xForm.inverse(); @@ -618,7 +601,8 @@ void Execute(const open3d::geometry::PointCloud& pcd, // Add the interpolation constraints if (point_weight > 0) { profiler.start(); - if (exact_interpolation) { + // Use approximate interpolation (default behavior) + if (false) { iInfo = FEMTree:: template InitializeExactPointInterpolationInfo( tree, samples, @@ -728,9 +712,7 @@ TriangleMesh::CreateFromPointCloudPoisson(const PointCloud& pcd, int n_threads, int full_depth, float samples_per_node, - float point_weight, - float confidence, - bool exact_interpolation) { + float point_weight) { static const BoundaryType BType = DEFAULT_FEM_BOUNDARY; typedef IsotropicUIntPack< DIMENSION, FEMDegreeAndBType::Signature> @@ -756,7 +738,7 @@ TriangleMesh::CreateFromPointCloudPoisson(const PointCloud& pcd, std::vector densities; Execute(pcd, mesh, densities, static_cast(depth), width, scale, linear_fit, full_depth, samples_per_node, point_weight, - confidence, exact_interpolation, FEMSigs()); + FEMSigs()); ThreadPool::Terminate(); diff --git a/cpp/open3d/geometry/TriangleMesh.h b/cpp/open3d/geometry/TriangleMesh.h index 74edf172e2b..cf3a970eebf 100644 --- a/cpp/open3d/geometry/TriangleMesh.h +++ b/cpp/open3d/geometry/TriangleMesh.h @@ -547,13 +547,24 @@ class TriangleMesh : public MeshBase { /// estimate the positions of iso-vertices. /// \param n_threads Number of threads used for reconstruction. Set to -1 to /// automatically determine it. - /// \param full_depth Minimum depth for density estimation. Below this depth, - /// the octree is complete. - /// \param samples_per_node Minimum number of sample points per octree node. - /// \param point_weight Importance of point interpolation constraints (2.0 * FEM degree). - /// \param confidence Confidence exponent for normal length-based weighting (0 = no weighting). - /// \param exact_interpolation If true, use exact point interpolation constraints. + /// \param full_depth Minimum depth for density estimation (default: 5). + /// Below this depth, the octree is complete (fully subdivided). + /// Higher values provide more stability in sparse regions but consume more + /// memory. + /// Recommended range: 3-7. Use higher values (6-7) if your point cloud has + /// sparse regions. + /// \param samples_per_node Minimum number of sample points per octree node + /// (default: 1.5). Controls adaptive octree refinement based on local point + /// density. Lower values (e.g., 1.0) allow finer subdivision and capture + /// more detail but may increase noise. Higher values (e.g., 3.0) suppress + /// noise but may lose fine details. Recommended range: 1.0-3.0. + /// \param point_weight Importance of point interpolation constraints + /// (default: 4.0). Controls the trade-off between data fidelity and surface + /// smoothness. Higher values (e.g., 10.0) prioritize fitting input points + /// exactly, resulting in surfaces closer to the data. Lower values produce + /// smoother surfaces. Recommended range: 2.0-10.0. /// \return The estimated TriangleMesh, and per vertex density values that + /// can be used to trim the mesh. static std::tuple, std::vector> CreateFromPointCloudPoisson(const PointCloud &pcd, size_t depth = 8, @@ -563,9 +574,7 @@ class TriangleMesh : public MeshBase { int n_threads = -1, int full_depth = 5, float samples_per_node = 1.5f, - float point_weight = 4.0f, - float confidence = 0.0f, - bool exact_interpolation = false); + float point_weight = 4.0f); /// Factory function to create a tetrahedron mesh (trianglemeshfactory.cpp). /// the mesh centroid will be at (0,0,0) and \p radius defines the diff --git a/cpp/open3d/visualization/gui/PickPointsInteractor.cpp b/cpp/open3d/visualization/gui/PickPointsInteractor.cpp index 0633e28ac67..0871ed11a5c 100644 --- a/cpp/open3d/visualization/gui/PickPointsInteractor.cpp +++ b/cpp/open3d/visualization/gui/PickPointsInteractor.cpp @@ -74,8 +74,7 @@ class SelectionIndexLookup { std::string name; size_t start_index; - Obj(const std::string &n, size_t start) - : name(n), start_index(start) {}; + Obj(const std::string &n, size_t start) : name(n), start_index(start){}; bool IsValid() const { return !name.empty(); } }; diff --git a/cpp/pybind/geometry/trianglemesh.cpp b/cpp/pybind/geometry/trianglemesh.cpp index ea4d8b46be0..8172ef7c206 100644 --- a/cpp/pybind/geometry/trianglemesh.cpp +++ b/cpp/pybind/geometry/trianglemesh.cpp @@ -352,8 +352,7 @@ void pybind_trianglemesh_definitions(py::module &m) { "pcd"_a, "depth"_a = 8, "width"_a = 0, "scale"_a = 1.1, "linear_fit"_a = false, "n_threads"_a = -1, "full_depth"_a = 5, "samples_per_node"_a = 1.5f, - "point_weight"_a = 4.0f, "confidence"_a = 0.0f, - "exact_interpolation"_a = false) + "point_weight"_a = 4.0f) .def_static( "create_from_oriented_bounding_box", &TriangleMesh::CreateFromOrientedBoundingBox, diff --git a/python/test/geometry/test_poisson_parameters.py b/python/test/geometry/test_poisson_parameters.py index 3950ad32057..6ea87e07aac 100644 --- a/python/test/geometry/test_poisson_parameters.py +++ b/python/test/geometry/test_poisson_parameters.py @@ -10,16 +10,13 @@ import pytest -@pytest.fixture -def sample_point_cloud(): - """Create a simple point cloud for testing.""" +def _create_point_cloud(num_points=100): + """Helper to create a point cloud with normals.""" + np.random.seed(42) # Fixed seed for reproducible tests pcd = o3d.geometry.PointCloud() - pcd.points = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) + pcd.points = o3d.utility.Vector3dVector(np.random.rand(num_points, 3) - 0.5) pcd.normals = o3d.utility.Vector3dVector( - np.random.rand(100, 3) - 0.5 - ) + np.random.rand(num_points, 3) - 0.5) pcd.normalize_normals() return pcd @@ -32,62 +29,63 @@ def _assert_valid_mesh(mesh, densities): assert len(densities) == len(mesh.vertices) +@pytest.fixture +def sample_point_cloud(): + """Fixture that returns a simple point cloud for testing.""" + return _create_point_cloud() + + def test_poisson_default_parameters(sample_point_cloud): """Test Poisson reconstruction with default parameters.""" mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - sample_point_cloud, depth=6 - ) + sample_point_cloud, depth=6) _assert_valid_mesh(mesh, densities) -@pytest.mark.parametrize("params,expected_valid", [ - ({"depth": 6, "full_depth": 4, "samples_per_node": 2.0, - "point_weight": 5.0, "confidence": 0.5, "exact_interpolation": True}, True), - ({"depth": 6, "full_depth": 3}, True), - ({"depth": 6, "full_depth": 5}, True), - ({"depth": 5, "samples_per_node": 1.0}, True), - ({"depth": 5, "samples_per_node": 3.0}, True), +@pytest.mark.parametrize("params", [ + { + "depth": 6, + "full_depth": 4, + "samples_per_node": 2.0, + "point_weight": 5.0 + }, + { + "depth": 6, + "full_depth": 3 + }, + { + "depth": 6, + "full_depth": 5 + }, + { + "depth": 5, + "samples_per_node": 1.0 + }, + { + "depth": 5, + "samples_per_node": 3.0 + }, + { + "depth": 5, + "point_weight": 4.0 + }, + { + "depth": 5, + "point_weight": 10.0 + }, ]) -def test_poisson_with_various_parameters(sample_point_cloud, params, expected_valid): +def test_poisson_with_various_parameters(sample_point_cloud, params): """Test Poisson reconstruction with various parameter combinations.""" mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - sample_point_cloud, **params - ) - if expected_valid: - _assert_valid_mesh(mesh, densities) - - -def test_poisson_parameter_variation(sample_point_cloud): - """Test that different parameters produce different results.""" - # Run with default point_weight - mesh1, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - sample_point_cloud, depth=5, point_weight=4.0 - ) - - # Run with higher point_weight (should produce different result) - mesh2, _ = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - sample_point_cloud, depth=5, point_weight=10.0 - ) - - # Meshes should be different (different vertex counts or positions) - # We just check they both succeeded and have positive vertex counts - assert len(mesh1.vertices) > 0 - assert len(mesh2.vertices) > 0 + sample_point_cloud, **params) + _assert_valid_mesh(mesh, densities) def test_poisson_backward_compatibility(): """Test that old API calls still work (backward compatibility).""" - pcd = o3d.geometry.PointCloud() - pcd.points = o3d.utility.Vector3dVector( - np.random.rand(50, 3) - 0.5 - ) - pcd.normals = o3d.utility.Vector3dVector( - np.random.rand(50, 3) - 0.5 - ) - pcd.normalize_normals() - + pcd = _create_point_cloud(num_points=50) + # Old-style call without new parameters mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson( - pcd, depth=5, scale=1.1, linear_fit=False - ) + pcd, depth=5, scale=1.1, linear_fit=False) _assert_valid_mesh(mesh, densities) From 33fe4ed27048498c3b838b2cc20fcd9147e9b866 Mon Sep 17 00:00:00 2001 From: Chevi Koren Date: Mon, 23 Feb 2026 23:28:52 +0200 Subject: [PATCH 5/6] Remove dead code and clarify parameter comments - Remove unreachable exact_interpolation code path (if false block) - Update comment to clarify confidence and exact_interpolation were removed - Simplify code to only use approximate interpolation Addresses code review feedback from @ssheorey and Copilot AI --- .../geometry/SurfaceReconstructionPoisson.cpp | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp index fafe5ec638a..b06ef6d0819 100644 --- a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp +++ b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp @@ -420,8 +420,7 @@ void Execute(const open3d::geometry::PointCloud& pcd, XForm xForm, iXForm; xForm = XForm::Identity(); - // Keep other internal parameters hardcoded - float datax = 32.f; + // Other internal parameters remain hardcoded int base_depth = 0; int base_v_cycles = 1; float confidence_bias = 0.f; @@ -601,29 +600,17 @@ void Execute(const open3d::geometry::PointCloud& pcd, // Add the interpolation constraints if (point_weight > 0) { profiler.start(); - // Use approximate interpolation (default behavior) - if (false) { - iInfo = FEMTree:: - template InitializeExactPointInterpolationInfo( - tree, samples, - ConstraintDual( - targetValue, - (Real)point_weight * pointWeightSum), - SystemDual((Real)point_weight * - pointWeightSum), - true, false); - } else { - iInfo = FEMTree:: - template InitializeApproximatePointInterpolationInfo< - Real, 0>( - tree, samples, - ConstraintDual( - targetValue, - (Real)point_weight * pointWeightSum), - SystemDual((Real)point_weight * - pointWeightSum), - true, 1); - } + // Use approximate interpolation (always) + iInfo = FEMTree:: + template InitializeApproximatePointInterpolationInfo< + Real, 0>( + tree, samples, + ConstraintDual( + targetValue, + (Real)point_weight * pointWeightSum), + SystemDual((Real)point_weight * + pointWeightSum), + true, 1); tree.addInterpolationConstraints(constraints, solveDepth, *iInfo); profiler.dumpOutput("#Set point constraints:"); } From 9a29e4d645fc2983c7a44b46cd85cb737b6c7ead Mon Sep 17 00:00:00 2001 From: Chevi Koren Date: Sun, 8 Mar 2026 17:13:28 +0200 Subject: [PATCH 6/6] Fix missing datax parameter in Poisson surface reconstruction --- cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp index b06ef6d0819..1462ddb6093 100644 --- a/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp +++ b/cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp @@ -43,6 +43,9 @@ namespace { // The order of the B-Spline used to splat in data for color interpolation static const int DATA_DEGREE = 0; +// Default pull factor for color/auxiliary data interpolation (PoissonRecon +// --data default) +static const float DEFAULT_DATAX = 32.f; // The order of the B-Spline used to splat in the weights for density estimation static const int WEIGHT_DEGREE = 2; // The order of the B-Spline used to splat in the normals for constructing the @@ -426,6 +429,7 @@ void Execute(const open3d::geometry::PointCloud& pcd, float confidence_bias = 0.f; float cg_solver_accuracy = 1e-3f; int iters = 8; + float datax = DEFAULT_DATAX; // Parameters are now passed as function arguments: // full_depth, samples_per_node, point_weight @@ -602,8 +606,8 @@ void Execute(const open3d::geometry::PointCloud& pcd, profiler.start(); // Use approximate interpolation (always) iInfo = FEMTree:: - template InitializeApproximatePointInterpolationInfo< - Real, 0>( + template InitializeApproximatePointInterpolationInfo( tree, samples, ConstraintDual( targetValue,