Rust-native spatial computing
Point clouds · wgpu · COPC · RANSAC · ICP — native Rust, no C++ binding layer.
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.
| ⚡ 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 |
DBSCAN clustering and voxel occupancy grids, generated by examples/make_gifs.py through the Python bindings.
| 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.lasPartial 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.lazThe 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().
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.
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.
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.pyIndicative 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.
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 |
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.
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.pyFour 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.
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.
One dataflow, focused crates — each pipeline stage maps to the crate that implements it, all sitting on a small math/core/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 |
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 extensionAligned 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:
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.pyBoth 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.
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 -> targetAnd 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 depthGenerated 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.pngPrebuilt 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.
cargo test --workspace
cargo test -p spatialrust --features mvp
cargo doc --workspace --opencargo 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.lasGPU 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).
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))?;PCD/PLY/LAS/COPC -> voxel downsample -> normals -> plane RANSAC -> clustering -> ICP -> save
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.pyAfter maturin develop in crates/spatialrust-py/:
python crates/spatialrust-py/examples/pyg_pointnet_demo.pySee also crates/spatialrust-py/examples/make_gifs.py and examples/ml_preprocess.py.
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_previewOutputs: 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).
Upload docs/assets/social_preview.svg (or export to PNG) as the GitHub repository social image under Settings → General → Social preview.
Licensed under MIT OR Apache-2.0 at your option.
Footnotes
-
resize_pack_chwcombines Q11 bilinear resize,f32scaling/normalization, and planar CHW packing without an intermediate HWC image. Against OpenCV 4.13dnn.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 -
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
-
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
-
resize_rgb_to_graycombines 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 -
The VGA cell retains the Epic 111 historical baseline. The band-local 3×3/5×5
u8engine 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. ↩ -
The grayscale
u83×3 first-derivative path replaces the generic full-imagef64intermediate with parallel three-rowi16rings, writesf32directly, 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_u8additionally 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 -
Rectangular morphology was remeasured separately with OpenCV 4.13, OpenCL off, with both allocated and caller-owned-output Python API timing scopes.
MorphologyWorkspaceretains 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 -
The 3×3 fast path keeps inspectable intermediates opt-in, adds caller-owned output plus reusable
CannyWorkspace, and replaces the fulli32magnitude 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







