multimodalart HF Staff commited on
Commit
7405e85
·
verified ·
1 Parent(s): 7166b72

CodecSplat demo

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +50 -0
  2. LICENSE-CodecSplat +21 -0
  3. README.md +44 -7
  4. app.py +338 -0
  5. codecsplat_pipeline.py +336 -0
  6. colmap_sfm.py +177 -0
  7. config/dataset/dl3dv.yaml +27 -0
  8. config/dataset/re10k.yaml +21 -0
  9. config/dataset/view_sampler/all.yaml +1 -0
  10. config/dataset/view_sampler/arbitrary.yaml +7 -0
  11. config/dataset/view_sampler/bounded.yaml +12 -0
  12. config/dataset/view_sampler/boundedv2.yaml +15 -0
  13. config/dataset/view_sampler/boundedv2_360.yaml +17 -0
  14. config/dataset/view_sampler/evaluation.yaml +4 -0
  15. config/dataset/view_sampler_dataset_specific_config/bounded_re10k.yaml +11 -0
  16. config/dataset/view_sampler_dataset_specific_config/boundedv2_dl3dv.yaml +14 -0
  17. config/dataset/view_sampler_dataset_specific_config/evaluation_dl3dv.yaml +7 -0
  18. config/dataset/view_sampler_dataset_specific_config/evaluation_re10k.yaml +5 -0
  19. config/experiment/dl3dv.yaml +41 -0
  20. config/experiment/re10k.yaml +40 -0
  21. config/loss/lpips.yaml +3 -0
  22. config/loss/mse.yaml +2 -0
  23. config/main.yaml +115 -0
  24. config/model/decoder/splatting_cuda.yaml +1 -0
  25. config/model/decoder/splatting_gsplat.yaml +10 -0
  26. config/model/encoder/codecsplat.yaml +48 -0
  27. examples/scene_a/00.jpg +3 -0
  28. examples/scene_a/01.jpg +3 -0
  29. examples/scene_a/02.jpg +3 -0
  30. examples/scene_a/03.jpg +3 -0
  31. examples/scene_a/04.jpg +3 -0
  32. examples/scene_a/05.jpg +3 -0
  33. examples/scene_a/06.jpg +3 -0
  34. examples/scene_a/07.jpg +3 -0
  35. examples/scene_a/08.jpg +3 -0
  36. examples/scene_a/09.jpg +3 -0
  37. examples/scene_a/10.jpg +3 -0
  38. examples/scene_a/11.jpg +3 -0
  39. examples/scene_a/12.jpg +3 -0
  40. examples/scene_a/13.jpg +3 -0
  41. examples/scene_a/14.jpg +3 -0
  42. examples/scene_a/15.jpg +3 -0
  43. examples/scene_a/16.jpg +3 -0
  44. examples/scene_a/17.jpg +3 -0
  45. examples/scene_a/18.jpg +3 -0
  46. examples/scene_a/19.jpg +3 -0
  47. examples/scene_a/20.jpg +3 -0
  48. examples/scene_a/21.jpg +3 -0
  49. examples/scene_a/22.jpg +3 -0
  50. examples/scene_a/23.jpg +3 -0
.gitattributes CHANGED
@@ -33,3 +33,53 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ examples/scene_a/00.jpg filter=lfs diff=lfs merge=lfs -text
37
+ examples/scene_a/01.jpg filter=lfs diff=lfs merge=lfs -text
38
+ examples/scene_a/02.jpg filter=lfs diff=lfs merge=lfs -text
39
+ examples/scene_a/03.jpg filter=lfs diff=lfs merge=lfs -text
40
+ examples/scene_a/04.jpg filter=lfs diff=lfs merge=lfs -text
41
+ examples/scene_a/05.jpg filter=lfs diff=lfs merge=lfs -text
42
+ examples/scene_a/06.jpg filter=lfs diff=lfs merge=lfs -text
43
+ examples/scene_a/07.jpg filter=lfs diff=lfs merge=lfs -text
44
+ examples/scene_a/08.jpg filter=lfs diff=lfs merge=lfs -text
45
+ examples/scene_a/09.jpg filter=lfs diff=lfs merge=lfs -text
46
+ examples/scene_a/10.jpg filter=lfs diff=lfs merge=lfs -text
47
+ examples/scene_a/11.jpg filter=lfs diff=lfs merge=lfs -text
48
+ examples/scene_a/12.jpg filter=lfs diff=lfs merge=lfs -text
49
+ examples/scene_a/13.jpg filter=lfs diff=lfs merge=lfs -text
50
+ examples/scene_a/14.jpg filter=lfs diff=lfs merge=lfs -text
51
+ examples/scene_a/15.jpg filter=lfs diff=lfs merge=lfs -text
52
+ examples/scene_a/16.jpg filter=lfs diff=lfs merge=lfs -text
53
+ examples/scene_a/17.jpg filter=lfs diff=lfs merge=lfs -text
54
+ examples/scene_a/18.jpg filter=lfs diff=lfs merge=lfs -text
55
+ examples/scene_a/19.jpg filter=lfs diff=lfs merge=lfs -text
56
+ examples/scene_a/20.jpg filter=lfs diff=lfs merge=lfs -text
57
+ examples/scene_a/21.jpg filter=lfs diff=lfs merge=lfs -text
58
+ examples/scene_a/22.jpg filter=lfs diff=lfs merge=lfs -text
59
+ examples/scene_a/23.jpg filter=lfs diff=lfs merge=lfs -text
60
+ examples/scene_a/24.jpg filter=lfs diff=lfs merge=lfs -text
61
+ examples/scene_b/00.jpg filter=lfs diff=lfs merge=lfs -text
62
+ examples/scene_b/01.jpg filter=lfs diff=lfs merge=lfs -text
63
+ examples/scene_b/02.jpg filter=lfs diff=lfs merge=lfs -text
64
+ examples/scene_b/03.jpg filter=lfs diff=lfs merge=lfs -text
65
+ examples/scene_b/04.jpg filter=lfs diff=lfs merge=lfs -text
66
+ examples/scene_b/05.jpg filter=lfs diff=lfs merge=lfs -text
67
+ examples/scene_b/06.jpg filter=lfs diff=lfs merge=lfs -text
68
+ examples/scene_b/07.jpg filter=lfs diff=lfs merge=lfs -text
69
+ examples/scene_b/08.jpg filter=lfs diff=lfs merge=lfs -text
70
+ examples/scene_b/09.jpg filter=lfs diff=lfs merge=lfs -text
71
+ examples/scene_b/10.jpg filter=lfs diff=lfs merge=lfs -text
72
+ examples/scene_b/11.jpg filter=lfs diff=lfs merge=lfs -text
73
+ examples/scene_b/12.jpg filter=lfs diff=lfs merge=lfs -text
74
+ examples/scene_b/13.jpg filter=lfs diff=lfs merge=lfs -text
75
+ examples/scene_b/14.jpg filter=lfs diff=lfs merge=lfs -text
76
+ examples/scene_b/15.jpg filter=lfs diff=lfs merge=lfs -text
77
+ examples/scene_b/16.jpg filter=lfs diff=lfs merge=lfs -text
78
+ examples/scene_b/17.jpg filter=lfs diff=lfs merge=lfs -text
79
+ examples/scene_b/18.jpg filter=lfs diff=lfs merge=lfs -text
80
+ examples/scene_b/19.jpg filter=lfs diff=lfs merge=lfs -text
81
+ examples/scene_b/20.jpg filter=lfs diff=lfs merge=lfs -text
82
+ examples/scene_b/21.jpg filter=lfs diff=lfs merge=lfs -text
83
+ examples/scene_b/22.jpg filter=lfs diff=lfs merge=lfs -text
84
+ examples/scene_b/23.jpg filter=lfs diff=lfs merge=lfs -text
85
+ examples/scene_b/24.jpg filter=lfs diff=lfs merge=lfs -text
LICENSE-CodecSplat ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pengpeng Yu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,13 +1,50 @@
1
  ---
2
- title: Codecsplat
3
- emoji: 👀
4
- colorFrom: green
5
- colorTo: purple
6
  sdk: gradio
7
  sdk_version: 6.30.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: CodecSplat
3
+ emoji: 🗜️
4
+ colorFrom: red
5
+ colorTo: yellow
6
  sdk: gradio
7
  sdk_version: 6.30.0
 
8
  app_file: app.py
9
+ short_description: Compress photos into a tiny 3D Gaussian splat bitstream
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 1h
12
+ models:
13
+ - pengpeng-yu/CodecSplat
14
  ---
15
 
