Download app.py from topabaem/CC-4DGS: direct link, hf CLI and curl.
- Browser
- Download file 13.6 kB
-
https://huggingface.co/spaces/topabaem/CC-4DGS/resolve/main/app.py
- Command line
-
hf download hf://spaces/topabaem/CC-4DGS/app.py
-
curl -L -o app.py https://huggingface.co/spaces/topabaem/CC-4DGS/resolve/main/app.py
13.6 kB
| """CC-4DGS viewer for Hugging Face Spaces (ZeroGPU). | |
| Loads a trained CC-4DGS checkpoint (raw CDF ``point_cloud.ply`` or the CDF+CCA | |
| ``compressed/`` bundle, plus ``deformation.pth``) and renders novel views of the | |
| dynamic scene at any time step. Only the rendering functions request a GPU. | |
| """ | |
| from __future__ import annotations | |
| # `spaces` must be imported before torch so ZeroGPU can patch CUDA initialisation. | |
| try: | |
| import spaces | |
| except ImportError: # running outside Hugging Face | |
| class _NoSpaces: | |
| def GPU(fn=None, **_kwargs): | |
| return fn if fn is not None else (lambda f: f) | |
| spaces = _NoSpaces() | |
| import os | |
| import shutil | |
| import sys | |
| import uuid | |
| import zipfile | |
| from pathlib import Path | |
| APP_DIR = Path(__file__).resolve().parent | |
| sys.path.insert(0, str(APP_DIR)) | |
| from cc4dgs_space import runtime as rt # noqa: E402 | |
| from cc4dgs_space.extensions import ensure_extensions # noqa: E402 | |
| ensure_extensions(rt.REPO_ROOT, wheel_dir=APP_DIR / "wheels") | |
| import gradio as gr # noqa: E402 | |
| import imageio.v2 as imageio # noqa: E402 | |
| rt.WORK_ROOT.mkdir(parents=True, exist_ok=True) | |
| MAX_ZIP_BYTES = int(os.environ.get("CC4DGS_MAX_ZIP_MB", "4096")) * 1024 * 1024 | |
| DEFAULT_REPO = os.environ.get("CC4DGS_HUB_REPO", "") | |
| DEFAULT_SUBFOLDER = os.environ.get("CC4DGS_HUB_SUBFOLDER", "") | |
| DEFAULT_REPO_TYPE = os.environ.get("CC4DGS_HUB_REPO_TYPE", "model") | |
| BACKGROUNDS = {"Checkpoint default": None, "White": [1.0, 1.0, 1.0], "Black": [0.0, 0.0, 0.0]} | |
| # -------------------------------------------------------------------------------------- | |
| # Checkpoint input | |
| # -------------------------------------------------------------------------------------- | |
| def _safe_extract(zip_path: str, dest: Path) -> None: | |
| with zipfile.ZipFile(zip_path) as zf: | |
| total = sum(i.file_size for i in zf.infolist()) | |
| if total > MAX_ZIP_BYTES: | |
| raise gr.Error(f"Zip expands to {total / 1e9:.1f} GB, above the {MAX_ZIP_BYTES / 1e9:.1f} GB limit.") | |
| root = dest.resolve() | |
| for info in zf.infolist(): | |
| target = (dest / info.filename).resolve() | |
| if not str(target).startswith(str(root) + os.sep) and target != root: | |
| raise gr.Error(f"Unsafe path in zip: {info.filename}") | |
| zf.extractall(dest) | |
| def _model_dir_from_inputs(source, zip_file, repo_id, subfolder, repo_type, session_dir: Path) -> Path: | |
| if source == "Upload zip": | |
| if not zip_file: | |
| raise gr.Error("Upload a checkpoint .zip first.") | |
| # Name the folder after the zip so the scene name can select the config. | |
| dest = session_dir / Path(zip_file).stem | |
| _safe_extract(zip_file, dest) | |
| entries = [p for p in dest.iterdir() if not p.name.startswith(("__MACOSX", "."))] | |
| return entries[0] if len(entries) == 1 and entries[0].is_dir() else dest | |
| if not repo_id.strip(): | |
| raise gr.Error("Enter a Hugging Face repo id (e.g. user/cc4dgs-checkpoints).") | |
| from huggingface_hub import snapshot_download | |
| sub = subfolder.strip().strip("/") | |
| local = snapshot_download( | |
| repo_id=repo_id.strip(), | |
| repo_type=repo_type, | |
| allow_patterns=[f"{sub}/**"] if sub else None, | |
| local_dir=str(rt.WORK_ROOT / "hub" / repo_id.strip().replace("/", "__")), | |
| token=os.environ.get("HF_TOKEN"), | |
| ) | |
| model_dir = Path(local) / sub if sub else Path(local) | |
| if not model_dir.exists(): | |
| raise gr.Error(f"Subfolder '{sub}' not found in {repo_id}.") | |
| return model_dir | |
| def _fmt_bytes(n: int) -> str: | |
| return f"{n / 1e6:.2f} MB" | |
| def load_checkpoint(source, zip_file, repo_id, subfolder, repo_type, camera_files, config, prefer_cca, | |
| progress=gr.Progress()): | |
| session_dir = rt.WORK_ROOT / "sessions" / uuid.uuid4().hex[:12] | |
| session_dir.mkdir(parents=True, exist_ok=True) | |
| progress(0.1, desc="Fetching checkpoint") | |
| model_dir = _model_dir_from_inputs(source, zip_file, repo_id, subfolder, repo_type, session_dir) | |
| cam_dir = session_dir / "cameras" | |
| for f in camera_files or []: | |
| cam_dir.mkdir(exist_ok=True) | |
| shutil.copy(f, cam_dir / Path(f).name) | |
| progress(0.4, desc="Decoding Gaussians and building the CDF") | |
| try: | |
| scene = rt.load_scene(model_dir, config=config, prefer_compressed=prefer_cca) | |
| except (FileNotFoundError, RuntimeError, ValueError, KeyError) as exc: | |
| raise gr.Error(str(exc)) from exc | |
| progress(0.8, desc="Preparing cameras") | |
| rig = rt.find_camera_rig([cam_dir, model_dir], scene) | |
| state = { | |
| "key": scene.key, | |
| "model_dir": str(model_dir), | |
| "config": scene.config, | |
| "prefer_cca": scene.source == "cca", | |
| "rig": rig, | |
| "out_dir": str(session_dir), | |
| } | |
| deploy = sum(v for k, v in scene.storage.items() if k != "point_cloud.ply") if scene.source == "cca" else sum( | |
| v for k, v in scene.storage.items() if k != "compressed/ (CCA)") | |
| storage = "\n".join(f"| `{k}` | {_fmt_bytes(v)} |" for k, v in scene.storage.items()) | |
| status = ( | |
| f"### Loaded `{scene.name}`\n" | |
| f"- checkpoint: `{scene.iter_dir.name}` · attributes from **{'CCA bundle' if scene.source == 'cca' else 'PLY'}**\n" | |
| f"- config: `{scene.config}`\n" | |
| f"- Gaussians: **{scene.num_points:,}** ({scene.num_dynamic:,} dynamic, deformed by the CDF)\n" | |
| f"- cameras: {rig.describe()}\n\n" | |
| f"| file | size |\n|---|---|\n{storage}\n| **rendered from** | **{_fmt_bytes(deploy)}** |" | |
| ) | |
| if scene.config_matched_by == "architecture": | |
| status += ("\n\n> The config was picked because its CDF architecture matches `deformation.pth`; several " | |
| "scene configs share an architecture but differ in deformation flags. Select the scene's config " | |
| "explicitly if the motion looks wrong.") | |
| if rig.kind.startswith("orbit"): | |
| status += ("\n\n> No `poses_bounds.npy` or COLMAP `images.bin`/`cameras.bin` found, so an orbit around the " | |
| "point cloud is used. Add the dataset's camera file for the original viewpoints.") | |
| names = [v.name for v in rig.fixed] | |
| return state, status, gr.update(choices=names, value=names[0]), gr.update(interactive=True), gr.update(interactive=True) | |
| # -------------------------------------------------------------------------------------- | |
| # GPU work | |
| # -------------------------------------------------------------------------------------- | |
| def _scene_from_state(state): | |
| try: | |
| return rt.get_scene(state["key"]) | |
| except KeyError: | |
| # A fresh ZeroGPU worker or an evicted cache entry: rebuild from disk. | |
| return rt.load_scene(state["model_dir"], config=state["config"], prefer_compressed=state["prefer_cca"]) | |
| def _pick_view(state, mode, cam_name, path_pos, scale): | |
| rig = state["rig"] | |
| if mode == "Fixed camera": | |
| view = next((v for v in rig.fixed if v.name == cam_name), rig.fixed[0]) | |
| else: | |
| idx = min(int(round(path_pos * (len(rig.path) - 1))), len(rig.path) - 1) | |
| view = rig.path[idx] | |
| return view.scaled(scale) | |
| def _render_frame_gpu(state, view, t, background): | |
| scene = _scene_from_state(state) | |
| return rt.render_views(scene, [view], [t], background=background)[0] | |
| def render_frame(state, mode, cam_name, path_pos, t, scale, bg_name): | |
| if not state: | |
| raise gr.Error("Load a checkpoint first.") | |
| view = _pick_view(state, mode, cam_name, path_pos, scale) | |
| return _render_frame_gpu(state, view, float(t), BACKGROUNDS[bg_name]) | |
| def render_frame_if_loaded(state, mode, cam_name, path_pos, t, scale, bg_name): | |
| """Slider callback: silently ignore moves before a checkpoint is loaded.""" | |
| if not state: | |
| return gr.update() | |
| return render_frame(state, mode, cam_name, path_pos, t, scale, bg_name) | |
| def _video_duration(state, views, times, background, fps, out_path): | |
| pixels = views[0].width * views[0].height if views else 0 | |
| return int(min(300, 20 + len(views) * (0.05 + pixels / 2.0e7))) | |
| def _render_video_gpu(state, views, times, background, fps, out_path): | |
| scene = _scene_from_state(state) | |
| frames = rt.render_views(scene, views, times, background=background) | |
| imageio.mimwrite(out_path, frames, fps=fps, quality=8, macro_block_size=1) | |
| return out_path | |
| def render_video(state, mode, cam_name, path_pos, t, scale, bg_name, n_frames, fps, motion, sweep_time): | |
| if not state: | |
| raise gr.Error("Load a checkpoint first.") | |
| rig = state["rig"] | |
| n = int(n_frames) | |
| if motion == "Move along trajectory": | |
| idx = [int(round(i * (len(rig.path) - 1) / max(n - 1, 1))) for i in range(n)] | |
| views = [rig.path[i].scaled(scale) for i in idx] | |
| else: | |
| views = [_pick_view(state, mode, cam_name, path_pos, scale)] * n | |
| times = [i / max(n - 1, 1) for i in range(n)] if sweep_time else [float(t)] * n | |
| out_path = str(Path(state["out_dir"]) / f"render_{uuid.uuid4().hex[:8]}.mp4") | |
| return _render_video_gpu(state, views, times, BACKGROUNDS[bg_name], int(fps), out_path) | |
| # -------------------------------------------------------------------------------------- | |
| # UI | |
| # -------------------------------------------------------------------------------------- | |
| HEADER = """ | |
| # CC-4DGS: Computational Deformation and Point-Cloud Compression for Storage-Efficient Dynamic Gaussian Splatting | |
| **IEEE TVCG 2026** · [arXiv](https://arxiv.org/abs/2609.02184) · [Code](https://github.com/KyungdaePark/CC-4DGS) · | |
| [Pretrained models](https://drive.google.com/drive/folders/1wSE9Nn9Xo8flSjNXRZsqgGDnOYsqV6qV?usp=drive_link) | |
| Load a trained scene (a zip of the scene folder, or a folder in a Hugging Face repo) that contains | |
| `point_cloud/iteration_<N>/deformation.pth` with `point_cloud.ply` (CDF) or `compressed/` (CDF+CCA). | |
| Add the dataset's `poses_bounds.npy` (N3DV) or COLMAP `images.bin` + `cameras.bin` (Technicolor) to | |
| render from the original cameras and spiral path. | |
| """ | |
| with gr.Blocks(title="CC-4DGS") as demo: | |
| state = gr.State(None) | |
| gr.Markdown(HEADER) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| source = gr.Radio(["Upload zip", "Hugging Face Hub"], value="Hugging Face Hub" if DEFAULT_REPO else "Upload zip", | |
| label="Checkpoint source") | |
| zip_file = gr.File(label="Scene folder (.zip)", file_types=[".zip"], type="filepath") | |
| with gr.Group(): | |
| repo_id = gr.Textbox(label="Repo id", value=DEFAULT_REPO, placeholder="user/cc4dgs-checkpoints") | |
| subfolder = gr.Textbox(label="Subfolder (scene)", value=DEFAULT_SUBFOLDER, | |
| placeholder="CC-4DGS_N3DV_CDF_CCA/cut_roasted_beef") | |
| repo_type = gr.Radio(["model", "dataset"], value=DEFAULT_REPO_TYPE, label="Repo type") | |
| camera_files = gr.File(label="Camera files (optional): poses_bounds.npy, or images.bin + cameras.bin", | |
| file_count="multiple", type="filepath") | |
| config = gr.Dropdown(["auto"] + rt.list_configs(), value="auto", label="Scene config", | |
| info="'auto' matches the scene name and checks the deformation.pth architecture.") | |
| prefer_cca = gr.Checkbox(True, label="Use the CCA compressed bundle when available") | |
| load_btn = gr.Button("Load checkpoint", variant="primary") | |
| status = gr.Markdown() | |
| with gr.Column(scale=2): | |
| image = gr.Image(label="Render", type="numpy", format="png", interactive=False) | |
| t = gr.Slider(0.0, 1.0, value=0.0, step=1 / 299, label="Time t (normalised)") | |
| with gr.Row(): | |
| mode = gr.Radio(["Trajectory", "Fixed camera"], value="Trajectory", label="Camera") | |
| cam_name = gr.Dropdown([], label="Fixed camera") | |
| path_pos = gr.Slider(0.0, 1.0, value=0.0, step=0.001, label="Position on trajectory") | |
| with gr.Row(): | |
| scale = gr.Slider(0.25, 1.0, value=0.5, step=0.05, label="Resolution scale") | |
| bg_name = gr.Radio(list(BACKGROUNDS), value="Checkpoint default", label="Background") | |
| frame_btn = gr.Button("Render frame", interactive=False) | |
| with gr.Accordion("Video", open=True): | |
| with gr.Row(): | |
| n_frames = gr.Slider(10, 300, value=120, step=1, label="Frames") | |
| fps = gr.Slider(10, 60, value=30, step=1, label="FPS") | |
| with gr.Row(): | |
| motion = gr.Radio(["Move along trajectory", "Hold current view"], value="Move along trajectory", | |
| label="Camera motion") | |
| sweep_time = gr.Checkbox(True, label="Sweep time 0 → 1 (otherwise hold t)") | |
| video_btn = gr.Button("Render video", variant="primary", interactive=False) | |
| video = gr.Video(label="Video", autoplay=True) | |
| view_inputs = [state, mode, cam_name, path_pos, t, scale, bg_name] | |
| load_btn.click( | |
| load_checkpoint, | |
| [source, zip_file, repo_id, subfolder, repo_type, camera_files, config, prefer_cca], | |
| [state, status, cam_name, frame_btn, video_btn], | |
| ).success(render_frame, view_inputs, image) | |
| frame_btn.click(render_frame, view_inputs, image) | |
| for slider in (t, path_pos): | |
| slider.release(render_frame_if_loaded, view_inputs, image) | |
| video_btn.click(render_video, view_inputs + [n_frames, fps, motion, sweep_time], video) | |
| if __name__ == "__main__": | |
| demo.queue(max_size=20).launch(allowed_paths=[str(rt.WORK_ROOT)]) | |