Skip to main content

Composable geometry-first SE(3)/Sim(3) registration for 3D Gaussian Splatting — the inverse of gsplat.

Project description

splatreg

splatreg

Register Gaussian splats — align & merge two 3DGS scans into one SE(3)/Sim(3) frame.

PyPI License Python Docs Colab gsplat

splatreg before/after registration

Is this for you?

  • Two 3DGS scans of the same scene / object that need to be mergedregister + merge finds the rigid or similarity transform and fuses them into one deduped .ply, no manual gizmo needed.
  • Object pose estimation against a known splatestimate_object_pose recovers the SE(3) pose between a reference model splat and a new observation (ADD / ADD-S / AUC out of the box).
  • Camera localization inside a known splatlocalize_camera places a new camera into a scene splat without retraining; coarse_localize_camera seeds it prior-free from a silhouette sweep.

Works with any 3DGS framework — gsplat, Nerfstudio, INRIA, custom — as long as you can pass Gaussian means and covariances as PyTorch tensors. Pure PyTorch — no meshing, no CUDA extension, no point-cloud detour.


What's new in v1.1

  • refine="photometric" — opt-in PhotoReg-style (arXiv 2410.05044) splat-to-splat photometric stage after the geometric solve, for the poses geometry can't see (symmetry / texture-only DoF) — no real images needed. Measured: on a rotation-symmetric colored sphere, geometric registration worsens 6.0°→11.2° while the photometric stage lands 2.2° (real gsplat rasterizer: 5°/7 mm → 0.36°/0.5 mm in ~1.1 s); on a dense-overlap real 103k-Gaussian pair it is neutral (+1.7 s) because geometry already pins the pose — so it ships opt-in. 21 tests + bench: when & why · recorded runs.
  • splatreg CLIalign / merge / info from the shell, standard 3DGS PLY in/out (the SplatTransform-style workflow: 3DGS practitioners are CLI-first). Measured: the recorded align run takes a source from 154 mm Chamfer off the target to 0.05 mm with no Python written. 10 end-to-end tests: CLI guide.
  • DC-only PLY round-trip fixload_ply used to return raw SH-DC values in the RGB slot, so a following save_ply double-encoded them and colors drifted every load→save cycle; DC-only loads now return true RGB and round-trip losslessly (full-SH round-trip stays bit-exact). Regression-locked in tests/test_io_roundtrip_dc.py.

Install

pip install splatreg
# editable / dev
git clone https://github.com/Archerkattri/splatreg.git
cd splatreg
pip install -e ".[test]"

Quickstart

From the shell — pip install puts a splatreg command on your PATH (standard 3DGS PLY in/out, so it composes with SuperSplat / gsplat / Nerfstudio exports; see the CLI guide):

splatreg align target.ply source.ply -o aligned.ply    # register + write the aligned source
splatreg merge a.ply b.ply -o fused.ply                # register + fuse + dedupe N splats
splatreg info x.ply                                    # count / bounds / SH degree / stats

In Python:

from splatreg.api import register, merge

# Align `source` onto `target` (both are Gaussians objects: .means, .covs, .opacities tensors).
result = register(target, source, transform="sim3")   # init="fast" by default (~17 ms)
# Real metre-scale scans: init="robust" (FPFH+RANSAC) or init="learned" (GeoTransformer, best accuracy)
print(result.T)          # recovered 4×4 similarity [[s·R, t], [0, 1]] — maps source → target
print(result.scale)      # recovered scale s  (1.0 for transform="se3")
print(result.converged)  # solver convergence flag

# Merge + dedupe a list of splats into one fused splat
fused = merge([source, target], transform="sim3")

Object pose and camera localization:

from splatreg import estimate_object_pose, localize_camera, coarse_localize_camera

# Object pose: recover T_SO between a model splat and an observation
result = estimate_object_pose(model_splat, observation_splat)

# Camera localization: refine camera pose through gsplat's differentiable rasteriser
result = localize_camera(scene_splat, frame, init_T_WC=T_init)
# Wide-baseline / prior-free: coarse seed from silhouette sweep (CPU-only, no rasteriser)
T_coarse = coarse_localize_camera(scene_splat, frame)

The Gaussian-SDF field standalone:

