Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Main
- 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).
Expand Down
81 changes: 32 additions & 49 deletions cpp/open3d/geometry/SurfaceReconstructionPoisson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -400,6 +403,9 @@ void Execute(const open3d::geometry::PointCloud& pcd,
float width,
float scale,
bool linear_fit,
int full_depth,
float samples_per_node,
float point_weight,
UIntPack<FEMSigs...>) {
static const int Dim = sizeof...(FEMSigs);
typedef UIntPack<FEMSigs...> Sigs;
Expand All @@ -417,17 +423,16 @@ void Execute(const open3d::geometry::PointCloud& pcd,
XForm<Real, Dim + 1> xForm, iXForm;
xForm = XForm<Real, Dim + 1>::Identity();

float datax = 32.f;
// Other internal parameters remain hardcoded
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;
float datax = DEFAULT_DATAX;

// Parameters are now passed as function arguments:
// full_depth, samples_per_node, point_weight

double startTime = Time();
Real isoValue = 0;
Expand Down Expand Up @@ -463,31 +468,16 @@ void Execute(const open3d::geometry::PointCloud& pcd,
pointStream.xform_ = &xForm;

{
auto ProcessDataWithConfidence = [&](const Point<Real, Dim>& 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<Real, Dim>& 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<Dim, Real>::template Initialize<
Open3DData>(tree.spaceRoot(), pointStream, depth,
samples, sampleData, true,
tree.nodeAllocators[0], tree.initializer(),
ProcessDataWithConfidence);
} else {
pointCount = FEMTreeInitializer<Dim, Real>::template Initialize<
Open3DData>(tree.spaceRoot(), pointStream, depth,
samples, sampleData, true,
tree.nodeAllocators[0], tree.initializer(),
ProcessData);
}
pointCount = FEMTreeInitializer<Dim, Real>::template Initialize<
Open3DData>(tree.spaceRoot(), pointStream, depth, samples,
sampleData, true, tree.nodeAllocators[0],
tree.initializer(), ProcessData);
}
iXForm = xForm.inverse();

Expand Down Expand Up @@ -614,28 +604,17 @@ void Execute(const open3d::geometry::PointCloud& pcd,
// Add the interpolation constraints
if (point_weight > 0) {
profiler.start();
if (exact_interpolation) {
iInfo = FEMTree<Dim, Real>::
template InitializeExactPointInterpolationInfo<Real, 0>(
tree, samples,
ConstraintDual<Dim, Real>(
targetValue,
(Real)point_weight * pointWeightSum),
SystemDual<Dim, Real>((Real)point_weight *
pointWeightSum),
true, false);
} else {
iInfo = FEMTree<Dim, Real>::
template InitializeApproximatePointInterpolationInfo<
Real, 0>(
tree, samples,
ConstraintDual<Dim, Real>(
targetValue,
(Real)point_weight * pointWeightSum),
SystemDual<Dim, Real>((Real)point_weight *
pointWeightSum),
true, 1);
}
// Use approximate interpolation (always)
iInfo = FEMTree<Dim, Real>::
template InitializeApproximatePointInterpolationInfo<Real,
0>(
tree, samples,
ConstraintDual<Dim, Real>(
targetValue,
(Real)point_weight * pointWeightSum),
SystemDual<Dim, Real>((Real)point_weight *
pointWeightSum),
true, 1);
tree.addInterpolationConstraints(constraints, solveDepth, *iInfo);
profiler.dumpOutput("#Set point constraints:");
}
Expand Down Expand Up @@ -721,7 +700,10 @@ 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) {
static const BoundaryType BType = DEFAULT_FEM_BOUNDARY;
typedef IsotropicUIntPack<
DIMENSION, FEMDegreeAndBType</* Degree */ 1, BType>::Signature>
Expand All @@ -746,7 +728,8 @@ TriangleMesh::CreateFromPointCloudPoisson(const PointCloud& pcd,
auto mesh = std::make_shared<TriangleMesh>();
std::vector<double> densities;
Execute<float>(pcd, mesh, densities, static_cast<int>(depth), width, scale,
linear_fit, FEMSigs());
linear_fit, full_depth, samples_per_node, point_weight,
FEMSigs());

ThreadPool::Terminate();

Expand Down
23 changes: 21 additions & 2 deletions cpp/open3d/geometry/TriangleMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -547,15 +547,34 @@ 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 (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
Comment thread
Chevi-Koren marked this conversation as resolved.
/// can be used to to trim the mesh.
/// can be used to trim the mesh.
static std::tuple<std::shared_ptr<TriangleMesh>, std::vector<double>>
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);

/// Factory function to create a tetrahedron mesh (trianglemeshfactory.cpp).
/// the mesh centroid will be at (0,0,0) and \p radius defines the
Expand Down
3 changes: 1 addition & 2 deletions cpp/open3d/visualization/gui/PickPointsInteractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }
};

Expand Down
4 changes: 3 additions & 1 deletion cpp/pybind/geometry/trianglemesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,9 @@ 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)
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Python bindings are missing the confidence and exact_interpolation parameters that were mentioned in the PR description as being exposed. The PR description states that 5 parameters are being exposed, but only 3 are present in the Python bindings. Either add the missing parameters to match the description, or update the PR description to reflect that only 3 parameters are being exposed.

Suggested change
"point_weight"_a = 4.0f)
"point_weight"_a = 4.0f, "confidence"_a = false,
"exact_interpolation"_a = false)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

.def_static(
"create_from_oriented_bounding_box",
&TriangleMesh::CreateFromOrientedBoundingBox,
Expand Down
91 changes: 91 additions & 0 deletions python/test/geometry/test_poisson_parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# ----------------------------------------------------------------------------
# - 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 _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(num_points, 3) - 0.5)
pcd.normals = o3d.utility.Vector3dVector(
np.random.rand(num_points, 3) - 0.5)
pcd.normalize_normals()
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)


@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)
_assert_valid_mesh(mesh, densities)


@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):
"""Test Poisson reconstruction with various parameter combinations."""
mesh, densities = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(
sample_point_cloud, **params)
_assert_valid_mesh(mesh, densities)


def test_poisson_backward_compatibility():
"""Test that old API calls still work (backward compatibility)."""
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)
_assert_valid_mesh(mesh, densities)
Loading