"""CodecSplat: Ultra-Compact Latent Coding for Feed-Forward 3D Gaussian Splatting. Upload overlapping photos of a static scene. The Space recovers the cameras with COLMAP (pycolmap, CPU), then CodecSplat (DL3DV 8-view 256x448 checkpoint): 1. encodes the 8 context views into an intermediate 2D latent, 2. entropy-codes that latent (+ cameras) into a real scene bitstream at the chosen rate point lambda, 3. decodes the bitstream back into 3D Gaussians, 4. renders held-out views / a fly-through with gsplat. The inference path follows the authors' evaluation (`scripts/test_dl3dv_view8_256x448.sh` -> `ModelWrapper.test_step` -> `EncoderCodecSplat.codec_forward`), with the exact same encoder/decoder overrides. """ from __future__ import annotations import glob import os import sys import tempfile import time import types from pathlib import Path APP_DIR = Path(__file__).parent.resolve() sys.path.insert(0, str(APP_DIR)) _BISECT = os.environ.get("BISECT", "") def _bisect(stage: str) -> None: """Diagnostics: boot only the GPU probe after `stage` (set via the BISECT variable).""" if _BISECT == stage: print(f"[bisect] stopping after stage {stage}", flush=True) exec(open(APP_DIR / "probe_app.py").read(), {"__name__": "probe"}) raise SystemExit(0) _bisect("minimal") os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "12.0") os.environ.setdefault("MAX_JOBS", str(min(32, (os.cpu_count() or 8)))) os.environ.setdefault("WANDB_MODE", "disabled") # pycolmap's bundled OpenBLAS crashes when many matcher threads each spawn BLAS threads. os.environ["OPENBLAS_NUM_THREADS"] = "1" import spaces # noqa: E402 (must precede any CUDA-touching import) import torch # noqa: E402 torch.set_float32_matmul_precision("high") def _nvidia_fds(stage: str) -> None: """Diagnostics: /dev/nvidia* handles in the GPU-less main process mean the CUDA driver was touched there, which breaks ZeroGPU's forked workers.""" fds = set() for fd in os.listdir("/proc/self/fd"): try: t = os.readlink(f"/proc/self/fd/{fd}") except OSError: continue if "nvidia" in t: fds.add(t) libs = set() try: for line in open("/proc/self/maps"): for key in ("libcuda.so", "libnvidia-ml", "libcudart"): if key in line: libs.add(line.split()[-1].rsplit("/", 1)[-1]) except OSError: pass init = getattr(torch.cuda, "_initialized", None) print(f"[diag] {stage}: nvidia fds={sorted(fds)} libs={sorted(libs)} torch.cuda._initialized={init}", flush=True) _nvidia_fds("after torch import") _bisect("s1_torch") # --------------------------------------------------------------------------- # 1. gsplat CUDA backend (JIT-compiled once at startup against the ZeroGPU # image's CUDA toolkit -- gsplat has no prebuilt sm_120 wheel). # --------------------------------------------------------------------------- def _pick_cuda_home() -> str | None: torch_cuda = (torch.version.cuda or "").split(".")[0] candidates: list[str] = [] for pattern in ("/cuda-image/usr/local/cuda-*", "/usr/local/cuda-*"): candidates += sorted(glob.glob(pattern), reverse=True) candidates += ["/cuda-image/usr/local/cuda", "/usr/local/cuda"] usable = [c for c in candidates if os.path.isfile(os.path.join(c, "bin", "nvcc"))] if not usable: return None for c in usable: if torch_cuda and f"cuda-{torch_cuda}" in c: return c return usable[0] CUDA_HOME = _pick_cuda_home() print(f"[setup] torch {torch.__version__} (cuda {torch.version.cuda}) | CUDA_HOME={CUDA_HOME}", flush=True) if CUDA_HOME: os.environ["CUDA_HOME"] = CUDA_HOME os.environ["CUDA_PATH"] = CUDA_HOME os.environ["PATH"] = os.path.join(CUDA_HOME, "bin") + os.pathsep + os.environ.get("PATH", "") _libdir = os.path.join(CUDA_HOME, "lib64") os.environ["LD_LIBRARY_PATH"] = _libdir + os.pathsep + os.environ.get("LD_LIBRARY_PATH", "") _bisect("s2_env") import torch.utils.cpp_extension as _cppext # noqa: E402 _cppext._check_cuda_version = lambda *a, **k: None print("[setup] compiling / loading gsplat CUDA kernels...", flush=True) _t0 = time.time() from gsplat.cuda._backend import _C as _GSPLAT_C # noqa: E402 if _GSPLAT_C is None: raise RuntimeError(f"gsplat CUDA backend unavailable (CUDA_HOME={CUDA_HOME}).") print(f"[setup] gsplat backend ready in {time.time() - _t0:.1f}s", flush=True) _nvidia_fds("after gsplat") _bisect("s3_gsplat") # --------------------------------------------------------------------------- # 2. Compatibility shims for the vendored reference code # --------------------------------------------------------------------------- import matplotlib.cm # noqa: E402 import matplotlib.pyplot as _plt # noqa: E402 if not hasattr(matplotlib.cm, "get_cmap"): matplotlib.cm.get_cmap = _plt.get_cmap if "skvideo" not in sys.modules: # training-only import in some helpers _sk = types.ModuleType("skvideo") _sk.__path__ = [] _skio = types.ModuleType("skvideo.io") _sk.io = _skio sys.modules["skvideo"] = _sk sys.modules["skvideo.io"] = _skio # torchvision's CUDA build (pulled in by `compressai/__init__`, lpips, lightning) breaks # the ZeroGPU forked worker ("No CUDA GPUs are available" in worker_init), bisected on # this Space. CodecSplat never uses torchvision at inference time, so: # * import compressai's entropy-model / ops submodules without running its package # __init__ (which imports models/datasets -> torchvision), # * stub the two unused `torchvision` imports in the vendored dataset/vis helpers. import importlib.machinery # noqa: E402 import importlib.util # noqa: E402 _ca_spec = importlib.util.find_spec("compressai") # does not execute the package _ca = types.ModuleType("compressai") _ca.__path__ = list(_ca_spec.submodule_search_locations) _ca.__spec__ = _ca_spec _ca.get_entropy_coder = lambda: "ans" _ca.available_entropy_coders = lambda: ["ans"] sys.modules["compressai"] = _ca class _UnusedModule(types.ModuleType): def __getattr__(self, name): if name.startswith("__"): raise AttributeError(name) qual = f"{self.__name__}.{name}" def _unavailable(*args, **kwargs): raise ImportError(f"{qual} is not available in this Space") return type(name, (), {"__init__": _unavailable, "__call__": _unavailable}) for _name in ("torchvision", "torchvision.transforms", "torchvision.utils"): _m = _UnusedModule(_name) _m.__path__ = [] _m.__spec__ = importlib.machinery.ModuleSpec(_name, None, is_package=True) sys.modules[_name] = _m sys.modules["torchvision"].transforms = sys.modules["torchvision.transforms"] sys.modules["torchvision"].utils = sys.modules["torchvision.utils"] # --------------------------------------------------------------------------- # 3. Model # --------------------------------------------------------------------------- import gradio as gr # noqa: E402 import imageio.v2 as imageio # noqa: E402 import numpy as np # noqa: E402 from huggingface_hub import hf_hub_download # noqa: E402 from PIL import Image # noqa: E402 from scipy.spatial.transform import Rotation, Slerp # noqa: E402 from colmap_io import load_colmap_scene, select_context_views, select_target_views # noqa: E402 _bisect("imp_pre_colmap") from colmap_sfm import IMAGE_EXT, run_sfm # noqa: E402 _bisect("imp_colmap") MODEL_REPO = "pengpeng-yu/CodecSplat" CHECKPOINT_FILE = "codecsplat-base-dl3dv-256x448-view8-075062d0.pth" NUM_CONTEXT = 8 IMAGE_SHAPE = (256, 448) NEAR, FAR = 0.5, 200.0 # config/experiment/dl3dv.yaml # Exactly the overrides of scripts/test_dl3dv_view8_256x448.sh OVERRIDES = [ "+experiment=dl3dv", "mode=test", f"dataset.image_shape=[{IMAGE_SHAPE[0]},{IMAGE_SHAPE[1]}]", "model.encoder.num_scales=2", "model.encoder.upsample_factor=4", "model.encoder.lowest_feature_resolution=8", "model.encoder.monodepth_vit_type=vitb", "model.encoder.enable_feature_codec=true", "model.encoder.enable_depth_guided_mv_refiner=true", "model.encoder.enable_post_vae_mv_refiner=true", "model.encoder.supervise_intermediate_depth=false", "model/decoder=splatting_gsplat", "model.decoder.use_covariances=true", "model.decoder.rasterize_mode=antialiased", "model.decoder.eps2d=0.1", "model.decoder.packed=true", "model.decoder.scale_invariant=false", "trainer.precision=bf16-mixed", "wandb.mode=disabled", "output_dir=/tmp/codecsplat_out", ] def build_model(checkpoint: str): from hydra import compose, initialize_config_dir from hydra.core.global_hydra import GlobalHydra GlobalHydra.instance().clear() with initialize_config_dir(config_dir=str(APP_DIR / "config"), version_base=None): cfg_dict = compose(config_name="main", overrides=OVERRIDES) # Typed configs built directly with dacite (same as src.config.load_typed_config), # without importing src.config -> model_wrapper -> pytorch_lightning / wandb / lpips. from dacite import Config as DaciteConfig, from_dict from omegaconf import OmegaConf from src.model.decoder import DecoderSplattingGsplatCfg, get_decoder from src.model.encoder import EncoderCodecSplatCfg, get_encoder dcfg = DaciteConfig(type_hooks={Path: Path}) enc_cfg = from_dict(EncoderCodecSplatCfg, OmegaConf.to_container(cfg_dict.model.encoder, resolve=True), config=dcfg) dec_cfg = from_dict(DecoderSplattingGsplatCfg, OmegaConf.to_container(cfg_dict.model.decoder, resolve=True), config=dcfg) dataset_cfg = types.SimpleNamespace(background_color=list(cfg_dict.dataset.background_color)) encoder = get_encoder(enc_cfg) decoder = get_decoder(dec_cfg, dataset_cfg) state = torch.load(checkpoint, map_location="cpu", weights_only=False) if "state_dict" in state: state = state["state_dict"] enc_sd = {k[len("encoder."):]: v for k, v in state.items() if k.startswith("encoder.")} dec_sd = {k[len("decoder."):]: v for k, v in state.items() if k.startswith("decoder.")} missing, unexpected = encoder.load_state_dict(enc_sd, strict=False) print(f"[setup] encoder: {len(enc_sd)} tensors, missing={len(missing)} unexpected={len(unexpected)}", flush=True) if missing: print(f"[setup] missing (first 10): {missing[:10]}", flush=True) if unexpected: print(f"[setup] unexpected (first 10): {unexpected[:10]}", flush=True) if dec_sd: decoder.load_state_dict(dec_sd, strict=False) encoder.eval() decoder.eval() return encoder, decoder, encoder.get_data_shim() _bisect("s4_imports") print("[setup] downloading checkpoint...", flush=True) CHECKPOINT_PATH = hf_hub_download(MODEL_REPO, CHECKPOINT_FILE) print("[setup] building CodecSplat...", flush=True) ENCODER, DECODER, DATA_SHIM = build_model(CHECKPOINT_PATH) _nvidia_fds("after build (cpu)") _bisect("s5_build") ENCODER.to("cuda") DECODER.to("cuda") print("[setup] model ready", flush=True) _nvidia_fds("after model build") _bisect("s6_cuda") from src.dataset.shims.crop_shim import rescale_and_crop # noqa: E402 from src.model.ply_export import export_ply # noqa: E402 MAX_INPUT_IMAGES = 40 VIEWER_MAX_GAUSSIANS = 600_000 VIEWER_MIN_OPACITY = 0.02 # --------------------------------------------------------------------------- # 4. Helpers # --------------------------------------------------------------------------- def _to_uint8(t) -> np.ndarray: return (t.clamp(0, 1).permute(1, 2, 0).float().cpu().numpy() * 255).round().astype(np.uint8) def _load_views(paths, intrinsics, shape): """Load images + normalized intrinsics and rescale/center-crop them to `shape` with the same crop shim the DL3DV loader uses.""" imgs = [] for p in paths: im = np.asarray(Image.open(p).convert("RGB"), dtype=np.float32) / 255.0 imgs.append(torch.from_numpy(im).permute(2, 0, 1)) h0 = min(i.shape[1] for i in imgs) w0 = min(i.shape[2] for i in imgs) imgs = torch.stack([i[:, :h0, :w0] for i in imgs]) K = torch.tensor(np.asarray(intrinsics), dtype=torch.float32) return rescale_and_crop(imgs, K, shape, depths=None) def _interpolate_path(c2w: torch.Tensor, frames_per_segment: int = 12) -> torch.Tensor: """Smooth fly-through visiting the context cameras in order (slerp + lerp).""" c2w = c2w.cpu().numpy() out = [] for a, b in zip(c2w[:-1], c2w[1:]): slerp = Slerp([0, 1], Rotation.from_matrix(np.stack([a[:3, :3], b[:3, :3]]))) for t in np.linspace(0, 1, frames_per_segment, endpoint=False): m = np.eye(4, dtype=np.float32) m[:3, :3] = slerp([t]).as_matrix()[0] m[:3, 3] = (1 - t) * a[:3, 3] + t * b[:3, 3] out.append(m) out.append(c2w[-1]) return torch.from_numpy(np.stack(out)).float() def export_viewer_ply(gaussians, extrinsics, path: Path) -> str: """Compact, well-framed copy of the Gaussians for the in-browser splat viewer (DC colour only, near-transparent splats pruned, re-centred and re-scaled).""" means = gaussians.means[0] scales = gaussians.scales[0] rotations = gaussians.rotations[0] harmonics = gaussians.harmonics[0] opacities = gaussians.opacities[0] keep = opacities > VIEWER_MIN_OPACITY if int(keep.sum()) < 1000: keep = torch.ones_like(opacities, dtype=torch.bool) idx = torch.nonzero(keep, as_tuple=False).squeeze(1) if idx.numel() > VIEWER_MAX_GAUSSIANS: idx = idx[torch.randperm(idx.numel(), device=idx.device)[:VIEWER_MAX_GAUSSIANS]] means, scales = means[idx].float(), scales[idx].float() rotations, harmonics, opacities = rotations[idx], harmonics[idx], opacities[idx] rot = extrinsics[:3, :3].float().inverse().to(means.device) world = (rot @ means.T).T center = world.median(dim=0).values radius = torch.quantile((world - center).norm(dim=-1), 0.7) s = float(3.0 / max(float(radius), 1e-6)) means = means * s - rot.T @ (center * s) scales = scales * s export_ply(extrinsics, means, scales, rotations, harmonics[..., :1], opacities, path, align_to_view=True) return str(path) # --------------------------------------------------------------------------- # 5. GPU stage: encode -> bitstream -> decode -> render # --------------------------------------------------------------------------- @spaces.GPU(duration=int(os.environ.get("GPU_DURATION", "40"))) def codecsplat_run(scene_dir: str, lmb: float = 1024.0): """Run CodecSplat on a COLMAP scene directory (undistorted images + sparse model).""" out = Path(scene_dir) / "out" out.mkdir(parents=True, exist_ok=True) scene = load_colmap_scene(scene_dir, "sparse", "images") n = len(scene["image_paths"]) if n < 2: raise gr.Error(f"Only {n} usable image(s) after COLMAP -- need at least 2.") w0, h0 = Image.open(scene["image_paths"][0]).size shape = IMAGE_SHAPE if w0 >= h0 else IMAGE_SHAPE[::-1] num_context = min(NUM_CONTEXT, n) ctx_idx = select_context_views(scene["c2w"], num_context, "fps") tgt_idx = select_target_views(n, ctx_idx, "remaining", 8) if len(tgt_idx) == 0: tgt_idx = ctx_idx.copy() ctx_img, ctx_K = _load_views([scene["image_paths"][i] for i in ctx_idx], scene["intrinsics"][ctx_idx], shape) tgt_img, tgt_K = _load_views([scene["image_paths"][i] for i in tgt_idx], scene["intrinsics"][tgt_idx], shape) c2w = torch.tensor(scene["c2w"], dtype=torch.float32) pivot = torch.inverse(c2w[ctx_idx[len(ctx_idx) // 2]]) c2w = pivot[None] @ c2w # express all cameras in the middle context view's frame ctx_c2w, tgt_c2w = c2w[ctx_idx], c2w[tgt_idx] def views(img, K, E): v = len(E) return { "image": img[None].cuda(), "intrinsics": K[None].cuda(), "extrinsics": E[None].cuda(), "near": torch.full((1, v), NEAR, device="cuda"), "far": torch.full((1, v), FAR, device="cuda"), "index": torch.arange(v, device="cuda")[None], } batch = DATA_SHIM({ "context": views(ctx_img, ctx_K, ctx_c2w), "target": views(tgt_img, tgt_K, tgt_c2w), "scene": ["user_scene"], }) h, w = batch["context"]["image"].shape[-2:] ENCODER.feature_codec.cfg.lmb = float(lmb) t0 = time.time() with torch.no_grad(), torch.autocast("cuda", dtype=torch.bfloat16): enc = ENCODER(batch["context"], 0, return_bitstream=True) gaussians = enc["gaussians"] def render(E, K, chunk=8): frames = [] for i in range(0, E.shape[1], chunk): o = DECODER.forward( gaussians, E[:, i:i + chunk], K[:, i:i + chunk], torch.full_like(E[:, i:i + chunk, 0, 0], NEAR), torch.full_like(E[:, i:i + chunk, 0, 0], FAR), (h, w), depth_mode=None, ) frames.append(o.color[0].float()) return torch.cat(frames) rendered = render(batch["target"]["extrinsics"], batch["target"]["intrinsics"]) path_E = _interpolate_path(batch["context"]["extrinsics"][0])[None].cuda() path_K = batch["context"]["intrinsics"][:, :1].expand(-1, path_E.shape[1], -1, -1) video = render(path_E, path_K) torch.cuda.synchronize() t_run = time.time() - t0 gt = batch["target"]["image"][0].float() mse = ((gt - rendered.clamp(0, 1)) ** 2).flatten(1).mean(dim=1) psnr = float((-10.0 * torch.log10(mse.clamp_min(1e-10))).mean()) bitstream = enc["bitstream"] bin_path = out / f"codecsplat_scene_lmb{int(lmb)}.bin" bin_path.write_bytes(bitstream) video_path = out / "flythrough.mp4" imageio.mimwrite(video_path, [_to_uint8(f) for f in video], fps=24, quality=8, macro_block_size=1) gallery = [] for i, (r, g) in enumerate(zip(rendered, gt)): p = out / f"heldout_{i:02d}.png" Image.fromarray(np.concatenate([_to_uint8(r), _to_uint8(g)], axis=1)).save(p) gallery.append((str(p), f"held-out view {int(tgt_idx[i])}: CodecSplat (left) | real photo (right)")) ref_E = batch["context"]["extrinsics"][0, len(ctx_idx) // 2] ply_path = out / "codecsplat_gaussians.ply" export_ply(ref_E, gaussians.means[0], gaussians.scales[0], gaussians.rotations[0], gaussians.harmonics[0], gaussians.opacities[0], ply_path, align_to_view=True) viewer_ply = export_viewer_ply(gaussians, ref_E, out / "codecsplat_viewer.ply") stats = { "num_bytes": len(bitstream), "camera_bytes": float(enc["codec_camera_num_bytes"]), "feature_bytes": float(enc["codec_feature_num_bytes"]), "num_gaussians": int(gaussians.means.shape[1]), "ply_bytes": os.path.getsize(ply_path), "num_context": len(ctx_idx), "num_target": len(tgt_idx), "resolution": f"{h}x{w}", "psnr": psnr, "t_run": t_run, "t_encode": float(enc["codec_encode_context_seconds"] + enc["codec_compress_seconds"]), "t_decode": float(enc["codec_decompress_seconds"]), } print(f"[run] lmb={lmb} bytes={len(bitstream)} psnr={psnr:.2f} t={t_run:.1f}s", flush=True) return viewer_ply, str(video_path), gallery, str(bin_path), str(ply_path), stats def reconstruct( images, lmb: float = 1024.0, matcher: str = "exhaustive", progress=gr.Progress(track_tqdm=False), ): """COLMAP + CodecSplat: compress a scene into a tiny bitstream and decode it to 3D Gaussians. Args: images: Overlapping photos of one static scene (8-30 works well). lmb: Rate point lambda in [16, 1024]. Higher = more bytes, better quality. matcher: COLMAP matching -- "exhaustive" (unordered photos) or "sequential" (video frames). Returns: Interactive 3D Gaussian splat, a rendered fly-through video, held-out view comparisons, the compressed scene bitstream, the decoded Gaussians as .ply, and a summary. """ if not images: raise gr.Error("Please upload at least 3 overlapping images of the same scene.") if isinstance(images, (str, Path)): images = [images] paths = [str(im if isinstance(im, (str, Path)) else getattr(im, "name", im)) for im in images] paths = sorted([p for p in paths if p.lower().endswith(IMAGE_EXT)], key=os.path.basename) if len(paths) < 3: raise gr.Error(f"Got {len(paths)} usable image(s); COLMAP needs at least 3 overlapping photos.") if len(paths) > MAX_INPUT_IMAGES: paths = [paths[i] for i in np.linspace(0, len(paths) - 1, MAX_INPUT_IMAGES).astype(int)] t_start = time.time() workdir = Path(tempfile.mkdtemp(prefix="codecsplat_")) progress(0.05, desc=f"COLMAP: estimating cameras for {len(paths)} images") t_sfm = time.time() try: sfm = run_sfm(paths, workdir, max_image_size=960, matcher=matcher, log=lambda m: print(m, flush=True)) except ValueError as exc: raise gr.Error(str(exc)) from exc t_sfm = time.time() - t_sfm _nvidia_fds("after COLMAP") progress(0.6, desc="CodecSplat: encode -> bitstream -> decode -> render") viewer_ply, video, gallery, bin_path, ply_path, s = codecsplat_run(sfm["scene_dir"], float(lmb)) ratio = s["ply_bytes"] / max(s["num_bytes"], 1) summary = ( f"### Scene bitstream: **{s['num_bytes'] / 1024:.1f} KB** at λ = {lmb:g}\n" f"(features {s['feature_bytes'] / 1024:.1f} KB + cameras {s['camera_bytes'] / 1024:.2f} KB) → decodes to " f"**{s['num_gaussians']:,} Gaussians** (uncompressed .ply: {s['ply_bytes'] / 2**20:.1f} MB, **{ratio:,.0f}× smaller**).\n\n" f"Held-out views ({s['num_target']}): **PSNR {s['psnr']:.2f} dB** at {s['resolution']} \n" f"COLMAP registered {sfm['num_registered']}/{sfm['num_input']} images in {t_sfm:.1f}s · " f"encode {s['t_encode']:.2f}s · decode {s['t_decode']:.2f}s · {time.time() - t_start:.1f}s total." ) return viewer_ply, video, gallery, bin_path, ply_path, summary # --------------------------------------------------------------------------- # 6. UI # --------------------------------------------------------------------------- EXAMPLES_DIR = APP_DIR / "examples" EXAMPLES = [ [sorted(str(p) for p in (EXAMPLES_DIR / s).glob("*.jpg"))] for s in ("kitchen", "garden") if (EXAMPLES_DIR / s).is_dir() ] CSS = """ #col-container { max-width: 1200px; margin: 0 auto; } .dark .gradio-container { color: var(--body-text-color); } """ DESCRIPTION = """# CodecSplat: Ultra-Compact Latent Coding for Feed-Forward 3D Gaussian Splatting CodecSplat doesn't compress the final 3D Gaussians. It entropy-codes the **2D latent features** a feed-forward 3DGS model uses to predict them, so a whole scene fits in a **few kilobytes** and decodes back to Gaussians in one forward pass. Upload overlapping photos of a scene. COLMAP estimates the cameras, CodecSplat compresses 8 views into a real bitstream, then decodes it into 3D Gaussians you can fly through. [Paper](https://huggingface.co/papers/2605.25563) · [Code](https://github.com/pengpeng-yu/CodecSplat) · [Weights](https://huggingface.co/pengpeng-yu/CodecSplat) """ with gr.Blocks(title="CodecSplat") as demo: with gr.Column(elem_id="col-container"): gr.Markdown(DESCRIPTION) with gr.Row(): with gr.Column(scale=1): image_files = gr.File( label="Photos of one static scene (8-30, overlapping)", file_count="multiple", file_types=["image"], type="filepath", ) lmb = gr.Slider(16, 1024, value=1024, step=1, label="Rate point λ", info="Higher λ = more bytes and higher fidelity. The model covers 16-1024.") run_btn = gr.Button("Compress & reconstruct", variant="primary") with gr.Accordion("Advanced options", open=False): matcher = gr.Radio(["exhaustive", "sequential"], value="exhaustive", label="COLMAP matching", info="exhaustive = unordered photos; sequential = video frames.") gr.Markdown( "**Tips**: walk around the scene, keep lots of overlap between neighbouring shots, " "and avoid moving objects. The 8 context views are picked by farthest-point sampling " "over the COLMAP cameras. The remaining views are held out for evaluation. " "**Viewer**: drag to orbit, right-drag to pan, scroll to zoom." ) with gr.Column(scale=2): summary_out = gr.Markdown() splat_out = gr.Model3D(label="Decoded 3D Gaussians (interactive)", height=440, clear_color=[0.0, 0.0, 0.0, 1.0]) video_out = gr.Video(label="Fly-through through the context cameras", autoplay=True, loop=True) gallery_out = gr.Gallery(label="Held-out views: CodecSplat | real photo", columns=2, height=260, object_fit="contain") with gr.Row(): bin_out = gr.File(label="Compressed scene bitstream (.bin)") ply_out = gr.File(label="Decoded 3D Gaussians (.ply)") outputs = [splat_out, video_out, gallery_out, bin_out, ply_out, summary_out] run_btn.click(reconstruct, inputs=[image_files, lmb, matcher], outputs=outputs, api_name="reconstruct") if EXAMPLES: gr.Examples( examples=EXAMPLES, inputs=[image_files], outputs=outputs, fn=reconstruct, cache_examples=True, cache_mode="lazy", label="Mip-NeRF 360 scenes (kitchen, garden): 14 frames each", ) if __name__ == "__main__": demo.queue(max_size=12).launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True, show_error=True)