Skip to content

rsasaki0109/SpatialRust

Repository files navigation

SpatialRust

SpatialRust hero: public PCL table_scene_lms400 scan, voxel downsample, plane RANSAC, and Euclidean cluster labels from a real MVP pipeline run

Rust-native spatial computing
Point clouds · wgpu · COPC · RANSAC · ICP — native Rust, no C++ binding layer.

CI Docs Changelog License Rust 1.75+ wgpu

The hero GIF above is real MVP pipeline output (not a mockup): it uses the public PCL table_scene_lms400.pcd sample, voxel-downsamples it, RANSAC peels off the dominant plane, and Euclidean clustering lights up objects in color — every frame rendered straight from a live pipeline run.

SpatialRust MVP pipeline preview: RANSAC plane inliers, Euclidean cluster labels, and the pipeline stages

⚡ GPU-accelerated 🗂️ COPC-native 🦀 Pure Rust 🧩 Composable
explicit wgpu voxel and normal kernels, automatic CPU fallback bounds + LOD partial reads straight off disk — no full-tile load no C++ / FFI binding layer to fight one MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster label The same scene voxelized into a rotating 3D occupancy grid of cyan blocks

DBSCAN clustering and voxel occupancy grids, generated by examples/make_gifs.py through the Python bindings.

Why SpatialRust?

Typical C++ stack (PCL / Open3D / OpenCV bindings) SpatialRust
Core language C++ + FFI glue Native Rust
Vision runtime OpenCV linked into the app OpenCV optional for tests only — production vision is Rust
GPU path varies by wrapper wgpu voxel / normals with CPU fallback
COPC bolt-on scripts bounds + LOD queries in library & CLI
Pipeline glue code across image + cloud libs one MVP + north-star graph: IO → filter → segment → register → scene

One command from LAS/COPC to labeled clusters:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- scan.las labeled.las

Partial COPC read + pipeline — stream only the region of interest straight off disk, no full-tile load:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
  --bounds 0,0,-1,100,100,1 --resolution 0.5 scan.copc.laz roi.copc.laz

COPC partial read: a bounds box selects a region of interest from the full tile, then the recentered subset is read out to roi.copc.laz

Performance

The voxel downsampler runs on CPU or GPU (wgpu). The current end-to-end point_xyzi centroid rebaseline finds no GPU crossover through 2M points, so ExecutionPolicy::Auto stays on CPU for this mode. Explicit GPU execution is available for profiling and GPU-resident workflows; callers opt into it with without_gpu_min_points().

2026-07-16 end-to-end centroid voxel latency: CPU remains faster through 2M points, while GPU submit batching reduces the GPU path latency

End-to-end centroid filter latency (point_xyzi, leaf=4.0, release build):

Points CPU GPU Winner
10k ~0.252 ms ~8.18 ms CPU
65,536 ~1.72 ms ~16.0 ms CPU
100k ~2.64 ms ~21.0 ms CPU
200k ~5.09 ms ~24.5 ms CPU
500k ~11.6 ms ~35.8 ms CPU
750k ~18.3 ms ~55.0 ms CPU
1M ~23.9 ms ~65.9 ms CPU
2M ~47.3 ms ~105 ms CPU

The CPU values use the 100-sample Criterion rebaseline. GPU optimization probes use isolated 10-sample processes to bound driver allocation growth. GPU model identity is intentionally omitted; the run used a high-performance discrete adapter with the Vulkan backend. See the dated CPU receipt and GPU receipt.

Reproduce: cargo bench -p spatialrust-filtering --features filter-voxel-gpu --bench voxel_downsample.

Normal estimation has an optional wgpu path (GpuNormalEstimator, feature-normal-gpu). In radius mode the neighbor search runs entirely on the GPU via a uniform grid (covariance + Jacobi eigensolver included), which is up to ~50× faster than the CPU KD-tree estimator:

Points CPU (KD-tree) GPU grid Speedup
100k ~220 ms ~8.6 ms ~26×
200k ~442 ms ~15 ms ~29×
500k ~1.47 s ~29 ms ~50×

(A k-nearest mode that keeps neighbor search on the CPU is also available but only ~1.1× — see notes.) Reproduce: cargo bench -p spatialrust-features --features feature-normal-gpu --bench normals.

