Quickstart¶
Install¶
Optional extras:
pip install "splatreg[render]" # gsplat — camera localization + photometric residual
pip install "splatreg[pypose]" # PyPose solver backend
pip install "splatreg[theseus]" # Theseus solver backend
Development install:
git clone https://github.com/Archerkattri/splatreg.git
cd splatreg
pip install -e ".[test]"
python -m pytest tests/ -q
Register two splats¶
from splatreg import register
from splatreg.io import load_ply
target = load_ply("scan_a.ply") # the reference — stays fixed
source = load_ply("scan_b.ply") # gets aligned onto the target
result = register(target, source, transform="sim3") # or "se3" for rigid
result is a RegisterResult:
result.T # (4, 4) transform mapping source -> target ([[s*R, t], [0, 1]] for sim3)
result.scale # recovered scale s (1.0 for se3)
result.converged # solver convergence flag
result.info # diagnostics: rmse, n_iters, cost, ambiguous/confidence (feature inits)
The default init="fast" (FPFH + GPU-batched RANSAC seed, ~17 ms) suits objects and
full-overlap captures. For real metre-scale scans use init="robust" or init="learned";
see Init modes.
If the shape under-constrains the pose (rotational symmetry, texture-carried detail), add the opt-in photometric stage — geometric residuals can't see color, this stage can:
result = register(target, source, transform="se3",
refine="photometric") # needs `pip install "splatreg[render]"`
Measured: on a rotation-symmetric colored sphere the geometric solve worsens 6.0°→11.2° while the photometric stage lands 2.2° (0.36° with the real gsplat rasterizer); on dense-overlap scans it is neutral. When & why.
Merge + dedupe¶
from splatreg import merge
from splatreg.io import save_ply
fused = merge([target, source]) # registers everything onto splats[ref] (default 0),
save_ply(fused, "fused.ply") # fuses, and dedupes the double-density overlap
merge is not a naive cat: each non-reference splat is registered (Sim(3) by default, so
scale differences between captures are absorbed), the transform is baked into its
means/quats/scales, and the overlap region is deduped (voxel-grid by default, "knn"
available) so the seam collapses to single density.
Bring your own tensors (no PLY)¶
Any 3DGS framework works — pass gsplat-style tensors directly:
from splatreg.io import from_gsplat, to_gsplat
g = from_gsplat(means, quats, scales, opacities, colors) # wraps, no copy
out = gsplat.rasterization(viewmats=..., Ks=..., width=W, height=H, **to_gsplat(g))
Object pose + camera localization¶
from splatreg import estimate_object_pose, localize_camera, coarse_localize_camera
# 6-DoF pose of a known object splat in a new observation (ADD / ADD-S / AUC out of the box)
result = estimate_object_pose(model_splat, observation_splat)
# Refine a camera pose against a scene splat through gsplat's differentiable rasteriser
result = localize_camera(scene_splat, frame, init_T_WC=T_init) # needs splatreg[render]
# Wide-baseline / prior-free coarse seed (CPU-only, no rasteriser)
T_coarse = coarse_localize_camera(scene_splat, frame)
Real-time tracking¶
from splatreg import Tracker
tracker = Tracker(target, residuals=[...]) # fixed target, warm-started across frames
for frame in stream:
result = tracker.track(frame) # ~17 ms/frame
The Gaussian-SDF field, standalone¶
splatreg's flagship residual is a reusable primitive — a smooth signed-distance field derived directly from the target Gaussians (no mesh, no marching cubes):
from splatreg.geometry.gaussian_sdf import gaussian_sdf, gaussian_sdf_grad
sdf, normal = gaussian_sdf(target, query_points, sigma=0.02)
sdf, grad = gaussian_sdf_grad(target, query_points, sigma=0.02) # exact closed-form grad
Know the edges¶
Honest limitations
- Overlap ≤ 40% is genuinely ambiguous — the disambiguating geometry is physically
absent. splatreg flags these (
result.info["ambiguous"]/["confidence"]) instead of silently wrong-posing.mergeis designed for high-overlap captures. - Scale is unobservable under thin overlap (~20% shared geometry): the Sim(3) scale valley is flat — no algorithm can recover what the geometry doesn't carry.
- On rigid SE(3), plain ICP is far cheaper and reaches the same success on easy cases;
the SDF residual buys scale + implicit-field robustness at a real compute cost. Use
Trackerfor the warm-start real-time path.