from splatreg.geometry.gaussian_sdf import gaussian_sdf, gaussian_sdf_grad
sdf, normal = gaussian_sdf(target, query_points, sigma=0.02)       # signed distance + surface normal
sdf, grad   = gaussian_sdf_grad(target, query_points, sigma=0.02)  # signed distance + exact ∇_p d

Results

splatreg reference
Real-splat merge (real 103k-Gaussian capture) Chamfer 10.3→2.0 mm (5.1×) · overlap 0.03→0.67 (22×) naive concat
vs splat competitors (real splat, known GT Sim3) 5.2° (SE3) · recovers scale (Sim3) splatalign 15.3° · GaussianSplattingRegistration 36.3°
Sim(3) scale estimation ✅ native ✗ none of these do it
Object pose (YCB-CAD, 14 models × 4 poses) ADD-S AUC 0.995, 100% < 2 cm
Camera localization (real splat, known perturbation) median 5°/10 mm → 0.11°/1.35 mm, 11/12 converged
Official 3DMatch recall (1279 pairs, Choi/Zeng protocol) 91.5% mean · 93.5% pooled GeoTransformer ~92% · Open3D ~77%
Official 3DLoMatch (hard, 10–30% overlap) 72.5% mean · 74.4% pooled GeoTransformer ~74% · Open3D ~20%
Registration speed ~17 ms (fast) · 104 ms (learned) GeoTransformer ~50 ms · Open3D 142 ms

splatreg is the only library that registers native Gaussian splats with SE(3)+Sim(3) behind a closed-form-Jacobian Gaussian-SDF. It beats both splat-specific tools outright (5.2° vs 15.3° / 36.3°) and matches GeoTransformer on official 3DMatch while adding the Sim(3) scale DoF they lack.

Init modes — trade speed ↔ robustness

init= what when
"fast" (default) FPFH + GPU-batched RANSAC seed → closed-form LM objects / full-overlap, ~17 ms
"robust" Open3D FPFH+RANSAC seed → splatreg refine + scale real metre-scale scans
"learned" pretrained GeoTransformer seed → splatreg refine + scale best accuracy on real scans
"global" blind super-Fibonacci SO(3) sweep robust fallback, any rotation

How it works

splatreg takes two splats and finds the rigid (SE(3)) or similarity (Sim(3), +scale) transform that aligns them — then optionally merges + dedupes them into one. It is the missing registration half of the Gaussian-splatting toolchain — the splat-to-splat alignment SuperSplat / INRIA / geospatial users keep asking for, where today's tooling punts to a manual gizmo.

The pipeline is two stages:

flowchart LR
    A["splat A<br/>(target)"]:::s --> G
    B["splat B<br/>(source)"]:::s --> G
    G["<b>Global aligner</b><br/>super-Fibonacci SO(3) seeds<br/>+ batched trimmed ICP<br/><i>(or FPFH / learned)</i>"]:::g --> L
    L["<b>Levenberg–Marquardt</b><br/>multi-residual:<br/>ICP + Gaussian-SDF<br/>SE(3) / Sim(3)"]:::l --> T["T*  (4×4)<br/>+ merge / dedupe"]:::o
    classDef s fill:#e8f6f8,stroke:#17becf,color:#0b3d44;
    classDef g fill:#fff1ee,stroke:#ff6b5b,color:#5a1a12;
    classDef l fill:#eef7ee,stroke:#2e8b57,color:#143d22;
    classDef o fill:#f3eefc,stroke:#7d52c7,color:#2c1654;
  1. Global init — a coarse pose from a dense super-Fibonacci rotation sweep + batched trimmed ICP (no local-minimum trap), with optional FPFH+RANSAC and learned (GeoTransformer) seeds for harder real scans.
  2. Refinement — a from-scratch Levenberg–Marquardt core over ICP (point-to-point / point-to-plane) and splatreg's flagship Gaussian-SDF residual, solving the full SE(3) or Sim(3) tangent.

The Gaussian-SDF residual

No competitor packages this. splatreg derives a smooth signed-distance field directly from the target Gaussians — no mesh, no marching cubes — and drives registration by it:

w_i(p) = exp(−‖p − q_i‖² / 2σ²)              # Gaussian kernel weight per anchor
q̃(p)   = Σ w_i q_i / Σ w_i                    # kernel-weighted centroid
ñ(p)   = Σ w_i n_i / ‖Σ w_i n_i‖              # kernel-weighted surface normal
d(p)   = (p − q̃(p)) · ñ(p)                    # signed distance — the residual

d(p) vanishes exactly when source points land on the target surface. It has a closed-form, audited Jacobian and is a reusable primitive: gaussian_sdf(splat, points, sigma=...) → (sdf, normal).


Validation

Every number is reproducible; full record in RESULTS.md.

python -m pytest tests/ -q                        # 105 passing
python tests/test_jacobians.py                    # analytic vs numerical Jacobian audit
SPLATREG_DEVICE=cuda python examples/validate_recovery.py --device cuda   # 36/36 recovery
SPLATREG_DEVICE=cuda python benchmarks/robustness_bench.py --device cuda
python examples/merge_demo.py                     # real-splat merge demo

Limitations

splatreg is honest about its edges (full detail in RESULTS.md):

  • Heavy overlap (≤ 40%) is genuinely ambiguous. At keep ≤ 40% the rotation-disambiguating geometry is physically absent — even the true pose doesn't seat cleanly. The aligner flags these honestly (result.info['ambiguous'] / ['confidence']) and never silently wrong-poses. merge and track are designed for high-overlap captures.
  • Scale is unobservable under thin overlap. Under ~20% shared geometry the Sim(3) scale residual valley is flat — the golden-section line-search tightens scale on its own objective but cannot recover what the geometry doesn't carry. merge is reliable for high-overlap captures.
  • Cost on rigid SE(3). Plain ICP reaches the same SE(3) success and is far faster; the SDF residual buys scale + implicit-field robustness at a real compute cost. Use track() (~17 ms/frame) for the warm-start real-time path.

Documentation

Full docs at https://archerkattri.github.io/splatreg/quickstart, CLI guide, init modes, photometric refinement (when & why, with the measured three-case table), PLY interop (splatfacto/INRIA/SuperSplat round-trip + the SH-under-rotation detail), benchmarks, and the API reference. Or run the Colab quickstart (CPU-only, no assets needed).

Citation

If splatreg is useful in your research, please cite it (see CITATION.cff — GitHub's "Cite this repository" button gives BibTeX/APA):

@software{attri_splatreg,
  author  = {Attri, Krishi},
  title   = {splatreg: composable SE(3)/Sim(3) registration for 3D Gaussian Splatting},
  url     = {https://github.com/Archerkattri/splatreg},
  version = {1.1.0},
  year    = {2026}
}

License & layout

BSD 3-Clause — permissive, composes with the gsplat / Theseus / GTSAM ecosystem. splatreg/ — library (api, align, align_features, bundle, spatial_index, core/lie, geometry/gaussian_sdf, residuals/, solvers/lm, cli). tests/ · benchmarks/ · examples/ · docs_site/. Full validation record: RESULTS.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

splatreg-1.1.0.tar.gz (192.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

splatreg-1.1.0-py3-none-any.whl (167.6 kB view details)

Uploaded Python 3

File details

Details for the file splatreg-1.1.0.tar.gz.

File metadata

  • Download URL: splatreg-1.1.0.tar.gz
  • Upload date:
  • Size: 192.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for splatreg-1.1.0.tar.gz
Algorithm Hash digest
SHA256 3c5d0241496a9e1552ae84b29afea2cff7ca26a739a23b5ba03e4b260fff99a6
MD5 d0ac51ece9d98304d2db84a887316878
BLAKE2b-256 706b3c151e8f0738aaca36d923e0ee776c537548f910d1545b1476c4a9e331be

See more details on using hashes here.

File details

Details for the file splatreg-1.1.0-py3-none-any.whl.

File metadata

  • Download URL: splatreg-1.1.0-py3-none-any.whl
  • Upload date:
  • Size: 167.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for splatreg-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2ca0dc08be0ab2f8e8f4e90587fe942d749964242fc56b6ff44d9ce9ae0033f0
MD5 bed9889ffb48d9d4f645568ce8f3c9ca
BLAKE2b-256 82bc45f2728f9d80a166319daf17899038547515d4f04d06f5e14667b9bc3fb0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page