vs PCL

A reproducible, apples-to-apples comparison against PCL 1.15.1 — both libraries process the same public PCL table_scene_lms400.pcd scan (460,400 points) with matching parameters (harness). Values below are from a local Windows release run using MSYS2 g++ 16.1.0 and vcpkg; rerun the harness before publishing fresh cross-machine numbers.

powershell -ExecutionPolicy Bypass -File bench\pcl_comparison\run.ps1
Operation SpatialRust PCL
Radius Outlier Removal 0.0899 s 1.8784 s 20.89× faster
Statistical Outlier Removal 0.1664 s 2.0933 s 12.58× faster
Normal estimation (k=10) 0.1461 s 1.9750 s 13.52× faster
Voxel downsample 0.0104 s 0.0181 s 1.74× faster

SpatialRust wins 4 of 4 against this PCL run; voxel downsampling now uses a specialized XYZ centroid path with compact u32 voxel keys for the common min-origin case.

vs Open3D

An Open3D comparison harness is available at bench/open3d_comparison. It runs the same public PCL table_scene_lms400.pcd scan through SpatialRust and Open3D with matching voxel, normal, statistical outlier, and radius outlier parameters:

python bench/open3d_comparison/run.py

Indicative local result on one Windows machine (Open3D 0.19.0, Python 3.12, 460,400-point public PCL sample):

Operation SpatialRust Open3D
Voxel downsample 0.0132 s 0.0234 s 1.77× faster
Normal estimation 0.1997 s 0.4946 s 2.48× faster
Statistical Outlier Removal 0.2105 s 0.6565 s 3.12× faster
Radius Outlier Removal 0.1049 s 66.4701 s 633.65× faster

Record CPU, Open3D version, Python version, and thread settings before publishing new numbers.

vs OpenCV

SpatialRust is not “OpenCV rewritten in Rust.” OpenCV remains a strong tuned image kernel library; we use it as a correctness oracle (vision harness, RGB-D harness), not as a production dependency. SpatialRust instead focuses on an explicit, Rust-native spatial pipeline:

OpenCV-centered stack SpatialRust
Rust production deps Often pulls OpenCV/C++ through FFI No OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuity Image modules, then a separate point-cloud stack One repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devices cv::Mat habits; copies are easy to hide Explicit, named host↔device transfers; production APIs forbid silent copies
Safety C++ ABI + wrappers Public crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data model Arrays + ad-hoc metadata Versioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORB Private learned BRIEF table Documented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surface Not the primary product COPC bounds+LOD, MVP cloud pipeline, TSDF/USDA/Gaussian, ReleaseGate

CPU vision speed

Seeded, interleaved Python API timings on one Windows 11 host (OpenCV 4.10, 12 threads, OpenCL off; CPython 3.12; three warmups; VGA/1080p/4K use 20/8/3 samples). Each cell names the faster implementation and median-latency ratio; these are machine-specific measurements, not universal guarantees.

Workload VGA 1080p 4K
AI CHW preprocess, allocate SpatialRust 4.48× SpatialRust 9.27× SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocate SpatialRust 8.16× SpatialRust 14.56× SpatialRust 15.78×
Fused resize → normalized CHW, allocate1 SpatialRust 2.21× SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1 SpatialRust 3.56× SpatialRust 3.02×
Bilinear resize, allocate2 OpenCV 1.19× OpenCV 1.49× OpenCV 1.60×
Bilinear resize, reuse2 SpatialRust 1.10× OpenCV 2.40× OpenCV 2.01×
RGB to gray, allocate3 OpenCV 1.73× SpatialRust 1.03× SpatialRust 1.05×
RGB to gray, reuse3 OpenCV 1.22× OpenCV 1.08× OpenCV 1.03×
Fused 2× resize → gray, allocate4 SpatialRust 1.12× OpenCV 1.01×
Fused 2× resize → gray, reuse4 OpenCV 1.90× OpenCV 1.58×
Gaussian blur 5×55 OpenCV 139.02× OpenCV 1.74× OpenCV 1.68×
Sobel X 3×3, allocate6 OpenCV 1.07× SpatialRust 1.88× SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6 SpatialRust 3.81× SpatialRust 4.87× SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6 SpatialRust 2.95× SpatialRust 6.63× SpatialRust 8.68×
Morphology open 5×5, allocate7 OpenCV 4.51× OpenCV 1.98× OpenCV 2.30×
Morphology open 5×5, reuse7 OpenCV 1.90× SpatialRust 1.22× OpenCV 1.50×
Morphology open 511×511, allocate7 OpenCV 2.10× SpatialRust 2.61× SpatialRust 2.40×
Morphology open 511×511, reuse7 OpenCV 2.46× SpatialRust 3.25× SpatialRust 2.77×
Canny 3×3, reuse, document lines8 OpenCV 1.40× SpatialRust 1.38× SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8 OpenCV 2.29× SpatialRust 2.59× SpatialRust 2.75×
Exact Euclidean distance transform, allocate OpenCV 1.99× OpenCV 1.85× OpenCV 1.45×
Exact Euclidean distance transform, reuse OpenCV 1.02× OpenCV 1.06× SpatialRust 1.07×

