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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

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.

CIDocsChangelogLicenseRust 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 fallbackbounds + LOD partial reads straight off disk — no full-tile loadno C++ / FFI binding layer to fightone MVP crate: IO → filter → segment → register

A multi-object point cloud rotating, each object colored by its DBSCAN cluster labelThe 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 languageC++ + FFI glueNative Rust
Vision runtimeOpenCV linked into the appOpenCV optional for tests only — production vision is Rust
GPU pathvaries by wrapperwgpu voxel / normals with CPU fallback
COPCbolt-on scriptsbounds + LOD queries in library & CLI
Pipelineglue code across image + cloud libsone 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):

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

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:

PointsCPU (KD-tree)GPU gridSpeedup
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
OperationSpatialRustPCL
Radius Outlier Removal0.0899 s1.8784 s20.89× faster
Statistical Outlier Removal0.1664 s2.0933 s12.58× faster
Normal estimation (k=10)0.1461 s1.9750 s13.52× faster
Voxel downsample0.0104 s0.0181 s1.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):

OperationSpatialRustOpen3D
Voxel downsample0.0132 s0.0234 s1.77× faster
Normal estimation0.1997 s0.4946 s2.48× faster
Statistical Outlier Removal0.2105 s0.6565 s3.12× faster
Radius Outlier Removal0.1049 s66.4701 s633.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 stackSpatialRust
Rust production depsOften pulls OpenCV/C++ through FFINo OpenCV in the Rust runtime — pure Rust crates; OpenCV only in optional Python comparison benches
2D → 3D continuityImage modules, then a separate point-cloud stackOne repo: filters/Feature2D/geometry → RGB-D → clouds → wgpu → sync/scene/export
Memory / devicescv::Mat habits; copies are easy to hideExplicit, named host↔device transfers; production APIs forbid silent copies
SafetyC++ ABI + wrappersPublic crates keep #![deny(unsafe_code)] outside audited FFI/GPU boundaries
Data modelArrays + ad-hoc metadataVersioned SpatialRecord, schema evolution, episodes, MCAP XYZ, ROS 2 CDR PointCloud2
Reproducible ORBPrivate learned BRIEF tableDocumented fixed-seed BRIEF with interoperable Hamming distances
3D / robotics surfaceNot the primary productCOPC 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.

WorkloadVGA1080p4K
AI CHW preprocess, allocateSpatialRust 4.48×SpatialRust 9.27×SpatialRust 9.14×
AI CHW preprocess, reuse vs OpenCV allocateSpatialRust 8.16×SpatialRust 14.56×SpatialRust 15.78×
Fused resize → normalized CHW, allocate1SpatialRust 2.21×SpatialRust 2.02×
Fused resize → normalized CHW, reuse vs OpenCV allocate1SpatialRust 3.56×SpatialRust 3.02×
Bilinear resize, allocate2OpenCV 1.19×OpenCV 1.49×OpenCV 1.60×
Bilinear resize, reuse2SpatialRust 1.10×OpenCV 2.40×OpenCV 2.01×
RGB to gray, allocate3OpenCV 1.73×SpatialRust 1.03×SpatialRust 1.05×
RGB to gray, reuse3OpenCV 1.22×OpenCV 1.08×OpenCV 1.03×
Fused 2× resize → gray, allocate4SpatialRust 1.12×OpenCV 1.01×
Fused 2× resize → gray, reuse4OpenCV 1.90×OpenCV 1.58×
Gaussian blur 5×55OpenCV 139.02×OpenCV 1.74×OpenCV 1.68×
Sobel X 3×3, allocate6OpenCV 1.07×SpatialRust 1.88×SpatialRust 2.03×
Fused abs(Sobel X) → binary mask, allocate6SpatialRust 3.81×SpatialRust 4.87×SpatialRust 6.64×
Fused abs(Sobel X) → binary mask, reuse6SpatialRust 2.95×SpatialRust 6.63×SpatialRust 8.68×
Morphology open 5×5, allocate7OpenCV 4.51×OpenCV 1.98×OpenCV 2.30×
Morphology open 5×5, reuse7OpenCV 1.90×SpatialRust 1.22×OpenCV 1.50×
Morphology open 511×511, allocate7OpenCV 2.10×SpatialRust 2.61×SpatialRust 2.40×
Morphology open 511×511, reuse7OpenCV 2.46×SpatialRust 3.25×SpatialRust 2.77×
Canny 3×3, reuse, document lines8OpenCV 1.40×SpatialRust 1.38×SpatialRust 1.47×
Canny 3×3, reuse, sensor noise8OpenCV 2.29×SpatialRust 2.59×SpatialRust 2.75×
Exact Euclidean distance transform, allocateOpenCV 1.99×OpenCV 1.85×OpenCV 1.45×
Exact Euclidean distance transform, reuseOpenCV 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 candidatesOpenCV dnn.NMSBoxesSpatialRust nmsResult
1000.298 ms0.033 msSpatialRust 8.95×
1,0008.720 ms2.286 msSpatialRust 3.82×
8,400 (YOLO-style)407.086 ms126.562 msSpatialRust 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 profileOpenCVSpatialRustResult
1,000 candidates / 20 classes3.538 ms0.134 msSpatialRust 26.38×
8,400 candidates / 80 classes211.762 ms2.178 msSpatialRust 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 profileMethodOpenCVSpatialRustResult
100 candidatesLinear0.092 ms0.015 msSpatialRust 6.33×
100 candidatesGaussian0.108 ms0.015 msSpatialRust 7.40×
1,000 candidatesLinear5.636 ms1.649 msSpatialRust 3.42×
1,000 candidatesGaussian6.047 ms1.293 msSpatialRust 4.68×
8,400 candidatesLinear310.709 ms76.660 msSpatialRust 4.05×
8,400 candidatesGaussian213.696 ms39.816 msSpatialRust 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:

ProfilePatternOpenCV SAUFSpatialRustResult
VGASegmentation blobs1.284 ms0.413 msSpatialRust 3.11×
VGADocument lines1.271 ms0.352 msSpatialRust 3.61×
1080pSegmentation blobs6.763 ms2.815 msSpatialRust 2.40×
1080pDocument lines6.649 ms2.407 msSpatialRust 2.76×
4KSegmentation blobs21.356 ms9.838 msSpatialRust 2.17×
4KDocument lines21.075 ms8.606 msSpatialRust 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:

WorkloadOpenCV comparison result at VGA / 1080p / 4K
Bilinear resizeCanonical half-scale exact; 300 arbitrary-size cases max error 1/255
RGB to grayMax error 1/255; 99.72%–99.74% exact pixels across VGA–8K
Fused bilinear resize → grayExact versus SpatialRust unfused; OpenCV max error 1/255 across 300 randomized cases and 1080p–8K half reductions
AI CHW preprocessMax float error 5.96e-8
Fused resize → normalized CHWExact versus SpatialRust unfused; OpenCV max float error 0.003921628 across 300 randomized cases
Gaussian blurCanonical 5×5 profiles exact; 300 randomized 3×3/5×5/7×7 cases max error 2/255
Sobel X 3×3Exact values (max error 0)
Morphology open 5×5Exact pixels (max error 0)
CannyPrecision, recall, F1, and IoU all 1.0
Exact Euclidean distance transformExact 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):

MethodRecovery errorTimeNotes
ICP (point-to-point)0.0196 m~147 msslow to converge on planar surfaces
Point-to-plane ICP0.0007 m~6.5 msbest speed/accuracy balance
GICP0.0006 m~26 msmost accurate; per-point covariance (optional GPU covariance ~1.7×, register-gicp-gpu)
NDT0.0008 m~8.7 msvoxel 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 and docs/FEATURE_MATRIX.md for the optional-feature and CPU/GPU execution contract.

The opt-in Visual stack adds borrowed visualization contracts, explicit wgpu rendering, native inspection/debug overlays, bounded COPC LOD, and shared Web/Python/Jupyter viewer state. Start with the visualization guide, then see the visual-1 migration policy and release receipt.

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.

SpatialRust 1.2 adds deterministic bounded-memory point-cloud execution across local/HTTP IO, chunk-safe operations, Rust/CLI/Python workflows, and explicit spill. See the streaming release receipt and migration guide for limits, stability, and reproduction commands.

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

CrateRole
spatialrustMeta crate / stable re-exports
spatialrust-corePoint schema, metadata, execution traits
spatialrust-mathVec/Mat/Pose math primitives
spatialrust-imageTyped image buffers and zero-copy strided views
spatialrust-image-ioBounded PNG/JPEG/PNM codecs; opt-in TIFF/OpenEXR
spatialrust-tensorRuntime-independent dtype/shape/stride/device ownership and DLPack
spatialrust-aiExplicit-copy inference contracts and opt-in ONNX Runtime providers
spatialrust-cameraPinhole/Brown–Conrady camera models and RGB-D conversion
spatialrust-visionCPU filters, Feature2D/ORB matching, resize/preprocess, warps, detection postprocess, masks, and dense spatial maps
spatialrust-ioPoint cloud readers/writers (PCD, PLY, LAS, COPC)
spatialrust-searchKD-tree search, k-NN / radius graphs
spatialrust-filteringVoxel / FPS downsample, outlier removal, crop, MLS
spatialrust-featuresNormals (CPU + wgpu), ISS keypoints, FPFH, boundary, normal orientation
spatialrust-segmentationRANSAC plane / sphere / cylinder, Euclidean, DBSCAN, region growing, ground
spatialrust-registrationICP (point-to-point, point-to-plane), GICP, NDT, FPFH global
spatialrust-transformAffine transforms, recenter / normalize, merge, AABB / OBB
spatialrust-voxelizeVoxel occupancy grids and LiDAR range images
spatialrust-metricsChamfer / Hausdorff cloud distances
spatialrust-pipelineComposable MVP pipelines
spatialrust-gpuwgpu runtime and voxel kernels

Python

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

importnumpyasnpimportspatialrustassrcloud=sr.PointCloud.from_xyz(points) # (N, 3) float32 -> native cloudresult=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 idssr.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 / _ndtT=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) occupancyedge_index=sr.knn_graph(sampled, k=16) # (2, E) PyG-style graphrimg=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/ — GPU sparse-grid construction matches CPU cluster labels; deterministic component union remains an explicit host stage.

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)?;

For datasets on an external SSD, resolve logical input/output paths explicitly and emit a size/SHA-256 manifest:

cargo run -p spatialrust --features mvp --bin spatialrust-mvp -- \
--input-root /media/sasaki/aiueo/datasets \
--output-root /media/sasaki/aiueo/spatialrust-results \
--manifest runs/scan.json boreas/scan.las runs/scan.ply

See docs/EXTERNAL_STORAGE.md for the Python and bounded-streaming equivalents.

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). GPU sparse-grid construction and deterministic host component labeling are exposed in the stage receipt through MvpPipelineResult::receipt together with explicit transfer accounting.

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. 23

  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. 234

  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

21 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages