Repository navigation
Add a low-memory mode for point-cloud DBSCAN - #7572
trueoneplusone wants to merge 4 commits into
Conversation
Query radius neighborhoods on demand when precompute_neighbors is false, while retaining parallel precomputation by default. Label points on discovery to keep the expansion frontier linear in both modes. Expose the option through legacy and tensor C++/Python APIs, document the memory and parallelism tradeoff, and cover dense neighborhoods, border ownership, radius boundaries, and real point-cloud labels.
|
Thanks for submitting this pull request! The maintainers of this repository would appreciate if you could update the CHANGELOG.md based on your changes. |
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
Avoid Cppcheck treating the new argument literal assignments as Boolean values assigned to pointers. The exposed signatures, defaults, and clustering behavior are unchanged.
|
The low-memory path and validation look very thorough. One API/ABI detail seems worth addressing before merge. Adding the defaulted "precompute_neighbors" parameter changes the C++ "ClusterDBSCAN" symbol, so this is source-compatible for rebuilt callers but not binary-compatible with code linked against the existing 3-argument overload. The PR description already notes that binary consumers would need to rebuild. Since this is an additive opt-in mode and the existing 3-argument behavior remains exactly the default, could we preserve the current overload and add the new form separately? For example, conceptually: ClusterDBSCAN(double eps, size_t min_points, ClusterDBSCAN(double eps, size_t min_points, with the existing overload forwarding to the new implementation using "precompute_neighbors=true". That would preserve the old exported symbol while still exposing the fourth argument through C++ and Python. It also seems aligned with the repository guidance to preserve existing public APIs unless the task requires changing them. AI assistance disclosure: reviewed with OpenAI ChatGPT GPT-5.6 Sol. |
Restore the legacy three-argument ClusterDBSCAN symbols for both point-cloud APIs while keeping the new four-argument low-memory overloads. Bind Python explicitly to the four-argument forms and verify both signatures in C++ tests.
|
Thanks for catching the ABI issue. The latest commit |
Type
Related to #1600.
Motivation and Context
ClusterDBSCANretains every radius neighborhood before clustering. Dense clouds can therefore require quadratic memory, as reported in #1600. The expansion work-list can also retain repeated copies of pending points; removing the neighborhood cache alone does not bound memory.This adds
precompute_neighbors=Falseto legacy and tensor point-cloud DBSCAN in C++ and Python. It performs exact radius queries on demand with O(N) auxiliary storage. The existing parallel precomputation remains the default. Dense clouds still require substantial computation, and the sequential search mode trades parallelism for lower memory use.Checklist
python util/check_style.py --apply --changed-only --no_parallelto apply Open3D code style to my code.Description
Plan followed: lock exact-label compatibility and linear auxiliary storage; add API and semantic regressions; implement the shared query path and bounded frontier; validate both public APIs against edge cases and the real point-cloud fixture; benchmark pristine, default, and low-memory modes with identical build settings. The original three-argument C++ symbols are preserved as forwarding overloads; existing source and already-linked binary callers keep the default precomputed path. Python stays bound explicitly to the four-argument overload.
Validation
Built pristine
b6c5e196and this patch as Release shared libraries, C++ tests, and Python bindings with identical CPU-only configuration (Ubuntu 24.04 under WSL2, GCC 13.3, Python 3.12).ABI compatibility follow-up (
9643cb4): exact three- and four-argument member-function signatures compile for both legacy and tensor point clouds. The rebuilt shared library exports all four symbols. The pristine pre-PR test binary was then run unchanged against the rebuilt library and both of its DBSCAN tests passed, proving existing linked callers still resolve the old symbols.ABI-fix validation: 5 focused C++ overload/edge-case tests passed; the Python DBSCAN suite passed 40 tests; changed-file style and whitespace checks passed.
build/bin/tests --gtest_filter='*ClusterDBSCAN*': 5 passed, including the 196,133-pointPLYPointCloudfixture and exact equality of both modes.pytest python/test/t/geometry/test_pointcloud.py -k cluster_dbscan: 40 passed. These exercise both public bindings, Float32/Float64 and CPU output-device preservation.Independent core-component/union-find oracle: 317 cases passed on pristine Open3D, and 634 comparisons passed across both patched modes, including seeded random clouds. This oracle does not reuse the expansion algorithm.
Progress enabled/disabled: both modes and both APIs return identical labels in 8 additional smoke checks.
Official style checks passed on Linux and Windows; both notebook edits are confined to Markdown documentation cells.
CUDA/SYCL and other operating-system builds were not run locally; this does not claim the complete Open3D test suite passed.
Measurements
Measurements use actual native C++ binaries, separately preserved pristine/patched libraries, GCC 13.3 Release, and the same build options. Hardware: AMD EPYC 9654, Ubuntu 24.04 under WSL2. Open3D/TBB and BLAS are limited to 8 threads. Compilation had finished before measuring; no CPU affinity was set.
Each cell is total process peak RSS (MiB) / clustering time (seconds), reporting medians of three fresh-process runs after one warmup, with rotated mode order. RSS comes from
/usr/bin/time -v; clustering time excludes input generation/loading and label output. These native measurements do not include Python import memory.For the real cloud at eps=0.05, the opt-in mode reduces total peak RSS by 85.6%, with 2.64x the pristine clustering time. For the complete-neighborhood 12,000-point synthetic case, RSS falls 97.4%, with 7.65x the time. Its three streaming runs ranged from 21.38 to 24.87 seconds. The default mode also benefits from removing duplicate pending entries. Small control-case RSS differences are measurement noise, not claimed improvements.
All 84 processes completed under an 8-GiB address-space limit and a 180-second timeout. Full int32 label SHA256 values matched across all three implementations and every repetition for each input, including numeric cluster IDs.
The real input is the unchanged, complete official
PLYPointCloudfragment.ply, MD5831ecffd4d7cbbbe02494c5c351aa6e5, with min_points=10. Dense synthetic inputs have diameter <0.14 and eps=1, so every point is a neighbor of every other point. Sparse inputs are all noise; the groups control has 60 disconnected clusters. These results are a memory/time tradeoff, not a claim of universal speedup.Compact C++ memory reproduction
After building/installing the patched library, save this as
reproduce_dbscan_memory.cpp:Use this
CMakeLists.txt:Repeat at 3,000 and 12,000 points. This short reproduction compares both modes of the patched library. To measure pristine Open3D, build the same program against a separately preserved pristine installation and replace the four-argument call with
cloud.ClusterDBSCAN(1.0, 10, false). Each invocation checks the complete expected label vector. The table above uses a separate harness that also records all label bytes, library paths and repeated-run statistics.