The current CPU result is deliberately mixed: SpatialRust's fused typed CHW path wins, while OpenCV's tuned general-purpose image kernels lead the present SpatialRust scalar paths. Full medians, p95, dispersion, throughput, and raw samples are produced by the performance harness; the dated Epic 111 receipt records the exact environment and methodology.

The additive paired-gradient path keeps standalone Sobel compatibility while also exposing exact fused 3×3 L1 magnitude (abs(Gx) + abs(Gy)). On a newer OpenCV 4.13 receipt, the fused allocated Python call is 1.86× faster at 1080p, 2.19× at 4K, and 2.42× at 8K because SpatialRust writes one result instead of materializing paired gradients, two absolute-value images, and an addition result. Caller-owned reuse ties at 1080p and favors OpenCV at 4K/8K; OpenCV also remains faster for standalone spatialGradient. See the focused harness and dated receipt.

The EDT fast path is exact on the canonical masks and reduced the native 4K allocation benchmark from 451.63 ms to about 75 ms. With caller-owned output and DistanceTransformWorkspace, the optimized native canonical Criterion median is about 35 ms. The Python API comparison above gives SpatialRust a measured 1.07× 4K reuse lead, with maximum error zero; VGA and 1080p remain narrow OpenCV wins. See the acceleration receipt.

For AI detection post-processing, the seeded Python NMS harness uses identical float32 boxes, scores, and thresholds and requires exact kept-index parity before publishing timings:

NMS candidates OpenCV dnn.NMSBoxes SpatialRust nms Result
100 0.298 ms 0.033 ms SpatialRust 8.95×
1,000 8.720 ms 2.286 ms SpatialRust 3.82×
8,400 (YOLO-style) 407.086 ms 126.562 ms SpatialRust 3.22×

These Windows-host medians include each Python API call and returned indices; see the NMS harness and dated receipt.

Class-aware post-processing uses the same exact-index gate against OpenCV dnn.NMSBoxesBatched. SpatialRust stores kept indices by class, so candidates never scan already-kept boxes from unrelated classes:

Batched NMS profile OpenCV SpatialRust Result
1,000 candidates / 20 classes 3.538 ms 0.134 ms SpatialRust 26.38×
8,400 candidates / 80 classes 211.762 ms 2.178 ms SpatialRust 97.25×

Both profiles returned exactly the same globally score-ordered indices. See the batched NMS harness and dated receipt.

Soft-NMS retains overlapping detections while decaying their scores. The linear and Gaussian methods use an active-candidate max scan, cached box areas, and a non-overlap fast path:

Soft-NMS profile Method OpenCV SpatialRust Result
100 candidates Linear 0.092 ms 0.015 ms SpatialRust 6.33×
100 candidates Gaussian 0.108 ms 0.015 ms SpatialRust 7.40×
1,000 candidates Linear 5.636 ms 1.649 ms SpatialRust 3.42×
1,000 candidates Gaussian 6.047 ms 1.293 ms SpatialRust 4.68×
8,400 candidates Linear 310.709 ms 76.660 ms SpatialRust 4.05×
8,400 candidates Gaussian 213.696 ms 39.816 ms SpatialRust 5.37×

All profiles exactly matched OpenCV's kept-index order; updated float32 scores stayed within 1.79e-7. See the Soft-NMS harness and dated receipt.