16
+ # CodecSplat: Ultra-Compact Latent Coding for Feed-Forward 3D Gaussian Splatting
17
+
18
+ Demo of [**CodecSplat**](https://huggingface.co/papers/2605.25563) (Yu et al.). CodecSplat
19
+ entropy-codes the 2D features that a feed-forward 3DGS model (DepthSplat-style) uses for depth
20
+ and Gaussian prediction. A scene becomes a bitstream of a few tens of KB, which is then decoded
21
+ into renderable 3D Gaussians.
22
+
23
+ How the Space works:
24
+
25
+ 1. **COLMAP** (`pycolmap`, CPU) estimates intrinsics and camera poses for the uploaded photos.
26
+ The reconstruction is normalised and undistorted to PINHOLE.
27
+ 2. 8 context views are picked by farthest-point sampling over the cameras. They are resized and
28
+ center-cropped to 256×448 with the authors' crop shim.
29
+ 3. CodecSplat (`codecsplat-base-dl3dv-256x448-view8`, the authors' DL3DV evaluation preset)
30
+ runs the **complete bitstream path** from `ModelWrapper.test_step`: encode context →
31
+ compress to a real bitstream at the chosen λ → decompress → predict Gaussians, using bf16
32
+ autocast like `trainer.precision=bf16-mixed`.
33
+ 4. The authors' gsplat decoder (antialiased, eps2d=0.1) renders held-out views (PSNR is reported
34
+ against the real photos) and a fly-through video. The decoded Gaussians are shown in an
35
+ interactive `gr.Model3D` splat viewer, and the `.bin` bitstream is available to download.
36
+
37
+ Notes:
38
+ * gsplat's CUDA kernels are JIT-compiled at startup against the CUDA toolkit in the ZeroGPU
39
+ image, following `hugging-apps/resplat-recurrent-gs`.
40
+ * The viewer gets a compact copy of the Gaussians: DC colour only, near-transparent splats
41
+ pruned, re-centred so the scene is framed.
42
+
43
+ Code: https://github.com/pengpeng-yu/CodecSplat (MIT) · Weights: https://huggingface.co/pengpeng-yu/CodecSplat (MIT)
44
+
45
+ ## Example scenes
46
+
47
+ The example frames are from the DL3DV demo scene that the DepthSplat authors redistribute in
48
+ [`haofeixu/depthsplat`](https://huggingface.co/datasets/haofeixu/depthsplat) (MIT-labelled).
49
+ The imagery itself comes from [DL3DV-10K](https://dl3dv-10k.github.io/DL3DV-10K/), credit to
50
+ the DL3DV authors.
app.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CodecSplat: Ultra-Compact Latent Coding for Feed-Forward 3D Gaussian Splatting.
2
+
3
+ Upload overlapping photos of a static scene. The Space runs COLMAP (pycolmap, CPU) to get
4
+ camera poses, then CodecSplat (authors' DL3DV 8-view 256x448 checkpoint):
5
+ context views -> 2D latent features -> learned entropy coder -> real bitstream
6
+ -> decoder -> 3D Gaussians -> gsplat rendering.
7
+ You get an interactive splat viewer, a fly-through video, held-out novel views, the
8
+ size of the compressed scene bitstream (and the bitstream itself).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import ctypes
14
+ import glob
15
+ import os
16
+ import sys
17
+ import tempfile
18
+ import time
19
+ from pathlib import Path
20
+
21
+ APP_DIR = Path(__file__).parent.resolve()
22
+ sys.path.insert(0, str(APP_DIR))
23
+
24
+ os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "12.0")
25
+ os.environ.setdefault("MAX_JOBS", str(min(32, (os.cpu_count() or 8))))
26
+
27
+ import spaces # noqa: E402 (must precede any CUDA-touching import)
28
+ import torch # noqa: E402
29
+
30
+ torch.set_float32_matmul_precision("high")
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # 1. gsplat CUDA backend: JIT-compile once at startup against the CUDA toolkit
35
+ # shipped in the ZeroGPU image (same approach as hugging-apps/resplat-recurrent-gs).
36
+ # ---------------------------------------------------------------------------
37
+ def _pick_cuda_home() -> str | None:
38
+ torch_cuda = (torch.version.cuda or "").split(".")[0]
39
+ candidates: list[str] = []
40
+ for pattern in ("/cuda-image/usr/local/cuda-*", "/usr/local/cuda-*"):
41
+ candidates += sorted(glob.glob(pattern), reverse=True)
42
+ candidates += ["/cuda-image/usr/local/cuda", "/usr/local/cuda"]
43
+ usable = [c for c in candidates if os.path.isfile(os.path.join(c, "bin", "nvcc"))]
44
+ if not usable:
45
+ return None
46
+ for c in usable:
47
+ if torch_cuda and f"cuda-{torch_cuda}" in c:
48
+ return c
49
+ return usable[0]
50
+
51
+
52
+ CUDA_HOME = _pick_cuda_home()
53
+ print(f"[setup] torch {torch.__version__} (cuda {torch.version.cuda}) | CUDA_HOME={CUDA_HOME}", flush=True)
54
+ if CUDA_HOME:
55
+ os.environ["CUDA_HOME"] = CUDA_HOME
56
+ os.environ["CUDA_PATH"] = CUDA_HOME
57
+ os.environ["PATH"] = os.path.join(CUDA_HOME, "bin") + os.pathsep + os.environ.get("PATH", "")
58
+ _libdir = os.path.join(CUDA_HOME, "lib64")
59
+ os.environ["LD_LIBRARY_PATH"] = _libdir + os.pathsep + os.environ.get("LD_LIBRARY_PATH", "")
60
+ for _so in sorted(glob.glob(os.path.join(_libdir, "libcudart.so*")), reverse=True):
61
+ try:
62
+ ctypes.CDLL(_so, mode=ctypes.RTLD_GLOBAL)
63
+ break
64
+ except OSError:
65
+ continue
66
+
67
+ import torch.utils.cpp_extension as _cppext # noqa: E402
68
+
69
+ _cppext._check_cuda_version = lambda *a, **k: None
70
+
71
+ print("[setup] compiling gsplat CUDA kernels (one-off, several minutes)...", flush=True)
72
+ _t0 = time.time()
73
+ from gsplat.cuda._backend import _C as _GSPLAT_C # noqa: E402
74
+
75
+ if _GSPLAT_C is None:
76
+ raise RuntimeError(f"gsplat CUDA backend unavailable (CUDA_HOME={CUDA_HOME}).")
77
+ print(f"[setup] gsplat backend ready in {time.time() - _t0:.1f}s", flush=True)
78
+
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # 2. Model
82
+ # ---------------------------------------------------------------------------
83
+ import imageio.v2 as imageio # noqa: E402
84
+ import numpy as np # noqa: E402
85
+ import gradio as gr # noqa: E402
86
+ import pycolmap # noqa: E402
87
+ from huggingface_hub import hf_hub_download # noqa: E402
88
+ from PIL import Image # noqa: E402
89
+
90
+ import codecsplat_pipeline as cp # noqa: E402
91
+ from colmap_sfm import IMAGE_EXT, run_sfm # noqa: E402
92
+
93
+ MODEL_REPO = "pengpeng-yu/CodecSplat"
94
+ CHECKPOINT_FILE = "codecsplat-base-dl3dv-256x448-view8-075062d0.pth"
95
+ NUM_CONTEXT = 8
96
+ NUM_TARGET = 8
97
+ MAX_INPUT_IMAGES = 60
98
+
99
+ print("[setup] downloading checkpoint...", flush=True)
100
+ ckpt_path = hf_hub_download(MODEL_REPO, CHECKPOINT_FILE)
101
+ print("[setup] building CodecSplat...", flush=True)
102
+ ENCODER = cp.build_encoder(ckpt_path).to("cuda")
103
+ DECODER = cp.build_decoder().to("cuda")
104
+ print("[setup] model ready", flush=True)
105
+
106
+
107
+ def _u8(t: torch.Tensor) -> np.ndarray:
108
+ return (t.clamp(0, 1).permute(1, 2, 0).float().cpu().numpy() * 255).round().astype(np.uint8)
109
+
110
+
111
+ # ---------------------------------------------------------------------------
112
+ # 3. GPU part: encode -> bitstream -> decode -> render
113
+ # ---------------------------------------------------------------------------
114
+ @spaces.GPU(duration=60)
115
+ def codecsplat_gpu(image_paths: list[str], c2w: np.ndarray, K: np.ndarray, lmb: float = 1024.0):
116
+ ctx_idx, tgt_idx = cp.select_views(c2w, NUM_CONTEXT, NUM_TARGET)
117
+ batch = cp.to_device(cp.build_example(image_paths, c2w, K, ctx_idx, tgt_idx), "cuda")
118
+ h, w = batch["context"]["image"].shape[-2:]
119
+
120
+ torch.cuda.synchronize()
121
+ t0 = time.perf_counter()
122
+ out = cp.encode_decode(ENCODER, batch, lmb)
123
+ torch.cuda.synchronize()
124
+ t_codec = time.perf_counter() - t0
125
+ g = out["gaussians"]
126
+
127
+ # held-out novel views (+ PSNR against the real photos)
128
+ renders, score = None, float("nan")
129
+ if len(tgt_idx) > 0:
130
+ renders = cp.render(DECODER, g, batch["target"]["extrinsics"][0],
131
+ batch["target"]["intrinsics"][0], (h, w))
132
+ score = cp.psnr(batch["target"]["image"][0].float(), renders)
133
+
134
+ # smooth fly-through through the context cameras
135
+ traj_c2w, traj_K = cp.interpolate_trajectory(
136
+ batch["context"]["extrinsics"][0], batch["context"]["intrinsics"][0]
137
+ )
138
+ video = cp.render(DECODER, g, traj_c2w.cuda(), traj_K.cuda(), (h, w))
139
+ t_total = time.perf_counter() - t0
140
+
141
+ gauss = {
142
+ "means": g.means[0].float().cpu(),
143
+ "scales": g.scales[0].float().cpu(),
144
+ "rotations": g.rotations[0].float().cpu(),
145
+ "harmonics": g.harmonics[0].float().cpu(),
146
+ "opacities": g.opacities[0].float().cpu(),
147
+ }
148
+ return {
149
+ "bitstream": out["bitstream"],
150
+ "num_bytes": float(out["codec_num_bytes"]),
151
+ "camera_bytes": float(out["codec_camera_num_bytes"]),
152
+ "feature_bytes": float(out["codec_feature_num_bytes"]),
153
+ "gaussians": gauss,
154
+ "context_c2w": batch["context"]["extrinsics"][0].cpu(),
155
+ "context_images": [_u8(x) for x in batch["context"]["image"][0]],
156
+ "target_images": [_u8(x) for x in batch["target"]["image"][0]],
157
+ "renders": [] if renders is None else [_u8(x) for x in renders],
158
+ "video": [_u8(x) for x in video],
159
+ "psnr": score,
160
+ "t_codec": t_codec,
161
+ "t_total": t_total,
162
+ "hw": (int(h), int(w)),
163
+ "ctx_idx": ctx_idx.tolist(),
164
+ "tgt_idx": tgt_idx.tolist(),
165
+ }
166
+
167
+
168
+ # ---------------------------------------------------------------------------
169
+ # 4. Full pipeline (CPU COLMAP -> GPU CodecSplat -> CPU export)
170
+ # ---------------------------------------------------------------------------
171
+ def _read_colmap_scene(scene_dir: str):
172
+ rec = pycolmap.Reconstruction(os.path.join(scene_dir, "sparse"))
173
+ items = []
174
+ for _, img in rec.images.items():
175
+ cam = rec.cameras[img.camera_id]
176
+ p = cam.params
177
+ if cam.model.name == "PINHOLE":
178
+ fx, fy, cx, cy = p[:4]
179
+ elif cam.model.name == "SIMPLE_PINHOLE":
180
+ fx = fy = p[0]
181
+ cx, cy = p[1:3]
182
+ else:
183
+ continue
184
+ K = np.eye(3, dtype=np.float32)
185
+ K[0, 0], K[1, 1] = fx / cam.width, fy / cam.height
186
+ K[0, 2], K[1, 2] = cx / cam.width, cy / cam.height
187
+ w2c = np.eye(4, dtype=np.float64)
188
+ w2c[:3, :4] = img.cam_from_world().matrix() if callable(img.cam_from_world) else img.cam_from_world.matrix()
189
+ path = os.path.join(scene_dir, "images", img.name)
190
+ if os.path.exists(path):
191
+ items.append((img.name, path, np.linalg.inv(w2c).astype(np.float32), K))
192
+ items.sort(key=lambda x: x[0])
193
+ if len(items) < 2:
194
+ raise gr.Error("COLMAP registered fewer than 2 usable images.")
195
+ return [i[1] for i in items], np.stack([i[2] for i in items]), np.stack([i[3] for i in items])
196
+
197
+
198
+ def reconstruct(images, lmb: float = 1024.0, matcher: str = "exhaustive",
199
+ progress=gr.Progress(track_tqdm=False)):
200
+ """Reconstruct a compressed 3D Gaussian scene from multi-view photos with CodecSplat.
201
+
202
+ Args:
203
+ images: 3-60 overlapping photos of one static scene (camera poses are estimated with COLMAP).
204
+ lmb: rate-distortion trade-off lambda (16 = smallest bitstream, 1024 = best quality).
205
+ matcher: COLMAP matching mode, "exhaustive" (unordered photos) or "sequential" (video frames).
206
+
207
+ Returns:
208
+ Interactive splat (.ply), fly-through video, held-out novel views (render vs. photo),
209
+ selected input views, the scene bitstream (.bin) and a summary.
210
+ """
211
+ if not images:
212
+ raise gr.Error("Please upload at least 3 overlapping photos of the same scene.")
213
+ if isinstance(images, (str, Path)):
214
+ images = [images]
215
+ paths = [str(im if isinstance(im, (str, Path)) else getattr(im, "name", im)) for im in images]
216
+ paths = sorted([p for p in paths if p.lower().endswith(IMAGE_EXT)], key=os.path.basename)
217
+ if len(paths) < 3:
218
+ raise gr.Error(f"Got {len(paths)} usable image(s); COLMAP needs at least 3 overlapping photos.")
219
+ note = ""
220
+ if len(paths) > MAX_INPUT_IMAGES:
221
+ paths = [paths[i] for i in np.linspace(0, len(paths) - 1, MAX_INPUT_IMAGES).astype(int)]
222
+ note = f" (sub-sampled to {MAX_INPUT_IMAGES} images)"
223
+
224
+ t_start = time.time()
225
+ workdir = Path(tempfile.mkdtemp(prefix="codecsplat_"))
226
+ progress(0.05, desc=f"COLMAP on {len(paths)} images")
227
+ try:
228
+ sfm = run_sfm(paths, workdir, max_image_size=1024, matcher=matcher,
229
+ log=lambda m: print(m, flush=True))
230
+ except ValueError as exc:
231
+ raise gr.Error(str(exc)) from exc
232
+ t_sfm = time.time() - t_start
233
+ img_paths, c2w, K = _read_colmap_scene(sfm["scene_dir"])
234
+
235
+ progress(0.55, desc="CodecSplat: encode -> bitstream -> decode -> render")
236
+ r = codecsplat_gpu(img_paths, c2w, K, float(lmb))
237
+
238
+ progress(0.85, desc="Exporting Gaussians")
239
+ out = workdir / "out"
240
+ out.mkdir(exist_ok=True)
241
+ bin_path = out / f"codecsplat_scene_lmb{int(round(lmb))}.bin"
242
+ bin_path.write_bytes(r["bitstream"])
243
+ viewer_ply = cp.export_gaussians(r["gaussians"], r["context_c2w"], out)
244
+
245
+ video_path = str(out / "flythrough.mp4")
246
+ imageio.mimwrite(video_path, r["video"], fps=24, quality=8, macro_block_size=16)
247
+
248
+ gallery_ctx = [(im, f"input view {i}") for im, i in zip(r["context_images"], r["ctx_idx"])]
249
+ gallery_nv = []
250
+ for rend, gt, i in zip(r["renders"], r["target_images"], r["tgt_idx"]):
251
+ gallery_nv.append((np.concatenate([rend, gt], axis=1), f"held-out view {i}: render | photo"))
252
+
253
+ n_g = int(r["gaussians"]["means"].shape[0])
254
+ raw_bytes = n_g * (3 + 3 + 4 + 1 + r["gaussians"]["harmonics"][0].numel()) * 4
255
+ kb = r["num_bytes"] / 1024
256
+ summary = (
257
+ f"**Compressed scene: {kb:.1f} KB** (features {r['feature_bytes']/1024:.1f} KB + cameras "
258
+ f"{r['camera_bytes']/1024:.2f} KB) at λ = {lmb:g} — decodes to **{n_g:,} Gaussians** "
259
+ f"(≈{raw_bytes/2**20:.0f} MB as raw float32, **{raw_bytes/max(r['num_bytes'],1):,.0f}× smaller**).\n\n"
260
+ f"Held-out novel views: **PSNR {r['psnr']:.2f} dB** · {len(r['ctx_idx'])} input views at "
261
+ f"{r['hw'][0]}×{r['hw'][1]} · COLMAP registered {sfm['num_registered']}/{sfm['num_input']} "
262
+ f"images{note} in {t_sfm:.1f}s · CodecSplat encode+decode {r['t_codec']:.2f}s · "
263
+ f"total {time.time() - t_start:.1f}s"
264
+ )
265
+ return viewer_ply, video_path, gallery_nv, gallery_ctx, str(bin_path), summary
266
+
267
+
268
+ # ---------------------------------------------------------------------------
269
+ # 5. UI
270
+ # ---------------------------------------------------------------------------
271
+ EX_DIR = APP_DIR / "examples"
272
+ EXAMPLES = [sorted(str(p) for p in (EX_DIR / s).glob("*.jpg")) for s in ("scene_a", "scene_b")]
273
+
274
+ DESCRIPTION = """
275
+ # CodecSplat: Ultra-Compact Latent Coding for Feed-Forward 3D Gaussian Splatting
276
+
277
+ CodecSplat turns a few photos into 3D Gaussians **through a real compressed bitstream**: the 2D
278
+ features used for depth and Gaussian prediction are entropy-coded (tens of KB per scene), then
279
+ decoded into a renderable 3D Gaussian scene. Upload overlapping photos of a static scene —
280
+ camera poses are estimated with COLMAP right here — and pick a rate (λ).
281
+
282
+ [Paper](https://huggingface.co/papers/2605.25563) · [Code](https://github.com/pengpeng-yu/CodecSplat) · [Weights](https://huggingface.co/pengpeng-yu/CodecSplat)
283
+ """
284
+
285
+ CSS = """
286
+ #col-container { max-width: 1280px; margin: 0 auto; }
287
+ .dark .gradio-container { color: var(--body-text-color); }
288
+ """
289
+
290
+ with gr.Blocks(title="CodecSplat") as demo:
291
+ with gr.Column(elem_id="col-container"):
292
+ gr.Markdown(DESCRIPTION)
293
+ with gr.Row():
294
+ with gr.Column(scale=1):
295
+ image_files = gr.File(
296
+ label="Photos of one static scene (8-30 overlapping shots work best)",
297
+ file_count="multiple", file_types=["image"], type="filepath",
298
+ )
299
+ lmb = gr.Slider(16, 1024, value=1024, step=1, label="Rate λ",
300
+ info="16 = smallest bitstream, 1024 = highest quality")
301
+ run_btn = gr.Button("Compress & reconstruct", variant="primary")
302
+ with gr.Accordion("Advanced options", open=False):
303
+ matcher = gr.Radio(["exhaustive", "sequential"], value="exhaustive",
304
+ label="COLMAP matching",
305
+ info="exhaustive = unordered photos; sequential = video frames")
306
+ gr.Markdown(
307
+ "8 input views are picked by farthest-point sampling over the COLMAP cameras and "
308
+ "resized/cropped to 256×448 (the checkpoint's training resolution); up to 8 other "
309
+ "photos are held out to measure novel-view quality.\n\n"
310
+ "**Viewer**: drag to orbit, right-drag to pan, scroll to zoom."
311
+ )
312
+ with gr.Column(scale=2):
313
+ summary = gr.Markdown()
314
+ splat = gr.Model3D(label="Decoded 3D Gaussians (interactive)", height=440,
315
+ clear_color=[0.0, 0.0, 0.0, 1.0])
316
+ video = gr.Video(label="Fly-through rendered from the decoded Gaussians", autoplay=True, loop=True)
317
+ bin_out = gr.File(label="Compressed scene bitstream (.bin)")
318
+ novel = gr.Gallery(label="Held-out novel views: render | real photo", columns=2, height=320,
319
+ object_fit="contain")
320
+ inputs_gal = gr.Gallery(label="Selected input views (after crop)", columns=4, height=200,
321
+ object_fit="contain")
322
+
323
+ outputs = [splat, video, novel, inputs_gal, bin_out, summary]
324
+ run_btn.click(reconstruct, inputs=[image_files, lmb, matcher], outputs=outputs,
325
+ api_name="reconstruct")
326
+
327
+ gr.Examples(
328
+ examples=[[EXAMPLES[0], 1024], [EXAMPLES[1], 64]],
329
+ inputs=[image_files, lmb],
330
+ outputs=outputs,
331
+ fn=reconstruct,
332
+ cache_examples=True,
333
+ cache_mode="lazy",
334
+ label="Frames from the DL3DV demo scene (via the DepthSplat authors)",
335
+ )
336
+
337
+ if __name__ == "__main__":
338
+ demo.queue(max_size=12).launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True, show_error=True)
codecsplat_pipeline.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CodecSplat inference helpers for the Space.
2
+
3
+ Everything here mirrors the authors' evaluation path
4
+ (`scripts/test_dl3dv_view8_256x448.sh` -> `src/main.py` -> `ModelWrapper.test_step`):
5
+ the encoder runs the *complete* learned-codec path (encode context -> entropy-code the
6
+ 2D latent features into a real bitstream -> decode the bitstream -> predict Gaussians),
7
+ and the gsplat decoder renders novel views, all under bf16 autocast like
8
+ `trainer.precision=bf16-mixed`.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ from pathlib import Path
15
+ from types import SimpleNamespace
16
+
17
+ import numpy as np
18
+ import torch
19
+ from dacite import Config as DaciteConfig, from_dict
20
+ from einops import rearrange, repeat
21
+ from omegaconf import OmegaConf
22
+ from plyfile import PlyData, PlyElement
23
+ from scipy.spatial.transform import Rotation as R
24
+ from scipy.spatial.transform import Slerp
25
+
26
+ from src.dataset.shims.crop_shim import apply_crop_shim
27
+ from src.dataset.shims.patch_shim import apply_patch_shim
28
+ from src.misc.sh_rotation import rotate_sh
29
+ from src.model.encoder import get_encoder
30
+ from src.model.encoder.encoder_codecsplat import EncoderCodecSplatCfg
31
+
32
+ APP_DIR = Path(__file__).parent.resolve()
33
+
34
+ # ---- authors' DL3DV 8-view 256x448 evaluation preset ---------------------------------
35
+ IMAGE_SHAPE = (256, 448)
36
+ NEAR, FAR = 0.5, 200.0 # config/experiment/dl3dv.yaml
37
+ ENCODER_OVERRIDES = {
38
+ "num_depth_candidates": 128,
39
+ "shim_patch_size": 16,
40
+ "num_scales": 2,
41
+ "upsample_factor": 4,
42
+ "lowest_feature_resolution": 8,
43
+ "monodepth_vit_type": "vitb",
44
+ "enable_feature_codec": True,
45
+ "enable_depth_guided_mv_refiner": True,
46
+ "enable_post_vae_mv_refiner": True,
47
+ "codec_lmb_range": [16.0, 1024.0],
48
+ "supervise_intermediate_depth": False,
49
+ }
50
+ DECODER_OVERRIDES = {
51
+ "use_covariances": True,
52
+ "rasterize_mode": "antialiased",
53
+ "eps2d": 0.1,
54
+ "packed": True,
55
+ "scale_invariant": False,
56
+ }
57
+
58
+
59
+ def build_encoder(checkpoint_path: str):
60
+ enc_cfg = OmegaConf.load(APP_DIR / "config/model/encoder/codecsplat.yaml")
61
+ enc_cfg = OmegaConf.merge(enc_cfg, ENCODER_OVERRIDES)
62
+ enc_cfg = from_dict(
63
+ EncoderCodecSplatCfg,
64
+ OmegaConf.to_container(enc_cfg, resolve=True),
65
+ config=DaciteConfig(cast=[tuple], strict=False),
66
+ )
67
+ encoder = get_encoder(enc_cfg)
68
+
69
+ ckpt = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
70
+ state = ckpt.get("state_dict", ckpt)
71
+ state = {k[len("encoder."):]: v for k, v in state.items() if k.startswith("encoder.")}
72
+ encoder.load_state_dict(state, strict=True)
73
+ # eval() also runs `compress_mode` -> builds the entropy-coder CDF tables (on CPU).
74
+ encoder.eval()
75
+ return encoder
76
+
77
+
78
+ def build_decoder():
79
+ from src.model.decoder.decoder_splatting_gsplat import (
80
+ DecoderSplattingGsplat,
81
+ DecoderSplattingGsplatCfg,
82
+ )
83
+
84
+ dec_cfg = OmegaConf.load(APP_DIR / "config/model/decoder/splatting_gsplat.yaml")
85
+ dec_cfg = OmegaConf.merge(dec_cfg, DECODER_OVERRIDES)
86
+ dec_cfg = from_dict(
87
+ DecoderSplattingGsplatCfg,
88
+ OmegaConf.to_container(dec_cfg, resolve=True),
89
+ config=DaciteConfig(strict=False),
90
+ )
91
+ decoder = DecoderSplattingGsplat(dec_cfg, SimpleNamespace(background_color=[0.0, 0.0, 0.0]))
92
+ return decoder.eval()
93
+
94
+
95
+ # ---- view selection ------------------------------------------------------------------
96
+ def farthest_point_sample(xyz: np.ndarray, npoint: int) -> np.ndarray:
97
+ n = len(xyz)
98
+ if npoint >= n:
99
+ return np.arange(n)
100
+ dist = np.full(n, 1e10)
101
+ farthest = int(np.argmax(((xyz - xyz.mean(0)) ** 2).sum(-1)))
102
+ out = []
103
+ for _ in range(npoint):
104
+ out.append(farthest)
105
+ d = ((xyz - xyz[farthest]) ** 2).sum(-1)
106
+ dist = np.minimum(dist, d)
107
+ farthest = int(np.argmax(dist))
108
+ return np.sort(np.array(out))
109
+
110
+
111
+ def select_views(c2w: np.ndarray, num_context: int = 8, num_target: int = 8):
112
+ n = len(c2w)
113
+ ctx = farthest_point_sample(c2w[:, :3, 3], min(num_context, n))
114
+ rest = np.setdiff1d(np.arange(n), ctx)
115
+ if len(rest) > num_target:
116
+ rest = rest[np.linspace(0, len(rest) - 1, num_target).astype(int)]
117
+ return ctx, rest
118
+
119
+
120
+ # ---- batch construction (same conventions as the dataset loaders) --------------------
121
+ def _load_images(paths):
122
+ from PIL import Image
123
+
124
+ imgs = []
125
+ for p in paths:
126
+ im = np.asarray(Image.open(p).convert("RGB"), dtype=np.float32) / 255.0
127
+ imgs.append(torch.from_numpy(im).permute(2, 0, 1))
128
+ return torch.stack(imgs)
129
+
130
+
131
+ def image_shape_for(h: int, w: int) -> tuple[int, int]:
132
+ # The checkpoint is trained at 256x448 (landscape). Portrait inputs use 448x256.
133
+ return IMAGE_SHAPE if w >= h else (IMAGE_SHAPE[1], IMAGE_SHAPE[0])
134
+
135
+
136
+ def build_example(image_paths, c2w, K, ctx_idx, tgt_idx):
137
+ """Build a single (un-batched) example, apply the authors' crop + patch shims."""
138
+ images = _load_images(image_paths) # [N, 3, H, W]
139
+ shape = image_shape_for(images.shape[-2], images.shape[-1])
140
+ c2w = torch.as_tensor(c2w, dtype=torch.float32)
141
+ K = torch.as_tensor(K, dtype=torch.float32)
142
+
143
+ # Express all poses relative to the middle context camera (rigid change of frame).
144
+ pivot = c2w[ctx_idx[len(ctx_idx) // 2]]
145
+ c2w = torch.linalg.inv(pivot)[None] @ c2w
146
+
147
+ def views(idx):
148
+ idx = torch.as_tensor(np.asarray(idx), dtype=torch.long)
149
+ return {
150
+ "extrinsics": c2w[idx],
151
+ "intrinsics": K[idx],
152
+ "image": images[idx],
153
+ "near": torch.full((len(idx),), NEAR),
154
+ "far": torch.full((len(idx),), FAR),
155
+ "index": idx,
156
+ }
157
+
158
+ example = {"context": views(ctx_idx), "target": views(tgt_idx), "scene": "user_scene"}
159
+ example = apply_crop_shim(example, shape)
160
+ batch = {
161
+ "context": {k: v[None] for k, v in example["context"].items()},
162
+ "target": {k: v[None] for k, v in example["target"].items()},
163
+ "scene": ["user_scene"],
164
+ }
165
+ # shim_patch_size * downscale_factor = 16 * 4
166
+ return apply_patch_shim(batch, patch_size=64)
167
+
168
+
169
+ def to_device(batch, device):
170
+ out = {}
171
+ for k, v in batch.items():
172
+ if isinstance(v, dict):
173
+ out[k] = {kk: vv.to(device) for kk, vv in v.items()}
174
+ else:
175
+ out[k] = v
176
+ return out
177
+
178
+
179
+ # ---- smooth fly-through trajectory through the context cameras ----------------------
180
+ def order_cameras(c2w: torch.Tensor) -> list[int]:
181
+ """Greedy nearest-neighbour ordering of cameras (start from an extreme one)."""
182
+ pos = c2w[:, :3, 3].cpu().numpy()
183
+ n = len(pos)
184
+ if n <= 2:
185
+ return list(range(n))
186
+ start = int(np.argmax(((pos - pos.mean(0)) ** 2).sum(-1)))
187
+ order, left = [start], set(range(n)) - {start}
188
+ while left:
189
+ last = pos[order[-1]]
190
+ nxt = min(left, key=lambda i: float(((pos[i] - last) ** 2).sum()))
191
+ order.append(nxt)
192
+ left.remove(nxt)
193
+ return order
194
+
195
+
196
+ def interpolate_trajectory(c2w: torch.Tensor, K: torch.Tensor, frames_per_segment: int = 12):
197
+ order = order_cameras(c2w)
198
+ c2w = c2w[order].cpu().double().numpy()
199
+ K = K[order].cpu()
200
+ poses, intrs = [], []
201
+ for i in range(len(order) - 1):
202
+ rots = R.from_matrix(c2w[i : i + 2, :3, :3])
203
+ slerp = Slerp([0.0, 1.0], rots)
204
+ for t in np.linspace(0, 1, frames_per_segment, endpoint=False):
205
+ m = np.eye(4)
206
+ m[:3, :3] = slerp([t]).as_matrix()[0]
207
+ m[:3, 3] = (1 - t) * c2w[i, :3, 3] + t * c2w[i + 1, :3, 3]
208
+ poses.append(m)
209
+ intrs.append((1 - t) * K[i] + t * K[i + 1])
210
+ last = np.eye(4)
211
+ last[:3] = c2w[-1, :3]
212
+ poses.append(last)
213
+ intrs.append(K[-1])
214
+ poses = poses + poses[::-1][1:] # ping-pong loop
215
+ intrs = intrs + intrs[::-1][1:]
216
+ return (
217
+ torch.tensor(np.stack(poses), dtype=torch.float32),
218
+ torch.stack([torch.as_tensor(k, dtype=torch.float32) for k in intrs]),
219
+ )
220
+
221
+
222
+ # ---- core inference ------------------------------------------------------------------
223
+ @torch.no_grad()
224
+ def encode_decode(encoder, batch, lmb: float):
225
+ """Full learned-codec round trip: context -> bitstream -> Gaussians."""
226
+ encoder.feature_codec.cfg.lmb = float(lmb)
227
+ with torch.autocast("cuda", dtype=torch.bfloat16, enabled=batch["context"]["image"].is_cuda):
228
+ out = encoder(batch["context"], 0, visualization_dump={}, return_bitstream=True)
229
+ return out
230
+
231
+
232
+ @torch.no_grad()
233
+ def render(decoder, gaussians, extrinsics, intrinsics, hw, chunk: int = 16):
234
+ """Render [V] views. `extrinsics`/`intrinsics` are [V,4,4]/[V,3,3] on device."""
235
+ v = extrinsics.shape[0]
236
+ near = torch.full((1, v), NEAR, device=extrinsics.device)
237
+ far = torch.full((1, v), FAR, device=extrinsics.device)
238
+ colors = []
239
+ with torch.autocast("cuda", dtype=torch.bfloat16, enabled=extrinsics.is_cuda):
240
+ for s in range(0, v, chunk):
241
+ o = decoder.forward(
242
+ gaussians,
243
+ extrinsics[None, s : s + chunk],
244
+ intrinsics[None, s : s + chunk],
245
+ near[:, s : s + chunk],
246
+ far[:, s : s + chunk],
247
+ hw,
248
+ depth_mode=None,
249
+ )
250
+ colors.append(o.color[0].float().clamp(0, 1))
251
+ return torch.cat(colors, dim=0)
252
+
253
+
254
+ def psnr(gt: torch.Tensor, pred: torch.Tensor) -> float:
255
+ mse = ((gt - pred) ** 2).mean(dim=(1, 2, 3)).clamp_min(1e-10)
256
+ return float((-10 * torch.log10(mse)).mean())
257
+
258
+
259
+ # ---- Gaussian export -----------------------------------------------------------------
260
+ def world_rotations(rotations: torch.Tensor, context_c2w: torch.Tensor) -> torch.Tensor:
261
+ """Per-pixel Gaussian rotations are predicted in each context camera's frame."""
262
+ v = context_c2w.shape[0]
263
+ n = rotations.shape[0]
264
+ c2w_rot = repeat(context_c2w[:, :3, :3], "v i j -> (v r) i j", r=n // v)
265
+ local = R.from_quat(rotations.cpu().double().numpy()).as_matrix()
266
+ world = c2w_rot.cpu().double().numpy() @ local
267
+ return torch.from_numpy(R.from_matrix(world).as_quat()).float()
268
+
269
+
270
+ def write_ply(
271
+ path,
272
+ pivot_c2w: torch.Tensor,
273
+ means: torch.Tensor,
274
+ scales: torch.Tensor,
275
+ rotations_xyzw_world: torch.Tensor,
276
+ harmonics: torch.Tensor,
277
+ opacities: torch.Tensor,
278
+ ):
279
+ """Same output as the authors' `export_ply(..., align_to_view=True)`, vectorised."""
280
+ means = means.float().cpu()
281
+ scales = scales.float().cpu()
282
+ harmonics = harmonics.float().cpu()
283
+ opacities = opacities.float().cpu()
284
+ pivot = pivot_c2w.float().cpu()
285
+
286
+ view_rot = pivot[:3, :3].T
287
+ means = (means - pivot[:3, 3]) @ view_rot.T
288
+ harmonics = rotate_sh(harmonics, view_rot)
289
+ rot = R.from_quat(rotations_xyzw_world.double().numpy()).as_matrix()
290
+ rot = R.from_matrix(view_rot.double().numpy() @ rot).as_quat() # xyzw
291
+ rot = np.concatenate([rot[:, 3:4], rot[:, :3]], axis=1) # wxyz
292
+
293
+ dc = harmonics[..., 0].numpy()
294
+ rest = harmonics[..., 1:].flatten(start_dim=1).numpy()
295
+ logit_op = torch.logit(opacities.clamp(1e-6, 1 - 1e-6))[:, None].numpy()
296
+ log_scales = scales.clamp_min(1e-12).log().numpy()
297
+ m = means.numpy()
298
+
299
+ cols = [m, np.zeros_like(m), dc, rest, logit_op, log_scales, rot]
300
+ data = np.concatenate(cols, axis=1).astype(np.float32)
301
+ names = ["x", "y", "z", "nx", "ny", "nz", "f_dc_0", "f_dc_1", "f_dc_2"]
302
+ names += [f"f_rest_{i}" for i in range(rest.shape[1])]
303
+ names += ["opacity", "scale_0", "scale_1", "scale_2", "rot_0", "rot_1", "rot_2", "rot_3"]
304
+ arr = np.empty(data.shape[0], dtype=[(n, "f4") for n in names])
305
+ for i, n in enumerate(names):
306
+ arr[n] = data[:, i]
307
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
308
+ PlyData([PlyElement.describe(arr, "vertex")]).write(str(path))
309
+ return str(path)
310
+
311
+
312
+ def export_gaussians(g: dict, context_c2w: torch.Tensor, outdir: Path,
313
+ viewer_max: int = 600_000, viewer_min_opacity: float = 0.02):
314
+ """Write a compact (DC colour, pruned), re-framed .ply of the Gaussians for the web viewer."""
315
+ pivot = context_c2w[len(context_c2w) // 2]
316
+ rot_world = world_rotations(g["rotations"], context_c2w)
317
+
318
+ keep = g["opacities"] > viewer_min_opacity
319
+ if int(keep.sum()) < 1000:
320
+ keep = torch.ones_like(keep)
321
+ idx = torch.nonzero(keep).squeeze(1)
322
+ if idx.numel() > viewer_max:
323
+ idx = idx[torch.randperm(idx.numel())[:viewer_max]]
324
+ means, scales = g["means"][idx], g["scales"][idx]
325
+
326
+ # Re-centre / re-scale (in the pivot camera frame) so the viewer's orbit camera frames it.
327
+ view_rot = pivot[:3, :3].T
328
+ local = (means - pivot[:3, 3]) @ view_rot.T
329
+ center = local.median(dim=0).values
330
+ radius = torch.quantile((local - center).norm(dim=-1).float(), 0.7)
331
+ s = float(3.0 / max(float(radius), 1e-6))
332
+ # shift in world coordinates so that the pivot-frame centre maps to the origin
333
+ means = (means - pivot[:3, 3] - center @ view_rot) * s + pivot[:3, 3]
334
+ viewer_path = write_ply(outdir / "codecsplat_viewer.ply", pivot, means, scales * s,
335
+ rot_world[idx], g["harmonics"][idx][..., :1], g["opacities"][idx])
336
+ return viewer_path
colmap_sfm.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """COLMAP structure-from-motion preprocessing (runs inside the Space, on CPU).
2
+
3
+ Takes a set of raw multi-view images of a scene, estimates camera intrinsics /
4
+ poses / a sparse point cloud with COLMAP (via `pycolmap`), undistorts the images
5
+ to a PINHOLE camera model and writes a standard COLMAP scene layout
6
+
7
+ <out>/images/*.jpg
8
+ <out>/sparse/{cameras,images,points3D}.bin
9
+
10
+ which is exactly what CodecSplat's reference COLMAP loader expects.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import shutil
16
+ from pathlib import Path
17
+
18
+ import pycolmap
19
+ from PIL import Image, ImageOps
20
+
21
+ IMAGE_EXT = (".png", ".jpg", ".jpeg", ".webp", ".bmp", ".tif", ".tiff")
22
+
23
+
24
+ def prepare_images(image_paths, dest: Path, max_image_size: int = 1280) -> list[str]:
25
+ """Copy the uploaded images into `dest`, EXIF-rotated, RGB, size-capped."""
26
+ dest.mkdir(parents=True, exist_ok=True)
27
+ names: list[str] = []
28
+ for i, src in enumerate(str(p) for p in image_paths): # caller decides the order
29
+ if not src.lower().endswith(IMAGE_EXT):
30
+ continue
31
+ try:
32
+ img = Image.open(src)
33
+ img = ImageOps.exif_transpose(img).convert("RGB")
34
+ except Exception as exc:
35
+ print(f"[colmap] skipping unreadable image {src}: {exc}", flush=True)
36
+ continue
37
+ scale = max_image_size / max(img.size)
38
+ if scale < 1.0:
39
+ img = img.resize(
40
+ (max(1, int(round(img.width * scale))), max(1, int(round(img.height * scale)))),
41
+ Image.LANCZOS,
42
+ )
43
+ name = f"{i:04d}.jpg"
44
+ img.save(dest / name, quality=95)
45
+ names.append(name)
46
+ return names
47
+
48
+
49
+ def _largest_reconstruction(maps) -> pycolmap.Reconstruction | None:
50
+ recs = list(maps.values()) if isinstance(maps, dict) else list(maps)
51
+ if not recs:
52
+ return None
53
+ return max(recs, key=lambda r: r.num_reg_images())
54
+
55
+
56
+ def run_sfm(
57
+ image_paths,
58
+ workdir: Path,
59
+ max_image_size: int = 1280,
60
+ matcher: str = "exhaustive",
61
+ max_num_features: int = 8192,
62
+ log=print,
63
+ ) -> dict:
64
+ """Run COLMAP SfM on `image_paths`; return info about the written scene.
65
+
66
+ Returns a dict with `scene_dir`, `sparse_sub`, `images_sub`, `num_input`,
67
+ `num_registered`, `num_points3D`, `camera_model`.
68
+ """
69
+ workdir = Path(workdir)
70
+ image_dir = workdir / "sfm_images"
71
+ names = prepare_images(image_paths, image_dir, max_image_size)
72
+ if len(names) < 3:
73
+ raise ValueError(
74
+ f"COLMAP needs at least 3 usable images, got {len(names)}. "
75
+ "Upload several overlapping photos of the same scene."
76
+ )
77
+ log(f"[colmap] {len(names)} images prepared at <= {max_image_size}px")
78
+
79
+ database_path = workdir / "colmap.db"
80
+ if database_path.exists():
81
+ database_path.unlink()
82
+
83
+ extraction_options = pycolmap.FeatureExtractionOptions()
84
+ extraction_options.sift.max_num_features = int(max_num_features)
85
+ extraction_options.sift.estimate_affine_shape = False
86
+ extraction_options.sift.domain_size_pooling = False
87
+ extraction_options.use_gpu = False
88
+ extraction_options.max_image_size = int(max_image_size)
89
+
90
+ reader_options = pycolmap.ImageReaderOptions()
91
+ reader_options.camera_model = "SIMPLE_RADIAL"
92
+
93
+ pycolmap.extract_features(
94
+ database_path=database_path,
95
+ image_path=image_dir,
96
+ camera_mode=pycolmap.CameraMode.SINGLE,
97
+ reader_options=reader_options,
98
+ extraction_options=extraction_options,
99
+ device=pycolmap.Device.cpu,
100
+ )
101
+ log("[colmap] features extracted")
102
+
103
+ if matcher == "sequential":
104
+ pairing = pycolmap.SequentialPairingOptions()
105
+ pairing.overlap = 10
106
+ pairing.quadratic_overlap = True
107
+ pycolmap.match_sequential(
108
+ database_path=database_path,
109
+ pairing_options=pairing,
110
+ device=pycolmap.Device.cpu,
111
+ )
112
+ else:
113
+ pycolmap.match_exhaustive(database_path=database_path, device=pycolmap.Device.cpu)
114
+ log(f"[colmap] {matcher} matching done")
115
+
116
+ sparse_dir = workdir / "sparse_raw"
117
+ sparse_dir.mkdir(parents=True, exist_ok=True)
118
+ options = pycolmap.IncrementalPipelineOptions()
119
+ options.min_num_matches = 15
120
+ options.multiple_models = False
121
+ options.extract_colors = True
122
+ maps = pycolmap.incremental_mapping(
123
+ database_path=database_path,
124
+ image_path=image_dir,
125
+ output_path=sparse_dir,
126
+ options=options,
127
+ )
128
+ rec = _largest_reconstruction(maps)
129
+ if rec is None or rec.num_reg_images() < 3:
130
+ raise ValueError(
131
+ "COLMAP could not reconstruct this image set (only "
132
+ f"{0 if rec is None else rec.num_reg_images()} of {len(names)} images registered). "
133
+ "Use photos with plenty of visual overlap between neighbours, avoid pure "
134
+ "rotation / blurry frames, and keep the scene static."
135
+ )
136
+ # Deterministic world scale (same convention COLMAP itself uses), so that the
137
+ # fixed near/far planes of the CodecSplat checkpoint stay meaningful.
138
+ rec.normalize(fixed_scale=False, extent=10.0)
139
+ log(f"[colmap] mapped {rec.num_reg_images()}/{len(names)} images, {rec.num_points3D()} points")
140
+
141
+ model_dir = workdir / "sparse_model"
142
+ if model_dir.exists():
143
+ shutil.rmtree(model_dir)
144
+ model_dir.mkdir(parents=True)
145
+ rec.write(str(model_dir))
146
+
147
+ scene_dir = workdir / "colmap_scene"
148
+ if scene_dir.exists():
149
+ shutil.rmtree(scene_dir)
150
+ pycolmap.undistort_images(
151
+ output_path=scene_dir,
152
+ input_path=model_dir,
153
+ image_path=image_dir,
154
+ )
155
+ log("[colmap] undistorted to PINHOLE")
156
+
157
+ # Keep only what CodecSplat reads (the undistorter also writes stereo/ scaffolding).
158
+ for extra in ("stereo",):
159
+ p = scene_dir / extra
160
+ if p.is_dir():
161
+ shutil.rmtree(p, ignore_errors=True)
162
+
163
+ cam_model = "PINHOLE"
164
+ try:
165
+ cam_model = list(rec.cameras.values())[0].model.name
166
+ except Exception:
167
+ pass
168
+
169
+ return {
170
+ "scene_dir": str(scene_dir),
171
+ "sparse_sub": "sparse",
172
+ "images_sub": "images",
173
+ "num_input": len(names),
174
+ "num_registered": int(rec.num_reg_images()),
175
+ "num_points3D": int(rec.num_points3D()),
176
+ "camera_model": cam_model,
177
+ }
config/dataset/dl3dv.yaml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - view_sampler: boundedv2_360
3
+
4
+ name: dl3dv
5
+ roots: [datasets/DL3DV-ALL-480P-Processed]
6
+ augment: true
7
+
8
+ image_shape: [270, 480]
9
+ background_color: [0.0, 0.0, 0.0]
10
+ cameras_are_circular: false
11
+
12
+ max_fov: 100.0
13
+
14
+ skip_bad_shape: true
15
+ near: -1.
16
+ far: -1.
17
+ shuffle_val: true
18
+ test_len: -1
19
+ test_chunk_interval: 1
20
+ sort_target_index: true
21
+ sort_context_index: true
22
+
23
+ train_times_per_scene: 1
24
+ test_times_per_scene: 1
25
+ ori_image_shape: [270, 480]
26
+ overfit_max_views: 148
27
+ use_index_to_load_chunk: false
config/dataset/re10k.yaml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - view_sampler: bounded
3
+
4
+ name: re10k
5
+ roots: [datasets/RE10K-Torch-360p]
6
+ augment: true
7
+
8
+ image_shape: [180, 320]
9
+ background_color: [0.0, 0.0, 0.0]
10
+ cameras_are_circular: false
11
+
12
+ max_fov: 100.0
13
+
14
+ skip_bad_shape: true
15
+ near: -1.
16
+ far: -1.
17
+ shuffle_val: true
18
+ test_len: -1
19
+ test_chunk_interval: 1
20
+
21
+ use_index_to_load_chunk: false
config/dataset/view_sampler/all.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ name: all
config/dataset/view_sampler/arbitrary.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ name: arbitrary
2
+
3
+ num_target_views: 1
4
+ num_context_views: 2
5
+
6
+ # If you want to hard-code context views, do so here.
7
+ context_views: null
config/dataset/view_sampler/bounded.yaml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: bounded
2
+
3
+ num_target_views: 1
4
+ num_context_views: 2
5
+
6
+ min_distance_between_context_views: 2
7
+ max_distance_between_context_views: 6
8
+ min_distance_to_context_views: 0
9
+
10
+ warm_up_steps: 0
11
+ initial_min_distance_between_context_views: 2
12
+ initial_max_distance_between_context_views: 6
config/dataset/view_sampler/boundedv2.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: boundedv2
2
+
3
+ num_target_views: 1
4
+ num_context_views: 2
5
+
6
+ min_distance_between_context_views: 2
7
+ max_distance_between_context_views: 6
8
+ max_distance_to_context_views: 0
9
+
10
+ context_gap_warm_up_steps: 0
11
+ target_gap_warm_up_steps: 0
12
+
13
+ initial_min_distance_between_context_views: 2
14
+ initial_max_distance_between_context_views: 6
15
+ initial_max_distance_to_context_views: 0
config/dataset/view_sampler/boundedv2_360.yaml ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: boundedv2
2
+
3
+ num_target_views: 4
4
+ num_context_views: 4
5
+
6
+ min_distance_between_context_views: 20
7
+ max_distance_between_context_views: 50
8
+ max_distance_to_context_views: 0
9
+
10
+ context_gap_warm_up_steps: 10000
11
+ target_gap_warm_up_steps: 0
12
+
13
+ initial_min_distance_between_context_views: 15
14
+ initial_max_distance_between_context_views: 30
15
+ initial_max_distance_to_context_views: 0
16
+ extra_views_sampling_strategy: farthest_point
17
+ target_views_replace_sample: false
config/dataset/view_sampler/evaluation.yaml ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ name: evaluation
2
+
3
+ index_path: assets/evaluation_index_re10k_video.json
4
+ num_context_views: 2
config/dataset/view_sampler_dataset_specific_config/bounded_re10k.yaml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+
3
+ dataset:
4
+ view_sampler:
5
+ min_distance_between_context_views: 45
6
+ max_distance_between_context_views: 135
7
+ min_distance_to_context_views: 0
8
+ warm_up_steps: 30000
9
+ initial_min_distance_between_context_views: 25
10
+ initial_max_distance_between_context_views: 45
11
+ num_target_views: 4
config/dataset/view_sampler_dataset_specific_config/boundedv2_dl3dv.yaml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+
3
+ dataset:
4
+ view_sampler:
5
+ min_distance_between_context_views: 20
6
+ max_distance_between_context_views: 50
7
+ max_distance_to_context_views: 0
8
+ context_gap_warm_up_steps: 10000
9
+ target_gap_warm_up_steps: 0
10
+ initial_min_distance_between_context_views: 15
11
+ initial_max_distance_between_context_views: 30
12
+ initial_max_distance_to_context_views: 0
13
+ extra_views_sampling_strategy: farthest_point
14
+ num_target_views: 4
config/dataset/view_sampler_dataset_specific_config/evaluation_dl3dv.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+
3
+ dataset:
4
+ image_shape: [256, 448]
5
+ view_sampler:
6
+ index_path: assets/dl3dv_evaluation/dl3dv_start_0_distance_40_ctx_8v_tgt_8v.json
7
+ num_context_views: 8
config/dataset/view_sampler_dataset_specific_config/evaluation_re10k.yaml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # @package _global_
2
+
3
+ dataset:
4
+ view_sampler:
5
+ index_path: assets/evaluation_index_re10k.json
config/experiment/dl3dv.yaml ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+
3
+ defaults:
4
+ - override /dataset: dl3dv
5
+ - override /model/encoder: codecsplat
6
+ - override /loss: [mse, lpips]
7
+ - override /dataset/view_sampler: boundedv2_360
8
+
9
+ wandb:
10
+ name: dl3dv
11
+ tags: [dl3dv, 270x480]
12
+
13
+ data_loader:
14
+ train:
15
+ batch_size: 1
16
+
17
+ trainer:
18
+ max_steps: 300_001
19
+ num_nodes: 1
20
+
21
+ model:
22
+ encoder:
23
+ num_depth_candidates: 128
24
+ shim_patch_size: 16
25
+
26
+ # lpips loss
27
+ loss:
28
+ lpips:
29
+ apply_after_step: 0
30
+ weight: 0.05
31
+
32
+ dataset:
33
+ near: 0.5
34
+ far: 200.
35
+ min_views: 0
36
+ max_views: 0
37
+ highres: false
38
+
39
+ test:
40
+ eval_time_skip_steps: 0
41
+ compute_scores: true
config/experiment/re10k.yaml ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @package _global_
2
+
3
+ defaults:
4
+ - override /dataset: re10k
5
+ - override /model/encoder: codecsplat
6
+ - override /loss: [mse, lpips]
7
+
8
+ wandb:
9
+ name: re10k
10
+ tags: [re10k, 256x256]
11
+
12
+ data_loader:
13
+ train:
14
+ batch_size: 14
15
+
16
+ trainer:
17
+ max_steps: 300_001
18
+ num_nodes: 1
19
+
20
+ model:
21
+ encoder:
22
+ num_depth_candidates: 128
23
+
24
+ # lpips loss
25
+ loss:
26
+ lpips:
27
+ apply_after_step: 0
28
+ weight: 0.05
29
+
30
+ dataset:
31
+ image_shape: [256, 256]
32
+ roots: [datasets/RE10K-Torch-360p]
33
+ near: 0.5
34
+ far: 100.
35
+ train_times_per_scene: 1
36
+ highres: false
37
+
38
+ test:
39
+ eval_time_skip_steps: 0
40
+ compute_scores: true
config/loss/lpips.yaml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ lpips:
2
+ weight: 0.05
3
+ apply_after_step: 150_000
config/loss/mse.yaml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ mse:
2
+ weight: 1.0
config/main.yaml ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - dataset: re10k
3
+ - optional dataset/view_sampler_dataset_specific_config: ${dataset/view_sampler}_${dataset}
4
+ - model/encoder: codecsplat
5
+ - model/decoder: splatting_gsplat
6
+ - loss: [mse]
7
+ - _self_
8
+
9
+ wandb:
10
+ project: codecsplat
11
+ entity: null
12
+ name: placeholder
13
+ mode: online
14
+ id: null
15
+
16
+ mode: train
17
+
18
+ dataset:
19
+ overfit_to_scene: null
20
+
21
+ data_loader:
22
+ train:
23
+ num_workers: 10
24
+ persistent_workers: true
25
+ batch_size: 4
26
+ seed: 1234
27
+ test:
28
+ num_workers: 4
29
+ persistent_workers: false
30
+ batch_size: 1
31
+ seed: 2345
32
+ val:
33
+ num_workers: 1
34
+ persistent_workers: true
35
+ batch_size: 1
36
+ seed: 3456
37
+
38
+ optimizer:
39
+ lr: 2.e-4
40
+ lr_monodepth: 2.e-6
41
+ warm_up_steps: 2000
42
+ weight_decay: 0.01
43
+ div_factor: 25.
44
+ final_div_factor: 1.e4
45
+
46
+ checkpointing:
47
+ load: null
48
+ every_n_train_steps: 5000
49
+ save_top_k: 5
50
+ pretrained_model: null
51
+ pretrained_monodepth: null
52
+ pretrained_mvdepth: null
53
+ pretrained_depth: null
54
+ no_strict_load: false
55
+ resume: false
56
+
57
+ train:
58
+ depth_mode: null
59
+ extended_visualization: false
60
+ print_log_every_n_steps: 100
61
+ eval_model_every_n_val: 2 # quantitative evaluation every n val
62
+ eval_data_length: 999999
63
+ eval_time_skip_steps: 3
64
+ eval_save_model: true
65
+ l1_loss: false
66
+ intermediate_loss_weight: 0.9
67
+ no_viz_video: false
68
+ train_ignore_large_loss: 0.
69
+
70
+ test:
71
+ compute_scores: true
72
+ eval_time_skip_steps: 0
73
+ save_image: false
74
+ save_video: false
75
+ save_gt_image: false
76
+ save_input_images: false
77
+ save_depth: false
78
+ save_depth_npy: false
79
+ save_depth_concat_img: false
80
+ save_gaussian: false
81
+ save_bitstream: false
82
+ render_chunk_size: null
83
+ stabilize_camera: false
84
+ stab_camera_kernel: 50
85
+ gaussian_export_all_points: true
86
+ gaussian_export_align_to_view: false
87
+
88
+ seed: 111123
89
+
90
+ trainer:
91
+ max_steps: -1
92
+ val_check_interval: 0.5
93
+ gradient_clip_val: 0.5
94
+ accumulate_grad_batches: 1
95
+ precision: 32-true
96
+ num_sanity_val_steps: 2
97
+ num_nodes: 1
98
+
99
+ output_dir: checkpoints/tmp
100
+
101
+ use_plugins: false
102
+
103
+ hydra:
104
+ run:
105
+ dir: ${output_dir}/hydra/${now:%Y-%m-%d-%H-%M-%S}
106
+ job:
107
+ chdir: false
108
+ output_subdir: .
109
+ job_logging:
110
+ handlers:
111
+ file:
112
+ filename: /dev/null
113
+ root:
114
+ handlers:
115
+ - console
config/model/decoder/splatting_cuda.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ name: splatting_cuda
config/model/decoder/splatting_gsplat.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ name: splatting_gsplat
2
+ render_mode: RGB
3
+ rasterize_mode: antialiased
4
+ use_covariances: true
5
+ packed: true
6
+ eps2d: 0.1
7
+ sparse_grad: false
8
+ absgrad: false
9
+ channel_chunk: 32
10
+ scale_invariant: false
config/model/encoder/codecsplat.yaml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: codecsplat
2
+
3
+ num_depth_candidates: 128
4
+ num_surfaces: 1
5
+
6
+ gaussian_adapter:
7
+ gaussian_scale_min: 1e-10
8
+ gaussian_scale_max: 3.
9
+ sh_degree: 2
10
+ exp_scale_bias: 4.
11
+ no_rotate_sh: false
12
+ no_sh_mask: false
13
+
14
+ # Input alignment
15
+ downscale_factor: 4
16
+ shim_patch_size: 4
17
+
18
+ # monodepth
19
+ monodepth_vit_type: vits
20
+
21
+ # return depth
22
+ supervise_intermediate_depth: true
23
+ return_depth: true
24
+
25
+ # mv_unimatch
26
+ num_scales: 1
27
+ upsample_factor: 4
28
+ lowest_feature_resolution: 4
29
+ depth_unet_channels: 128
30
+ grid_sample_disable_cudnn: false
31
+ local_mv_match: 2
32
+
33
+ # color branch
34
+ init_sh_input_img: false
35
+ gaussian_regressor_channels: 64
36
+
37
+ # feature codec path
38
+ enable_feature_codec: false
39
+ enable_depth_guided_mv_refiner: false
40
+ enable_post_vae_mv_refiner: false
41
+ codec_loss_weight: 1.0
42
+ codec_depth_loss_weight: 0.1
43
+ codec_lmb_range: [16.0, 1024.0]
44
+ freeze_legacy_modules_for_codec: false
45
+ codec_estimated_feature_num_bytes: false
46
+ deflate_feature_ablation: false
47
+ deflate_feature_dtype: float16
48
+ deflate_feature_level: 9
examples/scene_a/00.jpg ADDED

Git LFS Details

  • SHA256: 864f1efe8677fd03f2e0a825c1b6e36eb9dcdc6c1fd7b5e69f3ee07f5ba62fa0
  • Pointer size: 131 Bytes
  • Size of remote file: 151 kB
examples/scene_a/01.jpg ADDED

Git LFS Details

  • SHA256: 414937f97e97b07a47c3b4e81b0325f4627818ea89f638f754fe738caecc2962
  • Pointer size: 131 Bytes
  • Size of remote file: 151 kB
examples/scene_a/02.jpg ADDED

Git LFS Details

  • SHA256: 97312a3a25fc1f01df44d648cdb8681802d5341a3721f6fd12c24afd5df480de
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
examples/scene_a/03.jpg ADDED

Git LFS Details

  • SHA256: 67054b1d5cf5563913502c2ca32f4c47739af7def7c10185ae840ee4cf812c86
  • Pointer size: 131 Bytes
  • Size of remote file: 148 kB
examples/scene_a/04.jpg ADDED

Git LFS Details

  • SHA256: 1d733527d3fa5e7d3c48b35af5db16d15f182ffeb2a656e075879f6983a8364f
  • Pointer size: 131 Bytes
  • Size of remote file: 149 kB
examples/scene_a/05.jpg ADDED

Git LFS Details

  • SHA256: 3d3891369fd5687128e5e50f5020c323304008b04270ca928698a9f3e93af3fe
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
examples/scene_a/06.jpg ADDED

Git LFS Details

  • SHA256: 8f2f368c20a98f027e3cbd8cde63830c3db02ed4e6d032a72c7595c6cc708563
  • Pointer size: 131 Bytes
  • Size of remote file: 142 kB
examples/scene_a/07.jpg ADDED

Git LFS Details

  • SHA256: 6e1c35fac53bb629bb048395d5d7593bced09a8df310e6805f09bee8cdae9323
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
examples/scene_a/08.jpg ADDED

Git LFS Details

  • SHA256: f04ed65071df1cbef47180edbdc6a135d4c3e6a40e1d1c93dfe539297d226aa5
  • Pointer size: 131 Bytes
  • Size of remote file: 145 kB
examples/scene_a/09.jpg ADDED

Git LFS Details

  • SHA256: d8d92f95fedc7b924046d3ef56a08e084943f5846fe0197269eb6d94d12fcbc0
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
examples/scene_a/10.jpg ADDED

Git LFS Details

  • SHA256: 6771ccb3ee35aa5b61df20cc0e38f5423d0a815a53506d08ded513475b1d1ff3
  • Pointer size: 131 Bytes
  • Size of remote file: 151 kB
examples/scene_a/11.jpg ADDED

Git LFS Details

  • SHA256: f6eb4c48ec8c18dcc44dd0ed140cb335a7705fef25545430d9e4847ac7294f56
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
examples/scene_a/12.jpg ADDED

Git LFS Details

  • SHA256: f0fa1ed39c728f9e1f8464ac6a0ed919361a39b1b1bd21c38b307fd0d4adab29
  • Pointer size: 131 Bytes
  • Size of remote file: 154 kB
examples/scene_a/13.jpg ADDED

Git LFS Details

  • SHA256: 03291cbdef1c6f7d3e00c2ee85787df834cf0e8e15698750cdc1b3ad73d04893
  • Pointer size: 131 Bytes
  • Size of remote file: 152 kB
examples/scene_a/14.jpg ADDED

Git LFS Details

  • SHA256: b0d2135cb30ef243a02fffdd7d20b61239eca64baab7665158d8854693744c77
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
examples/scene_a/15.jpg ADDED

Git LFS Details

  • SHA256: 295911b689adc5f498451c23b5d1fd1ed668b6dad98284109b59e973756fdf62
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB
examples/scene_a/16.jpg ADDED

Git LFS Details

  • SHA256: 2080b7bce75b0714e37987719a22e116c836d884a02e10435c36d57e2fe27002
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
examples/scene_a/17.jpg ADDED

Git LFS Details

  • SHA256: 1606e2337ee00c78b700612e6957d1bca17aeb8edbce4808d595614a3af538f6
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
examples/scene_a/18.jpg ADDED

Git LFS Details

  • SHA256: 8e6444b113ccafd634cb184b5a878a9e73b3fcfee198c863ac9930879f76daed
  • Pointer size: 131 Bytes
  • Size of remote file: 144 kB
examples/scene_a/19.jpg ADDED

Git LFS Details

  • SHA256: 116b1524e488083cc2b0262c115f9c9955840f761f91299caf92220b929481b2
  • Pointer size: 131 Bytes
  • Size of remote file: 144 kB
examples/scene_a/20.jpg ADDED

Git LFS Details

  • SHA256: 2656b43536aba2536d8a070b3e2ca3dab1af93e84161f07464a89d1dc4995387
  • Pointer size: 131 Bytes
  • Size of remote file: 144 kB
examples/scene_a/21.jpg ADDED

Git LFS Details

  • SHA256: 03bb71d6292638d7c052504eefdba3555164a6c21618c447963d2799cc94c60e
  • Pointer size: 131 Bytes
  • Size of remote file: 143 kB
examples/scene_a/22.jpg ADDED

Git LFS Details

  • SHA256: 5f3eb3f7f8fa5c942e4c1d3aaabdacbdf21ae54d3c4e20cdc5da82122c47a052
  • Pointer size: 131 Bytes
  • Size of remote file: 136 kB
examples/scene_a/23.jpg ADDED

Git LFS Details

  • SHA256: a6b1ff00c86b98bda90b26526626239eab2cf24959954798d74ac9c57d222a17
  • Pointer size: 131 Bytes
  • Size of remote file: 136 kB