splatreg 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- splatreg-1.0.0/LICENSE +29 -0
- splatreg-1.0.0/PKG-INFO +218 -0
- splatreg-1.0.0/README.md +171 -0
- splatreg-1.0.0/pyproject.toml +76 -0
- splatreg-1.0.0/setup.cfg +4 -0
- splatreg-1.0.0/splatreg/__init__.py +37 -0
- splatreg-1.0.0/splatreg/align.py +381 -0
- splatreg-1.0.0/splatreg/align_features.py +1849 -0
- splatreg-1.0.0/splatreg/api.py +736 -0
- splatreg-1.0.0/splatreg/core/__init__.py +3 -0
- splatreg-1.0.0/splatreg/core/lie.py +221 -0
- splatreg-1.0.0/splatreg/core/types.py +95 -0
- splatreg-1.0.0/splatreg/fuse.py +294 -0
- splatreg-1.0.0/splatreg/geometry/__init__.py +10 -0
- splatreg-1.0.0/splatreg/geometry/gaussian_sdf.py +370 -0
- splatreg-1.0.0/splatreg/io.py +424 -0
- splatreg-1.0.0/splatreg/py.typed +0 -0
- splatreg-1.0.0/splatreg/quality.py +332 -0
- splatreg-1.0.0/splatreg/residuals/__init__.py +31 -0
- splatreg-1.0.0/splatreg/residuals/base.py +49 -0
- splatreg-1.0.0/splatreg/residuals/icp.py +192 -0
- splatreg-1.0.0/splatreg/residuals/photometric.py +337 -0
- splatreg-1.0.0/splatreg/residuals/prior.py +117 -0
- splatreg-1.0.0/splatreg/residuals/sdf.py +237 -0
- splatreg-1.0.0/splatreg/solvers/__init__.py +10 -0
- splatreg-1.0.0/splatreg/solvers/_backend_common.py +84 -0
- splatreg-1.0.0/splatreg/solvers/base.py +21 -0
- splatreg-1.0.0/splatreg/solvers/lm.py +360 -0
- splatreg-1.0.0/splatreg/solvers/pypose_backend.py +113 -0
- splatreg-1.0.0/splatreg/solvers/theseus_backend.py +143 -0
- splatreg-1.0.0/splatreg/testing.py +75 -0
- splatreg-1.0.0/splatreg/track.py +211 -0
- splatreg-1.0.0/splatreg.egg-info/PKG-INFO +218 -0
- splatreg-1.0.0/splatreg.egg-info/SOURCES.txt +40 -0
- splatreg-1.0.0/splatreg.egg-info/dependency_links.txt +1 -0
- splatreg-1.0.0/splatreg.egg-info/requires.txt +43 -0
- splatreg-1.0.0/splatreg.egg-info/top_level.txt +1 -0
- splatreg-1.0.0/tests/test_backends.py +237 -0
- splatreg-1.0.0/tests/test_jacobians.py +159 -0
- splatreg-1.0.0/tests/test_lie.py +451 -0
- splatreg-1.0.0/tests/test_recovery.py +163 -0
- splatreg-1.0.0/tests/test_solver.py +550 -0
splatreg-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Krishi Attri
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
splatreg-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: splatreg
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Composable geometry-first SE(3)/Sim(3) registration for 3D Gaussian Splatting — the inverse of gsplat.
|
|
5
|
+
Author: Krishi Attri
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Keywords: gaussian-splatting,3dgs,registration,pose-estimation,se3,sim3,gsplat
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: torch
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Provides-Extra: render
|
|
14
|
+
Requires-Dist: gsplat>=1.4; extra == "render"
|
|
15
|
+
Provides-Extra: pypose
|
|
16
|
+
Requires-Dist: pypose; extra == "pypose"
|
|
17
|
+
Provides-Extra: theseus
|
|
18
|
+
Requires-Dist: theseus-ai; extra == "theseus"
|
|
19
|
+
Provides-Extra: gtsam
|
|
20
|
+
Requires-Dist: gtsam; extra == "gtsam"
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
24
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
25
|
+
Requires-Dist: mypy>=1.5; extra == "dev"
|
|
26
|
+
Requires-Dist: isort>=5.12; extra == "dev"
|
|
27
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff; extra == "dev"
|
|
29
|
+
Requires-Dist: hypothesis; extra == "dev"
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
32
|
+
Requires-Dist: pytest-xdist; extra == "test"
|
|
33
|
+
Requires-Dist: black>=24.0; extra == "test"
|
|
34
|
+
Requires-Dist: mypy>=1.5; extra == "test"
|
|
35
|
+
Requires-Dist: isort>=5.12; extra == "test"
|
|
36
|
+
Requires-Dist: flake8>=6.0; extra == "test"
|
|
37
|
+
Requires-Dist: ruff; extra == "test"
|
|
38
|
+
Provides-Extra: docs
|
|
39
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
40
|
+
Requires-Dist: furo; extra == "docs"
|
|
41
|
+
Requires-Dist: myst-nb; extra == "docs"
|
|
42
|
+
Provides-Extra: all
|
|
43
|
+
Requires-Dist: gsplat>=1.4; extra == "all"
|
|
44
|
+
Requires-Dist: pypose; extra == "all"
|
|
45
|
+
Requires-Dist: theseus-ai; extra == "all"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
<div align="center">
|
|
49
|
+
|
|
50
|
+
# splatreg
|
|
51
|
+
|
|
52
|
+
### Register Gaussian splats — align & merge two 3DGS scans into one SE(3)/Sim(3) frame.
|
|
53
|
+
|
|
54
|
+
*The inverse of [gsplat](https://github.com/nerfstudio-project/gsplat): gsplat **renders** Gaussians, splatreg **registers** against them.* Pure PyTorch — no meshing, no CUDA extension, no point-cloud detour.
|
|
55
|
+
|
|
56
|
+
[](LICENSE)
|
|
57
|
+
[](pyproject.toml)
|
|
58
|
+
[](https://pytorch.org)
|
|
59
|
+
[](tests)
|
|
60
|
+
[](tests/test_jacobians.py)
|
|
61
|
+
|
|
62
|
+
<img src="assets/registration_demo.png" alt="splatreg before/after registration" width="92%">
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## What it is
|
|
69
|
+
|
|
70
|
+
A 3D Gaussian Splat is a cloud of oriented Gaussians that already traces an object's surface. **splatreg takes two such 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.
|
|
71
|
+
|
|
72
|
+
The pipeline is two stages:
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
flowchart LR
|
|
76
|
+
A["splat A<br/>(target)"]:::s --> G
|
|
77
|
+
B["splat B<br/>(source)"]:::s --> G
|
|
78
|
+
G["<b>Global aligner</b><br/>super-Fibonacci SO(3) seeds<br/>+ batched trimmed ICP<br/><i>(or FPFH / learned)</i>"]:::g --> L
|
|
79
|
+
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
|
|
80
|
+
classDef s fill:#e8f6f8,stroke:#17becf,color:#0b3d44;
|
|
81
|
+
classDef g fill:#fff1ee,stroke:#ff6b5b,color:#5a1a12;
|
|
82
|
+
classDef l fill:#eef7ee,stroke:#2e8b57,color:#143d22;
|
|
83
|
+
classDef o fill:#f3eefc,stroke:#7d52c7,color:#2c1654;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
2. **Refinement** — a from-scratch **Levenberg–Marquardt** core over a stack of residuals: classic **ICP** (point-to-point / point-to-plane) *and* splatreg's flagship **Gaussian-SDF** residual, solving the full SE(3) or Sim(3) tangent.
|
|
88
|
+
|
|
89
|
+
It **composes, it doesn't compete**: bring gsplat tensors directly; the LM loop and residual stack are pluggable.
|
|
90
|
+
|
|
91
|
+
### The differentiator — the Gaussian-SDF residual
|
|
92
|
+
|
|
93
|
+
No competitor packages this. splatreg derives a smooth, queryable **signed-distance field directly from the target Gaussians** — no mesh, no marching cubes — and drives registration by it:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
w_i(p) = exp(−‖p − q_i‖² / 2σ²) # Gaussian kernel weight per anchor
|
|
97
|
+
q̃(p) = Σ w_i q_i / Σ w_i # kernel-weighted centroid
|
|
98
|
+
ñ(p) = Σ w_i n_i / ‖Σ w_i n_i‖ # kernel-weighted surface normal
|
|
99
|
+
d(p) = (p − q̃(p)) · ñ(p) # signed distance — the residual
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`d(p)` vanishes exactly when the source points land on the target's surface. It has a **closed-form, audited Jacobian** and is a standalone, reusable implicit-field primitive: `gaussian_sdf(splat, points, sigma=...) → (sdf, normal)`.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Headline results
|
|
107
|
+
|
|
108
|
+
| | **splatreg** | reference |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| **Official 3DMatch registration recall** (Choi/Zeng protocol, 1279 pairs) | **91.5%** mean · 93.5% pooled | GeoTransformer ~92% · Open3D ~77% |
|
|
111
|
+
| **Official 3DMatch rotation / translation error** | **1.81° / 0.071 m** | — |
|
|
112
|
+
| **Official 3DLoMatch** (hard, 10–30% overlap) | 72.5% mean · **74.4%** pooled | GeoTransformer ~74% · Open3D ~20% |
|
|
113
|
+
| **Real-splat merge** (real 103k-Gaussian capture) | Chamfer **10.3→2.0 mm (5.1×)** · overlap **0.03→0.67 (22×)** | naive concat |
|
|
114
|
+
| **vs splat competitors** (real splat, known GT Sim3) | **5.2°** (SE3) · recovers scale (Sim3) | splatalign 15.3° · GaussianSplattingRegistration 36.3° |
|
|
115
|
+
| **Sim(3) scale estimation** | ✅ native | ✗ none of these do it |
|
|
116
|
+
| **Registration speed** | **~17 ms** (fast) · 104 ms (learned) | GeoTransformer ~50 ms · Open3D 142 ms |
|
|
117
|
+
| **Real-time tracking** | **~17 ms/frame** | GaussianFeels tracker ~45 ms |
|
|
118
|
+
|
|
119
|
+
splatreg is the **only library** that registers native Gaussian splats with SE(3)+**Sim(3)** behind a closed-form-Jacobian Gaussian-SDF.
|
|
120
|
+
|
|
121
|
+
- **Matches GeoTransformer on official 3DMatch** — 91.5% mean / 93.5% pooled recall vs their published ~92% — because the `learned` path **rides GeoTransformer's matcher at its native 0.025 m resolution** and then layers splatreg's SDF/LM refine + Sim(3) scale **on top**. The recall is GeoTransformer's; what splatreg *adds* is **accuracy** (RRE 1.87° → 1.81°), the unique **Sim(3) scale DoF**, and a **verified no-regression floor** (a per-pair audit found **0 demotions** — the refine only tightens already-successful pairs, never breaks them). On hard 3DLoMatch it reaches **74.4% pooled**, matching GeoTransformer's ~74%.
|
|
122
|
+
- **Decisively beats classical Open3D** (~77% / ~20%) on both splits.
|
|
123
|
+
- **Wins outright vs the splat-registration tools** — **5.2°** vs splatalign's 15.3° and GaussianSplattingRegistration's 36.3° on a real splat — and is the **only one that recovers Sim(3) scale.**
|
|
124
|
+
- **Real-splat merge** (`examples/merge_demo.py`) fuses two overlapping captures (a real 103k-Gaussian splat) into one deduped `.ply`: post-merge Chamfer **10.3 → 2.0 mm (5.1× closer)** and overlap **0.03 → 0.67 (22× more)** vs a naive `torch.cat`, removing ~9k overlap duplicates (verified on GPU, two independent runs).
|
|
125
|
+
|
|
126
|
+
### Four init modes — trade speed ↔ robustness
|
|
127
|
+
|
|
128
|
+
| `init=` | what | when |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `"fast"` *(default)* | FPFH + GPU-batched RANSAC seed → closed-form LM | objects / full-overlap, **~17 ms** |
|
|
131
|
+
| `"robust"` | Open3D FPFH+RANSAC seed → splatreg refine + scale | real metre-scale scans |
|
|
132
|
+
| `"learned"` | pretrained GeoTransformer seed → splatreg refine + scale | best accuracy on real scans |
|
|
133
|
+
| `"global"` | blind super-Fibonacci SO(3) sweep | robust fallback, any rotation |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Install
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
git clone https://github.com/Archerkattri/splatreg.git
|
|
141
|
+
cd splatreg
|
|
142
|
+
pip install -e . # pure PyTorch + numpy; pip install -e ".[test]" for test extras
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Quickstart
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from splatreg.api import register, merge
|
|
149
|
+
|
|
150
|
+
# two Gaussian splats of the same object, in unknown relative pose/scale.
|
|
151
|
+
# register aligns `source` onto the reference `target` (target is the first arg).
|
|
152
|
+
result = register(target, source, transform="sim3") # init="fast" by default (objects / full-overlap)
|
|
153
|
+
# real metre-scale scans -> init="robust" (FPFH+RANSAC) or init="learned" (GeoTransformer seed, best accuracy)
|
|
154
|
+
print(result.T) # recovered 4×4 similarity [[s·R, t], [0, 1]] — maps source -> target
|
|
155
|
+
print(result.scale) # recovered scale s (1.0 for transform="se3")
|
|
156
|
+
print(result.converged) # solver convergence flag
|
|
157
|
+
|
|
158
|
+
# register + dedupe a list of splats into one fused splat (registers internally)
|
|
159
|
+
fused = merge([source, target], transform="sim3")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The Gaussian-SDF field on its own:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from splatreg.geometry.gaussian_sdf import gaussian_sdf, gaussian_sdf_grad
|
|
166
|
+
sdf, normal = gaussian_sdf(target, query_points, sigma=0.02) # signed distance + surface normal
|
|
167
|
+
sdf, grad = gaussian_sdf_grad(target, query_points, sigma=0.02) # signed distance + EXACT ∇_p d
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Validation
|
|
173
|
+
|
|
174
|
+
Every number is reproducible; the full record — commands, seeds, and honest limitations — is in [`RESULTS.md`](RESULTS.md).
|
|
175
|
+
|
|
176
|
+
- **Synthetic Sim(3)/SE(3) recovery** — apply a known transform, recover it: **36/36 = 100%**, median rot 0.03°, scale error 0.34%, Chamfer 0.6 mm (`examples/validate_recovery.py`).
|
|
177
|
+
- **Jacobian audit** — every analytic Jacobian checked against a tangent-space numerical one in float64 (`tests/test_jacobians.py`): ICP point-to-point/plane, the **Gaussian-SDF closed-form gradient** (~1e-8 vs numerical), and SE(3)/Sim(3) exp·log incl. near-π. Ships a reusable `assert_residual_jacobian` so every future residual gets the audit.
|
|
178
|
+
- **vs plain ICP** — splatreg **27/27 Sim(3)** vs ICP's **9/27** (plain ICP cannot estimate scale; the LM Sim(3) solve is load-bearing).
|
|
179
|
+
- **Robustness** — sensor noise **9/9**, outlier clutter **9/9**, symmetric sphere **9/9**; partial overlap **6/9 solved** (all keep ≥ 60% at 0.00°) + 3 honestly flagged ambiguous (heavy keep ≤ 40% crops), **0 silent-wrong**.
|
|
180
|
+
- **Official 3DMatch / 3DLoMatch** — canonical Choi/Zeng protocol, 1279 pairs, covariance-weighted error (`benchmarks/threedmatch_official_bench.py`): **91.5% / 74.4%** as above.
|
|
181
|
+
- **Test suite** — `pytest tests/` → **44 passing**; `black` + `mypy` clean, ships `py.typed`.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pip install -e ".[test]"
|
|
185
|
+
python -m pytest tests/ -q # 44 passing: audit + Lie + solver
|
|
186
|
+
python tests/test_jacobians.py # the numerical-vs-analytic Jacobian audit
|
|
187
|
+
SPLATREG_DEVICE=cuda python examples/validate_recovery.py --device cuda # recovery 36/36
|
|
188
|
+
SPLATREG_DEVICE=cuda python benchmarks/robustness_bench.py --device cuda
|
|
189
|
+
python examples/merge_demo.py # real-splat merge demo
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Limitations
|
|
195
|
+
|
|
196
|
+
splatreg is honest about its edges (full detail in [`RESULTS.md`](RESULTS.md)):
|
|
197
|
+
|
|
198
|
+
- **Partial overlap — moderate crops now solve; heavy crops are genuinely ambiguous.** The overlap basin-sweep now keeps a deep candidate pool (`topk=200`) and ranks the refined seeds by a **symmetric** overlap residual (target→source *and* source→target), so the moderate keep ≈ 60% crops resolve to the true basin instead of a ~170° mirror — robustness sweep **PARTIAL solved-count 4/9 → 6/9** (all keep ≥ 60% at 0.00°). Heavy one-sided crops (keep ≤ 40%) delete the rotation-disambiguating geometry — there *even the true pose* no longer seats cleanly (symmetric residual 0.003 vs a forest of ~0.017 wrong basins), so the aligner returns an **honest ambiguity flag** (`result.info['ambiguous']` / `['confidence']`) rather than a silent wrong pose (0 silent-wrong, verified). This deep sweep costs ~22 s/cell (registration path only, never the real-time tracker); the heavy-crop case is genuinely open.
|
|
199
|
+
- **Scale is loose under low overlap.** A dedicated golden-section **scale line-search** against the symmetric overlap residual now refines the Sim(3) scale DoF after the pose solve (the one-directional fit is scale-blind). It tightens scale on its own objective, but on a thin shared band the scale still has a wide valley — under ~20% overlap the recovered scale can drift; `merge` is reliable for high-overlap captures.
|
|
200
|
+
- **The 3DMatch recall is GeoTransformer's, not ours.** splatreg `learned` **matches** GeoTransformer by riding its matcher; it does not beat that matcher's recall. What splatreg adds is accuracy, the Sim(3) scale DoF, and the no-regression floor. Closing the gap with splatreg's *own* dense correspondence is open work.
|
|
201
|
+
- **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.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Roadmap
|
|
206
|
+
|
|
207
|
+
Shipped: official 3DMatch + 3DLoMatch (Choi/Zeng, 91.5% / 74.4% pooled, matching GeoTransformer); pluggable `fast`/`robust`/`learned` init backends; CI regression gates (determinism + worst-case + PR-comment benchmark); the real-splat merge demo (`examples/merge_demo.py`); the head-to-head vs `splatalign` / `GaussianSplattingRegistration` (only tool recovering Sim(3) scale); the seeded-RANSAC determinism fix; and the partial-overlap basin sweep (4/9 → 6/9 solved). Remaining:
|
|
208
|
+
|
|
209
|
+
- [ ] **Heavy partial overlap (keep ≤ 40%) to publication.** The moderate keep ≈ 60% crops now solve; the heavy one-sided crops are correctly flagged ambiguous but still unsolved. Needs an overlap-region-restricted descriptor / learned overlap prior to push past the geometry-only basin ambiguity.
|
|
210
|
+
- [ ] **Tighten Sim(3) scale under low overlap.** The scale line-search helps but a thin shared band leaves a wide scale valley; a multi-scale or descriptor-anchored scale constraint is the next step.
|
|
211
|
+
- [ ] Close the gap to GeoTransformer's full coarse-to-fine matcher with splatreg's *own* dense correspondence (not just a borrowed seed).
|
|
212
|
+
- [ ] 6-DoF object-pose mode + FoundationPose/YCB benchmark (v0.2)
|
|
213
|
+
- [ ] Camera localization in a splat (v0.2)
|
|
214
|
+
- [ ] PyPI release
|
|
215
|
+
|
|
216
|
+
## License & layout
|
|
217
|
+
|
|
218
|
+
BSD 3-Clause — permissive, composes with the gsplat / Theseus / GTSAM ecosystem. `splatreg/` — library (`api`, `align`, `align_features`, `core/lie`, `geometry/gaussian_sdf`, `residuals/`, `solvers/lm`). `tests/` · `benchmarks/` · `examples/`. Full validation record: [`RESULTS.md`](RESULTS.md).
|
splatreg-1.0.0/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# splatreg
|
|
4
|
+
|
|
5
|
+
### Register Gaussian splats — align & merge two 3DGS scans into one SE(3)/Sim(3) frame.
|
|
6
|
+
|
|
7
|
+
*The inverse of [gsplat](https://github.com/nerfstudio-project/gsplat): gsplat **renders** Gaussians, splatreg **registers** against them.* Pure PyTorch — no meshing, no CUDA extension, no point-cloud detour.
|
|
8
|
+
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](pyproject.toml)
|
|
11
|
+
[](https://pytorch.org)
|
|
12
|
+
[](tests)
|
|
13
|
+
[](tests/test_jacobians.py)
|
|
14
|
+
|
|
15
|
+
<img src="assets/registration_demo.png" alt="splatreg before/after registration" width="92%">
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What it is
|
|
22
|
+
|
|
23
|
+
A 3D Gaussian Splat is a cloud of oriented Gaussians that already traces an object's surface. **splatreg takes two such 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.
|
|
24
|
+
|
|
25
|
+
The pipeline is two stages:
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart LR
|
|
29
|
+
A["splat A<br/>(target)"]:::s --> G
|
|
30
|
+
B["splat B<br/>(source)"]:::s --> G
|
|
31
|
+
G["<b>Global aligner</b><br/>super-Fibonacci SO(3) seeds<br/>+ batched trimmed ICP<br/><i>(or FPFH / learned)</i>"]:::g --> L
|
|
32
|
+
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
|
|
33
|
+
classDef s fill:#e8f6f8,stroke:#17becf,color:#0b3d44;
|
|
34
|
+
classDef g fill:#fff1ee,stroke:#ff6b5b,color:#5a1a12;
|
|
35
|
+
classDef l fill:#eef7ee,stroke:#2e8b57,color:#143d22;
|
|
36
|
+
classDef o fill:#f3eefc,stroke:#7d52c7,color:#2c1654;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
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.
|
|
40
|
+
2. **Refinement** — a from-scratch **Levenberg–Marquardt** core over a stack of residuals: classic **ICP** (point-to-point / point-to-plane) *and* splatreg's flagship **Gaussian-SDF** residual, solving the full SE(3) or Sim(3) tangent.
|
|
41
|
+
|
|
42
|
+
It **composes, it doesn't compete**: bring gsplat tensors directly; the LM loop and residual stack are pluggable.
|
|
43
|
+
|
|
44
|
+
### The differentiator — the Gaussian-SDF residual
|
|
45
|
+
|
|
46
|
+
No competitor packages this. splatreg derives a smooth, queryable **signed-distance field directly from the target Gaussians** — no mesh, no marching cubes — and drives registration by it:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
w_i(p) = exp(−‖p − q_i‖² / 2σ²) # Gaussian kernel weight per anchor
|
|
50
|
+
q̃(p) = Σ w_i q_i / Σ w_i # kernel-weighted centroid
|
|
51
|
+
ñ(p) = Σ w_i n_i / ‖Σ w_i n_i‖ # kernel-weighted surface normal
|
|
52
|
+
d(p) = (p − q̃(p)) · ñ(p) # signed distance — the residual
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`d(p)` vanishes exactly when the source points land on the target's surface. It has a **closed-form, audited Jacobian** and is a standalone, reusable implicit-field primitive: `gaussian_sdf(splat, points, sigma=...) → (sdf, normal)`.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Headline results
|
|
60
|
+
|
|
61
|
+
| | **splatreg** | reference |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| **Official 3DMatch registration recall** (Choi/Zeng protocol, 1279 pairs) | **91.5%** mean · 93.5% pooled | GeoTransformer ~92% · Open3D ~77% |
|
|
64
|
+
| **Official 3DMatch rotation / translation error** | **1.81° / 0.071 m** | — |
|
|
65
|
+
| **Official 3DLoMatch** (hard, 10–30% overlap) | 72.5% mean · **74.4%** pooled | GeoTransformer ~74% · Open3D ~20% |
|
|
66
|
+
| **Real-splat merge** (real 103k-Gaussian capture) | Chamfer **10.3→2.0 mm (5.1×)** · overlap **0.03→0.67 (22×)** | naive concat |
|
|
67
|
+
| **vs splat competitors** (real splat, known GT Sim3) | **5.2°** (SE3) · recovers scale (Sim3) | splatalign 15.3° · GaussianSplattingRegistration 36.3° |
|
|
68
|
+
| **Sim(3) scale estimation** | ✅ native | ✗ none of these do it |
|
|
69
|
+
| **Registration speed** | **~17 ms** (fast) · 104 ms (learned) | GeoTransformer ~50 ms · Open3D 142 ms |
|
|
70
|
+
| **Real-time tracking** | **~17 ms/frame** | GaussianFeels tracker ~45 ms |
|
|
71
|
+
|
|
72
|
+
splatreg is the **only library** that registers native Gaussian splats with SE(3)+**Sim(3)** behind a closed-form-Jacobian Gaussian-SDF.
|
|
73
|
+
|
|
74
|
+
- **Matches GeoTransformer on official 3DMatch** — 91.5% mean / 93.5% pooled recall vs their published ~92% — because the `learned` path **rides GeoTransformer's matcher at its native 0.025 m resolution** and then layers splatreg's SDF/LM refine + Sim(3) scale **on top**. The recall is GeoTransformer's; what splatreg *adds* is **accuracy** (RRE 1.87° → 1.81°), the unique **Sim(3) scale DoF**, and a **verified no-regression floor** (a per-pair audit found **0 demotions** — the refine only tightens already-successful pairs, never breaks them). On hard 3DLoMatch it reaches **74.4% pooled**, matching GeoTransformer's ~74%.
|
|
75
|
+
- **Decisively beats classical Open3D** (~77% / ~20%) on both splits.
|
|
76
|
+
- **Wins outright vs the splat-registration tools** — **5.2°** vs splatalign's 15.3° and GaussianSplattingRegistration's 36.3° on a real splat — and is the **only one that recovers Sim(3) scale.**
|
|
77
|
+
- **Real-splat merge** (`examples/merge_demo.py`) fuses two overlapping captures (a real 103k-Gaussian splat) into one deduped `.ply`: post-merge Chamfer **10.3 → 2.0 mm (5.1× closer)** and overlap **0.03 → 0.67 (22× more)** vs a naive `torch.cat`, removing ~9k overlap duplicates (verified on GPU, two independent runs).
|
|
78
|
+
|
|
79
|
+
### Four init modes — trade speed ↔ robustness
|
|
80
|
+
|
|
81
|
+
| `init=` | what | when |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `"fast"` *(default)* | FPFH + GPU-batched RANSAC seed → closed-form LM | objects / full-overlap, **~17 ms** |
|
|
84
|
+
| `"robust"` | Open3D FPFH+RANSAC seed → splatreg refine + scale | real metre-scale scans |
|
|
85
|
+
| `"learned"` | pretrained GeoTransformer seed → splatreg refine + scale | best accuracy on real scans |
|
|
86
|
+
| `"global"` | blind super-Fibonacci SO(3) sweep | robust fallback, any rotation |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Install
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/Archerkattri/splatreg.git
|
|
94
|
+
cd splatreg
|
|
95
|
+
pip install -e . # pure PyTorch + numpy; pip install -e ".[test]" for test extras
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quickstart
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from splatreg.api import register, merge
|
|
102
|
+
|
|
103
|
+
# two Gaussian splats of the same object, in unknown relative pose/scale.
|
|
104
|
+
# register aligns `source` onto the reference `target` (target is the first arg).
|
|
105
|
+
result = register(target, source, transform="sim3") # init="fast" by default (objects / full-overlap)
|
|
106
|
+
# real metre-scale scans -> init="robust" (FPFH+RANSAC) or init="learned" (GeoTransformer seed, best accuracy)
|
|
107
|
+
print(result.T) # recovered 4×4 similarity [[s·R, t], [0, 1]] — maps source -> target
|
|
108
|
+
print(result.scale) # recovered scale s (1.0 for transform="se3")
|
|
109
|
+
print(result.converged) # solver convergence flag
|
|
110
|
+
|
|
111
|
+
# register + dedupe a list of splats into one fused splat (registers internally)
|
|
112
|
+
fused = merge([source, target], transform="sim3")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The Gaussian-SDF field on its own:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from splatreg.geometry.gaussian_sdf import gaussian_sdf, gaussian_sdf_grad
|
|
119
|
+
sdf, normal = gaussian_sdf(target, query_points, sigma=0.02) # signed distance + surface normal
|
|
120
|
+
sdf, grad = gaussian_sdf_grad(target, query_points, sigma=0.02) # signed distance + EXACT ∇_p d
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Validation
|
|
126
|
+
|
|
127
|
+
Every number is reproducible; the full record — commands, seeds, and honest limitations — is in [`RESULTS.md`](RESULTS.md).
|
|
128
|
+
|
|
129
|
+
- **Synthetic Sim(3)/SE(3) recovery** — apply a known transform, recover it: **36/36 = 100%**, median rot 0.03°, scale error 0.34%, Chamfer 0.6 mm (`examples/validate_recovery.py`).
|
|
130
|
+
- **Jacobian audit** — every analytic Jacobian checked against a tangent-space numerical one in float64 (`tests/test_jacobians.py`): ICP point-to-point/plane, the **Gaussian-SDF closed-form gradient** (~1e-8 vs numerical), and SE(3)/Sim(3) exp·log incl. near-π. Ships a reusable `assert_residual_jacobian` so every future residual gets the audit.
|
|
131
|
+
- **vs plain ICP** — splatreg **27/27 Sim(3)** vs ICP's **9/27** (plain ICP cannot estimate scale; the LM Sim(3) solve is load-bearing).
|
|
132
|
+
- **Robustness** — sensor noise **9/9**, outlier clutter **9/9**, symmetric sphere **9/9**; partial overlap **6/9 solved** (all keep ≥ 60% at 0.00°) + 3 honestly flagged ambiguous (heavy keep ≤ 40% crops), **0 silent-wrong**.
|
|
133
|
+
- **Official 3DMatch / 3DLoMatch** — canonical Choi/Zeng protocol, 1279 pairs, covariance-weighted error (`benchmarks/threedmatch_official_bench.py`): **91.5% / 74.4%** as above.
|
|
134
|
+
- **Test suite** — `pytest tests/` → **44 passing**; `black` + `mypy` clean, ships `py.typed`.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install -e ".[test]"
|
|
138
|
+
python -m pytest tests/ -q # 44 passing: audit + Lie + solver
|
|
139
|
+
python tests/test_jacobians.py # the numerical-vs-analytic Jacobian audit
|
|
140
|
+
SPLATREG_DEVICE=cuda python examples/validate_recovery.py --device cuda # recovery 36/36
|
|
141
|
+
SPLATREG_DEVICE=cuda python benchmarks/robustness_bench.py --device cuda
|
|
142
|
+
python examples/merge_demo.py # real-splat merge demo
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Limitations
|
|
148
|
+
|
|
149
|
+
splatreg is honest about its edges (full detail in [`RESULTS.md`](RESULTS.md)):
|
|
150
|
+
|
|
151
|
+
- **Partial overlap — moderate crops now solve; heavy crops are genuinely ambiguous.** The overlap basin-sweep now keeps a deep candidate pool (`topk=200`) and ranks the refined seeds by a **symmetric** overlap residual (target→source *and* source→target), so the moderate keep ≈ 60% crops resolve to the true basin instead of a ~170° mirror — robustness sweep **PARTIAL solved-count 4/9 → 6/9** (all keep ≥ 60% at 0.00°). Heavy one-sided crops (keep ≤ 40%) delete the rotation-disambiguating geometry — there *even the true pose* no longer seats cleanly (symmetric residual 0.003 vs a forest of ~0.017 wrong basins), so the aligner returns an **honest ambiguity flag** (`result.info['ambiguous']` / `['confidence']`) rather than a silent wrong pose (0 silent-wrong, verified). This deep sweep costs ~22 s/cell (registration path only, never the real-time tracker); the heavy-crop case is genuinely open.
|
|
152
|
+
- **Scale is loose under low overlap.** A dedicated golden-section **scale line-search** against the symmetric overlap residual now refines the Sim(3) scale DoF after the pose solve (the one-directional fit is scale-blind). It tightens scale on its own objective, but on a thin shared band the scale still has a wide valley — under ~20% overlap the recovered scale can drift; `merge` is reliable for high-overlap captures.
|
|
153
|
+
- **The 3DMatch recall is GeoTransformer's, not ours.** splatreg `learned` **matches** GeoTransformer by riding its matcher; it does not beat that matcher's recall. What splatreg adds is accuracy, the Sim(3) scale DoF, and the no-regression floor. Closing the gap with splatreg's *own* dense correspondence is open work.
|
|
154
|
+
- **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.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Roadmap
|
|
159
|
+
|
|
160
|
+
Shipped: official 3DMatch + 3DLoMatch (Choi/Zeng, 91.5% / 74.4% pooled, matching GeoTransformer); pluggable `fast`/`robust`/`learned` init backends; CI regression gates (determinism + worst-case + PR-comment benchmark); the real-splat merge demo (`examples/merge_demo.py`); the head-to-head vs `splatalign` / `GaussianSplattingRegistration` (only tool recovering Sim(3) scale); the seeded-RANSAC determinism fix; and the partial-overlap basin sweep (4/9 → 6/9 solved). Remaining:
|
|
161
|
+
|
|
162
|
+
- [ ] **Heavy partial overlap (keep ≤ 40%) to publication.** The moderate keep ≈ 60% crops now solve; the heavy one-sided crops are correctly flagged ambiguous but still unsolved. Needs an overlap-region-restricted descriptor / learned overlap prior to push past the geometry-only basin ambiguity.
|
|
163
|
+
- [ ] **Tighten Sim(3) scale under low overlap.** The scale line-search helps but a thin shared band leaves a wide scale valley; a multi-scale or descriptor-anchored scale constraint is the next step.
|
|
164
|
+
- [ ] Close the gap to GeoTransformer's full coarse-to-fine matcher with splatreg's *own* dense correspondence (not just a borrowed seed).
|
|
165
|
+
- [ ] 6-DoF object-pose mode + FoundationPose/YCB benchmark (v0.2)
|
|
166
|
+
- [ ] Camera localization in a splat (v0.2)
|
|
167
|
+
- [ ] PyPI release
|
|
168
|
+
|
|
169
|
+
## License & layout
|
|
170
|
+
|
|
171
|
+
BSD 3-Clause — permissive, composes with the gsplat / Theseus / GTSAM ecosystem. `splatreg/` — library (`api`, `align`, `align_features`, `core/lie`, `geometry/gaussian_sdf`, `residuals/`, `solvers/lm`). `tests/` · `benchmarks/` · `examples/`. Full validation record: [`RESULTS.md`](RESULTS.md).
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "splatreg"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Composable geometry-first SE(3)/Sim(3) registration for 3D Gaussian Splatting — the inverse of gsplat."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "BSD-3-Clause" }
|
|
12
|
+
authors = [{ name = "Krishi Attri" }]
|
|
13
|
+
keywords = ["gaussian-splatting", "3dgs", "registration", "pose-estimation", "se3", "sim3", "gsplat"]
|
|
14
|
+
dependencies = ["torch", "numpy"]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
render = ["gsplat>=1.4"] # the Photometric residual + splatreg.render
|
|
18
|
+
pypose = ["pypose"] # solver backend
|
|
19
|
+
theseus = ["theseus-ai"] # differentiable solver backend
|
|
20
|
+
gtsam = ["gtsam"] # factor-graph backend (needs analytic Jacobians)
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=7.0",
|
|
23
|
+
"pytest-xdist",
|
|
24
|
+
"black>=24.0",
|
|
25
|
+
"mypy>=1.5",
|
|
26
|
+
"isort>=5.12",
|
|
27
|
+
"flake8>=6.0",
|
|
28
|
+
"ruff",
|
|
29
|
+
"hypothesis",
|
|
30
|
+
]
|
|
31
|
+
test = [
|
|
32
|
+
"pytest>=7.0",
|
|
33
|
+
"pytest-xdist",
|
|
34
|
+
"black>=24.0",
|
|
35
|
+
"mypy>=1.5",
|
|
36
|
+
"isort>=5.12",
|
|
37
|
+
"flake8>=6.0",
|
|
38
|
+
"ruff",
|
|
39
|
+
]
|
|
40
|
+
docs = ["sphinx", "furo", "myst-nb"]
|
|
41
|
+
all = ["gsplat>=1.4", "pypose", "theseus-ai"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
include = ["splatreg*"]
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.package-data]
|
|
47
|
+
splatreg = ["py.typed"]
|
|
48
|
+
|
|
49
|
+
[tool.black]
|
|
50
|
+
line-length = 110
|
|
51
|
+
target-version = ["py310", "py311"]
|
|
52
|
+
|
|
53
|
+
[tool.isort]
|
|
54
|
+
profile = "black"
|
|
55
|
+
line_length = 110
|
|
56
|
+
|
|
57
|
+
[tool.mypy]
|
|
58
|
+
python_version = "3.10"
|
|
59
|
+
ignore_missing_imports = true
|
|
60
|
+
# torch stubs are incomplete; suppress **kwargs unpacking noise for as_tensor calls
|
|
61
|
+
# and Optional→Tensor variance in the .to() convenience method.
|
|
62
|
+
[[tool.mypy.overrides]]
|
|
63
|
+
module = "splatreg.io"
|
|
64
|
+
ignore_errors = true
|
|
65
|
+
[[tool.mypy.overrides]]
|
|
66
|
+
module = "splatreg.core.types"
|
|
67
|
+
ignore_errors = true
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 110
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint]
|
|
73
|
+
# Default ruff lint set (pyflakes F + pycodestyle E/W). E731 (lambda assignment) and E741
|
|
74
|
+
# (ambiguous name `I`) flag intentional, readable code here (the Gaussians.to lambda; the
|
|
75
|
+
# identity matrix `I` in the Lie-group invariance tests), so they are silenced project-wide.
|
|
76
|
+
ignore = ["E731", "E741"]
|
splatreg-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""splatreg — composable geometry-first SE(3)/Sim(3) registration for 3D Gaussian Splatting.
|
|
2
|
+
|
|
3
|
+
*gsplat renders your Gaussians; splatreg registers against them.*
|
|
4
|
+
|
|
5
|
+
Public surface (filled in by the carve):
|
|
6
|
+
register(target, source, residuals=[...], transform="sim3", backend="builtin") -> RegisterResult
|
|
7
|
+
merge([a, b, ...], ref=0) -> Gaussians
|
|
8
|
+
Tracker(target, residuals=[...]).track(frame) -> RegisterResult
|
|
9
|
+
Residual, Solver (extension points)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .core.types import Gaussians, Frame, RegisterResult, LinearizedProblem, SE3Update
|
|
13
|
+
from .residuals.base import Residual
|
|
14
|
+
from .solvers.base import Solver
|
|
15
|
+
from .quality import QualityConfig, resolve_quality
|
|
16
|
+
|
|
17
|
+
# The high-level pipeline (splatreg.api) is added by the carve; tolerate its absence pre-build.
|
|
18
|
+
try:
|
|
19
|
+
from .api import register, merge, Tracker # noqa: F401
|
|
20
|
+
except ImportError:
|
|
21
|
+
register = merge = Tracker = None # type: ignore
|
|
22
|
+
|
|
23
|
+
__version__ = "0.0.1"
|
|
24
|
+
__all__ = [
|
|
25
|
+
"register",
|
|
26
|
+
"merge",
|
|
27
|
+
"Tracker",
|
|
28
|
+
"Residual",
|
|
29
|
+
"Solver",
|
|
30
|
+
"QualityConfig",
|
|
31
|
+
"resolve_quality",
|
|
32
|
+
"Gaussians",
|
|
33
|
+
"Frame",
|
|
34
|
+
"RegisterResult",
|
|
35
|
+
"LinearizedProblem",
|
|
36
|
+
"SE3Update",
|
|
37
|
+
]
|