Connected-component labeling uses horizontal runs plus union-find instead of per-pixel flood fill. Packed NumPy masks are borrowed without an input copy, and all non-zero uint8 values are foreground, matching OpenCV. Against OpenCV 4.13's explicit row-major SAUF algorithm on structured masks:

Profile Pattern OpenCV SAUF SpatialRust Result
VGA Segmentation blobs 1.284 ms 0.413 ms SpatialRust 3.11×
VGA Document lines 1.271 ms 0.352 ms SpatialRust 3.61×
1080p Segmentation blobs 6.763 ms 2.815 ms SpatialRust 2.40×
1080p Document lines 6.649 ms 2.407 ms SpatialRust 2.76×
4K Segmentation blobs 21.356 ms 9.838 ms SpatialRust 2.17×
4K Document lines 21.075 ms 8.606 ms SpatialRust 2.45×

Labels, areas, and bounding boxes matched exactly on every canonical profile and 320 additional seeded randomized 4/8-connectivity cases. The speed claim is limited to the named structured masks; dense random noise still favors OpenCV. See the connected-components harness and dated receipt.

Vision accuracy

The same deterministic RGB inputs passed all VGA, 1080p, and 4K gates:

Workload OpenCV comparison result at VGA / 1080p / 4K
Bilinear resize Canonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to gray Max error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → gray Exact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocess Max float error 5.96e-8
Fused resize → normalized CHW Exact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blur Canonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3 Exact values (max error 0)
Morphology open 5×5 Exact pixels (max error 0)
Canny Precision, recall, F1, and IoU all 1.0
Exact Euclidean distance transform Exact values on canonical profiles; separate irregular-mask max float error 9.54e-7
Connected components (SAUF ordering) Exact labels, areas, and bounding boxes on structured profiles and 320 randomized cases

The broader correctness harness also checks filters, analysis, keypoints, matching, and geometry with documented tolerances (exact pixels where we claim parity; residual/translation/disparity tolerances where OpenCV's private contracts differ). RGB-D unprojection tracks cv.rgbd.depthTo3d to ~1e-5 m.

On dense H×W×3 XYZ (320×240, OpenCL off, local Windows laptop), spatialrust.depth_to_xyz beats OpenCV rgbd.depthTo3d in the RGB-D harness — about 1.4–1.5× when both allocate, and about 2.1–2.2× when both fill a reused buffer (out= / OpenCV points3d). Colored rgbd_to_point_cloud is about 20× faster than OpenCV depthTo3d + NumPy mask/color gather. Re-run the harness before quoting numbers elsewhere; x86_64 builds use an audited AVX2 fill when available.

python bench\opencv_vision_comparison\run.py
python bench\opencv_vision_comparison\performance.py
python bench\opencv_rgbd_comparison\run.py
python bench\opencv_nms_comparison\performance.py

Registration methods

Four registration backends, compared on a synthetic box corner (7500 points, small misalignment):

Method Recovery error Time Notes
ICP (point-to-point) 0.0196 m ~147 ms slow to converge on planar surfaces
Point-to-plane ICP 0.0007 m ~6.5 ms best speed/accuracy balance
GICP 0.0006 m ~26 ms most accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT 0.0008 m ~8.7 ms voxel distributions + Levenberg–Marquardt

See notes. Reproduce: cargo bench -p spatialrust-registration --features register-icp,register-icp-point-to-plane,register-gicp,register-ndt --bench registration.

Status

MVP pipeline is implemented end-to-end: PCD/PLY/LAS/COPC IO, voxel downsampling (CPU + optional wgpu), normals, RANSAC plane segmentation, Euclidean clustering, region growing, and registration (ICP point-to-point/point-to-plane, GICP, NDT). See docs/ARCHITECTURE.md for the master design.

Browse the published algorithm catalog, Rust API reference, and Vision 2 performance program. The fail-closed Vision 2 release receipt and migration guide record the canonical performance/resource budgets and explicit CPU/GPU ownership guidance.

Workspace crates

One dataflow, focused crates — each pipeline stage maps to the crate that implements it, all sitting on a small math/core/search foundation:

SpatialRust architecture: Load → Voxel → Normals → Plane → Cluster → Register → Save dataflow with implementing crates, wgpu voxel acceleration, and the core/math/search foundation

Crate Role
spatialrust Meta crate / stable re-exports
spatialrust-core Point schema, metadata, execution traits
spatialrust-math Vec/Mat/Pose math primitives
spatialrust-image Typed image buffers and zero-copy strided views
spatialrust-image-io Bounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensor Runtime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-ai Explicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-camera Pinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-vision CPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-io Point cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-search KD-tree search, k-NN / radius graphs
spatialrust-filtering Voxel / FPS downsample, outlier removal, crop, MLS
spatialrust-features Normals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentation RANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registration ICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transform Affine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelize Voxel occupancy grids and LiDAR range images
spatialrust-metrics Chamfer / Hausdorff cloud distances
spatialrust-pipeline Composable MVP pipelines
spatialrust-gpu wgpu runtime and voxel kernels

Python

The whole pipeline is callable from Python with NumPy interop — no C++ binding layer:

import numpy as np
import spatialrust as sr

cloud = sr.PointCloud.from_xyz(points)            # (N, 3) float32 -> native cloud
result = sr.run_pipeline(cloud, leaf_size=0.1, cluster_tolerance=0.3)

print(result.plane_normal)                        # dominant plane normal (nx, ny, nz)
labels = result.labels()                          # (N,) int32 cluster ids
sr.write("labeled.las", result.output)            # LAS/PCD/PLY/COPC by extension

Aligned RGB-D images feed the same point-cloud pipeline without an OpenCV runtime dependency:

depth = np.ones((480, 640), dtype=np.float32)
rgb = np.zeros((480, 640, 3), dtype=np.uint8)
cloud = sr.rgbd_to_point_cloud(
    depth, rgb, fx=525.0, fy=525.0, cx=319.5, cy=239.5
)
result = sr.run_pipeline(cloud, leaf_size=0.03)

Rust users enable camera-rgbd; projection/unprojection supports optional Brown–Conrady radial and tangential distortion. The reproducible numerical and timing comparison against OpenCV is under bench/opencv_rgbd_comparison/.

The vision-full feature adds an AI-ready CPU image path with explicit data ownership: nearest/bilinear/bicubic/area resize, letterbox and CHW normalization, color conversion, remap/warps, IoU/NMS/Soft-NMS, connected components, contours, RLE masks, and depth/confidence/flow/point maps. Dense maps bridge explicitly to calibrated cameras and point clouds; no API performs a hidden device transfer.

model_image, transform = sr.letterbox_image(rgb, 640, 640)
chw = sr.normalize_image_chw(model_image)                  # float32 (3,H,W)
keep = sr.nms(boxes_xyxy, scores, iou_threshold=0.5)
cloud = sr.point_map_to_point_cloud(points, confidence, 0.5)

The reproducible algorithm comparison is in bench/opencv_vision_comparison/; the complete synthetic demo is crates/spatialrust-py/examples/vision_ai_pipeline.py.

The video E2E demo generates and reloads the same deterministic 12-frame PGM sequence in Rust and Python, estimates dense optical flow, detects the two moving objects, and preserves track IDs through the native IoU tracker:

Two textured objects moving in opposite directions with SpatialRust dense optical-flow vectors and stable track IDs 1 and 2

cargo run -p spatialrust --no-default-features --features image-io-standard,vision-video --example video_tracking_e2e
maturin develop --release --manifest-path crates/spatialrust-py/Cargo.toml
.venv/Scripts/python.exe crates/spatialrust-py/examples/video_tracking_e2e.py

Both paths assert object-center flow (+2,+1) / (-2,-1) for all 11 frame pairs and stable track IDs 1,2. The Python run regenerates the GIF above.

The same feature includes Harris, Shi–Tomasi, exact FAST-9/16, multi-scale ORB, and checked Hamming/L2 descriptor matching. Python exposes orb_features and NumPy matcher functions; OpenCV is used only by the numerical comparison suite.

An ONNX Runtime wheel is opt-in (maturin develop --features onnxruntime). Its Python API uses named CPU I/O Binding by default; copy=True is the explicit fallback for inputs that must be repacked:

session = sr.OnnxRuntimeSession("model.onnx", deterministic=True)
input_tensor = sr.tensor_copy_from_numpy(chw)
outputs = session.run({"images": input_tensor})
scores = np.from_dlpack(outputs["scores"])

The Rust features are ai, ai-onnxruntime, and separate ai-onnxruntime-{cuda,tensorrt,directml} provider gates. The optional ONNX Runtime adapter currently has a feature-specific Rust 1.88 MSRV; it does not raise the default workspace MSRV.

Top-down view of clusters segmented from the public PCL table_scene_lms400 point cloud via a single Python run_pipeline() call

Registration is callable too — align two scans with ICP / point-to-plane / GICP / NDT:

result = sr.register_gicp(source, target)   # also: register_icp / _point_to_plane / _ndt
T = result.transform()                       # 4x4 matrix mapping source -> target

Before/after of two scans aligned by SpatialRust: a misaligned orange source scan snaps onto the blue target after registration

And it's a preprocessing front-end for learned models — turn a scan into model-ready tensors in a few calls (clean → unit-sphere normalize → FPS → voxel grid / range image / k-NN edge_index):

sampled = sr.farthest_point_sampling(sr.normalize_unit_sphere(cloud), 2048)
occ, origin, vsize = sr.voxelize(sampled, voxel_size=0.06)   # (nz, ny, nx) occupancy
edge_index = sr.knn_graph(sampled, k=16)                     # (2, E) PyG-style graph
rimg = sr.range_image(sampled, width=256, height=64)         # (H, W) LiDAR depth

Four panels: FPS-sampled points, a voxel occupancy grid, a LiDAR range image, and a k-NN graph — the model-ready tensors SpatialRust produces from one scan

Generated by examples/ml_preprocess.py — see the Python README.

Build the extension with maturin and reproduce the Python previews from the same public sample:

pip install maturin numpy matplotlib
cd crates/spatialrust-py && maturin develop --release
mkdir -p ../../target/readme-data
curl -L --fail -o ../../target/readme-data/table_scene_lms400.pcd \
  https://raw.githubusercontent.com/PointCloudLibrary/data/master/tutorials/table_scene_lms400.pcd
PUBLIC=../../target/readme-data/table_scene_lms400.pcd
python examples/segment_room.py \
  --input "$PUBLIC" \
  --leaf-size 0.03 --plane-distance 0.025 \
  --cluster-tolerance 0.06 --min-cluster-size 8 \
  --png ../../docs/assets/python_segmentation.png
python examples/register_scans.py \
  --input "$PUBLIC" --leaf 0.05 \
  --png ../../docs/assets/python_registration.png
python examples/ml_preprocess.py \
  --input "$PUBLIC" \
  --png ../../docs/assets/ml_preprocess.png

Prebuilt abi3 wheels (CPython 3.8+) are produced by CI and published to PyPI on tagged releases (pip install spatialrust). See crates/spatialrust-py/README.md for the full Python API.

Quick start

cargo test --workspace
cargo test -p spatialrust --features mvp
cargo doc --workspace --open

CLI (MVP pipeline)

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- input.las output.las
cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
  --leaf-size 0.2 --voxel-policy auto scan.copc.laz out.copc.laz
cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
  --bounds 0,0,-1,100,100,1 scan.copc.laz roi.copc.laz
cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
  --bounds 0,0,-1,100,100,1 --resolution 0.5 scan.copc.laz roi.copc.laz
cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
  --resolution 0.5 scan.copc.laz coarse.copc.laz
cargo run -p spatialrust --features pipeline-mvp-gpu --bin spatialrust-mvp -- \
  --plane-policy auto --normal-policy auto --cluster-policy auto scan.las labeled.las

GPU stages (wgpu) share one policy surface: --voxel-policy, --plane-policy, --normal-policy, --cluster-policy (or MvpPipelineConfig::*_policy). Auto selects GPU from ~2k points for plane/cluster MVP paths and ~10k for normals. When GPU normals run without an explicit search_radius, MVP derives one from the voxel leaf (normal_gpu_radius_scale, default 2.0) to use the fast grid path. Full-cloud plane bench: ~11× speedup (bench/ransac_plane/). Cluster bench: bench/euclidean_cluster/ — grid union-find path matches CPU clusters; ~1.15× on MVP ~1.4k pts, ~1.0× on 460k full cloud (GPU KD-tree BFS path, Epic 69).

Library

Load or save by file extension:

use spatialrust::{read_point_cloud_file, write_point_cloud_file};

let cloud = read_point_cloud_file("scan.las")?;
write_point_cloud_file("output.ply", &cloud)?;

COPC partial read:

use spatialrust::{read_copc_file_with_query, CopcBounds, CopcQuery};

let bounds = CopcBounds::from_ranges((0.0, 100.0), (0.0, 100.0), (-1.0, 1.0));
let cloud = read_copc_file_with_query("scan.copc.laz", &CopcQuery::bounds(bounds))?;

MVP target pipeline

PCD/PLY/LAS/COPC -> voxel downsample -> normals -> plane RANSAC -> clustering -> ICP -> save

Terminal-style receipt of a real SpatialRust MVP run on the public PCL table_scene_lms400 cloud: left panel shows the evolving top-down result, right panel types measured load, voxel, plane, and cluster counts

GPU voxel downsampling (wgpu) is available behind features. ExecutionPolicy::Auto currently keeps centroid voxel filtering on CPU because the latest end-to-end receipt found no GPU crossover through 2M points. Explicit GPU execution remains available with the threshold disabled. GPU plane, normal, and Euclidean clustering use the same policy flags (--plane-policy, --normal-policy, --cluster-policy).

cargo test -p spatialrust-gpu --features gpu-wgpu
cargo test -p spatialrust --features filter-voxel-gpu
cargo test -p spatialrust --features mvp,pipeline-mvp-gpu --test mvp_public_copc
cargo test -p spatialrust --features mvp mvp_copc_pipeline_roundtrip
cargo test -p spatialrust --features mvp mvp_copc_query_pipeline
python bench/public_copc/run.py
python bench/ransac_plane/run.py
python bench/euclidean_cluster/run.py

Python (PyG demo)

After maturin develop in crates/spatialrust-py/:

python crates/spatialrust-py/examples/pyg_pointnet_demo.py

See also crates/spatialrust-py/examples/make_gifs.py and examples/ml_preprocess.py.

README visuals

The main README pipeline visuals use the public PCL table_scene_lms400.pcd sample, cached under target/readme-data/ at generation time rather than committed to the repository. Regenerate them with:

cargo run -p spatialrust --features mvp --example readme_mvp_preview

Outputs: readme_hero.gif (header), readme_mvp_preview.svg (pipeline panel), copc_query.gif (COPC partial read), benchmark_voxel.svg (Performance chart), architecture.svg (crates diagram), readme_mvp_pipeline.gif (pipeline receipt: measured log + top-down result), and social_preview.svg.

Use SPATIALRUST_README_CLOUD=/path/to/cloud.pcd to render the same assets from another local public dataset.

The rotating clusters_rotating.gif and voxelize_rotating.gif are generated through the Python bindings from the same public sample: python crates/spatialrust-py/examples/make_gifs.py --input target/readme-data/table_scene_lms400.pcd (needs maturin develop + Matplotlib/Pillow).

Social preview

Upload docs/assets/social_preview.svg (or export to PNG) as the GitHub repository social image under Settings → General → Social preview.

License

Licensed under MIT OR Apache-2.0 at your option.

Footnotes

  1. resize_pack_chw combines Q11 bilinear resize, f32 scaling/normalization, and planar CHW packing without an intermediate HWC image. Against OpenCV 4.13 dnn.blobFromImage, allocated calls measured 1.617 ms versus 3.570 ms for 1080p→640×640 and 2.117 ms versus 4.272 ms for 4K→640×640. The 4K→1280×720 profile measured 3.592 ms versus 8.359 ms (SpatialRust 2.33×). Caller-owned SpatialRust output is 3.02×–3.56× faster than OpenCV allocation. Three hundred randomized cases are bit-exact with the SpatialRust unfused path and differ from OpenCV by at most 1/255. See the focused harness. 2

  2. The packed RGB8 half-scale path precomputes arbitrary-scale Q11 sampling coefficients and specializes exact 2× downsampling as a row-parallel 2×2 average. On the OpenCV 4.13 focused receipt, caller-owned VGA output measured 0.120 ms versus 0.133 ms (SpatialRust 1.10×); 1080p, 4K, and 8K reuse remain OpenCV wins by 2.40×, 2.01×, and 1.85×. Canonical half-scale pixels are exact, and 300 arbitrary-size cases have maximum absolute error 1. See the focused harness. 2

  3. The packed RGB8 Q14 BT.601 path uses size-aware Rayon blocks and CPU target-feature dispatch. On the OpenCV 4.13 focused receipt, allocated SpatialRust calls measured 0.825 ms versus 0.850 ms at 1080p and 2.338 ms versus 2.452 ms at 4K. At 8K, caller-owned reuse measured 5.754 ms versus 5.885 ms (SpatialRust 1.02×). VGA and 1080p/4K reuse remain narrow OpenCV wins. Three hundred randomized cases retain maximum absolute error 1. See the focused harness. 2

  4. resize_rgb_to_gray combines the reusable Q11 bilinear plan and Q14 BT.601 conversion without materializing an intermediate RGB image. For the canonical 1920×1080→960×540 allocated pipeline, SpatialRust measured 0.677 ms versus OpenCV's two-call 0.755 ms (1.12×). The allocated 4K→1080p result was effectively tied (2.687 ms versus 2.665 ms), while OpenCV leads 8K allocation and every caller-owned-output profile. The fused result is bit-exact with SpatialRust's unfused path; 300 randomized cases and canonical profiles differ from OpenCV by at most 1/255. See the focused harness. 2

  5. The VGA cell retains the Epic 111 historical baseline. The band-local 3×3/5×5 u8 engine supersedes the 1080p/4K cells on the same Windows host with OpenCV 4.13: 3.443 ms vs 1.983 ms at 1080p and 12.402 ms vs 7.397 ms at 4K. Caller-output medians were 3.054/1.473 ms at 1080p and 10.635/5.169 ms at 4K (SpatialRust/OpenCV). The band pipeline improves the prior SpatialRust allocated medians by 1.80× and 1.70× respectively while retaining the existing error boundary. OpenCV still leads this standalone operation.

  6. The grayscale u8 3×3 first-derivative path replaces the generic full-image f64 intermediate with parallel three-row i16 rings, writes f32 directly, and borrows packed NumPy input without copying. Against OpenCV 4.13, standalone allocation measured 1.134 ms versus 2.137 ms at 1080p and 3.737 ms versus 7.582 ms at 4K, reversing the former 20.31×–23.30× deficits while retaining max error zero. VGA remains a narrow OpenCV win. sobel_threshold_3x3_u8 additionally fuses signed Sobel, absolute saturation, and binary threshold; it wins 3.81×–6.64× allocated and 2.95×–8.68× with caller-owned output. Three hundred randomized X/Y cases are bit-exact. See the focused harness. 2 3

  7. Rectangular morphology was remeasured separately with OpenCV 4.13, OpenCL off, with both allocated and caller-owned-output Python API timing scopes. MorphologyWorkspace retains all full-image and per-worker line scratch; out= retains object identity. The separable sliding min/max path is bit-exact across 980 randomized operation cases. A centered 5×5 Replicate path uses fixed extrema and direct row-major vertical passes instead of prefix/suffix buffers and two transposes. It cuts the old 5×5 gaps by 6.6×–31.8× and wins 1080p reuse by 1.22× on the dated host; OpenCV still leads the other 5×5 profiles. See the focused harness, small-kernel receipt, and workspace receipt. 2 3 4

  8. The 3×3 fast path keeps inspectable intermediates opt-in, adds caller-owned output plus reusable CannyWorkspace, and replaces the full i32 magnitude image with a parallel three-row-per-worker ring. When no weak edges exist, it also skips unnecessary hysteresis traversal. Weak-candidate frontier seeding avoids pushing every initial strong edge on dense noise. The focused OpenCV 4.13 receipt is bit-exact across 300 randomized images. Document-line reuse medians are OpenCV/SpatialRust 3.075/2.221 ms at 1080p and 11.832/8.034 ms at 4K. Sensor-noise reuse is a SpatialRust win at 1080p and 4K, while VGA remains an OpenCV win. Native 4K document lines improved from 96.914 ms inspectable to the allocation-light path. 2

About

Rust-native spatial computing for point clouds, computer vision, and GPU compute -- no C++/FFI layer.

Topics

Resources

Stars

16 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors