Switch the demo to the gradio GUI
Browse filesRun demo.py --with-gui gradio (Dockerfile CMD); add gradio + scipy to
requirements; refresh README. Sync the optgs source + demo.py to the current
upstream-merged tree (the gradio GUI: live decoder-render stream + interactive
Model3D, with the Z-up->Y-up splat reorientation and the thread-safe
optimize_iter). Prebuilt wheels and submodules are unchanged.
This view is limited to 50 files because it contains too many changes. See raw diff
- Dockerfile +6 -16
- README.md +5 -3
- demo.py +379 -6
- optgs/config.py +75 -88
- optgs/config/dataset/dl3dv.yaml +1 -28
- optgs/config/dataset/re10k.yaml +1 -9
- optgs/config/experiment/re10k_unified.yaml +0 -5
- optgs/config/experiment/train_dl3dv.yaml +2 -3
- optgs/config/experiment/train_l2s_sparse_dl3dv.yaml +15 -10
- optgs/config/experiment/train_l2s_sparse_dl3dv_no_delta.yaml +5 -7
- optgs/config/experiment/train_l2s_sparse_dl3dv_no_loss.yaml +5 -7
- optgs/config/loss/lpips.yaml +1 -0
- optgs/config/loss/mse.yaml +2 -0
- optgs/config/loss/sgd.yaml +3 -1
- optgs/config/loss/stability.yaml +2 -0
- optgs/config/main.yaml +21 -29
- optgs/config/meta_trainer/train/{replay_buffer_cfg → ckpt_buffer_cfg}/default.yaml +4 -4
- optgs/config/meta_trainer/train/{replay_buffer_cfg → ckpt_buffer_cfg}/none.yaml +4 -4
- optgs/config/scene_trainer/scene_initializer/edgs.yaml +2 -1
- optgs/config/scene_trainer/scene_initializer/resplat_v1.yaml +0 -10
- optgs/config/scene_trainer/scene_initializer/resplat_v2.yaml +0 -10
- optgs/config/scene_trainer/scene_optimizer/3dgs.yaml +2 -13
- optgs/config/scene_trainer/scene_optimizer/3dgs_star.yaml +6 -11
- optgs/config/scene_trainer/scene_optimizer/base.yaml +6 -6
- optgs/config/scene_trainer/scene_optimizer/knn_based.yaml +36 -51
- optgs/config/scene_trainer/scene_optimizer/learn2splat.yaml +14 -2
- optgs/config/scene_trainer/scene_optimizer/learn2splat_dense.yaml +8 -0
- optgs/config/scene_trainer/scene_optimizer/lr_scheduler/expon.yaml +28 -0
- optgs/config/scene_trainer/scene_optimizer/refiner/mcmc.yaml +1 -1
- optgs/config/scene_trainer/scene_optimizer/resplat_v2.yaml +2 -2
- optgs/config/scene_trainer/scene_optimizer/sgd.yaml +0 -22
- optgs/config_migrate.py +285 -3
- optgs/dataset/camera_datasets/camera.py +0 -141
- optgs/dataset/data_module.py +3 -3
- optgs/dataset/dataset_dl3dv.py +20 -212
- optgs/dataset/dataset_re10k.py +22 -129
- optgs/dataset/shims/augmentation_shim.py +17 -16
- optgs/dataset/shims/bounds_shim.py +0 -80
- optgs/dataset/view_sampler/view_sampler.py +0 -17
- optgs/evaluation/metric_computer.py +0 -115
- optgs/evaluation/metrics.py +1 -1
- optgs/experimental/api/api.py +34 -24
- optgs/experimental/api/integration/config_bridge.py +14 -25
- optgs/global_cfg.py +0 -19
- optgs/loss/loss_deltas.py +7 -2
- optgs/loss/loss_lpips.py +8 -2
- optgs/loss/loss_monodepth.py +2 -21
- optgs/loss/loss_mse.py +7 -5
- optgs/loss/loss_sgd.py +11 -10
- optgs/loss/loss_sh0.py +3 -0
Dockerfile
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# Learn2Splat — interactive demo for a Hugging Face Space (Docker SDK, GPU).
|
| 2 |
#
|
| 3 |
# Installs the optgs package + prebuilt CUDA-extension wheels, then runs
|
| 4 |
-
# demo.py's
|
| 5 |
# with the learned optimizer live in the browser.
|
| 6 |
#
|
| 7 |
# The CUDA extensions are NOT compiled here — the HF Docker builder runs out of
|
|
@@ -53,15 +53,6 @@ RUN pip install torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1 \
|
|
| 53 |
COPY --chown=user:user requirements.txt .
|
| 54 |
RUN pip install -r requirements.txt
|
| 55 |
|
| 56 |
-
# HF serves the Space over HTTP/2, and the WebSocket subprotocol viser uses to
|
| 57 |
-
# announce its client version doesn't survive the proxy — so viser's server
|
| 58 |
-
# reads the client version as "unknown" and rejects the connection (the GUI
|
| 59 |
-
# then hangs on "connecting"). Client and server are the same viser build
|
| 60 |
-
# here, so treat an undeterminable client version as a match, not a reject.
|
| 61 |
-
RUN VISER_INFRA="$(python -c 'import viser.infra._infra as m; print(m.__file__)')" \
|
| 62 |
-
&& sed -i 's/client_version_str = "unknown"/client_version_str = viser.__version__/' "$VISER_INFRA" \
|
| 63 |
-
&& grep -q 'client_version_str = viser.__version__' "$VISER_INFRA"
|
| 64 |
-
|
| 65 |
# Prebuilt CUDA-extension wheels — gsplat, nerfacc, pycolmap, fused-ssim,
|
| 66 |
# simple-knn, pointops, fused_knn_attn. Built on a matching machine (see
|
| 67 |
# DEPLOY.md) so the HF builder never compiles CUDA and never OOMs.
|
|
@@ -72,11 +63,10 @@ RUN pip install --no-deps ./wheels/*.whl
|
|
| 72 |
COPY --chown=user:user . .
|
| 73 |
RUN pip install --no-build-isolation --no-deps -e .
|
| 74 |
|
| 75 |
-
#
|
| 76 |
EXPOSE 7860
|
| 77 |
|
| 78 |
-
#
|
| 79 |
-
#
|
| 80 |
-
#
|
| 81 |
-
|
| 82 |
-
CMD ["python", "demo.py", "--with-gui", "server", "--gui-port", "7860"]
|
|
|
|
| 1 |
# Learn2Splat — interactive demo for a Hugging Face Space (Docker SDK, GPU).
|
| 2 |
#
|
| 3 |
# Installs the optgs package + prebuilt CUDA-extension wheels, then runs
|
| 4 |
+
# demo.py's gradio GUI: SfM-initialize a COLMAP scene and refine the Gaussians
|
| 5 |
# with the learned optimizer live in the browser.
|
| 6 |
#
|
| 7 |
# The CUDA extensions are NOT compiled here — the HF Docker builder runs out of
|
|
|
|
| 53 |
COPY --chown=user:user requirements.txt .
|
| 54 |
RUN pip install -r requirements.txt
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
# Prebuilt CUDA-extension wheels — gsplat, nerfacc, pycolmap, fused-ssim,
|
| 57 |
# simple-knn, pointops, fused_knn_attn. Built on a matching machine (see
|
| 58 |
# DEPLOY.md) so the HF builder never compiles CUDA and never OOMs.
|
|
|
|
| 63 |
COPY --chown=user:user . .
|
| 64 |
RUN pip install --no-build-isolation --no-deps -e .
|
| 65 |
|
| 66 |
+
# gradio serves the GUI here — must equal app_port in README.md.
|
| 67 |
EXPOSE 7860
|
| 68 |
|
| 69 |
+
# gradio mode: the optgs decoder renders the live optimization on the GPU and
|
| 70 |
+
# gradio streams the frames as images (column 2); the finished splats load into
|
| 71 |
+
# an interactive Model3D viewer (column 3). demo.py binds 0.0.0.0 on this port.
|
| 72 |
+
CMD ["python", "demo.py", "--with-gui", "gradio", "--gui-port", "7860"]
|
|
|
README.md
CHANGED
|
@@ -14,11 +14,13 @@ short_description: Interactive demo of the Learn2Splat learned 3DGS optimizer
|
|
| 14 |
A learned optimizer for 3D Gaussian Splatting. This Space SfM-initializes a
|
| 15 |
COLMAP scene and refines the Gaussians live in your browser: pick the
|
| 16 |
Learn2Splat optimizer (dense or sparse checkpoint) or a 3DGS Adam baseline,
|
| 17 |
-
press **Start**, and watch the
|
|
|
|
| 18 |
|
| 19 |
-
Runs `demo.py --with-gui
|
| 20 |
[Learn2Splat repository](https://github.com/autonomousvision/learn2splat);
|
| 21 |
-
the
|
|
|
|
| 22 |
|
| 23 |
> Requires GPU hardware. The demo holds two checkpoints in VRAM at once —
|
| 24 |
> an A10G (24 GB) is recommended.
|
|
|
|
| 14 |
A learned optimizer for 3D Gaussian Splatting. This Space SfM-initializes a
|
| 15 |
COLMAP scene and refines the Gaussians live in your browser: pick the
|
| 16 |
Learn2Splat optimizer (dense or sparse checkpoint) or a 3DGS Adam baseline,
|
| 17 |
+
press **Start**, and watch the decoder render converge — then explore the
|
| 18 |
+
finished splats in an interactive 3D viewer.
|
| 19 |
|
| 20 |
+
Runs `demo.py --with-gui gradio` from the
|
| 21 |
[Learn2Splat repository](https://github.com/autonomousvision/learn2splat);
|
| 22 |
+
the optimization is rendered on the GPU and streamed by gradio, and the
|
| 23 |
+
result loads into a `Model3D` splat viewer.
|
| 24 |
|
| 25 |
> Requires GPU hardware. The demo holds two checkpoints in VRAM at once —
|
| 26 |
> an A10G (24 GB) is recommended.
|
demo.py
CHANGED
|
@@ -27,6 +27,7 @@ Usage (run from the repo root, with ``optgs`` importable):
|
|
| 27 |
python demo.py # headless: dense + sparse checkpoints + an Adam baseline
|
| 28 |
python demo.py --with-gui server # interactive viser GUI (frames rendered by the decoder)
|
| 29 |
python demo.py --with-gui client # interactive viser GUI (viser's WebGL splat renderer)
|
|
|
|
| 30 |
|
| 31 |
The demo scene and the checkpoints are fetched from the Hugging Face Hub on
|
| 32 |
first run (cached under ./data and ./checkpoints). A CUDA device is required.
|
|
@@ -118,11 +119,13 @@ class Config:
|
|
| 118 |
seed: int = 42
|
| 119 |
|
| 120 |
# --- Interactive GUI ---
|
| 121 |
-
# Launch
|
| 122 |
-
# frames with the optgs decoder
|
| 123 |
-
# Gaussian-splat renderer.
|
| 124 |
-
|
| 125 |
-
#
|
|
|
|
|
|
|
| 126 |
gui_port: int = 8080
|
| 127 |
|
| 128 |
# --- OptGS learned optimizer ---
|
|
@@ -600,6 +603,373 @@ def run_gui(
|
|
| 600 |
console.print("\n[yellow]GUI stopped.[/]")
|
| 601 |
|
| 602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 603 |
def main(cfg: Config) -> None:
|
| 604 |
# Fetch the demo scene on first run, before anything else touches it.
|
| 605 |
ensure_data(cfg.data_dir)
|
|
@@ -663,7 +1033,10 @@ def main(cfg: Config) -> None:
|
|
| 663 |
for inst in instances.values():
|
| 664 |
inst.initialize_from_tensors(gaussians, train_bv)
|
| 665 |
|
| 666 |
-
|
|
|
|
|
|
|
|
|
|
| 667 |
return
|
| 668 |
|
| 669 |
val_c2w, val_Ks, val_images = collect_cameras(dataset, val_idx)
|
|
|
|
| 27 |
python demo.py # headless: dense + sparse checkpoints + an Adam baseline
|
| 28 |
python demo.py --with-gui server # interactive viser GUI (frames rendered by the decoder)
|
| 29 |
python demo.py --with-gui client # interactive viser GUI (viser's WebGL splat renderer)
|
| 30 |
+
python demo.py --with-gui gradio # interactive gradio GUI (streamed renders + Model3D splats)
|
| 31 |
|
| 32 |
The demo scene and the checkpoints are fetched from the Hugging Face Hub on
|
| 33 |
first run (cached under ./data and ./checkpoints). A CUDA device is required.
|
|
|
|
| 119 |
seed: int = 42
|
| 120 |
|
| 121 |
# --- Interactive GUI ---
|
| 122 |
+
# Launch an interactive GUI instead of the headless comparison. viser:
|
| 123 |
+
# "server" renders frames with the optgs decoder, "client" uses viser's
|
| 124 |
+
# built-in WebGL Gaussian-splat renderer. "gradio" runs a browser GUI
|
| 125 |
+
# (decoder renders streamed live + an interactive Model3D splat viewer for
|
| 126 |
+
# the result). Unset = headless run.
|
| 127 |
+
with_gui: Optional[Literal["client", "server", "gradio"]] = None
|
| 128 |
+
# Port for the GUI web server (--with-gui only).
|
| 129 |
gui_port: int = 8080
|
| 130 |
|
| 131 |
# --- OptGS learned optimizer ---
|
|
|
|
| 603 |
console.print("\n[yellow]GUI stopped.[/]")
|
| 604 |
|
| 605 |
|
| 606 |
+
def run_gradio_gui(
|
| 607 |
+
instances: dict,
|
| 608 |
+
gaussians: Gaussians,
|
| 609 |
+
train_bv: dict,
|
| 610 |
+
cfg: Config,
|
| 611 |
+
device: torch.device,
|
| 612 |
+
) -> None:
|
| 613 |
+
"""Interactive gradio GUI — a browser port of :func:`run_gui` (viser).
|
| 614 |
+
|
| 615 |
+
gradio can't stream the camera back to Python, so there is no free-camera
|
| 616 |
+
server rendering. Instead the optimization is *watched* as a streamed
|
| 617 |
+
decoder render from a chosen training view (``gr.Image``, refreshed every
|
| 618 |
+
step), and the finished scene is handed to an interactive ``gr.Model3D``
|
| 619 |
+
splat viewer (orbit / zoom in the browser). The controls mirror the viser
|
| 620 |
+
GUI: pick the optimizer (Learn2Splat dense/sparse or a 3DGS Adam baseline),
|
| 621 |
+
set the step budget / view-minibatch size / sampling strategy, Start, Reset.
|
| 622 |
+
|
| 623 |
+
``instances`` maps "dense"/"sparse" to their initialized ``OptGS``.
|
| 624 |
+
"""
|
| 625 |
+
import gc
|
| 626 |
+
|
| 627 |
+
import gradio as gr
|
| 628 |
+
|
| 629 |
+
from optgs.experimental.api.integration.config_bridge import build_adam_baseline
|
| 630 |
+
from optgs.misc.image_io import prep_image
|
| 631 |
+
from optgs.model.ply_export import save_gaussian_ply
|
| 632 |
+
|
| 633 |
+
# Optimizer dropdown label -> (instances key, swap in a 3DGS Adam baseline);
|
| 634 |
+
# mirrors run_gui's OPTIONS.
|
| 635 |
+
OPTIONS: Dict[str, Tuple[str, bool]] = {
|
| 636 |
+
"Learn2Splat (dense)": ("dense", False),
|
| 637 |
+
"Learn2Splat (sparse)": ("sparse", False),
|
| 638 |
+
"Adam (3DGS)": ("dense", True),
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
n_train_views = int(train_bv["image"].shape[1])
|
| 642 |
+
h_full, w_full = train_bv["image"].shape[3], train_bv["image"].shape[4]
|
| 643 |
+
init_gaussians = gaussians.clone() # pristine copy; each Start re-inits from it
|
| 644 |
+
|
| 645 |
+
# Shared state (single-GPU, single-session demo, like run_gui's globals): the
|
| 646 |
+
# Gaussians currently shown, the OptGS rendering them, and a counter for
|
| 647 |
+
# unique PLY filenames (so Model3D reloads instead of serving a stale cache).
|
| 648 |
+
holder = {"current": init_gaussians, "active": instances["dense"], "ply": 0}
|
| 649 |
+
|
| 650 |
+
ply_dir = os.path.join(cfg.result_dir, "gradio")
|
| 651 |
+
os.makedirs(ply_dir, exist_ok=True)
|
| 652 |
+
|
| 653 |
+
@torch.no_grad()
|
| 654 |
+
def render_decoder(inst, g: Gaussians, view_idx: float, height: float) -> np.ndarray:
|
| 655 |
+
"""Decoder-render Gaussians ``g`` from training view ``view_idx``.
|
| 656 |
+
|
| 657 |
+
Normalized intrinsics make the render resolution-independent; the width
|
| 658 |
+
is derived from ``height`` at the training views' aspect ratio.
|
| 659 |
+
"""
|
| 660 |
+
h = int(height)
|
| 661 |
+
w = max(1, round(h * w_full / h_full))
|
| 662 |
+
sl = slice(int(view_idx), int(view_idx) + 1)
|
| 663 |
+
out = inst.decoder.forward(
|
| 664 |
+
g,
|
| 665 |
+
train_bv["extrinsics"][:, sl],
|
| 666 |
+
train_bv["intrinsics"][:, sl],
|
| 667 |
+
train_bv["near"][:, sl],
|
| 668 |
+
train_bv["far"][:, sl],
|
| 669 |
+
image_shape=(h, w),
|
| 670 |
+
)
|
| 671 |
+
return prep_image(out.color[0, 0]) # [H, W, 3] uint8
|
| 672 |
+
|
| 673 |
+
def reorient_for_viewer(g: Gaussians) -> Gaussians:
|
| 674 |
+
"""Reorient a copy of ``g`` from the COLMAP world (this scene is Z-up)
|
| 675 |
+
into the Y-up frame the gradio Model3D shows upright.
|
| 676 |
+
|
| 677 |
+
gradio/Babylon flips the loaded splats' Y (``scaling.y *= -1``), so
|
| 678 |
+
exporting through the reflection E(p)=(x,-z,-y) makes the *displayed*
|
| 679 |
+
scene N(p)=(x,z,-y) — the world's Z-up mapped onto Babylon's Y-up. E's
|
| 680 |
+
point-reflection part leaves the covariance unchanged; its proper-rotation
|
| 681 |
+
part R_p=-E rotates the splat orientations to match.
|
| 682 |
+
"""
|
| 683 |
+
from scipy.spatial.transform import Rotation as Rsp
|
| 684 |
+
|
| 685 |
+
g = g.clone()
|
| 686 |
+
m = g.means[0]
|
| 687 |
+
g.means = torch.stack([m[:, 0], -m[:, 2], -m[:, 1]], dim=1)[None] # E
|
| 688 |
+
q = F.normalize(g.rotations_unnorm[0], dim=-1).detach().cpu().numpy() # xyzw
|
| 689 |
+
R_p = np.array([[-1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]])
|
| 690 |
+
q = (Rsp.from_matrix(R_p) * Rsp.from_quat(q)).as_quat() # R_p @ R, xyzw
|
| 691 |
+
g.rotations_unnorm = torch.from_numpy(q).to(g.means)[None]
|
| 692 |
+
return g
|
| 693 |
+
|
| 694 |
+
def export_ply(g: Gaussians) -> str:
|
| 695 |
+
"""Write ``g`` (reoriented to Y-up) to a fresh PLY path for the viewer."""
|
| 696 |
+
from pathlib import Path
|
| 697 |
+
|
| 698 |
+
holder["ply"] += 1
|
| 699 |
+
path = os.path.join(ply_dir, f"result_{holder['ply']}.ply")
|
| 700 |
+
save_gaussian_ply(reorient_for_viewer(g), save_path=Path(path))
|
| 701 |
+
return path
|
| 702 |
+
|
| 703 |
+
def start(optimizer_label, max_steps, batch_size, strategy, view_idx, height):
|
| 704 |
+
"""Generator: run the picked optimizer, streaming a decoder render each
|
| 705 |
+
step, then load the finished splats into the Model3D viewer."""
|
| 706 |
+
name, use_adam = OPTIONS[optimizer_label]
|
| 707 |
+
inst = instances[name]
|
| 708 |
+
holder["active"] = inst
|
| 709 |
+
# Re-init from the pristine copy so repeated Starts share one start point.
|
| 710 |
+
inst.initialize_from_tensors(init_gaussians.clone(), train_bv)
|
| 711 |
+
inst.num_refine = int(max_steps)
|
| 712 |
+
inst.opt_batch_size = min(int(batch_size), n_train_views)
|
| 713 |
+
inst.opt_batch_strategy = strategy
|
| 714 |
+
opt = build_adam_baseline(inst.num_refine).to(device) if use_adam else None
|
| 715 |
+
|
| 716 |
+
try:
|
| 717 |
+
# Disable Start, hide the viewer, keep the placeholder while running.
|
| 718 |
+
yield (
|
| 719 |
+
render_decoder(inst, init_gaussians, view_idx, height),
|
| 720 |
+
f"**optimizing** — step 0/{inst.num_refine} — "
|
| 721 |
+
f"{init_gaussians.means.shape[1]} Gaussians",
|
| 722 |
+
gr.update(visible=False, value=None), # hide the viewer
|
| 723 |
+
gr.update(interactive=False), # disable Start
|
| 724 |
+
gr.update(visible=True), # keep the placeholder
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
g = init_gaussians
|
| 728 |
+
for step, g in inst.optimize_iter(optimizer=opt):
|
| 729 |
+
holder["current"] = g
|
| 730 |
+
yield (
|
| 731 |
+
render_decoder(inst, g, view_idx, height),
|
| 732 |
+
f"**optimizing** — step {step + 1}/{inst.num_refine} — "
|
| 733 |
+
f"{g.means.shape[1]} Gaussians",
|
| 734 |
+
gr.update(), gr.update(), gr.update(), # no change mid-run
|
| 735 |
+
)
|
| 736 |
+
|
| 737 |
+
yield (
|
| 738 |
+
render_decoder(inst, g, view_idx, height),
|
| 739 |
+
f"**done** — {inst.num_refine} steps — {g.means.shape[1]} Gaussians",
|
| 740 |
+
gr.update(visible=True, value=export_ply(g)), # reveal the viewer
|
| 741 |
+
gr.update(interactive=True), # re-enable Start
|
| 742 |
+
gr.update(visible=False), # hide the placeholder
|
| 743 |
+
)
|
| 744 |
+
finally:
|
| 745 |
+
# Free the run's CUDA work (also runs if the user hits Stop mid-run),
|
| 746 |
+
# so GPU memory doesn't accumulate across repeated runs.
|
| 747 |
+
opt = None
|
| 748 |
+
gc.collect()
|
| 749 |
+
torch.cuda.empty_cache()
|
| 750 |
+
|
| 751 |
+
def reset(view_idx, height):
|
| 752 |
+
"""Restore the initialization: re-render it, hide the viewer.
|
| 753 |
+
|
| 754 |
+
No CUDA cleanup here — ``start``'s ``finally`` already frees each run's
|
| 755 |
+
work; reset only re-renders the init.
|
| 756 |
+
"""
|
| 757 |
+
holder["current"] = init_gaussians
|
| 758 |
+
holder["active"] = instances["dense"]
|
| 759 |
+
return (
|
| 760 |
+
render_decoder(instances["dense"], init_gaussians, view_idx, height),
|
| 761 |
+
"**Initialized.** Pick a method, then Start.",
|
| 762 |
+
gr.update(visible=False, value=None), # hide the viewer
|
| 763 |
+
gr.update(interactive=True), # enable Start
|
| 764 |
+
gr.update(visible=True), # show the placeholder
|
| 765 |
+
)
|
| 766 |
+
|
| 767 |
+
def rerender(view_idx, height):
|
| 768 |
+
"""Re-render the current Gaussians (preview view / height changed)."""
|
| 769 |
+
return render_decoder(holder["active"], holder["current"], view_idx, height)
|
| 770 |
+
|
| 771 |
+
initial_img = render_decoder(instances["dense"], init_gaussians, 0, 540)
|
| 772 |
+
|
| 773 |
+
# Open the interactive viewer on the same vantage as the live render's
|
| 774 |
+
# default preview (view 0). The viewer shows splats in the reoriented frame
|
| 775 |
+
# N(p)=(x,z,-y) (see reorient_for_viewer); the orbit camera sits at the
|
| 776 |
+
# training view's position and looks at the scene centroid. babylon_camera
|
| 777 |
+
# maps the world camera into N and inverts Babylon's ArcRotateCamera position
|
| 778 |
+
# formula rel=(r·cosα·sinβ, r·cosβ, r·sinα·sinβ) into (alpha°, beta°, radius).
|
| 779 |
+
def babylon_camera(c2w: np.ndarray, centroid: np.ndarray) -> tuple:
|
| 780 |
+
p = c2w[:3, 3] - centroid
|
| 781 |
+
rel = np.array([p[0], p[2], -p[1]], dtype=np.float64) # N applied to (cam - centroid)
|
| 782 |
+
radius = float(np.linalg.norm(rel)) or 1e-3
|
| 783 |
+
beta = float(np.degrees(np.arccos(np.clip(rel[1] / radius, -1.0, 1.0))))
|
| 784 |
+
alpha = float(np.degrees(np.arctan2(rel[2], rel[0])))
|
| 785 |
+
return (alpha, beta, radius)
|
| 786 |
+
|
| 787 |
+
means0 = init_gaussians.means[0].detach().float().cpu().numpy()
|
| 788 |
+
centroid0 = (means0.min(0) + means0.max(0)) / 2.0
|
| 789 |
+
cam0_c2w = train_bv["extrinsics"][0, 0].detach().float().cpu().numpy()
|
| 790 |
+
init_camera = babylon_camera(cam0_c2w, centroid0)
|
| 791 |
+
|
| 792 |
+
# --- Visual style: lifted from the Learn2Splat project page
|
| 793 |
+
# (https://naamapearl.github.io/learn2splat/) — plum accent (#B04080) with an
|
| 794 |
+
# indigo hover, a light slate canvas with white cards, Source Serif 4
|
| 795 |
+
# headings / Inter body / JetBrains Mono labels. ---
|
| 796 |
+
plum = gr.themes.Color(
|
| 797 |
+
c50="#faf0f6", c100="#f5e6ef", c200="#eccadd", c300="#dda3c2",
|
| 798 |
+
c400="#c66ba0", c500="#b04080", c600="#9b3570", c700="#7f2a5b",
|
| 799 |
+
c800="#6a2550", c900="#581f43", c950="#350e26", name="plum",
|
| 800 |
+
)
|
| 801 |
+
slate = gr.themes.Color(
|
| 802 |
+
c50="#f7f8fc", c100="#eef0f8", c200="#e2e5ef", c300="#c8cde0",
|
| 803 |
+
c400="#8890b0", c500="#5a6080", c600="#454b6b", c700="#343a56",
|
| 804 |
+
c800="#262b42", c900="#1a1d2e", c950="#0f1120", name="slate",
|
| 805 |
+
)
|
| 806 |
+
theme = gr.themes.Soft(
|
| 807 |
+
primary_hue=plum,
|
| 808 |
+
neutral_hue=slate,
|
| 809 |
+
font=["Inter", "system-ui", "sans-serif"],
|
| 810 |
+
font_mono=["JetBrains Mono", "ui-monospace", "monospace"],
|
| 811 |
+
).set(
|
| 812 |
+
body_background_fill="#f7f8fc",
|
| 813 |
+
body_text_color="#1a1d2e",
|
| 814 |
+
block_background_fill="#ffffff",
|
| 815 |
+
block_border_color="#e2e5ef",
|
| 816 |
+
block_radius="12px",
|
| 817 |
+
block_label_text_color="#5a6080",
|
| 818 |
+
block_title_text_color="#1a1d2e",
|
| 819 |
+
border_color_primary="#e2e5ef",
|
| 820 |
+
input_background_fill="#ffffff",
|
| 821 |
+
button_primary_background_fill="#b04080",
|
| 822 |
+
button_primary_background_fill_hover="#3d50c0",
|
| 823 |
+
button_primary_text_color="#ffffff",
|
| 824 |
+
button_secondary_background_fill="#ffffff",
|
| 825 |
+
button_secondary_border_color="#c8cde0",
|
| 826 |
+
button_secondary_text_color="#1a1d2e",
|
| 827 |
+
slider_color="#b04080",
|
| 828 |
+
link_text_color="#b04080",
|
| 829 |
+
)
|
| 830 |
+
# Source Serif 4 / Inter / JetBrains Mono, loaded into <head> like the page.
|
| 831 |
+
fonts_head = (
|
| 832 |
+
'<link rel="preconnect" href="https://fonts.googleapis.com">'
|
| 833 |
+
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>'
|
| 834 |
+
'<link rel="stylesheet" href="https://fonts.googleapis.com/css2?'
|
| 835 |
+
"family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&"
|
| 836 |
+
"family=Inter:wght@300;400;500;600&"
|
| 837 |
+
'family=JetBrains+Mono:wght@400;500&display=swap">'
|
| 838 |
+
)
|
| 839 |
+
css = """
|
| 840 |
+
.gradio-container { max-width: 1580px !important; margin: 0 auto !important; }
|
| 841 |
+
#l2s-hero { background: linear-gradient(180deg,#f3f4fb 0%,#f7f8fc 100%);
|
| 842 |
+
border:1px solid #e2e5ef; border-radius:14px; padding:26px 30px; margin-bottom:6px; }
|
| 843 |
+
#l2s-hero .eyebrow { font-family:'JetBrains Mono',monospace; font-size:12px;
|
| 844 |
+
letter-spacing:.16em; text-transform:uppercase; color:#b04080; font-weight:500;
|
| 845 |
+
display:inline-flex; align-items:center; gap:8px; }
|
| 846 |
+
#l2s-hero .eyebrow .dot { width:6px; height:6px; border-radius:50%; background:#b04080; }
|
| 847 |
+
#l2s-hero h1 { font-family:'Source Serif 4',Georgia,serif; font-weight:600; color:#1a1d2e;
|
| 848 |
+
font-size:clamp(1.55rem,3vw,2.25rem); line-height:1.2; margin:13px 0 10px; }
|
| 849 |
+
#l2s-hero p { color:#5a6080; font-size:15px; line-height:1.6; margin:0; max-width:820px; }
|
| 850 |
+
#l2s-hero a { color:#b04080; text-decoration:none; border-bottom:1px solid #e6cdd9;
|
| 851 |
+
white-space:nowrap; }
|
| 852 |
+
.l2s-eyebrow span { font-family:'JetBrains Mono',monospace; font-size:11px;
|
| 853 |
+
letter-spacing:.15em; text-transform:uppercase; color:#8890b0; font-weight:500; }
|
| 854 |
+
#l2s-start button, #l2s-reset button { font-family:'JetBrains Mono',monospace;
|
| 855 |
+
letter-spacing:.03em; font-weight:500; }
|
| 856 |
+
#l2s-status { background:#f7f8fc; border:1px solid #e2e5ef; border-left:3px solid #b04080;
|
| 857 |
+
border-radius:9px; padding:4px 14px; }
|
| 858 |
+
#l2s-status p { color:#5a6080; font-size:14px; margin:8px 0; }
|
| 859 |
+
#l2s-status strong { color:#1a1d2e; }
|
| 860 |
+
.l2s-ph { display:flex; align-items:center; justify-content:center; text-align:center;
|
| 861 |
+
height:420px; border:1px dashed #c8cde0; border-radius:12px; background:#fbfcff;
|
| 862 |
+
color:#8890b0; font-family:'JetBrains Mono',monospace; font-size:12.5px;
|
| 863 |
+
letter-spacing:.04em; line-height:1.7; }
|
| 864 |
+
footer { display:none !important; }
|
| 865 |
+
"""
|
| 866 |
+
hero_html = (
|
| 867 |
+
"<div class='eyebrow'><span class='dot'></span>Learn2Splat · Interactive demo</div>"
|
| 868 |
+
"<h1>Extending the Horizon of Learned 3DGS Optimization</h1>"
|
| 869 |
+
"<p>SfM-initialize a COLMAP scene, then refine the Gaussians with the "
|
| 870 |
+
"meta-learned optimizer — pick a method, press <b>Start</b>, and watch the "
|
| 871 |
+
"decoder render converge. The finished splats load in the interactive 3D "
|
| 872 |
+
"viewer. <a href='https://naamapearl.github.io/learn2splat/' target='_blank' "
|
| 873 |
+
"rel='noopener'>Project page ↗</a></p>"
|
| 874 |
+
)
|
| 875 |
+
|
| 876 |
+
with gr.Blocks(
|
| 877 |
+
title="Learn2Splat — Demo", theme=theme, css=css, head=fonts_head,
|
| 878 |
+
analytics_enabled=False,
|
| 879 |
+
) as ui:
|
| 880 |
+
gr.HTML(hero_html, elem_id="l2s-hero")
|
| 881 |
+
with gr.Row(equal_height=False):
|
| 882 |
+
# Column 1 — controls.
|
| 883 |
+
with gr.Column(scale=3, min_width=300):
|
| 884 |
+
with gr.Group():
|
| 885 |
+
gr.HTML("<div class='l2s-eyebrow'><span>Optimizer</span></div>")
|
| 886 |
+
optimizer_dd = gr.Dropdown(
|
| 887 |
+
list(OPTIONS), value=next(iter(OPTIONS)), label="Method"
|
| 888 |
+
)
|
| 889 |
+
with gr.Row():
|
| 890 |
+
max_steps_input = gr.Number(
|
| 891 |
+
value=cfg.max_steps, minimum=1, maximum=1000, step=1,
|
| 892 |
+
precision=0, label="Max steps",
|
| 893 |
+
)
|
| 894 |
+
batch_size_input = gr.Number(
|
| 895 |
+
value=min(cfg.opt_batch_size, n_train_views),
|
| 896 |
+
minimum=1, maximum=n_train_views, step=1, precision=0,
|
| 897 |
+
label="Opt batch size",
|
| 898 |
+
)
|
| 899 |
+
strategy_dd = gr.Dropdown(
|
| 900 |
+
["random", "sequential", "fps"],
|
| 901 |
+
value=cfg.opt_batch_strategy, label="Batch strategy",
|
| 902 |
+
)
|
| 903 |
+
with gr.Group():
|
| 904 |
+
gr.HTML("<div class='l2s-eyebrow'><span>Preview</span></div>")
|
| 905 |
+
view_slider = gr.Slider(
|
| 906 |
+
0, n_train_views - 1, value=0, step=1, label="Preview view"
|
| 907 |
+
)
|
| 908 |
+
height_slider = gr.Slider(
|
| 909 |
+
240, 1080, value=540, step=60, label="Render height"
|
| 910 |
+
)
|
| 911 |
+
with gr.Row():
|
| 912 |
+
start_btn = gr.Button(
|
| 913 |
+
"Start optimization", variant="primary",
|
| 914 |
+
elem_id="l2s-start", scale=2,
|
| 915 |
+
)
|
| 916 |
+
reset_btn = gr.Button(
|
| 917 |
+
"Reset", variant="secondary", elem_id="l2s-reset", scale=1
|
| 918 |
+
)
|
| 919 |
+
status_md = gr.Markdown(
|
| 920 |
+
"**Initialized.** Pick a method, then Start.", elem_id="l2s-status"
|
| 921 |
+
)
|
| 922 |
+
# Column 2 — live decoder render (streamed during optimization).
|
| 923 |
+
with gr.Column(scale=5, min_width=380):
|
| 924 |
+
image_out = gr.Image(
|
| 925 |
+
value=initial_img, label="Optimizer · live",
|
| 926 |
+
height=540, format="jpeg", interactive=False,
|
| 927 |
+
)
|
| 928 |
+
# Column 3 — interactive splats (hidden until a run finishes; a
|
| 929 |
+
# placeholder holds the column so the 3-up layout stays balanced).
|
| 930 |
+
with gr.Column(scale=5, min_width=380):
|
| 931 |
+
model3d_out = gr.Model3D(
|
| 932 |
+
label="Refined splats · interactive", height=540,
|
| 933 |
+
visible=False, camera_position=init_camera,
|
| 934 |
+
)
|
| 935 |
+
placeholder = gr.HTML(
|
| 936 |
+
"<div class='l2s-ph'>The interactive 3D splats<br>"
|
| 937 |
+
"appear here once a run finishes.</div>"
|
| 938 |
+
)
|
| 939 |
+
|
| 940 |
+
start_inputs = [
|
| 941 |
+
optimizer_dd, max_steps_input, batch_size_input, strategy_dd,
|
| 942 |
+
view_slider, height_slider,
|
| 943 |
+
]
|
| 944 |
+
gui_outputs = [image_out, status_md, model3d_out, start_btn, placeholder]
|
| 945 |
+
# One shared GPU lane (concurrency_id) so Start / Reset / preview re-renders
|
| 946 |
+
# never run on the GPU at the same time — overlapping runs were the path to
|
| 947 |
+
# runaway VRAM growth.
|
| 948 |
+
start_btn.click(
|
| 949 |
+
start, inputs=start_inputs, outputs=gui_outputs, concurrency_id="gpu"
|
| 950 |
+
)
|
| 951 |
+
reset_btn.click(
|
| 952 |
+
reset, inputs=[view_slider, height_slider], outputs=gui_outputs,
|
| 953 |
+
concurrency_id="gpu",
|
| 954 |
+
)
|
| 955 |
+
# Slider release (not change) — re-render once when the user lets go.
|
| 956 |
+
view_slider.release(
|
| 957 |
+
rerender, [view_slider, height_slider], image_out, concurrency_id="gpu"
|
| 958 |
+
)
|
| 959 |
+
height_slider.release(
|
| 960 |
+
rerender, [view_slider, height_slider], image_out, concurrency_id="gpu"
|
| 961 |
+
)
|
| 962 |
+
|
| 963 |
+
console.print(
|
| 964 |
+
f"[green]✓[/] gradio GUI on port [cyan]{cfg.gui_port}[/]"
|
| 965 |
+
f" — forward the port over SSH and open the printed URL"
|
| 966 |
+
)
|
| 967 |
+
ui.queue(default_concurrency_limit=1).launch(
|
| 968 |
+
server_name="0.0.0.0", server_port=cfg.gui_port, share=False,
|
| 969 |
+
show_error=True,
|
| 970 |
+
)
|
| 971 |
+
|
| 972 |
+
|
| 973 |
def main(cfg: Config) -> None:
|
| 974 |
# Fetch the demo scene on first run, before anything else touches it.
|
| 975 |
ensure_data(cfg.data_dir)
|
|
|
|
| 1033 |
for inst in instances.values():
|
| 1034 |
inst.initialize_from_tensors(gaussians, train_bv)
|
| 1035 |
|
| 1036 |
+
if cfg.with_gui == "gradio":
|
| 1037 |
+
run_gradio_gui(instances, gaussians, train_bv, cfg, device)
|
| 1038 |
+
else:
|
| 1039 |
+
run_gui(instances, gaussians, train_bv, cfg, device, dtype)
|
| 1040 |
return
|
| 1041 |
|
| 1042 |
val_c2w, val_Ks, val_images = collect_cameras(dataset, val_idx)
|
optgs/config.py
CHANGED
|
@@ -1,29 +1,34 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
from copy import deepcopy
|
| 3 |
from dataclasses import dataclass
|
| 4 |
from pathlib import Path
|
| 5 |
-
from typing import Literal, Optional, Type, TypeVar
|
| 6 |
|
| 7 |
-
import hydra
|
| 8 |
import torch
|
| 9 |
from dacite import Config, from_dict, UnionMatchError
|
| 10 |
-
from hydra.core.global_hydra import GlobalHydra
|
| 11 |
from hydra.core.hydra_config import HydraConfig
|
| 12 |
-
from hydra.types import RunMode
|
| 13 |
from omegaconf import DictConfig
|
| 14 |
from omegaconf import OmegaConf
|
| 15 |
from pytorch_lightning.strategies import DDPStrategy, FSDPStrategy
|
| 16 |
|
| 17 |
from .config_migrate import migrate, CURRENT_CFG_VERSION
|
| 18 |
from .dataset.data_module import DataLoaderCfg, DatasetCfg
|
| 19 |
-
from .global_cfg import set_cfg
|
| 20 |
from .loss import LossCfgWrapper
|
| 21 |
from .misc.io import CustomPath
|
| 22 |
from .misc.io import cyan, read_omega_cfg
|
| 23 |
from .misc.checkpointing import find_latest_ckpt
|
| 24 |
-
from .misc.hf_ckpt import maybe_resolve_hf_ref
|
| 25 |
-
from .paths import CKPT_DIR, RESULTS_DIR
|
| 26 |
-
from .scene_trainer.scene_trainer_cfg import SceneTrainerCfg
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
# In order to extract filename or dirname from a path in the config
|
|
@@ -58,15 +63,28 @@ class CheckpointingCfg:
|
|
| 58 |
resume_update_module: str | None
|
| 59 |
load_existing_cfg: bool
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
def __post_init__(self):
|
| 62 |
# Resolve any Hugging Face Hub references (hf://org/repo/file[@rev]) to
|
| 63 |
# local cached paths so all downstream torch.load calls work unchanged.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
for attr in ("pretrained_model", "pretrained_optimizer", "pretrained_initializer",
|
| 65 |
"pretrained_monodepth", "pretrained_mvdepth", "pretrained_depth",
|
| 66 |
"pretrained_scale_predictor", "pretrained_depth_teacher",
|
| 67 |
"resume_update_module"):
|
| 68 |
-
|
| 69 |
-
if
|
|
|
|
|
|
|
|
|
|
| 70 |
setattr(self, attr, resolved)
|
| 71 |
|
| 72 |
for attr in ("pretrained_model", "pretrained_optimizer", "pretrained_initializer"):
|
|
@@ -90,6 +108,7 @@ class MetaTrainerCfg:
|
|
| 90 |
eval_index: str | None
|
| 91 |
limit_test_batches: int | float
|
| 92 |
limit_train_batches: int | float
|
|
|
|
| 93 |
test: TestCfg
|
| 94 |
train: TrainCfg
|
| 95 |
|
|
@@ -109,9 +128,9 @@ class MetaTrainerCfg:
|
|
| 109 |
return has_trainable
|
| 110 |
|
| 111 |
dist_strategy = FSDPStrategy(auto_wrap_policy=only_wrap_trainable)
|
| 112 |
-
if self.train.
|
| 113 |
-
# When resampling from the
|
| 114 |
-
# we don't project the condition_features to state, so the
|
| 115 |
dist_strategy = "ddp_find_unused_parameters_true"
|
| 116 |
return dist_strategy
|
| 117 |
|
|
@@ -123,14 +142,13 @@ class RootCfg:
|
|
| 123 |
dataset: DatasetCfg
|
| 124 |
data_loader: DataLoaderCfg
|
| 125 |
scene_trainer: SceneTrainerCfg
|
| 126 |
-
meta_optimizer: MetaOptimizerCfg ## TODO Naama: should we move under meta trainer config?
|
| 127 |
checkpointing: CheckpointingCfg
|
| 128 |
meta_trainer: MetaTrainerCfg
|
| 129 |
loss: list[LossCfgWrapper]
|
| 130 |
seed: int
|
| 131 |
use_plugins: bool
|
| 132 |
output_dir: str
|
| 133 |
-
version: int | None
|
| 134 |
debug_cfg: bool
|
| 135 |
|
| 136 |
def __post_init__(self):
|
|
@@ -202,12 +220,6 @@ TYPE_HOOKS = {
|
|
| 202 |
T = TypeVar("T")
|
| 203 |
|
| 204 |
|
| 205 |
-
def get_class_by_path(path: str):
|
| 206 |
-
module_path, class_name = path.rsplit('.', 1)
|
| 207 |
-
module = importlib.import_module(module_path)
|
| 208 |
-
return getattr(module, class_name)
|
| 209 |
-
|
| 210 |
-
|
| 211 |
def _diagnose_union_error(e: UnionMatchError, data: dict, dacite_config: Config) -> str:
|
| 212 |
"""Try each union member individually and report per-member errors."""
|
| 213 |
import dataclasses
|
|
@@ -273,32 +285,6 @@ def separate_loss_cfg_wrappers(joined: dict) -> list[LossCfgWrapper]:
|
|
| 273 |
]
|
| 274 |
|
| 275 |
|
| 276 |
-
def universal_target_hook(cfg: dict, _: Type) -> Any:
|
| 277 |
-
"""Generic hook to construct config objects from `__target__`."""
|
| 278 |
-
if not isinstance(cfg, dict):
|
| 279 |
-
return None
|
| 280 |
-
if "__target__" not in cfg:
|
| 281 |
-
return None # Let decite handle it
|
| 282 |
-
|
| 283 |
-
cfg_copy = deepcopy(cfg) # avoid mutating original
|
| 284 |
-
target = cfg_copy.pop("__target__")
|
| 285 |
-
|
| 286 |
-
if isinstance(target, str):
|
| 287 |
-
target_type = get_class_by_path(target)
|
| 288 |
-
else:
|
| 289 |
-
target_type = target
|
| 290 |
-
|
| 291 |
-
# Use recursive loading with known additional hooks
|
| 292 |
-
return load_typed_config(
|
| 293 |
-
DictConfig(cfg_copy),
|
| 294 |
-
target_type,
|
| 295 |
-
)
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
def make_target_hook_for_type(t: Type) -> Callable:
|
| 299 |
-
return lambda cfg: universal_target_hook(cfg, t)
|
| 300 |
-
|
| 301 |
-
|
| 302 |
def load_typed_root_config(cfg: DictConfig) -> RootCfg:
|
| 303 |
# scene_trainer/scene_optimizer=none loads a full dict from none.yaml;
|
| 304 |
# dacite can't match that dict to the None arm of SceneOptimizerCfg | None.
|
|
@@ -316,30 +302,17 @@ def load_typed_root_config(cfg: DictConfig) -> RootCfg:
|
|
| 316 |
)
|
| 317 |
|
| 318 |
|
| 319 |
-
def should_run(cfg_dict):
|
| 320 |
-
if cfg_dict.mode == "test":
|
| 321 |
-
if cfg_dict.meta_trainer.test.skip_if_outputs_exist:
|
| 322 |
-
output_dir = cfg_dict.output_dir
|
| 323 |
-
if not output_dir.exists():
|
| 324 |
-
return True
|
| 325 |
-
metrics_path_pattern = output_dir / "metrics" / "target_*_psnr.json"
|
| 326 |
-
metric_paths = list(metrics_path_pattern.parent.glob(metrics_path_pattern.name))
|
| 327 |
-
if len(metric_paths) > 0:
|
| 328 |
-
print(cyan(f"Test metrics already exist at {metric_paths}."))
|
| 329 |
-
return False
|
| 330 |
-
return True
|
| 331 |
-
|
| 332 |
-
|
| 333 |
def setup_cfg(cfg_dict):
|
| 334 |
# Get the original config from the output directory, when testing or resuming.
|
| 335 |
cfg_dict = merge_config_from_file(cfg_dict)
|
| 336 |
eval_cfg = get_eval_cfg(cfg_dict)
|
| 337 |
cfg = load_typed_root_config(cfg_dict)
|
| 338 |
-
# Set global cfg object.
|
| 339 |
-
set_cfg(cfg_dict)
|
| 340 |
# Set up the output directory.
|
| 341 |
setup_output_dir(cfg, cfg_dict)
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
| 343 |
|
| 344 |
|
| 345 |
def flatten_wandb(cfg):
|
|
@@ -375,6 +348,11 @@ def _apply_cli_overrides(merged_cfg: DictConfig, orig_cli_cfg: DictConfig, raw_o
|
|
| 375 |
print(cyan(f"Re-applying {len(raw_overrides)} CLI overrides onto merged config."))
|
| 376 |
OmegaConf.set_struct(merged_cfg, False)
|
| 377 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
# Architecture subtrees: CLI group default fills in *new* fields only;
|
| 379 |
# checkpoint values win for fields that already exist.
|
| 380 |
ARCH_KEYS = {"scene_optimizer", "scene_initializer"}
|
|
@@ -390,7 +368,7 @@ def _apply_cli_overrides(merged_cfg: DictConfig, orig_cli_cfg: DictConfig, raw_o
|
|
| 390 |
if cli_val is None:
|
| 391 |
# No direct config path — e.g. +experiment=re10k is a defaults-list override
|
| 392 |
# whose effect is already baked into orig_cli_cfg; nothing to apply.
|
| 393 |
-
|
| 394 |
continue
|
| 395 |
|
| 396 |
# For architecture group overrides: fill in missing fields from CLI defaults
|
|
@@ -401,7 +379,7 @@ def _apply_cli_overrides(merged_cfg: DictConfig, orig_cli_cfg: DictConfig, raw_o
|
|
| 401 |
dotkey_parts = set(dotkey.split("."))
|
| 402 |
if dotkey_parts & CLI_WINS_SUBKEYS:
|
| 403 |
OmegaConf.update(merged_cfg, dotkey, cli_val, merge=False)
|
| 404 |
-
|
| 405 |
continue
|
| 406 |
|
| 407 |
existing_val = OmegaConf.select(merged_cfg, dotkey, default=None)
|
|
@@ -414,15 +392,15 @@ def _apply_cli_overrides(merged_cfg: DictConfig, orig_cli_cfg: DictConfig, raw_o
|
|
| 414 |
if cli_subval is not None:
|
| 415 |
OmegaConf.set_struct(new_val, False)
|
| 416 |
OmegaConf.update(new_val, subkey, cli_subval, merge=False)
|
| 417 |
-
|
| 418 |
OmegaConf.update(merged_cfg, dotkey, new_val, merge=False)
|
| 419 |
-
|
| 420 |
continue
|
| 421 |
|
| 422 |
# Group overrides and complex values replace the whole subtree;
|
| 423 |
# scalars are merged so sibling keys are preserved.
|
| 424 |
replace = is_group_override
|
| 425 |
-
|
| 426 |
OmegaConf.update(merged_cfg, dotkey, cli_val, merge=not replace)
|
| 427 |
|
| 428 |
OmegaConf.set_struct(merged_cfg, True)
|
|
@@ -548,12 +526,16 @@ def _merge_test_mode(
|
|
| 548 |
) -> tuple[DictConfig, DictConfig]:
|
| 549 |
"""
|
| 550 |
Test mode: CLI config is the base for all settings (dataset, test flags, etc.).
|
| 551 |
-
|
|
|
|
|
|
|
| 552 |
|
| 553 |
Initializer source priority:
|
| 554 |
1. separate initializer_config_path (pretrained_initializer ckpt with a config file)
|
| 555 |
-
2.
|
| 556 |
-
|
|
|
|
|
|
|
| 557 |
|
| 558 |
Returns (merged_cfg, orig_cli_cfg); orig_cli_cfg is the snapshot taken before any
|
| 559 |
checkpoint patches so that _apply_cli_overrides can restore explicit CLI values.
|
|
@@ -572,17 +554,28 @@ def _merge_test_mode(
|
|
| 572 |
print(cyan("Test mode: patching scene_trainer.scene_optimizer from checkpoint config."))
|
| 573 |
OmegaConf.update(merged_cfg, "scene_trainer.scene_optimizer", optimizer_subcfg, merge=True)
|
| 574 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
# Patch initializer architecture (priority order above)
|
| 576 |
if initializer_config_path is not None and initializer_config_path.exists():
|
| 577 |
_patch_scene_initializer(merged_cfg, initializer_config_path, context="Test mode")
|
| 578 |
elif pretrained_initializer is None:
|
| 579 |
-
|
| 580 |
-
#
|
| 581 |
-
#
|
| 582 |
-
#
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
|
|
|
|
|
|
| 586 |
else:
|
| 587 |
print(cyan("pretrained_initializer set but has no config file; using CLI scene_initializer config."))
|
| 588 |
|
|
@@ -677,14 +670,8 @@ def setup_output_dir(cfg, cfg_dict):
|
|
| 677 |
output_dir = CustomPath(output_dir)
|
| 678 |
output_dir.mkdir(exist_ok=True, parents=True)
|
| 679 |
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
# TODO Naama, need to move to post_init of cfg
|
| 683 |
-
output_dir = CustomPath(hydra.core.hydra_config.HydraConfig.get()["run"]["dir"])
|
| 684 |
-
print(cyan(f"Multirun detected, setting output_dir to {CustomPath(output_dir):link}"))
|
| 685 |
-
# save checkoint path to a file for debugging
|
| 686 |
-
ckpt_path = cfg.checkpointing.pretrained_model or cfg.checkpointing.pretrained_optimizer
|
| 687 |
-
(output_dir / "ckpt_dir.txt").write_text(str(ckpt_path))
|
| 688 |
cfg_dict.output_dir = output_dir
|
| 689 |
cfg.output_dir = output_dir
|
| 690 |
output_dir.mkdir(exist_ok=True, parents=True)
|
|
|
|
| 1 |
+
"""Typed configuration schema and Hydra-to-dataclass loading.
|
| 2 |
+
|
| 3 |
+
`RootCfg` is the top-level config dataclass (dataset, scene_trainer, meta_trainer, loss, checkpointing,
|
| 4 |
+
...); `load_typed_root_config` converts the raw Hydra `DictConfig` into it via dacite, after running
|
| 5 |
+
`config_migrate` so older checkpoints' configs load. `__post_init__` also resolves the output directory
|
| 6 |
+
for `mode=test`. The per-component schemas live next to their components (e.g.
|
| 7 |
+
`scene_trainer_cfg.py`, `meta_trainer_cfg.py`).
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
from copy import deepcopy
|
| 11 |
from dataclasses import dataclass
|
| 12 |
from pathlib import Path
|
| 13 |
+
from typing import Literal, Optional, Type, TypeVar
|
| 14 |
|
|
|
|
| 15 |
import torch
|
| 16 |
from dacite import Config, from_dict, UnionMatchError
|
|
|
|
| 17 |
from hydra.core.hydra_config import HydraConfig
|
|
|
|
| 18 |
from omegaconf import DictConfig
|
| 19 |
from omegaconf import OmegaConf
|
| 20 |
from pytorch_lightning.strategies import DDPStrategy, FSDPStrategy
|
| 21 |
|
| 22 |
from .config_migrate import migrate, CURRENT_CFG_VERSION
|
| 23 |
from .dataset.data_module import DataLoaderCfg, DatasetCfg
|
|
|
|
| 24 |
from .loss import LossCfgWrapper
|
| 25 |
from .misc.io import CustomPath
|
| 26 |
from .misc.io import cyan, read_omega_cfg
|
| 27 |
from .misc.checkpointing import find_latest_ckpt
|
| 28 |
+
from .misc.hf_ckpt import hf_sibling_config, is_hf_ref, maybe_resolve_hf_ref
|
| 29 |
+
from .paths import CKPT_DIR, RESULTS_DIR, DEBUG
|
| 30 |
+
from .scene_trainer.scene_trainer_cfg import SceneTrainerCfg
|
| 31 |
+
from .meta_trainer.meta_trainer_cfg import MetaOptimizerCfg, TestCfg, TrainCfg
|
| 32 |
|
| 33 |
|
| 34 |
# In order to extract filename or dirname from a path in the config
|
|
|
|
| 63 |
resume_update_module: str | None
|
| 64 |
load_existing_cfg: bool
|
| 65 |
|
| 66 |
+
# Checkpoints whose sibling config.yaml carries the model architecture that
|
| 67 |
+
# mode=test rebuilds from (the optimizer/initializer the Gaussians were
|
| 68 |
+
# trained with).
|
| 69 |
+
_ARCH_CHECKPOINTS = ("pretrained_model", "pretrained_optimizer", "pretrained_initializer")
|
| 70 |
+
|
| 71 |
def __post_init__(self):
|
| 72 |
# Resolve any Hugging Face Hub references (hf://org/repo/file[@rev]) to
|
| 73 |
# local cached paths so all downstream torch.load calls work unchanged.
|
| 74 |
+
# Rebuilding the model architecture needs both the .ckpt and its sibling
|
| 75 |
+
# config.yaml, but hf_hub_download pulls only the .ckpt, so fetch the
|
| 76 |
+
# sibling config.yaml too. It lands in the same checkpoints/ layout as
|
| 77 |
+
# the resolved .ckpt, so _find_config_for_checkpoint later discovers it
|
| 78 |
+
# on the local path.
|
| 79 |
for attr in ("pretrained_model", "pretrained_optimizer", "pretrained_initializer",
|
| 80 |
"pretrained_monodepth", "pretrained_mvdepth", "pretrained_depth",
|
| 81 |
"pretrained_scale_predictor", "pretrained_depth_teacher",
|
| 82 |
"resume_update_module"):
|
| 83 |
+
ref = getattr(self, attr)
|
| 84 |
+
if attr in self._ARCH_CHECKPOINTS and is_hf_ref(ref):
|
| 85 |
+
hf_sibling_config(ref)
|
| 86 |
+
resolved = maybe_resolve_hf_ref(ref)
|
| 87 |
+
if resolved != ref:
|
| 88 |
setattr(self, attr, resolved)
|
| 89 |
|
| 90 |
for attr in ("pretrained_model", "pretrained_optimizer", "pretrained_initializer"):
|
|
|
|
| 108 |
eval_index: str | None
|
| 109 |
limit_test_batches: int | float
|
| 110 |
limit_train_batches: int | float
|
| 111 |
+
meta_optimizer: MetaOptimizerCfg
|
| 112 |
test: TestCfg
|
| 113 |
train: TrainCfg
|
| 114 |
|
|
|
|
| 128 |
return has_trainable
|
| 129 |
|
| 130 |
dist_strategy = FSDPStrategy(auto_wrap_policy=only_wrap_trainable)
|
| 131 |
+
if self.train.use_ckpt_buffer:
|
| 132 |
+
# When resampling from the ckpt buffer,
|
| 133 |
+
# we don't project the condition_features to state, so the state_proj is not used
|
| 134 |
dist_strategy = "ddp_find_unused_parameters_true"
|
| 135 |
return dist_strategy
|
| 136 |
|
|
|
|
| 142 |
dataset: DatasetCfg
|
| 143 |
data_loader: DataLoaderCfg
|
| 144 |
scene_trainer: SceneTrainerCfg
|
|
|
|
| 145 |
checkpointing: CheckpointingCfg
|
| 146 |
meta_trainer: MetaTrainerCfg
|
| 147 |
loss: list[LossCfgWrapper]
|
| 148 |
seed: int
|
| 149 |
use_plugins: bool
|
| 150 |
output_dir: str
|
| 151 |
+
version: int | float | None # point versions like 2.1 are floats
|
| 152 |
debug_cfg: bool
|
| 153 |
|
| 154 |
def __post_init__(self):
|
|
|
|
| 220 |
T = TypeVar("T")
|
| 221 |
|
| 222 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
def _diagnose_union_error(e: UnionMatchError, data: dict, dacite_config: Config) -> str:
|
| 224 |
"""Try each union member individually and report per-member errors."""
|
| 225 |
import dataclasses
|
|
|
|
| 285 |
]
|
| 286 |
|
| 287 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
def load_typed_root_config(cfg: DictConfig) -> RootCfg:
|
| 289 |
# scene_trainer/scene_optimizer=none loads a full dict from none.yaml;
|
| 290 |
# dacite can't match that dict to the None arm of SceneOptimizerCfg | None.
|
|
|
|
| 302 |
)
|
| 303 |
|
| 304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
def setup_cfg(cfg_dict):
|
| 306 |
# Get the original config from the output directory, when testing or resuming.
|
| 307 |
cfg_dict = merge_config_from_file(cfg_dict)
|
| 308 |
eval_cfg = get_eval_cfg(cfg_dict)
|
| 309 |
cfg = load_typed_root_config(cfg_dict)
|
|
|
|
|
|
|
| 310 |
# Set up the output directory.
|
| 311 |
setup_output_dir(cfg, cfg_dict)
|
| 312 |
+
# cfg is the typed config, used for attribute access throughout the code. cfg_dict is the
|
| 313 |
+
# raw OmegaConf tree: wandb logs it in full, the eval-config and migration helpers operate
|
| 314 |
+
# on it as nested dicts, and it holds fields outside the typed schema (e.g. profiling).
|
| 315 |
+
return cfg, cfg_dict, eval_cfg
|
| 316 |
|
| 317 |
|
| 318 |
def flatten_wandb(cfg):
|
|
|
|
| 348 |
print(cyan(f"Re-applying {len(raw_overrides)} CLI overrides onto merged config."))
|
| 349 |
OmegaConf.set_struct(merged_cfg, False)
|
| 350 |
|
| 351 |
+
# Per-key merge decisions are noisy on every run; show them only under a debugger.
|
| 352 |
+
def _trace(msg: str) -> None:
|
| 353 |
+
if DEBUG:
|
| 354 |
+
print(cyan(msg))
|
| 355 |
+
|
| 356 |
# Architecture subtrees: CLI group default fills in *new* fields only;
|
| 357 |
# checkpoint values win for fields that already exist.
|
| 358 |
ARCH_KEYS = {"scene_optimizer", "scene_initializer"}
|
|
|
|
| 368 |
if cli_val is None:
|
| 369 |
# No direct config path — e.g. +experiment=re10k is a defaults-list override
|
| 370 |
# whose effect is already baked into orig_cli_cfg; nothing to apply.
|
| 371 |
+
_trace(f" Skipping '{key}' (no direct config path in cli)")
|
| 372 |
continue
|
| 373 |
|
| 374 |
# For architecture group overrides: fill in missing fields from CLI defaults
|
|
|
|
| 379 |
dotkey_parts = set(dotkey.split("."))
|
| 380 |
if dotkey_parts & CLI_WINS_SUBKEYS:
|
| 381 |
OmegaConf.update(merged_cfg, dotkey, cli_val, merge=False)
|
| 382 |
+
_trace(f" '{dotkey}': replace from cli (CLI wins)")
|
| 383 |
continue
|
| 384 |
|
| 385 |
existing_val = OmegaConf.select(merged_cfg, dotkey, default=None)
|
|
|
|
| 392 |
if cli_subval is not None:
|
| 393 |
OmegaConf.set_struct(new_val, False)
|
| 394 |
OmegaConf.update(new_val, subkey, cli_subval, merge=False)
|
| 395 |
+
_trace(f" '{dotkey}.{subkey}': CLI override applied (CLI wins)")
|
| 396 |
OmegaConf.update(merged_cfg, dotkey, new_val, merge=False)
|
| 397 |
+
_trace(f" '{dotkey}': fill-missing from cli (checkpoint values preserved)")
|
| 398 |
continue
|
| 399 |
|
| 400 |
# Group overrides and complex values replace the whole subtree;
|
| 401 |
# scalars are merged so sibling keys are preserved.
|
| 402 |
replace = is_group_override
|
| 403 |
+
_trace(f" '{dotkey}': {'replace' if replace else 'update'} from cli")
|
| 404 |
OmegaConf.update(merged_cfg, dotkey, cli_val, merge=not replace)
|
| 405 |
|
| 406 |
OmegaConf.set_struct(merged_cfg, True)
|
|
|
|
| 526 |
) -> tuple[DictConfig, DictConfig]:
|
| 527 |
"""
|
| 528 |
Test mode: CLI config is the base for all settings (dataset, test flags, etc.).
|
| 529 |
+
The optimizer and initializer *architecture* and the decoder are patched in from the
|
| 530 |
+
checkpoint config, so the model is rebuilt and rendered the way it was trained. An explicit
|
| 531 |
+
CLI override still wins for any of these via _apply_cli_overrides.
|
| 532 |
|
| 533 |
Initializer source priority:
|
| 534 |
1. separate initializer_config_path (pretrained_initializer ckpt with a config file)
|
| 535 |
+
2. full pretrained_model checkpoint (it bundles the initializer weights, so its
|
| 536 |
+
architecture must match the checkpoint config)
|
| 537 |
+
3. CLI config as-is (pretrained_optimizer alone carries no initializer
|
| 538 |
+
weights; or pretrained_initializer has no config file)
|
| 539 |
|
| 540 |
Returns (merged_cfg, orig_cli_cfg); orig_cli_cfg is the snapshot taken before any
|
| 541 |
checkpoint patches so that _apply_cli_overrides can restore explicit CLI values.
|
|
|
|
| 554 |
print(cyan("Test mode: patching scene_trainer.scene_optimizer from checkpoint config."))
|
| 555 |
OmegaConf.update(merged_cfg, "scene_trainer.scene_optimizer", optimizer_subcfg, merge=True)
|
| 556 |
|
| 557 |
+
# Patch decoder from checkpoint so rendering uses the rasterizer the Gaussians were trained
|
| 558 |
+
# for. Merge over the CLI decoder (checkpoint values win) so any field the checkpoint lacks
|
| 559 |
+
# is filled from the CLI default -- old checkpoints predate some DecoderCfg fields and there
|
| 560 |
+
# is no decoder migration. An explicit CLI decoder override still wins via _apply_cli_overrides.
|
| 561 |
+
decoder_subcfg = OmegaConf.select(loaded_cfg, "scene_trainer.decoder", default=None)
|
| 562 |
+
if decoder_subcfg is not None:
|
| 563 |
+
print(cyan("Test mode: patching scene_trainer.decoder from checkpoint config (CLI fills missing fields)."))
|
| 564 |
+
OmegaConf.update(merged_cfg, "scene_trainer.decoder", decoder_subcfg, merge=True)
|
| 565 |
+
|
| 566 |
# Patch initializer architecture (priority order above)
|
| 567 |
if initializer_config_path is not None and initializer_config_path.exists():
|
| 568 |
_patch_scene_initializer(merged_cfg, initializer_config_path, context="Test mode")
|
| 569 |
elif pretrained_initializer is None:
|
| 570 |
+
# No separate initializer checkpoint. A full pretrained_model bundles the initializer
|
| 571 |
+
# weights (load_full_model), so its architecture must come from the checkpoint config.
|
| 572 |
+
# A pretrained_optimizer checkpoint carries no initializer weights, so the CLI
|
| 573 |
+
# scene_initializer config is kept as-is.
|
| 574 |
+
if cli_cfg.checkpointing.pretrained_model is not None:
|
| 575 |
+
initializer_subcfg = OmegaConf.select(loaded_cfg, "scene_trainer.scene_initializer", default=None)
|
| 576 |
+
if initializer_subcfg is not None:
|
| 577 |
+
print(cyan("Test mode: patching scene_trainer.scene_initializer from full-model checkpoint config."))
|
| 578 |
+
OmegaConf.update(merged_cfg, "scene_trainer.scene_initializer", initializer_subcfg, merge=True)
|
| 579 |
else:
|
| 580 |
print(cyan("pretrained_initializer set but has no config file; using CLI scene_initializer config."))
|
| 581 |
|
|
|
|
| 670 |
output_dir = CustomPath(output_dir)
|
| 671 |
output_dir.mkdir(exist_ok=True, parents=True)
|
| 672 |
|
| 673 |
+
# Single point where output_dir is finalized. Downstream code reads cfg.output_dir; the
|
| 674 |
+
# value is also written into cfg_dict so the saved config.yaml records it. Keep both in sync.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
cfg_dict.output_dir = output_dir
|
| 676 |
cfg.output_dir = output_dir
|
| 677 |
output_dir.mkdir(exist_ok=True, parents=True)
|
optgs/config/dataset/dl3dv.yaml
CHANGED
|
@@ -4,19 +4,16 @@ defaults:
|
|
| 4 |
|
| 5 |
name: dl3dv
|
| 6 |
roots: [datasets/dl3dv]
|
| 7 |
-
make_baseline_1: false
|
| 8 |
augment: true
|
| 9 |
|
| 10 |
|
| 11 |
image_shape: [270, 480]
|
| 12 |
|
| 13 |
-
baseline_epsilon: 1e-3
|
| 14 |
max_fov: 100.0
|
| 15 |
|
| 16 |
skip_bad_shape: true
|
| 17 |
near: -1.
|
| 18 |
far: -1.
|
| 19 |
-
baseline_scale_bounds: false
|
| 20 |
shuffle_val: true
|
| 21 |
test_len: -1
|
| 22 |
test_chunk_interval: 1
|
|
@@ -27,35 +24,11 @@ train_times_per_scene: 1
|
|
| 27 |
test_times_per_scene: 1
|
| 28 |
ori_image_shape: [270, 480]
|
| 29 |
overfit_max_views: 148
|
| 30 |
-
use_index_to_load_chunk: false
|
| 31 |
|
| 32 |
-
mix_tartanair: false
|
| 33 |
no_mix_test_set: true
|
| 34 |
-
load_depth: false
|
| 35 |
-
center_pose: false
|
| 36 |
-
|
| 37 |
-
pose_align_first_view: false
|
| 38 |
-
|
| 39 |
-
scale_extrinsics: 1.
|
| 40 |
-
metric_scale_align_dl3dv: false
|
| 41 |
|
| 42 |
# view filtering
|
| 43 |
min_views: 0
|
| 44 |
max_views: 0
|
| 45 |
-
highres: false
|
| 46 |
-
|
| 47 |
-
# mix re10k & dl3dv
|
| 48 |
-
mix_re10k: false
|
| 49 |
-
re10k_min_view_dist: 40
|
| 50 |
-
re10k_max_view_dist: 300
|
| 51 |
-
|
| 52 |
-
# load remaining context views
|
| 53 |
-
load_remain_context: false
|
| 54 |
-
num_remain_context: 8
|
| 55 |
-
|
| 56 |
-
# random crop in training
|
| 57 |
-
random_crop: false
|
| 58 |
-
min_size: null
|
| 59 |
-
max_size: null
|
| 60 |
|
| 61 |
-
index_name: index.json
|
|
|
|
| 4 |
|
| 5 |
name: dl3dv
|
| 6 |
roots: [datasets/dl3dv]
|
|
|
|
| 7 |
augment: true
|
| 8 |
|
| 9 |
|
| 10 |
image_shape: [270, 480]
|
| 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
|
|
|
|
| 24 |
test_times_per_scene: 1
|
| 25 |
ori_image_shape: [270, 480]
|
| 26 |
overfit_max_views: 148
|
|
|
|
| 27 |
|
|
|
|
| 28 |
no_mix_test_set: true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
# view filtering
|
| 31 |
min_views: 0
|
| 32 |
max_views: 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
index_name: index.json
|
optgs/config/dataset/re10k.yaml
CHANGED
|
@@ -4,24 +4,16 @@ defaults:
|
|
| 4 |
|
| 5 |
name: re10k
|
| 6 |
roots: [datasets/re10k]
|
| 7 |
-
make_baseline_1: false
|
| 8 |
augment: true
|
| 9 |
|
| 10 |
image_shape: [180, 320]
|
| 11 |
highres: false
|
| 12 |
|
| 13 |
-
baseline_epsilon: 1e-3
|
| 14 |
max_fov: 100.0
|
| 15 |
|
| 16 |
skip_bad_shape: true
|
| 17 |
near: -1.
|
| 18 |
far: -1.
|
| 19 |
-
baseline_scale_bounds: true
|
| 20 |
shuffle_val: true
|
| 21 |
test_len: -1
|
| 22 |
-
test_chunk_interval: 1
|
| 23 |
-
|
| 24 |
-
use_index_to_load_chunk: false
|
| 25 |
-
|
| 26 |
-
average_pose: false
|
| 27 |
-
center_pose: false
|
|
|
|
| 4 |
|
| 5 |
name: re10k
|
| 6 |
roots: [datasets/re10k]
|
|
|
|
| 7 |
augment: true
|
| 8 |
|
| 9 |
image_shape: [180, 320]
|
| 10 |
highres: 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/config/experiment/re10k_unified.yaml
CHANGED
|
@@ -63,16 +63,11 @@ dataset:
|
|
| 63 |
roots: [datasets/re10k]
|
| 64 |
near: 0.01
|
| 65 |
far: 100.
|
| 66 |
-
baseline_scale_bounds: false
|
| 67 |
-
make_baseline_1: false
|
| 68 |
train_times_per_scene: 1
|
| 69 |
highres: false
|
| 70 |
scannet: false
|
| 71 |
tartanair: false
|
| 72 |
load_depth: false
|
| 73 |
-
pose_align_first_view: false
|
| 74 |
-
scale_extrinsics: 1.
|
| 75 |
-
load_remain_context: false
|
| 76 |
pose_align_middle_view: false
|
| 77 |
overfit_to_scene: null
|
| 78 |
opencv_pose_format: false
|
|
|
|
| 63 |
roots: [datasets/re10k]
|
| 64 |
near: 0.01
|
| 65 |
far: 100.
|
|
|
|
|
|
|
| 66 |
train_times_per_scene: 1
|
| 67 |
highres: false
|
| 68 |
scannet: false
|
| 69 |
tartanair: false
|
| 70 |
load_depth: false
|
|
|
|
|
|
|
|
|
|
| 71 |
pose_align_middle_view: false
|
| 72 |
overfit_to_scene: null
|
| 73 |
opencv_pose_format: false
|
optgs/config/experiment/train_dl3dv.yaml
CHANGED
|
@@ -21,7 +21,6 @@ meta_trainer:
|
|
| 21 |
max_steps: 50_000
|
| 22 |
val_check_interval: 0.25
|
| 23 |
train:
|
| 24 |
-
l1_loss: true
|
| 25 |
depth_smooth_loss_weight: 0.0
|
| 26 |
test:
|
| 27 |
eval_time_skip_steps: 0
|
|
@@ -31,6 +30,8 @@ meta_trainer:
|
|
| 31 |
|
| 32 |
# lpips loss
|
| 33 |
loss:
|
|
|
|
|
|
|
| 34 |
lpips:
|
| 35 |
apply_after_step: 0
|
| 36 |
weight: 0.5
|
|
@@ -40,8 +41,6 @@ dataset:
|
|
| 40 |
roots: [ datasets/dl3dv-480p-chunks ]
|
| 41 |
near: 0.01
|
| 42 |
far: 200.
|
| 43 |
-
min_size: [ 384,512 ]
|
| 44 |
-
max_size: [ 512,960 ]
|
| 45 |
image_shape: [ 256, 448 ]
|
| 46 |
view_sampler:
|
| 47 |
num_context_views: 8
|
|
|
|
| 21 |
max_steps: 50_000
|
| 22 |
val_check_interval: 0.25
|
| 23 |
train:
|
|
|
|
| 24 |
depth_smooth_loss_weight: 0.0
|
| 25 |
test:
|
| 26 |
eval_time_skip_steps: 0
|
|
|
|
| 30 |
|
| 31 |
# lpips loss
|
| 32 |
loss:
|
| 33 |
+
mse:
|
| 34 |
+
l1_loss: true
|
| 35 |
lpips:
|
| 36 |
apply_after_step: 0
|
| 37 |
weight: 0.5
|
|
|
|
| 41 |
roots: [ datasets/dl3dv-480p-chunks ]
|
| 42 |
near: 0.01
|
| 43 |
far: 200.
|
|
|
|
|
|
|
| 44 |
image_shape: [ 256, 448 ]
|
| 45 |
view_sampler:
|
| 46 |
num_context_views: 8
|
optgs/config/experiment/train_l2s_sparse_dl3dv.yaml
CHANGED
|
@@ -2,8 +2,8 @@
|
|
| 2 |
|
| 3 |
defaults:
|
| 4 |
- train_dl3dv
|
| 5 |
-
- override /meta_trainer/train/
|
| 6 |
-
- override /loss: [ mse, lpips, deltas ]
|
| 7 |
|
| 8 |
loss:
|
| 9 |
mse:
|
|
@@ -23,19 +23,24 @@ meta_trainer:
|
|
| 23 |
train:
|
| 24 |
loss_on_input_views: true
|
| 25 |
loss_on_input_views_num: 4
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
scene_trainer:
|
| 29 |
train_scene_opt: true
|
| 30 |
-
num_update_steps:
|
|
|
|
| 31 |
train_max_refine: 6
|
| 32 |
train_min_refine: 1
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
|
| 39 |
checkpointing:
|
| 40 |
-
|
|
|
|
| 41 |
no_strict_load: false
|
|
|
|
| 2 |
|
| 3 |
defaults:
|
| 4 |
- train_dl3dv
|
| 5 |
+
- override /meta_trainer/train/ckpt_buffer_cfg: default
|
| 6 |
+
- override /loss: [ mse, lpips, deltas, stability ]
|
| 7 |
|
| 8 |
loss:
|
| 9 |
mse:
|
|
|
|
| 23 |
train:
|
| 24 |
loss_on_input_views: true
|
| 25 |
loss_on_input_views_num: 4
|
| 26 |
+
use_ckpt_buffer: true
|
| 27 |
+
meta_optimizer:
|
| 28 |
+
lr: 1e-4
|
| 29 |
+
lr_monodepth: 0.0
|
| 30 |
|
| 31 |
scene_trainer:
|
| 32 |
train_scene_opt: true
|
| 33 |
+
num_update_steps: 2000
|
| 34 |
+
opt_batch_size: 8
|
| 35 |
train_max_refine: 6
|
| 36 |
train_min_refine: 1
|
| 37 |
+
# Decoder settings for the learn2splat paper weights (the generic gsplat default is
|
| 38 |
+
# antialiased / eps2d=0.3; these weights are trained with classic rasterization).
|
| 39 |
+
decoder:
|
| 40 |
+
rasterize_mode: classic
|
| 41 |
+
eps2d: 1e-4
|
| 42 |
|
| 43 |
checkpointing:
|
| 44 |
+
# ReSplat initializer
|
| 45 |
+
pretrained_initializer: hf://autonomousvision/learn2splat/resplat_init/checkpoints/epoch_20-step_100000.ckpt
|
| 46 |
no_strict_load: false
|
optgs/config/experiment/train_l2s_sparse_dl3dv_no_delta.yaml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
defaults:
|
| 4 |
- train_dl3dv
|
| 5 |
-
- override /meta_trainer/train/
|
| 6 |
- override /loss: [ mse, lpips ]
|
| 7 |
|
| 8 |
loss:
|
|
@@ -17,7 +17,10 @@ meta_trainer:
|
|
| 17 |
train:
|
| 18 |
loss_on_input_views: true
|
| 19 |
loss_on_input_views_num: 4
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
scene_trainer:
|
| 23 |
train_scene_opt: true
|
|
@@ -25,11 +28,6 @@ scene_trainer:
|
|
| 25 |
train_max_refine: 6
|
| 26 |
train_min_refine: 1
|
| 27 |
|
| 28 |
-
meta_optimizer:
|
| 29 |
-
lr: 1e-4
|
| 30 |
-
lr_monodepth: 0.0
|
| 31 |
-
|
| 32 |
-
|
| 33 |
checkpointing:
|
| 34 |
pretrained_initializer: checkpoints/optgs/unified-dl3dv-8views/init/checkpoints/epoch_20-step_100000.ckpt # resplat inititalizer
|
| 35 |
no_strict_load: false
|
|
|
|
| 2 |
|
| 3 |
defaults:
|
| 4 |
- train_dl3dv
|
| 5 |
+
- override /meta_trainer/train/ckpt_buffer_cfg: default
|
| 6 |
- override /loss: [ mse, lpips ]
|
| 7 |
|
| 8 |
loss:
|
|
|
|
| 17 |
train:
|
| 18 |
loss_on_input_views: true
|
| 19 |
loss_on_input_views_num: 4
|
| 20 |
+
use_ckpt_buffer: true
|
| 21 |
+
meta_optimizer:
|
| 22 |
+
lr: 1e-4
|
| 23 |
+
lr_monodepth: 0.0
|
| 24 |
|
| 25 |
scene_trainer:
|
| 26 |
train_scene_opt: true
|
|
|
|
| 28 |
train_max_refine: 6
|
| 29 |
train_min_refine: 1
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
checkpointing:
|
| 32 |
pretrained_initializer: checkpoints/optgs/unified-dl3dv-8views/init/checkpoints/epoch_20-step_100000.ckpt # resplat inititalizer
|
| 33 |
no_strict_load: false
|
optgs/config/experiment/train_l2s_sparse_dl3dv_no_loss.yaml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
defaults:
|
| 4 |
- train_dl3dv
|
| 5 |
-
- override /meta_trainer/train/
|
| 6 |
- override /loss: [ mse, lpips ]
|
| 7 |
|
| 8 |
loss:
|
|
@@ -17,7 +17,10 @@ meta_trainer:
|
|
| 17 |
train:
|
| 18 |
loss_on_input_views: true
|
| 19 |
loss_on_input_views_num: 4
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
scene_trainer:
|
| 23 |
train_scene_opt: true
|
|
@@ -25,11 +28,6 @@ scene_trainer:
|
|
| 25 |
train_max_refine: 6
|
| 26 |
train_min_refine: 1
|
| 27 |
|
| 28 |
-
meta_optimizer:
|
| 29 |
-
lr: 1e-4
|
| 30 |
-
lr_monodepth: 0.0
|
| 31 |
-
|
| 32 |
-
|
| 33 |
checkpointing:
|
| 34 |
pretrained_initializer: checkpoints/optgs/unified-dl3dv-8views/init/checkpoints/epoch_20-step_100000.ckpt # resplat inititalizer
|
| 35 |
no_strict_load: false
|
|
|
|
| 2 |
|
| 3 |
defaults:
|
| 4 |
- train_dl3dv
|
| 5 |
+
- override /meta_trainer/train/ckpt_buffer_cfg: default
|
| 6 |
- override /loss: [ mse, lpips ]
|
| 7 |
|
| 8 |
loss:
|
|
|
|
| 17 |
train:
|
| 18 |
loss_on_input_views: true
|
| 19 |
loss_on_input_views_num: 4
|
| 20 |
+
use_ckpt_buffer: true
|
| 21 |
+
meta_optimizer:
|
| 22 |
+
lr: 1e-4
|
| 23 |
+
lr_monodepth: 0.0
|
| 24 |
|
| 25 |
scene_trainer:
|
| 26 |
train_scene_opt: true
|
|
|
|
| 28 |
train_max_refine: 6
|
| 29 |
train_min_refine: 1
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
checkpointing:
|
| 32 |
pretrained_initializer: checkpoints/optgs/unified-dl3dv-8views/init/checkpoints/epoch_20-step_100000.ckpt # resplat inititalizer
|
| 33 |
no_strict_load: false
|
optgs/config/loss/lpips.yaml
CHANGED
|
@@ -2,3 +2,4 @@ lpips:
|
|
| 2 |
weight: 0.05
|
| 3 |
apply_after_step: 150_000
|
| 4 |
perceptual_loss: false
|
|
|
|
|
|
| 2 |
weight: 0.05
|
| 3 |
apply_after_step: 150_000
|
| 4 |
perceptual_loss: false
|
| 5 |
+
half_res: false
|
optgs/config/loss/mse.yaml
CHANGED
|
@@ -1,2 +1,4 @@
|
|
| 1 |
mse:
|
| 2 |
weight: 1.0
|
|
|
|
|
|
|
|
|
| 1 |
mse:
|
| 2 |
weight: 1.0
|
| 3 |
+
l1_loss: false
|
| 4 |
+
clamp_large_error: 0.0
|
optgs/config/loss/sgd.yaml
CHANGED
|
@@ -1,2 +1,4 @@
|
|
| 1 |
sgd:
|
| 2 |
-
weight: 1.0
|
|
|
|
|
|
|
|
|
| 1 |
sgd:
|
| 2 |
+
weight: 1.0
|
| 3 |
+
l1_loss: false
|
| 4 |
+
clamp_large_error: 0.0
|
optgs/config/loss/stability.yaml
CHANGED
|
@@ -1,2 +1,4 @@
|
|
| 1 |
stability:
|
| 2 |
weight: 1.0
|
|
|
|
|
|
|
|
|
| 1 |
stability:
|
| 2 |
weight: 1.0
|
| 3 |
+
# Also penalize inputs optimized on per-step view subsets (not fully tested in training).
|
| 4 |
+
subset_aware: false
|
optgs/config/main.yaml
CHANGED
|
@@ -5,7 +5,7 @@ defaults:
|
|
| 5 |
- scene_trainer/scene_optimizer: null
|
| 6 |
- scene_trainer/decoder: gsplat
|
| 7 |
- meta_trainer/test/postprocessing: none
|
| 8 |
-
- meta_trainer/train/
|
| 9 |
|
| 10 |
wandb:
|
| 11 |
project: placeholder
|
|
@@ -34,15 +34,6 @@ data_loader:
|
|
| 34 |
batch_size: 1
|
| 35 |
seed: 3456
|
| 36 |
|
| 37 |
-
meta_optimizer:
|
| 38 |
-
lr: 2.e-4
|
| 39 |
-
lr_monodepth: 2.e-6
|
| 40 |
-
lr_depth: 0.
|
| 41 |
-
warm_up_steps: 2000
|
| 42 |
-
weight_decay: 0.01
|
| 43 |
-
warm_up_ratio: 0.01
|
| 44 |
-
adamw_8bit: false
|
| 45 |
-
|
| 46 |
checkpointing:
|
| 47 |
load: null
|
| 48 |
every_n_train_steps: 1000
|
|
@@ -75,25 +66,30 @@ meta_trainer:
|
|
| 75 |
limit_test_batches: 1.0
|
| 76 |
limit_train_batches: 1.0
|
| 77 |
num_nodes: 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
train:
|
| 79 |
depth_mode: null
|
| 80 |
extended_visualization: false
|
| 81 |
print_log_every_n_steps: 100
|
| 82 |
eval_model_every_n_val: 2 # quantitative evaluation every n val
|
| 83 |
eval_data_length: 999999
|
| 84 |
-
eval_deterministic: false
|
| 85 |
eval_time_skip_steps: 3
|
| 86 |
eval_save_model: true
|
| 87 |
-
l1_loss: false
|
| 88 |
intermediate_loss_weight: 0.9
|
| 89 |
no_viz_video: false
|
| 90 |
eval_depth: false
|
| 91 |
-
train_ignore_large_loss: 0.
|
| 92 |
no_log_projections: true
|
| 93 |
no_log_video: true
|
| 94 |
depth_loss_weight: 0.
|
| 95 |
log_depth_loss: true
|
| 96 |
-
depth_smooth_loss_weight: 0.
|
| 97 |
depth_smooth_loss_nonorm: false
|
| 98 |
depth_smooth_loss_weight_nvs: 0. # for novel views
|
| 99 |
monodepth_loss_weight: 0. # for monocular depth loss
|
|
@@ -103,21 +99,17 @@ meta_trainer:
|
|
| 103 |
render_depth_loss_weight: 0.
|
| 104 |
viz_render_depth: false
|
| 105 |
use_gt_depth_range: false
|
| 106 |
-
depth_range_from_disparity: false
|
| 107 |
-
max_disparity: 128.
|
| 108 |
-
min_disparity: 4.
|
| 109 |
loss_on_input_views: false
|
| 110 |
loss_on_target_views: true
|
| 111 |
loss_on_input_views_num: 1
|
| 112 |
loss_on_target_views_num: -1
|
| 113 |
train_window_size: null
|
| 114 |
-
half_res_lpips_loss: false
|
| 115 |
viz_depth_separate: false
|
| 116 |
# L2 weight decay on Gaussian properties (meta-loss)
|
| 117 |
scale_l2_loss_weight: 0.
|
| 118 |
sh_l2_loss_weight: 0.
|
| 119 |
opacity_l2_loss_weight: 0.
|
| 120 |
-
|
| 121 |
test:
|
| 122 |
output_path: null
|
| 123 |
compute_scores: true
|
|
@@ -130,22 +122,22 @@ meta_trainer:
|
|
| 130 |
save_gt_image: false
|
| 131 |
save_render_depth: false
|
| 132 |
save_gt_depth: false
|
|
|
|
| 133 |
save_error_image: false
|
| 134 |
save_video: false
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
save_gaussian: false
|
| 145 |
save_poses: false
|
| 146 |
save_cameras_json: true
|
| 147 |
save_cameras_npz: true
|
| 148 |
-
save_point_cloud: false
|
| 149 |
render_chunk_size: null
|
| 150 |
dec_chunk_size: null
|
| 151 |
stablize_camera: false
|
|
|
|
| 5 |
- scene_trainer/scene_optimizer: null
|
| 6 |
- scene_trainer/decoder: gsplat
|
| 7 |
- meta_trainer/test/postprocessing: none
|
| 8 |
+
- meta_trainer/train/ckpt_buffer_cfg: none
|
| 9 |
|
| 10 |
wandb:
|
| 11 |
project: placeholder
|
|
|
|
| 34 |
batch_size: 1
|
| 35 |
seed: 3456
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
checkpointing:
|
| 38 |
load: null
|
| 39 |
every_n_train_steps: 1000
|
|
|
|
| 66 |
limit_test_batches: 1.0
|
| 67 |
limit_train_batches: 1.0
|
| 68 |
num_nodes: 1
|
| 69 |
+
meta_optimizer:
|
| 70 |
+
lr: 2.e-4
|
| 71 |
+
lr_monodepth: 2.e-6
|
| 72 |
+
lr_depth: 0.
|
| 73 |
+
warm_up_steps: 2000
|
| 74 |
+
weight_decay: 0.01
|
| 75 |
+
warm_up_ratio: 0.01
|
| 76 |
+
adamw_8bit: false
|
| 77 |
train:
|
| 78 |
depth_mode: null
|
| 79 |
extended_visualization: false
|
| 80 |
print_log_every_n_steps: 100
|
| 81 |
eval_model_every_n_val: 2 # quantitative evaluation every n val
|
| 82 |
eval_data_length: 999999
|
|
|
|
| 83 |
eval_time_skip_steps: 3
|
| 84 |
eval_save_model: true
|
|
|
|
| 85 |
intermediate_loss_weight: 0.9
|
| 86 |
no_viz_video: false
|
| 87 |
eval_depth: false
|
|
|
|
| 88 |
no_log_projections: true
|
| 89 |
no_log_video: true
|
| 90 |
depth_loss_weight: 0.
|
| 91 |
log_depth_loss: true
|
| 92 |
+
depth_smooth_loss_weight: 0. # only meaningful when train_scene_init=True
|
| 93 |
depth_smooth_loss_nonorm: false
|
| 94 |
depth_smooth_loss_weight_nvs: 0. # for novel views
|
| 95 |
monodepth_loss_weight: 0. # for monocular depth loss
|
|
|
|
| 99 |
render_depth_loss_weight: 0.
|
| 100 |
viz_render_depth: false
|
| 101 |
use_gt_depth_range: false
|
|
|
|
|
|
|
|
|
|
| 102 |
loss_on_input_views: false
|
| 103 |
loss_on_target_views: true
|
| 104 |
loss_on_input_views_num: 1
|
| 105 |
loss_on_target_views_num: -1
|
| 106 |
train_window_size: null
|
|
|
|
| 107 |
viz_depth_separate: false
|
| 108 |
# L2 weight decay on Gaussian properties (meta-loss)
|
| 109 |
scale_l2_loss_weight: 0.
|
| 110 |
sh_l2_loss_weight: 0.
|
| 111 |
opacity_l2_loss_weight: 0.
|
| 112 |
+
use_ckpt_buffer: false
|
| 113 |
test:
|
| 114 |
output_path: null
|
| 115 |
compute_scores: true
|
|
|
|
| 122 |
save_gt_image: false
|
| 123 |
save_render_depth: false
|
| 124 |
save_gt_depth: false
|
| 125 |
+
save_init_pred_depth: false
|
| 126 |
save_error_image: false
|
| 127 |
save_video: false
|
| 128 |
+
save_video_optim: false
|
| 129 |
+
save_video_view_index: 0
|
| 130 |
+
save_video_frame_repeat: 1
|
| 131 |
+
save_video_orbit: false
|
| 132 |
+
save_video_orbit_steps: null
|
| 133 |
+
save_video_orbit_with_optim: false
|
| 134 |
+
save_video_optim_orbit: false
|
| 135 |
+
save_video_optim_orbit_steps: null
|
| 136 |
+
save_video_orbit_span: 50
|
| 137 |
save_gaussian: false
|
| 138 |
save_poses: false
|
| 139 |
save_cameras_json: true
|
| 140 |
save_cameras_npz: true
|
|
|
|
| 141 |
render_chunk_size: null
|
| 142 |
dec_chunk_size: null
|
| 143 |
stablize_camera: false
|
optgs/config/meta_trainer/train/{replay_buffer_cfg → ckpt_buffer_cfg}/default.yaml
RENAMED
|
@@ -3,10 +3,10 @@ sample_batch_size: 1
|
|
| 3 |
sample_prob: 0.7
|
| 4 |
insert_prob: 0.7
|
| 5 |
return_prob: 0.99
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
max_t: null
|
| 11 |
push_only_if_not_full: false
|
| 12 |
remove_strategy_when_full: oldest
|
|
|
|
| 3 |
sample_prob: 0.7
|
| 4 |
insert_prob: 0.7
|
| 5 |
return_prob: 0.99
|
| 6 |
+
rollout: true
|
| 7 |
+
rollout_min_steps: 1
|
| 8 |
+
rollout_max_steps: 50
|
| 9 |
+
rollout_grow: 10000
|
| 10 |
max_t: null
|
| 11 |
push_only_if_not_full: false
|
| 12 |
remove_strategy_when_full: oldest
|
optgs/config/meta_trainer/train/{replay_buffer_cfg → ckpt_buffer_cfg}/none.yaml
RENAMED
|
@@ -3,10 +3,10 @@ sample_batch_size: 1
|
|
| 3 |
sample_prob: 0.0
|
| 4 |
insert_prob: 0.0
|
| 5 |
return_prob: 0.0
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
max_t: null
|
| 11 |
push_only_if_not_full: false
|
| 12 |
remove_strategy_when_full: oldest
|
|
|
|
| 3 |
sample_prob: 0.0
|
| 4 |
insert_prob: 0.0
|
| 5 |
return_prob: 0.0
|
| 6 |
+
rollout: false
|
| 7 |
+
rollout_min_steps: 0
|
| 8 |
+
rollout_max_steps: 0
|
| 9 |
+
rollout_grow: 0
|
| 10 |
max_t: null
|
| 11 |
push_only_if_not_full: false
|
| 12 |
remove_strategy_when_full: oldest
|
optgs/config/scene_trainer/scene_initializer/edgs.yaml
CHANGED
|
@@ -7,4 +7,5 @@ sh_degree: 3
|
|
| 7 |
init_opacity: 0.5
|
| 8 |
scaling_factor: 0.5
|
| 9 |
roma_model_type: outdoors
|
| 10 |
-
sample_init_gaussians: -1
|
|
|
|
|
|
| 7 |
init_opacity: 0.5
|
| 8 |
scaling_factor: 0.5
|
| 9 |
roma_model_type: outdoors
|
| 10 |
+
sample_init_gaussians: -1
|
| 11 |
+
cache_dir: cache/edgs
|
optgs/config/scene_trainer/scene_initializer/resplat_v1.yaml
CHANGED
|
@@ -71,19 +71,13 @@ init_pt_with_mv_attn_lowres: false
|
|
| 71 |
pt_head_channels: null
|
| 72 |
pt_head_concat_img: false
|
| 73 |
pt_head_conv: false
|
| 74 |
-
multi_scale_pt: false
|
| 75 |
attn_proj_channels: 64
|
| 76 |
-
fps_num_samples: null
|
| 77 |
knn_samples: 16
|
| 78 |
post_norm: false
|
| 79 |
no_rpe: true
|
| 80 |
no_knn_attn: false
|
| 81 |
num_blocks: 4
|
| 82 |
-
pt_downsample: 0
|
| 83 |
-
fps_agg_func: attn
|
| 84 |
-
subsample_method: fps
|
| 85 |
add_pt_residual: true
|
| 86 |
-
pt_pred_residual_position: false
|
| 87 |
|
| 88 |
# freeze depth
|
| 89 |
freeze_depth: false
|
|
@@ -99,10 +93,6 @@ bilinear_upsample_depth: false
|
|
| 99 |
no_upsample_depth: false
|
| 100 |
return_lowres_depth: false
|
| 101 |
|
| 102 |
-
# lvsm gaussian regressor
|
| 103 |
-
lvsm_gaussian_regressor: false
|
| 104 |
-
lvsm_layers: 6
|
| 105 |
-
|
| 106 |
# latent gaussian instead of pixel aligned gaussians
|
| 107 |
latent_gs: true
|
| 108 |
latent_downsample: 4
|
|
|
|
| 71 |
pt_head_channels: null
|
| 72 |
pt_head_concat_img: false
|
| 73 |
pt_head_conv: false
|
|
|
|
| 74 |
attn_proj_channels: 64
|
|
|
|
| 75 |
knn_samples: 16
|
| 76 |
post_norm: false
|
| 77 |
no_rpe: true
|
| 78 |
no_knn_attn: false
|
| 79 |
num_blocks: 4
|
|
|
|
|
|
|
|
|
|
| 80 |
add_pt_residual: true
|
|
|
|
| 81 |
|
| 82 |
# freeze depth
|
| 83 |
freeze_depth: false
|
|
|
|
| 93 |
no_upsample_depth: false
|
| 94 |
return_lowres_depth: false
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
# latent gaussian instead of pixel aligned gaussians
|
| 97 |
latent_gs: true
|
| 98 |
latent_downsample: 4
|
optgs/config/scene_trainer/scene_initializer/resplat_v2.yaml
CHANGED
|
@@ -77,19 +77,13 @@ init_pt_with_mv_attn_lowres: true
|
|
| 77 |
pt_head_channels: null
|
| 78 |
pt_head_concat_img: false
|
| 79 |
pt_head_conv: false
|
| 80 |
-
multi_scale_pt: false
|
| 81 |
attn_proj_channels: 64
|
| 82 |
-
fps_num_samples: null
|
| 83 |
knn_samples: 16
|
| 84 |
post_norm: false
|
| 85 |
no_rpe: true
|
| 86 |
no_knn_attn: false
|
| 87 |
num_blocks: 6
|
| 88 |
-
pt_downsample: 0
|
| 89 |
-
fps_agg_func: attn
|
| 90 |
-
subsample_method: fps
|
| 91 |
add_pt_residual: true
|
| 92 |
-
pt_pred_residual_position: false
|
| 93 |
|
| 94 |
# freeze depth
|
| 95 |
freeze_depth: false
|
|
@@ -105,10 +99,6 @@ bilinear_upsample_depth: false
|
|
| 105 |
no_upsample_depth: false
|
| 106 |
return_lowres_depth: false
|
| 107 |
|
| 108 |
-
# lvsm gaussian regressor
|
| 109 |
-
lvsm_gaussian_regressor: false
|
| 110 |
-
lvsm_layers: 6
|
| 111 |
-
|
| 112 |
# latent gaussian instead of pixel aligned gaussians
|
| 113 |
latent_gs: true
|
| 114 |
latent_downsample: 4
|
|
|
|
| 77 |
pt_head_channels: null
|
| 78 |
pt_head_concat_img: false
|
| 79 |
pt_head_conv: false
|
|
|
|
| 80 |
attn_proj_channels: 64
|
|
|
|
| 81 |
knn_samples: 16
|
| 82 |
post_norm: false
|
| 83 |
no_rpe: true
|
| 84 |
no_knn_attn: false
|
| 85 |
num_blocks: 6
|
|
|
|
|
|
|
|
|
|
| 86 |
add_pt_residual: true
|
|
|
|
| 87 |
|
| 88 |
# freeze depth
|
| 89 |
freeze_depth: false
|
|
|
|
| 99 |
no_upsample_depth: false
|
| 100 |
return_lowres_depth: false
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
# latent gaussian instead of pixel aligned gaussians
|
| 103 |
latent_gs: true
|
| 104 |
latent_downsample: 4
|
optgs/config/scene_trainer/scene_optimizer/3dgs.yaml
CHANGED
|
@@ -1,22 +1,11 @@
|
|
| 1 |
defaults:
|
| 2 |
- base
|
| 3 |
- override refiner: default
|
|
|
|
| 4 |
|
| 5 |
name: adam
|
| 6 |
|
| 7 |
# Adam optimizer
|
| 8 |
betas: [0.9, 0.999]
|
| 9 |
eps: 1e-15
|
| 10 |
-
weight_decay: 0.0
|
| 11 |
-
|
| 12 |
-
# learning rates (gsplat)
|
| 13 |
-
base_lr: 1
|
| 14 |
-
means_lr_init: 1.6e-4
|
| 15 |
-
means_lr_final: 1.6e-6
|
| 16 |
-
means_lr_delay_mult: 1.0
|
| 17 |
-
means_lr_max_steps: 30000 # should be equal to total optimization steps
|
| 18 |
-
scales_lr: 5e-3
|
| 19 |
-
rotations_lr: 1e-3
|
| 20 |
-
opacities_lr: 5e-2
|
| 21 |
-
sh0s_lr: 2.5e-3
|
| 22 |
-
shNs_lr: 1.25e-4
|
|
|
|
| 1 |
defaults:
|
| 2 |
- base
|
| 3 |
- override refiner: default
|
| 4 |
+
- override lr_scheduler: expon
|
| 5 |
|
| 6 |
name: adam
|
| 7 |
|
| 8 |
# Adam optimizer
|
| 9 |
betas: [0.9, 0.999]
|
| 10 |
eps: 1e-15
|
| 11 |
+
weight_decay: 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/config/scene_trainer/scene_optimizer/3dgs_star.yaml
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
defaults:
|
| 2 |
- base
|
| 3 |
- override refiner: default
|
|
|
|
| 4 |
|
| 5 |
name: adam
|
| 6 |
|
|
@@ -9,14 +10,8 @@ betas: [0.99, 0.999]
|
|
| 9 |
eps: 1e-15
|
| 10 |
weight_decay: 0.0
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
means_lr_max_steps: 30000 # should be equal to total optimization steps
|
| 18 |
-
scales_lr: 5e-3
|
| 19 |
-
rotations_lr: 1e-3
|
| 20 |
-
opacities_lr: 5e-2
|
| 21 |
-
sh0s_lr: 2.5e-3
|
| 22 |
-
shNs_lr: 1.25e-4
|
|
|
|
| 1 |
defaults:
|
| 2 |
- base
|
| 3 |
- override refiner: default
|
| 4 |
+
- override lr_scheduler: expon
|
| 5 |
|
| 6 |
name: adam
|
| 7 |
|
|
|
|
| 10 |
eps: 1e-15
|
| 11 |
weight_decay: 0.0
|
| 12 |
|
| 13 |
+
# 5x base LR; means held constant (lr_final == _means, so means init 5*1.6e-4 == final 5*1.6e-4).
|
| 14 |
+
lr_scheduler:
|
| 15 |
+
lr_data:
|
| 16 |
+
_base: 5
|
| 17 |
+
lr_final: 1.6e-4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/config/scene_trainer/scene_optimizer/base.yaml
CHANGED
|
@@ -6,12 +6,12 @@ defaults:
|
|
| 6 |
input_gradients_chunk_size: -1
|
| 7 |
|
| 8 |
# iterative refine
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
|
| 16 |
zero_state_on_densify: false
|
| 17 |
|
|
|
|
| 6 |
input_gradients_chunk_size: -1
|
| 7 |
|
| 8 |
# iterative refine
|
| 9 |
+
freeze_mean: false
|
| 10 |
+
freeze_scale: false
|
| 11 |
+
freeze_rotation: false
|
| 12 |
+
freeze_opacity: false
|
| 13 |
+
freeze_sh0: false
|
| 14 |
+
freeze_shN: false
|
| 15 |
|
| 16 |
zero_state_on_densify: false
|
| 17 |
|
optgs/config/scene_trainer/scene_optimizer/knn_based.yaml
CHANGED
|
@@ -5,19 +5,18 @@ name: knn_based
|
|
| 5 |
|
| 6 |
# iterative refine
|
| 7 |
no_render_error: false
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
num_refine_blocks: 1
|
| 11 |
concat_init_state: false
|
| 12 |
replace_init_state: false
|
| 13 |
state_channels: 256
|
| 14 |
-
|
| 15 |
-
|
| 16 |
pt_qk_norm: false
|
| 17 |
norm_pt_block: false
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
clamp_min_scale: 1e-6
|
| 22 |
clamp_min_raw_scales: -1e10
|
| 23 |
clamp_max_raw_scales: 1e10
|
|
@@ -30,15 +29,14 @@ clamp_min_shs: -1e10
|
|
| 30 |
clamp_max_shs: 1e10
|
| 31 |
clamp_shs_soft: false
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
multi_gaussian_scale_smaller: false
|
| 38 |
init_gaussian_multiple: 1
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
refine_same_num_points: false
|
| 42 |
input_error_rgb_no_shuffle: false
|
| 43 |
input_error_cache_resnet_feature: false
|
| 44 |
|
|
@@ -50,20 +48,19 @@ init_state_scale: 0
|
|
| 50 |
pt_heads: 1
|
| 51 |
|
| 52 |
# refine with mv attention
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
mv_attn_conv_with_norm: false
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
|
| 64 |
# KNN
|
| 65 |
use_fused_attn: true # fused KNN gather + attention CUDA kernel (faster, less memory)
|
| 66 |
-
prune_invisible_gaussians: false
|
| 67 |
knn_idx_update_every: 1
|
| 68 |
|
| 69 |
# inputs
|
|
@@ -103,25 +100,17 @@ input_gradient: false
|
|
| 103 |
input_gradient_log: false
|
| 104 |
input_gradient_log_clip_deltas: 0.001
|
| 105 |
input_gradient_scale: 1.
|
| 106 |
-
|
| 107 |
input_gradient_with_ssim_loss: false
|
| 108 |
input_gradient_same_loss: false
|
| 109 |
input_gradient_loss_reduction: mean
|
| 110 |
scale_residual_grads: false
|
| 111 |
|
| 112 |
-
|
| 113 |
-
window_global_refine: false
|
| 114 |
-
window_local_global_refine: false
|
| 115 |
-
|
| 116 |
-
# sliding window update to save training memory
|
| 117 |
-
update_window_size: 0
|
| 118 |
-
local_gaussian_render: false
|
| 119 |
-
|
| 120 |
-
train_global_update_only: false
|
| 121 |
|
| 122 |
# random size refine
|
| 123 |
# update more for low resolution, less for high
|
| 124 |
-
|
| 125 |
|
| 126 |
|
| 127 |
use_amp: true
|
|
@@ -131,9 +120,6 @@ pt_update_amp: true
|
|
| 131 |
use_checkpointing: false
|
| 132 |
recurrent_use_checkpointing: false
|
| 133 |
|
| 134 |
-
# Debugging
|
| 135 |
-
debug_refine_update_module: true
|
| 136 |
-
|
| 137 |
# Normalizing input
|
| 138 |
input_gradient_normalize: false
|
| 139 |
input_gradient_normalize_type: layer
|
|
@@ -146,21 +132,20 @@ predict_state_scale: false
|
|
| 146 |
predict_state_scale_norm: false
|
| 147 |
|
| 148 |
# Update head
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
update_head_scalar_scale_act: relu
|
| 158 |
|
| 159 |
# Per-parameter-group heads (Feature A): separate heads per param group, each with own normalize+scale
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
# Per-parameter scalar scales (Feature B): per-group scalar scales (requires
|
| 163 |
-
|
| 164 |
|
| 165 |
opt_scales_before_act: false
|
| 166 |
|
|
|
|
| 5 |
|
| 6 |
# iterative refine
|
| 7 |
no_render_error: false
|
| 8 |
+
num_basic_blocks: 4
|
| 9 |
+
num_blocks: 1
|
|
|
|
| 10 |
concat_init_state: false
|
| 11 |
replace_init_state: false
|
| 12 |
state_channels: 256
|
| 13 |
+
block_rmsnorm: false
|
| 14 |
+
block_layernorm: false
|
| 15 |
pt_qk_norm: false
|
| 16 |
norm_pt_block: false
|
| 17 |
+
delta_gaussian_multiple: 1
|
| 18 |
+
residual_init_state: false
|
| 19 |
+
clamp_max_scale: 3.0
|
| 20 |
clamp_min_scale: 1e-6
|
| 21 |
clamp_min_raw_scales: -1e10
|
| 22 |
clamp_max_raw_scales: 1e10
|
|
|
|
| 29 |
clamp_max_shs: 1e10
|
| 30 |
clamp_shs_soft: false
|
| 31 |
|
| 32 |
+
attn_proj_channels: 64
|
| 33 |
+
no_knn_attn: false
|
| 34 |
+
no_tran_block_norm: false
|
| 35 |
+
tran_block_act: gelu
|
| 36 |
multi_gaussian_scale_smaller: false
|
| 37 |
init_gaussian_multiple: 1
|
| 38 |
+
condition_pt_feature: true
|
| 39 |
+
same_num_points: false
|
|
|
|
| 40 |
input_error_rgb_no_shuffle: false
|
| 41 |
input_error_cache_resnet_feature: false
|
| 42 |
|
|
|
|
| 48 |
pt_heads: 1
|
| 49 |
|
| 50 |
# refine with mv attention
|
| 51 |
+
with_mv_attn: false
|
| 52 |
+
with_mv_attn_lowres: false
|
| 53 |
+
no_mv_attn: false
|
| 54 |
mv_attn_conv_with_norm: false
|
| 55 |
+
mv_shuffle_attn: false
|
| 56 |
+
mv_attn_with_pos_enc: false
|
| 57 |
+
shuffle_attn_no_norm: false
|
| 58 |
+
mv_unimatch_attn: false
|
| 59 |
+
knn_samples: 16
|
| 60 |
+
max_active_gaussians: 100_000
|
| 61 |
|
| 62 |
# KNN
|
| 63 |
use_fused_attn: true # fused KNN gather + attention CUDA kernel (faster, less memory)
|
|
|
|
| 64 |
knn_idx_update_every: 1
|
| 65 |
|
| 66 |
# inputs
|
|
|
|
| 100 |
input_gradient_log: false
|
| 101 |
input_gradient_log_clip_deltas: 0.001
|
| 102 |
input_gradient_scale: 1.
|
| 103 |
+
residual_grad_scale: 1.
|
| 104 |
input_gradient_with_ssim_loss: false
|
| 105 |
input_gradient_same_loss: false
|
| 106 |
input_gradient_loss_reduction: mean
|
| 107 |
scale_residual_grads: false
|
| 108 |
|
| 109 |
+
train_global_only: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
# random size refine
|
| 112 |
# update more for low resolution, less for high
|
| 113 |
+
random_step_with_size: false
|
| 114 |
|
| 115 |
|
| 116 |
use_amp: true
|
|
|
|
| 120 |
use_checkpointing: false
|
| 121 |
recurrent_use_checkpointing: false
|
| 122 |
|
|
|
|
|
|
|
|
|
|
| 123 |
# Normalizing input
|
| 124 |
input_gradient_normalize: false
|
| 125 |
input_gradient_normalize_type: layer
|
|
|
|
| 132 |
predict_state_scale_norm: false
|
| 133 |
|
| 134 |
# Update head
|
| 135 |
+
delta_head_concat_img: false
|
| 136 |
+
delta_head_layer_num: 2
|
| 137 |
+
delta_head_act: gelu
|
| 138 |
+
delta_head_final_act: identity
|
| 139 |
+
delta_head_hidden_dim_matches: "input" # rebuttal version. switch to "output" for submission version
|
| 140 |
|
| 141 |
+
delta_head_scalar_scale: false
|
| 142 |
+
delta_head_scalar_scale_act: relu
|
|
|
|
| 143 |
|
| 144 |
# Per-parameter-group heads (Feature A): separate heads per param group, each with own normalize+scale
|
| 145 |
+
delta_head_per_param_heads: false
|
| 146 |
+
delta_head_per_param_hidden_dim: 48 # tuned so total params ≈ baseline head (~81K)
|
| 147 |
+
# Per-parameter scalar scales (Feature B): per-group scalar scales (requires delta_head_scalar_scale=true)
|
| 148 |
+
delta_head_per_param_scales: false
|
| 149 |
|
| 150 |
opt_scales_before_act: false
|
| 151 |
|
optgs/config/scene_trainer/scene_optimizer/learn2splat.yaml
CHANGED
|
@@ -5,13 +5,25 @@ name: l2s
|
|
| 5 |
|
| 6 |
# General optimization settings
|
| 7 |
opt_scales_before_act: true
|
|
|
|
|
|
|
|
|
|
| 8 |
sh_d: 16 # should be the default, but just in case
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
# Input gradient settings
|
| 11 |
input_gradient: true
|
| 12 |
input_gradient_normalize: true
|
| 13 |
input_gradient_normalize_type: adam
|
| 14 |
input_gradient_with_ssim_loss: true
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# Freeze zero-grad gaussians ❄️($G_{∇=0})
|
| 17 |
update_only_nonzero_grad: true
|
|
@@ -20,8 +32,8 @@ update_only_nonzero_grad: true
|
|
| 20 |
predict_state_scale: true
|
| 21 |
|
| 22 |
# Delta scale
|
| 23 |
-
|
| 24 |
-
|
| 25 |
|
| 26 |
|
| 27 |
|
|
|
|
| 5 |
|
| 6 |
# General optimization settings
|
| 7 |
opt_scales_before_act: true
|
| 8 |
+
# Scales are refined in log space, so clamp in log space too.
|
| 9 |
+
clamp_min_raw_scales: -8.0
|
| 10 |
+
clamp_max_raw_scales: 1.0986 # ln(3.0)
|
| 11 |
sh_d: 16 # should be the default, but just in case
|
| 12 |
|
| 13 |
+
# SH coefficients are clamped to a fixed range during refinement.
|
| 14 |
+
clamp_min_shs: -2.0
|
| 15 |
+
clamp_max_shs: 2.0
|
| 16 |
+
|
| 17 |
+
# KNN neighborhood size for the point-transformer attention.
|
| 18 |
+
knn_samples: 4
|
| 19 |
+
|
| 20 |
# Input gradient settings
|
| 21 |
input_gradient: true
|
| 22 |
input_gradient_normalize: true
|
| 23 |
input_gradient_normalize_type: adam
|
| 24 |
input_gradient_with_ssim_loss: true
|
| 25 |
+
# Average the input gradient over pixels, then sum over the supervised views.
|
| 26 |
+
input_gradient_loss_reduction: mean_pixels_sum_views
|
| 27 |
|
| 28 |
# Freeze zero-grad gaussians ❄️($G_{∇=0})
|
| 29 |
update_only_nonzero_grad: true
|
|
|
|
| 32 |
predict_state_scale: true
|
| 33 |
|
| 34 |
# Delta scale
|
| 35 |
+
delta_head_scalar_scale: true
|
| 36 |
+
delta_head_scalar_scale_act: relu # should be the default, but just in case
|
| 37 |
|
| 38 |
|
| 39 |
|
optgs/config/scene_trainer/scene_optimizer/learn2splat_dense.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- learn2splat
|
| 3 |
+
|
| 4 |
+
# Dense variant (64-view SfM/COLMAP init): the optimizer state is initialized as random
|
| 5 |
+
# features rather than the sparse model's constant zero state.
|
| 6 |
+
init_state_wo_features: true
|
| 7 |
+
init_state_type: random
|
| 8 |
+
init_state_scale: 1.0
|
optgs/config/scene_trainer/scene_optimizer/lr_scheduler/expon.yaml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- base
|
| 3 |
+
|
| 4 |
+
name: expon
|
| 5 |
+
|
| 6 |
+
# 3DGS learning rates (gsplat); means decays, the rest are constant
|
| 7 |
+
lr_data:
|
| 8 |
+
_base: 1
|
| 9 |
+
_means: 1.6e-4
|
| 10 |
+
_scales: 5e-3
|
| 11 |
+
_quats: 1e-3
|
| 12 |
+
_opacities: 5e-2
|
| 13 |
+
_sh0: 2.5e-3
|
| 14 |
+
_shN: 1.25e-4
|
| 15 |
+
apply_scheduler:
|
| 16 |
+
_base: true # gates all params (Bool3DGSCfg.<param> = _base AND _param); must stay true
|
| 17 |
+
_means: true
|
| 18 |
+
_scales: false
|
| 19 |
+
_quats: false
|
| 20 |
+
_opacities: false
|
| 21 |
+
_sh0: false
|
| 22 |
+
_shN: false
|
| 23 |
+
|
| 24 |
+
# means decay: 1.6e-4 -> 1.6e-6 over the full step budget
|
| 25 |
+
lr_final: 1.6e-6
|
| 26 |
+
lr_delay_steps: 0
|
| 27 |
+
lr_delay_mult: 1.0
|
| 28 |
+
max_steps: 30000 # should be equal to total optimization steps
|
optgs/config/scene_trainer/scene_optimizer/refiner/mcmc.yaml
CHANGED
|
@@ -39,7 +39,7 @@ fallback_means_lr: 1.6e-4
|
|
| 39 |
relocate_copy_state: true # inherit alive Gaussian's optimizer state (better than zeroing)
|
| 40 |
|
| 41 |
# Cap (in scale units) applied to the *noise-only* covariance computation. Does NOT change the
|
| 42 |
-
# rendered Gaussian scales. Needed for knn_based whose network saturates
|
| 43 |
# which produces covariances 10²-10⁴× larger than vanilla's and makes the noise overflow the
|
| 44 |
# renderer's tile-binning math (silent CUDA OOB downstream). Tune to ~scene_scale / 5.
|
| 45 |
noise_scale_cap: 1.0
|
|
|
|
| 39 |
relocate_copy_state: true # inherit alive Gaussian's optimizer state (better than zeroing)
|
| 40 |
|
| 41 |
# Cap (in scale units) applied to the *noise-only* covariance computation. Does NOT change the
|
| 42 |
+
# rendered Gaussian scales. Needed for knn_based whose network saturates clamp_max_scale,
|
| 43 |
# which produces covariances 10²-10⁴× larger than vanilla's and makes the noise overflow the
|
| 44 |
# renderer's tile-binning math (silent CUDA OOB downstream). Tune to ~scene_scale / 5.
|
| 45 |
noise_scale_cap: 1.0
|
optgs/config/scene_trainer/scene_optimizer/resplat_v2.yaml
CHANGED
|
@@ -5,7 +5,7 @@ name: resplat_v2
|
|
| 5 |
input_error: true
|
| 6 |
input_error_mv_attn: true
|
| 7 |
input_error_add_rgb_feature: true
|
| 8 |
-
|
| 9 |
state_channels: 512
|
| 10 |
residual_state: true
|
| 11 |
-
|
|
|
|
| 5 |
input_error: true
|
| 6 |
input_error_mv_attn: true
|
| 7 |
input_error_add_rgb_feature: true
|
| 8 |
+
knn_samples: 8
|
| 9 |
state_channels: 512
|
| 10 |
residual_state: true
|
| 11 |
+
delta_head_layer_num: 4
|
optgs/config/scene_trainer/scene_optimizer/sgd.yaml
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
defaults:
|
| 2 |
-
- base
|
| 3 |
-
- override refiner: default
|
| 4 |
-
|
| 5 |
-
name: sgd
|
| 6 |
-
|
| 7 |
-
# Adam optimizer
|
| 8 |
-
betas: [0.9, 0.999]
|
| 9 |
-
eps: 1e-15
|
| 10 |
-
weight_decay: 0.0
|
| 11 |
-
|
| 12 |
-
# learning rates (gsplat)
|
| 13 |
-
base_lr: 1
|
| 14 |
-
means_lr_init: 1.6e-4
|
| 15 |
-
means_lr_final: 1e-5
|
| 16 |
-
means_lr_delay_mult: 0.01
|
| 17 |
-
means_lr_max_steps: 30000 # should be equal to total optimization steps
|
| 18 |
-
scales_lr: 5e-3
|
| 19 |
-
rotations_lr: 1e-3
|
| 20 |
-
opacities_lr: 5e-2
|
| 21 |
-
sh0s_lr: 2.5e-3
|
| 22 |
-
shNs_lr: 1.25e-4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/config_migrate.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
|
|
|
|
|
| 1 |
from omegaconf import OmegaConf
|
| 2 |
|
| 3 |
|
| 4 |
-
CURRENT_CFG_VERSION = 2
|
| 5 |
|
| 6 |
def migrate(cfg_dict):
|
| 7 |
was_omega = not isinstance(cfg_dict, dict)
|
|
@@ -27,6 +29,31 @@ def migrate(cfg_dict):
|
|
| 27 |
cfg_dict = migrate_v1_to_v2(cfg_dict)
|
| 28 |
version = 2
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
if version != CURRENT_CFG_VERSION:
|
| 31 |
raise ValueError(f"Unsupported config version: {version}")
|
| 32 |
|
|
@@ -43,16 +70,95 @@ def migrate(cfg_dict):
|
|
| 43 |
si["name"] = "resplat_v1"
|
| 44 |
|
| 45 |
# Strip stale postprocessing fields from old checkpoint configs
|
| 46 |
-
|
|
|
|
| 47 |
if isinstance(pp, dict):
|
| 48 |
pp.pop("__target__", None)
|
| 49 |
pp.pop("enabled", None)
|
| 50 |
pp.pop("lr", None)
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
# Strip stale foundationstereo fields (encoder removed)
|
| 53 |
si.pop("foundationstereo", None)
|
| 54 |
si.pop("fstereo_num_refine", None)
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
if was_omega:
|
| 57 |
return OmegaConf.create(cfg_container)
|
| 58 |
return cfg_container
|
|
@@ -74,6 +180,183 @@ def migrate_v1_to_v2(cfg_dict):
|
|
| 74 |
return cfg
|
| 75 |
|
| 76 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
def migrate_v0_to_v1(cfg):
|
| 78 |
"""
|
| 79 |
Migration from submission v0 (refine_*) to rebuttal v1 (input_error_*).
|
|
@@ -159,7 +442,6 @@ def migrate_v0_to_v1(cfg):
|
|
| 159 |
|
| 160 |
# update head
|
| 161 |
"final_head_act": "update_head_final_act",
|
| 162 |
-
"refine_output_scale_mag": "update_head_scale_mag",
|
| 163 |
"scalar_scale_out": "update_head_scalar_scale",
|
| 164 |
"scalar_scale_out_act": "update_head_scalar_scale_act",
|
| 165 |
|
|
|
|
| 1 |
+
from math import log
|
| 2 |
+
|
| 3 |
from omegaconf import OmegaConf
|
| 4 |
|
| 5 |
|
| 6 |
+
CURRENT_CFG_VERSION = 2.5
|
| 7 |
|
| 8 |
def migrate(cfg_dict):
|
| 9 |
was_omega = not isinstance(cfg_dict, dict)
|
|
|
|
| 29 |
cfg_dict = migrate_v1_to_v2(cfg_dict)
|
| 30 |
version = 2
|
| 31 |
|
| 32 |
+
if version == 2:
|
| 33 |
+
print("Migrating config from version 2 to version 2.1 (strip update_/refine_ prefixes)...")
|
| 34 |
+
cfg_dict = migrate_v2_to_v2_1(cfg_dict)
|
| 35 |
+
version = 2.1
|
| 36 |
+
|
| 37 |
+
if version == 2.1:
|
| 38 |
+
print("Migrating config from version 2.1 to version 2.2 (single-space scale clamp)...")
|
| 39 |
+
cfg_dict = migrate_v2_1_to_v2_2(cfg_dict)
|
| 40 |
+
version = 2.2
|
| 41 |
+
|
| 42 |
+
if version == 2.2:
|
| 43 |
+
print("Migrating config from version 2.2 to version 2.3 (replay_buffer -> ckpt_buffer, simulate_ahead -> rollout)...")
|
| 44 |
+
cfg_dict = migrate_v2_2_to_v2_3(cfg_dict)
|
| 45 |
+
version = 2.3
|
| 46 |
+
|
| 47 |
+
if version == 2.3:
|
| 48 |
+
print("Migrating config from version 2.3 to version 2.4 (adam flat LRs -> expon lr_scheduler)...")
|
| 49 |
+
cfg_dict = migrate_v2_3_to_v2_4(cfg_dict)
|
| 50 |
+
version = 2.4
|
| 51 |
+
|
| 52 |
+
if version == 2.4:
|
| 53 |
+
print("Migrating config from version 2.4 to version 2.5 (meta_optimizer moved under meta_trainer)...")
|
| 54 |
+
cfg_dict = migrate_v2_4_to_v2_5(cfg_dict)
|
| 55 |
+
version = 2.5
|
| 56 |
+
|
| 57 |
if version != CURRENT_CFG_VERSION:
|
| 58 |
raise ValueError(f"Unsupported config version: {version}")
|
| 59 |
|
|
|
|
| 70 |
si["name"] = "resplat_v1"
|
| 71 |
|
| 72 |
# Strip stale postprocessing fields from old checkpoint configs
|
| 73 |
+
te = cfg_container.get("meta_trainer", {}).get("test", {})
|
| 74 |
+
pp = te.get("postprocessing", None) if isinstance(te, dict) else None
|
| 75 |
if isinstance(pp, dict):
|
| 76 |
pp.pop("__target__", None)
|
| 77 |
pp.pop("enabled", None)
|
| 78 |
pp.pop("lr", None)
|
| 79 |
|
| 80 |
+
# save_point_cloud removed (the flag was never read by any code path).
|
| 81 |
+
if isinstance(te, dict):
|
| 82 |
+
te.pop("save_point_cloud", None)
|
| 83 |
+
|
| 84 |
+
# Video flags renamed (fixed_view->optim, fixed_iteration->orbit, combined->optim_orbit).
|
| 85 |
+
# Test flags come from the current run's config, so just drop the stale keys from old configs.
|
| 86 |
+
for k in ("save_video_fixed_view", "save_video_fixed_view_index", "save_video_fixed_view_duplicate",
|
| 87 |
+
"save_video_fixed_iteration", "save_video_fixed_iteration_indices",
|
| 88 |
+
"save_video_fixed_iteration_render_fixed_view", "save_video_combined",
|
| 89 |
+
"save_video_combined_iterations", "save_video_combined_fixed_iteration_length"):
|
| 90 |
+
if isinstance(te, dict):
|
| 91 |
+
te.pop(k, None)
|
| 92 |
+
|
| 93 |
# Strip stale foundationstereo fields (encoder removed)
|
| 94 |
si.pop("foundationstereo", None)
|
| 95 |
si.pop("fstereo_num_refine", None)
|
| 96 |
|
| 97 |
+
# Strip removed optimizer sliding-window fields (feature removed)
|
| 98 |
+
for k in ("window_size", "update_window_size", "local_gaussian_render",
|
| 99 |
+
"window_local_refine", "window_global_refine", "window_local_global_refine"):
|
| 100 |
+
so.pop(k, None)
|
| 101 |
+
|
| 102 |
+
# delta_head_scale_mag removed (experimental). Drop it under all historical names.
|
| 103 |
+
for k in ("delta_head_scale_mag", "update_head_scale_mag", "refine_output_scale_mag"):
|
| 104 |
+
so.pop(k, None)
|
| 105 |
+
|
| 106 |
+
# reinit_gaussian_when_multiple removed (reinit path was never implemented). Drop it
|
| 107 |
+
# under all historical names (reinit_gaussian_when_refine_multiple is the pre-2.1 name).
|
| 108 |
+
for k in ("reinit_gaussian_when_multiple", "reinit_gaussian_when_refine_multiple"):
|
| 109 |
+
so.pop(k, None)
|
| 110 |
+
|
| 111 |
+
# The initializer has no point-downsampling path, so pt_downsample,
|
| 112 |
+
# fps_agg_func and subsample_method are not valid config keys; drop them.
|
| 113 |
+
# multi_scale_pt and fps_num_samples are likewise not config keys (the point
|
| 114 |
+
# transformer is always PlainPointTransformer). subsample_method also appears
|
| 115 |
+
# on old optimizer configs, so strip it from there too.
|
| 116 |
+
for k in ("multi_scale_pt", "refine_multi_scale_pt", "subsample_method"):
|
| 117 |
+
so.pop(k, None)
|
| 118 |
+
for k in ("multi_scale_pt", "fps_num_samples", "pt_downsample", "fps_agg_func", "subsample_method"):
|
| 119 |
+
si.pop(k, None)
|
| 120 |
+
|
| 121 |
+
# The lvsm gaussian-regressor path was removed (disabled in every config), so
|
| 122 |
+
# lvsm_gaussian_regressor and lvsm_layers are no longer valid initializer keys.
|
| 123 |
+
for k in ("lvsm_gaussian_regressor", "lvsm_layers"):
|
| 124 |
+
si.pop(k, None)
|
| 125 |
+
|
| 126 |
+
# pt_pred_residual_position removed (residual-position prediction was disabled in every config).
|
| 127 |
+
si.pop("pt_pred_residual_position", None)
|
| 128 |
+
|
| 129 |
+
# sh_only removed: it was "freeze everything but SH", now expressed via per-group freeze_*.
|
| 130 |
+
# (refine_sh_only is the pre-2.1 name.)
|
| 131 |
+
sh_only = so.pop("sh_only", None)
|
| 132 |
+
if sh_only is None:
|
| 133 |
+
sh_only = so.pop("refine_sh_only", None)
|
| 134 |
+
if sh_only:
|
| 135 |
+
for fk in ("freeze_mean", "freeze_scale", "freeze_rotation", "freeze_opacity"):
|
| 136 |
+
so[fk] = True
|
| 137 |
+
|
| 138 |
+
# l1_loss / train_ignore_large_loss / half_res_lpips_loss moved off train into the per-loss
|
| 139 |
+
# cfgs: mse/sgd take l1_loss + clamp_large_error, lpips takes half_res.
|
| 140 |
+
train = cfg_container.get("meta_trainer", {}).get("train", {})
|
| 141 |
+
|
| 142 |
+
# depth_range_from_disparity removed (disabled in every config); its only consumers,
|
| 143 |
+
# max_disparity and min_disparity, are removed with it.
|
| 144 |
+
for k in ("depth_range_from_disparity", "max_disparity", "min_disparity"):
|
| 145 |
+
train.pop(k, None)
|
| 146 |
+
|
| 147 |
+
old_l1 = train.pop("l1_loss", None)
|
| 148 |
+
old_clamp = train.pop("train_ignore_large_loss", None)
|
| 149 |
+
old_half_res = train.pop("half_res_lpips_loss", None)
|
| 150 |
+
for loss_wrapper in cfg_container.get("loss", []) or []:
|
| 151 |
+
if not isinstance(loss_wrapper, dict):
|
| 152 |
+
continue
|
| 153 |
+
for name, lcfg in loss_wrapper.items():
|
| 154 |
+
if not isinstance(lcfg, dict):
|
| 155 |
+
continue
|
| 156 |
+
if name in ("mse", "sgd"):
|
| 157 |
+
lcfg.setdefault("l1_loss", old_l1 if old_l1 is not None else False)
|
| 158 |
+
lcfg.setdefault("clamp_large_error", old_clamp if old_clamp is not None else 0.0)
|
| 159 |
+
elif name == "lpips":
|
| 160 |
+
lcfg.setdefault("half_res", old_half_res if old_half_res is not None else False)
|
| 161 |
+
|
| 162 |
if was_omega:
|
| 163 |
return OmegaConf.create(cfg_container)
|
| 164 |
return cfg_container
|
|
|
|
| 180 |
return cfg
|
| 181 |
|
| 182 |
|
| 183 |
+
def migrate_v2_4_to_v2_5(cfg_dict):
|
| 184 |
+
"""Migration from v2.4 to v2.5: move top-level 'meta_optimizer' under 'meta_trainer'."""
|
| 185 |
+
cfg = OmegaConf.to_container(cfg_dict, resolve=False) if not isinstance(cfg_dict, dict) else dict(cfg_dict)
|
| 186 |
+
|
| 187 |
+
if "meta_optimizer" in cfg:
|
| 188 |
+
meta_trainer = cfg.setdefault("meta_trainer", {})
|
| 189 |
+
if "meta_optimizer" not in meta_trainer:
|
| 190 |
+
meta_trainer["meta_optimizer"] = cfg.pop("meta_optimizer")
|
| 191 |
+
|
| 192 |
+
cfg["version"] = 2.5
|
| 193 |
+
return cfg
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def migrate_v2_to_v2_1(cfg_dict):
|
| 197 |
+
"""
|
| 198 |
+
Migration from v2 to v2.1: strip redundant update_/refine_ prefixes from optimizer
|
| 199 |
+
config fields (the class is already named Optimizer), rename no_refine_* -> freeze_*,
|
| 200 |
+
and update_head_* -> delta_head_*.
|
| 201 |
+
"""
|
| 202 |
+
cfg = OmegaConf.to_container(cfg_dict, resolve=False) if not isinstance(cfg_dict, dict) else dict(cfg_dict)
|
| 203 |
+
|
| 204 |
+
so = cfg.get("scene_trainer", {}).get("scene_optimizer", {})
|
| 205 |
+
|
| 206 |
+
RENAME_MAP = {
|
| 207 |
+
# refine_
|
| 208 |
+
"num_basic_refine_blocks": "num_basic_blocks",
|
| 209 |
+
"num_refine_blocks": "num_blocks",
|
| 210 |
+
"refine_block_rmsnorm": "block_rmsnorm",
|
| 211 |
+
"refine_block_layernorm": "block_layernorm",
|
| 212 |
+
"refine_gaussian_multiple": "delta_gaussian_multiple",
|
| 213 |
+
"refine_residual_init_state": "residual_init_state",
|
| 214 |
+
"clamp_refine_max_scale": "clamp_max_scale",
|
| 215 |
+
"refine_condition_pt_feature": "condition_pt_feature",
|
| 216 |
+
"refine_same_num_points": "same_num_points",
|
| 217 |
+
"refine_knn_samples": "knn_samples",
|
| 218 |
+
"refine_with_mv_attn": "with_mv_attn",
|
| 219 |
+
"refine_with_mv_attn_lowres": "with_mv_attn_lowres",
|
| 220 |
+
"refine_no_mv_attn": "no_mv_attn",
|
| 221 |
+
"refine_mv_shuffle_attn": "mv_shuffle_attn",
|
| 222 |
+
"refine_mv_attn_with_pos_enc": "mv_attn_with_pos_enc",
|
| 223 |
+
"refine_shuffle_attn_no_norm": "shuffle_attn_no_norm",
|
| 224 |
+
"refine_mv_unimatch_attn": "mv_unimatch_attn",
|
| 225 |
+
# no_refine_ -> freeze_
|
| 226 |
+
"no_refine_mean": "freeze_mean",
|
| 227 |
+
"no_refine_scale": "freeze_scale",
|
| 228 |
+
"no_refine_rotation": "freeze_rotation",
|
| 229 |
+
"no_refine_opacity": "freeze_opacity",
|
| 230 |
+
"no_refine_sh0": "freeze_sh0",
|
| 231 |
+
"no_refine_shN": "freeze_shN",
|
| 232 |
+
# other update_
|
| 233 |
+
"update_attn_proj_channels": "attn_proj_channels",
|
| 234 |
+
"update_no_knn_attn": "no_knn_attn",
|
| 235 |
+
"update_no_tran_block_norm": "no_tran_block_norm",
|
| 236 |
+
"update_tran_block_act": "tran_block_act",
|
| 237 |
+
"train_global_update_only": "train_global_only",
|
| 238 |
+
"random_update_with_size": "random_step_with_size",
|
| 239 |
+
"gradient_update_scale": "residual_grad_scale",
|
| 240 |
+
# update_head_ -> delta_head_
|
| 241 |
+
"update_head_layer_num": "delta_head_layer_num",
|
| 242 |
+
"update_head_concat_img": "delta_head_concat_img",
|
| 243 |
+
"update_head_act": "delta_head_act",
|
| 244 |
+
"update_head_final_act": "delta_head_final_act",
|
| 245 |
+
"update_head_hidden_dim_matches": "delta_head_hidden_dim_matches",
|
| 246 |
+
"update_head_scalar_scale": "delta_head_scalar_scale",
|
| 247 |
+
"update_head_scalar_scale_act": "delta_head_scalar_scale_act",
|
| 248 |
+
"update_head_per_param_heads": "delta_head_per_param_heads",
|
| 249 |
+
"update_head_per_param_hidden_dim": "delta_head_per_param_hidden_dim",
|
| 250 |
+
"update_head_per_param_scales": "delta_head_per_param_scales",
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
for old, new in RENAME_MAP.items():
|
| 254 |
+
if old in so:
|
| 255 |
+
so[new] = so.pop(old)
|
| 256 |
+
|
| 257 |
+
cfg["version"] = 2.1
|
| 258 |
+
return cfg
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def migrate_v2_1_to_v2_2(cfg_dict):
|
| 262 |
+
"""
|
| 263 |
+
Migration from v2.1 to v2.2: scales are now clamped in a single space.
|
| 264 |
+
|
| 265 |
+
When opt_scales_before_act is set, scales are refined in log space, and the clamp is now
|
| 266 |
+
applied there (raw limits) both when unpacking and in the per-step update. Earlier configs
|
| 267 |
+
instead clamped once in activation space (clamp_min/max_scale) during unpack and once in log
|
| 268 |
+
space (clamp_min/max_raw_scales) during the update, so the scale was effectively bounded by
|
| 269 |
+
the intersection of the two. Fold that intersection into the raw limits so old checkpoints
|
| 270 |
+
keep the same effective bounds under the new single-space clamp.
|
| 271 |
+
"""
|
| 272 |
+
cfg = OmegaConf.to_container(cfg_dict, resolve=False) if not isinstance(cfg_dict, dict) else dict(cfg_dict)
|
| 273 |
+
|
| 274 |
+
so = cfg.get("scene_trainer", {}).get("scene_optimizer", {})
|
| 275 |
+
if so.get("opt_scales_before_act", False):
|
| 276 |
+
min_scale = float(so.get("clamp_min_scale", 1e-6))
|
| 277 |
+
max_scale = float(so.get("clamp_max_scale", 3.0))
|
| 278 |
+
min_raw = float(so.get("clamp_min_raw_scales", -1e10))
|
| 279 |
+
max_raw = float(so.get("clamp_max_raw_scales", 1e10))
|
| 280 |
+
so["clamp_min_raw_scales"] = max(log(min_scale), min_raw)
|
| 281 |
+
so["clamp_max_raw_scales"] = min(log(max_scale), max_raw)
|
| 282 |
+
|
| 283 |
+
cfg["version"] = 2.2
|
| 284 |
+
return cfg
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def migrate_v2_2_to_v2_3(cfg_dict):
|
| 288 |
+
"""
|
| 289 |
+
Migration from v2.2 to v2.3: the replay-buffer feature is renamed to ckpt_buffer and
|
| 290 |
+
simulate_ahead to rollout (use_replay_buffer -> use_ckpt_buffer, replay_buffer_cfg ->
|
| 291 |
+
ckpt_buffer_cfg, and the simulate_ahead* fields inside it -> rollout*).
|
| 292 |
+
"""
|
| 293 |
+
cfg = OmegaConf.to_container(cfg_dict, resolve=False) if not isinstance(cfg_dict, dict) else dict(cfg_dict)
|
| 294 |
+
|
| 295 |
+
train = cfg.get("meta_trainer", {}).get("train", {})
|
| 296 |
+
if "use_replay_buffer" in train:
|
| 297 |
+
train["use_ckpt_buffer"] = train.pop("use_replay_buffer")
|
| 298 |
+
if "replay_buffer_cfg" in train:
|
| 299 |
+
train["ckpt_buffer_cfg"] = train.pop("replay_buffer_cfg")
|
| 300 |
+
|
| 301 |
+
buffer_cfg = train.get("ckpt_buffer_cfg", {})
|
| 302 |
+
if isinstance(buffer_cfg, dict):
|
| 303 |
+
ROLLOUT_RENAME_MAP = {
|
| 304 |
+
"simulate_ahead": "rollout",
|
| 305 |
+
"simulate_ahead_min_steps": "rollout_min_steps",
|
| 306 |
+
"simulate_ahead_max_steps": "rollout_max_steps",
|
| 307 |
+
"simulate_ahead_grow": "rollout_grow",
|
| 308 |
+
}
|
| 309 |
+
for old, new in ROLLOUT_RENAME_MAP.items():
|
| 310 |
+
if old in buffer_cfg:
|
| 311 |
+
buffer_cfg[new] = buffer_cfg.pop(old)
|
| 312 |
+
|
| 313 |
+
cfg["version"] = 2.3
|
| 314 |
+
return cfg
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def migrate_v2_3_to_v2_4(cfg_dict):
|
| 318 |
+
"""
|
| 319 |
+
Migration from v2.3 to v2.4: the Adam baseline (name adam/sgd) no longer carries flat per-param
|
| 320 |
+
LR fields. They move into the inherited lr_scheduler cfg (name "expon"): per-param values into
|
| 321 |
+
lr_data, the means decay into the expon scheduler params, with apply_scheduler enabling the
|
| 322 |
+
schedule on means only.
|
| 323 |
+
|
| 324 |
+
The expon scheduler scales the whole curve by lr_data._base (= old base_lr) and the means LR is
|
| 325 |
+
additionally scaled by scene extent at runtime, so the bounds reproduce the old behavior:
|
| 326 |
+
old means LR = base_lr * scene_scale * expon(means_lr_init -> means_lr_final).
|
| 327 |
+
"""
|
| 328 |
+
cfg = OmegaConf.to_container(cfg_dict, resolve=False) if not isinstance(cfg_dict, dict) else dict(cfg_dict)
|
| 329 |
+
|
| 330 |
+
so = cfg.get("scene_trainer", {}).get("scene_optimizer", {})
|
| 331 |
+
if so.get("name") in ("adam", "sgd") and "means_lr_init" in so:
|
| 332 |
+
base = so.pop("base_lr", 1)
|
| 333 |
+
lr_scheduler = so.get("lr_scheduler", {}) or {}
|
| 334 |
+
lr_scheduler["name"] = "expon"
|
| 335 |
+
lr_scheduler["lr_data"] = {
|
| 336 |
+
"_base": base,
|
| 337 |
+
"_means": so.pop("means_lr_init"),
|
| 338 |
+
"_scales": so.pop("scales_lr"),
|
| 339 |
+
"_quats": so.pop("rotations_lr"),
|
| 340 |
+
"_opacities": so.pop("opacities_lr"),
|
| 341 |
+
"_sh0": so.pop("sh0s_lr"),
|
| 342 |
+
"_shN": so.pop("shNs_lr"),
|
| 343 |
+
}
|
| 344 |
+
# _base gates all params (Bool3DGSCfg.<param> = _base AND _param), so it must stay True;
|
| 345 |
+
# the schedule is enabled per-param via the individual flags (means only).
|
| 346 |
+
lr_scheduler["apply_scheduler"] = {
|
| 347 |
+
"_base": True, "_means": True, "_scales": False,
|
| 348 |
+
"_quats": False, "_opacities": False, "_sh0": False, "_shN": False,
|
| 349 |
+
}
|
| 350 |
+
lr_scheduler["lr_final"] = so.pop("means_lr_final")
|
| 351 |
+
lr_scheduler["lr_delay_steps"] = 0
|
| 352 |
+
lr_scheduler["lr_delay_mult"] = so.pop("means_lr_delay_mult", 1.0)
|
| 353 |
+
lr_scheduler["max_steps"] = so.pop("means_lr_max_steps", 30000)
|
| 354 |
+
so["lr_scheduler"] = lr_scheduler
|
| 355 |
+
|
| 356 |
+
cfg["version"] = 2.4
|
| 357 |
+
return cfg
|
| 358 |
+
|
| 359 |
+
|
| 360 |
def migrate_v0_to_v1(cfg):
|
| 361 |
"""
|
| 362 |
Migration from submission v0 (refine_*) to rebuttal v1 (input_error_*).
|
|
|
|
| 442 |
|
| 443 |
# update head
|
| 444 |
"final_head_act": "update_head_final_act",
|
|
|
|
| 445 |
"scalar_scale_out": "update_head_scalar_scale",
|
| 446 |
"scalar_scale_out_act": "update_head_scalar_scale_act",
|
| 447 |
|
optgs/dataset/camera_datasets/camera.py
CHANGED
|
@@ -10,8 +10,6 @@ import os
|
|
| 10 |
import json
|
| 11 |
|
| 12 |
from optgs.geometry.projection import get_fov, get_projection_matrix
|
| 13 |
-
from optgs.visualization.camera_trajectory.wobble import generate_wobble_transformation
|
| 14 |
-
from optgs.visualization.camera_trajectory.interpolation import interpolate_extrinsics, interpolate_intrinsics
|
| 15 |
|
| 16 |
|
| 17 |
def get_scene_scale(camtoworlds: Float[np.ndarray, "N 4 4"]) -> float:
|
|
@@ -143,133 +141,6 @@ class Camera(nn.Module):
|
|
| 143 |
indent=4,
|
| 144 |
)
|
| 145 |
|
| 146 |
-
@classmethod
|
| 147 |
-
def load_camera(cls, cam_dir: Path, data_device: torch.device):
|
| 148 |
-
extrinsics = torch.load(cam_dir / "extrinsics.pt")
|
| 149 |
-
intrinsics = torch.load(cam_dir / "intrinsics.pt")
|
| 150 |
-
image = torch.load(cam_dir / "image.pt")
|
| 151 |
-
|
| 152 |
-
if (cam_dir / "gt_alpha_mask.pt").exists():
|
| 153 |
-
gt_alpha_mask = torch.load(cam_dir / "gt_alpha_mask.pt")
|
| 154 |
-
else:
|
| 155 |
-
gt_alpha_mask = None
|
| 156 |
-
|
| 157 |
-
with open(cam_dir / "cam_info.json", "r") as f:
|
| 158 |
-
cam_info = json.load(f)
|
| 159 |
-
|
| 160 |
-
return cls(
|
| 161 |
-
colmap_id=cam_info["colmap_id"],
|
| 162 |
-
extrinsics=extrinsics.to(data_device),
|
| 163 |
-
intrinsics=intrinsics.to(data_device),
|
| 164 |
-
image=image.to(data_device),
|
| 165 |
-
gt_alpha_mask=gt_alpha_mask.to(data_device) if gt_alpha_mask is not None else None,
|
| 166 |
-
raw_image_shape=tuple(cam_info["raw_image_shape"]),
|
| 167 |
-
image_name=cam_info["image_name"],
|
| 168 |
-
uid=cam_info["uid"],
|
| 169 |
-
near=torch.Tensor([cam_info["near"]]).to(data_device),
|
| 170 |
-
far=torch.Tensor([cam_info["far"]]).to(data_device),
|
| 171 |
-
data_device=data_device,
|
| 172 |
-
).to(data_device)
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
def generate_cam_params_for_wobble(t: Tensor, cam_a: Camera, cam_b: Camera):
|
| 176 |
-
origin_a = cam_a.extrinsics[:3, 3]
|
| 177 |
-
origin_b = cam_b.extrinsics[:3, 3]
|
| 178 |
-
cam_a_extrinsics = cam_a.extrinsics
|
| 179 |
-
cam_b_extrinsics = cam_b.extrinsics
|
| 180 |
-
cam_a_intrinsics = cam_a.intrinsics
|
| 181 |
-
cam_b_intrinsics = cam_b.intrinsics
|
| 182 |
-
|
| 183 |
-
delta = (origin_a - origin_b).norm(dim=-1)
|
| 184 |
-
|
| 185 |
-
tf = generate_wobble_transformation(
|
| 186 |
-
radius=delta * 0.5,
|
| 187 |
-
t=t,
|
| 188 |
-
num_rotations=1,
|
| 189 |
-
scale_radius_with_t=False,
|
| 190 |
-
)
|
| 191 |
-
|
| 192 |
-
extrinsics = interpolate_extrinsics(
|
| 193 |
-
initial=cam_a_extrinsics,
|
| 194 |
-
final=cam_b_extrinsics,
|
| 195 |
-
t=(t - 2),
|
| 196 |
-
)
|
| 197 |
-
intrinsics = interpolate_intrinsics(
|
| 198 |
-
initial=cam_a_intrinsics,
|
| 199 |
-
final=cam_b_intrinsics,
|
| 200 |
-
t=(t - 2),
|
| 201 |
-
)
|
| 202 |
-
return extrinsics @ tf, intrinsics
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
def generate_cam_params_for_interpolation(t: Tensor, cam_a: Camera, cam_b: Camera):
|
| 206 |
-
cam_a_extrinsics = cam_a.extrinsics
|
| 207 |
-
cam_a_extrinsics_render_view = cam_a.extrinsics_render_view
|
| 208 |
-
cam_b_extrinsics = cam_b.extrinsics
|
| 209 |
-
cam_b_extrinsics_render_view = cam_b.extrinsics_render_view
|
| 210 |
-
cam_a_intrinsics = cam_a.intrinsics
|
| 211 |
-
cam_a_intrinsics_render_view = cam_a.intrinsics_render_view
|
| 212 |
-
cam_b_intrinsics = cam_b.intrinsics
|
| 213 |
-
cam_b_intrinsics_render_view = cam_b.intrinsics_render_view
|
| 214 |
-
|
| 215 |
-
extrinsics = interpolate_extrinsics(
|
| 216 |
-
initial=cam_a_extrinsics,
|
| 217 |
-
final=cam_b_extrinsics,
|
| 218 |
-
t=(t - 2),
|
| 219 |
-
)
|
| 220 |
-
intrinsics = interpolate_intrinsics(
|
| 221 |
-
initial=cam_a_intrinsics,
|
| 222 |
-
final=cam_b_intrinsics,
|
| 223 |
-
t=(t - 2),
|
| 224 |
-
)
|
| 225 |
-
extrinsics_render_view = interpolate_extrinsics(
|
| 226 |
-
initial=cam_a_extrinsics_render_view,
|
| 227 |
-
final=cam_b_extrinsics_render_view,
|
| 228 |
-
t=(t - 2),
|
| 229 |
-
)
|
| 230 |
-
intrinsics_render_view = interpolate_intrinsics(
|
| 231 |
-
initial=cam_a_intrinsics_render_view,
|
| 232 |
-
final=cam_b_intrinsics_render_view,
|
| 233 |
-
t=(t - 2),
|
| 234 |
-
)
|
| 235 |
-
return extrinsics, intrinsics, extrinsics_render_view, intrinsics_render_view
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
def get_intermediate_cameras(cam_a: Camera, cam_b: Camera, num_frames: int = 150, smooth: bool = False):
|
| 239 |
-
t = torch.linspace(0, 1, num_frames, dtype=torch.float32, device=cam_a.data_device)
|
| 240 |
-
if smooth: t = (torch.cos(torch.pi * (t + 1)) + 1) / 2
|
| 241 |
-
|
| 242 |
-
extrinsics, intrinsics, extrinsics_render_view, intrinsics_render_view = (
|
| 243 |
-
generate_cam_params_for_interpolation(t, cam_a, cam_b)
|
| 244 |
-
)
|
| 245 |
-
extrinsics = extrinsics.squeeze(0)
|
| 246 |
-
intrinsics = intrinsics.squeeze(0)
|
| 247 |
-
extrinsics_render_view = extrinsics_render_view.squeeze(0)
|
| 248 |
-
intrinsics_render_view = intrinsics_render_view.squeeze(0)
|
| 249 |
-
|
| 250 |
-
cameras = [
|
| 251 |
-
Camera(
|
| 252 |
-
colmap_id=cam_a.colmap_id,
|
| 253 |
-
image_name=f"{cam_a.image_name}_{index:04d}",
|
| 254 |
-
uid=index,
|
| 255 |
-
near=cam_a.znear,
|
| 256 |
-
far=cam_a.zfar,
|
| 257 |
-
data_device=cam_a.data_device,
|
| 258 |
-
image=cam_a.original_image, # These views have no ground truth image but we should never require images for mesh views
|
| 259 |
-
raw_image_shape=cam_a.raw_image_shape,
|
| 260 |
-
extrinsics=extrinsics[index],
|
| 261 |
-
intrinsics=intrinsics[index],
|
| 262 |
-
extrinsics_render_view=extrinsics_render_view[index],
|
| 263 |
-
intrinsics_render_view=intrinsics_render_view[index],
|
| 264 |
-
scale_matrix=cam_a.scale_matrix,
|
| 265 |
-
trans_matrix=cam_a.trans_matrix,
|
| 266 |
-
gt_alpha_mask=None
|
| 267 |
-
)
|
| 268 |
-
for index in range(num_frames)
|
| 269 |
-
]
|
| 270 |
-
return cameras
|
| 271 |
-
|
| 272 |
-
|
| 273 |
def patch_shim(cams: list[Camera], patch_size: int) -> list[Camera]:
|
| 274 |
new_cams = []
|
| 275 |
|
|
@@ -319,18 +190,6 @@ def patch_shim(cams: list[Camera], patch_size: int) -> list[Camera]:
|
|
| 319 |
return new_cams
|
| 320 |
|
| 321 |
|
| 322 |
-
def calculate_cameras_extent(cam_centers: Tensor):
|
| 323 |
-
avg_cam_center = cam_centers.mean(dim=0, keepdim=True)
|
| 324 |
-
dist = torch.norm(cam_centers - avg_cam_center, dim=-1, keepdim=True)
|
| 325 |
-
diagonal = dist.max()
|
| 326 |
-
|
| 327 |
-
center = avg_cam_center.flatten()
|
| 328 |
-
radius = diagonal * 1.1
|
| 329 |
-
|
| 330 |
-
translate = -center
|
| 331 |
-
return translate, radius.item()
|
| 332 |
-
|
| 333 |
-
|
| 334 |
def save_cameras(cameras: list[Camera], save_dir: Path):
|
| 335 |
os.makedirs(save_dir, exist_ok=True)
|
| 336 |
|
|
|
|
| 10 |
import json
|
| 11 |
|
| 12 |
from optgs.geometry.projection import get_fov, get_projection_matrix
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
def get_scene_scale(camtoworlds: Float[np.ndarray, "N 4 4"]) -> float:
|
|
|
|
| 141 |
indent=4,
|
| 142 |
)
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
def patch_shim(cams: list[Camera], patch_size: int) -> list[Camera]:
|
| 145 |
new_cams = []
|
| 146 |
|
|
|
|
| 190 |
return new_cams
|
| 191 |
|
| 192 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
def save_cameras(cameras: list[Camera], save_dir: Path):
|
| 194 |
os.makedirs(save_dir, exist_ok=True)
|
| 195 |
|
optgs/dataset/data_module.py
CHANGED
|
@@ -14,15 +14,15 @@ from .validation_wrapper import ValidationWrapper
|
|
| 14 |
from ..misc.step_tracker import StepTracker
|
| 15 |
|
| 16 |
|
| 17 |
-
def get_data_shim(
|
| 18 |
"""Get functions that modify the batch. It's sometimes necessary to modify batches
|
| 19 |
outside the data loader because GPU computations are required to modify the batch or
|
| 20 |
because the modification depends on something outside the data loader.
|
| 21 |
"""
|
| 22 |
|
| 23 |
shims: list[DataShim] = []
|
| 24 |
-
if hasattr(
|
| 25 |
-
shims.append(
|
| 26 |
|
| 27 |
def combined_shim(batch):
|
| 28 |
for shim in shims:
|
|
|
|
| 14 |
from ..misc.step_tracker import StepTracker
|
| 15 |
|
| 16 |
|
| 17 |
+
def get_data_shim(initializer: nn.Module) -> DataShim:
|
| 18 |
"""Get functions that modify the batch. It's sometimes necessary to modify batches
|
| 19 |
outside the data loader because GPU computations are required to modify the batch or
|
| 20 |
because the modification depends on something outside the data loader.
|
| 21 |
"""
|
| 22 |
|
| 23 |
shims: list[DataShim] = []
|
| 24 |
+
if hasattr(initializer, "get_data_shim"):
|
| 25 |
+
shims.append(initializer.get_data_shim())
|
| 26 |
|
| 27 |
def combined_shim(batch):
|
| 28 |
for shim in shims:
|
optgs/dataset/dataset_dl3dv.py
CHANGED
|
@@ -12,9 +12,6 @@ from jaxtyping import Float, UInt8
|
|
| 12 |
from PIL import Image
|
| 13 |
from torch import Tensor
|
| 14 |
from torch.utils.data import IterableDataset
|
| 15 |
-
import numpy as np
|
| 16 |
-
import os
|
| 17 |
-
import random
|
| 18 |
|
| 19 |
from ..geometry.projection import get_fov
|
| 20 |
from .dataset import DatasetCfgCommon
|
|
@@ -28,47 +25,24 @@ from .view_sampler import ViewSampler
|
|
| 28 |
class DatasetDL3DVCfg(DatasetCfgCommon):
|
| 29 |
name: Literal["dl3dv"]
|
| 30 |
roots: list[Path]
|
| 31 |
-
baseline_epsilon: float
|
| 32 |
max_fov: float
|
| 33 |
-
make_baseline_1: bool
|
| 34 |
augment: bool
|
| 35 |
test_len: int
|
| 36 |
test_chunk_interval: int
|
| 37 |
train_times_per_scene: int
|
| 38 |
test_times_per_scene: int
|
| 39 |
ori_image_shape: list[int]
|
| 40 |
-
# random crop training
|
| 41 |
-
random_crop: bool
|
| 42 |
-
max_size: list[int] | None
|
| 43 |
-
min_size: list[int] | None
|
| 44 |
|
| 45 |
skip_bad_shape: bool = True
|
| 46 |
near: float = -1.0
|
| 47 |
far: float = -1.0
|
| 48 |
-
baseline_scale_bounds: bool = True
|
| 49 |
shuffle_val: bool = True
|
| 50 |
no_mix_test_set: bool = True
|
| 51 |
-
load_depth: bool = False
|
| 52 |
min_views: int = 0
|
| 53 |
max_views: int = 0
|
| 54 |
-
highres: bool = False
|
| 55 |
sort_target_index: Optional[bool] = False
|
| 56 |
overfit_max_views: Optional[int] = None
|
| 57 |
sort_context_index: Optional[bool] = False
|
| 58 |
-
use_index_to_load_chunk: Optional[bool] = False
|
| 59 |
-
pose_align_first_view: bool = False # align the camera pose to the first view
|
| 60 |
-
scale_extrinsics: float = 1.
|
| 61 |
-
metric_scale_align_dl3dv: bool = False
|
| 62 |
-
center_pose: bool = False # center and normalize the pose by the distance to the center
|
| 63 |
-
|
| 64 |
-
# mix re10k & dl3dv
|
| 65 |
-
mix_re10k: bool = False
|
| 66 |
-
re10k_min_view_dist: int = 40
|
| 67 |
-
re10k_max_view_dist: int = 300
|
| 68 |
-
|
| 69 |
-
# load remaining context views
|
| 70 |
-
load_remain_context: bool = False
|
| 71 |
-
num_remain_context: int = 8
|
| 72 |
|
| 73 |
index_name: str = "index.json"
|
| 74 |
|
|
@@ -104,14 +78,9 @@ class DatasetDL3DV(IterableDataset):
|
|
| 104 |
self.chunks = []
|
| 105 |
for i, root in enumerate(cfg.roots):
|
| 106 |
root = root / self.data_stage
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
root_chunks = sorted(list(set(json_dict.values())))
|
| 111 |
-
else:
|
| 112 |
-
root_chunks = sorted(
|
| 113 |
-
[path for path in root.iterdir() if path.suffix == ".torch"]
|
| 114 |
-
)
|
| 115 |
|
| 116 |
# mixed data training only evaluate on a single test set
|
| 117 |
if cfg.no_mix_test_set and self.data_stage in ['val', 'test'] and i > 0:
|
|
@@ -133,17 +102,11 @@ class DatasetDL3DV(IterableDataset):
|
|
| 133 |
if self.stage == "val":
|
| 134 |
self.chunks = self.chunks * int(1e6 // len(self.chunks))
|
| 135 |
|
| 136 |
-
if self.cfg.metric_scale_align_dl3dv:
|
| 137 |
-
# read invalid scales
|
| 138 |
-
scale_dir = '/cluster/project/cvg/haofei/datasets/depthsplat/dl3dv_metric_scale_factor'
|
| 139 |
-
filename = os.path.join(scale_dir, 'dl3dv_invalid.txt')
|
| 140 |
-
with open(filename, "r") as f:
|
| 141 |
-
self.invalid_scale_scenes = [line.strip() for line in f]
|
| 142 |
-
|
| 143 |
# Calculate actual number of scenes (keys of index file that their chunks exist).
|
|
|
|
| 144 |
num_available_scenes = 0
|
| 145 |
for scene, chunk_path in self.index.items():
|
| 146 |
-
if chunk_path in
|
| 147 |
num_available_scenes += 1
|
| 148 |
self.num_available_scenes = num_available_scenes
|
| 149 |
|
|
@@ -151,24 +114,6 @@ class DatasetDL3DV(IterableDataset):
|
|
| 151 |
indices = torch.randperm(len(lst))
|
| 152 |
return [lst[x] for x in indices]
|
| 153 |
|
| 154 |
-
def _get_scale_factor(self, scene: str) -> float:
|
| 155 |
-
"""Get the scale factor for a scene."""
|
| 156 |
-
if self.cfg.metric_scale_align_dl3dv:
|
| 157 |
-
scale_dir = '/cluster/project/cvg/haofei/datasets/depthsplat/dl3dv_metric_scale_factor'
|
| 158 |
-
if self.stage == 'train':
|
| 159 |
-
folder = scene.split('_')[1]
|
| 160 |
-
else:
|
| 161 |
-
folder = scene
|
| 162 |
-
filename = os.path.join(scale_dir, folder, 'scale_factor.txt')
|
| 163 |
-
|
| 164 |
-
if not os.path.exists(filename) or folder in self.invalid_scale_scenes:
|
| 165 |
-
return self.cfg.scale_extrinsics
|
| 166 |
-
else:
|
| 167 |
-
with open(filename, "r") as f:
|
| 168 |
-
return float(f.read().strip())
|
| 169 |
-
else:
|
| 170 |
-
return self.cfg.scale_extrinsics
|
| 171 |
-
|
| 172 |
def _process_example_to_batch(
|
| 173 |
self,
|
| 174 |
example: dict,
|
|
@@ -176,7 +121,6 @@ class DatasetDL3DV(IterableDataset):
|
|
| 176 |
intrinsics: Tensor,
|
| 177 |
context_indices: Tensor,
|
| 178 |
target_indices: Tensor,
|
| 179 |
-
scale_factor: float,
|
| 180 |
) -> Optional[dict]:
|
| 181 |
"""
|
| 182 |
Process an example into a batch dict (original behavior).
|
|
@@ -184,19 +128,6 @@ class DatasetDL3DV(IterableDataset):
|
|
| 184 |
"""
|
| 185 |
scene = example["key"]
|
| 186 |
|
| 187 |
-
# Load remaining context views if configured
|
| 188 |
-
if self.cfg.load_remain_context:
|
| 189 |
-
remaining_indices = get_remaining_indices(
|
| 190 |
-
context_indices, target_indices, self.cfg.num_remain_context
|
| 191 |
-
)
|
| 192 |
-
remain_context_images = [
|
| 193 |
-
example["images"][index.item()] for index in remaining_indices
|
| 194 |
-
]
|
| 195 |
-
try:
|
| 196 |
-
remain_context_images = self.convert_images(remain_context_images)
|
| 197 |
-
except OSError:
|
| 198 |
-
return None
|
| 199 |
-
|
| 200 |
# Load context images
|
| 201 |
context_images = [
|
| 202 |
example["images"][index.item()] for index in context_indices
|
|
@@ -216,16 +147,7 @@ class DatasetDL3DV(IterableDataset):
|
|
| 216 |
return None
|
| 217 |
|
| 218 |
# Validate image shapes
|
| 219 |
-
|
| 220 |
-
if self.cfg.highres:
|
| 221 |
-
expected_shape = (3, 720, 1280)
|
| 222 |
-
else:
|
| 223 |
-
expected_shape = (3, 360, 640)
|
| 224 |
-
else:
|
| 225 |
-
expected_shape = tuple([3, *self.cfg.ori_image_shape])
|
| 226 |
-
|
| 227 |
-
if self.stage in ['test', 'val'] or 'dl3dv' in scene:
|
| 228 |
-
expected_shape = tuple([3, *self.cfg.ori_image_shape])
|
| 229 |
|
| 230 |
if self.cfg.skip_bad_shape:
|
| 231 |
if context_images.shape[1:] != expected_shape or target_images.shape[1:] != expected_shape:
|
|
@@ -236,9 +158,6 @@ class DatasetDL3DV(IterableDataset):
|
|
| 236 |
)
|
| 237 |
return None
|
| 238 |
|
| 239 |
-
if self.cfg.load_remain_context and remain_context_images.shape[1:] != expected_shape:
|
| 240 |
-
return None
|
| 241 |
-
|
| 242 |
# Apply pose transformations
|
| 243 |
if self.cfg.pose_align_middle_view:
|
| 244 |
mid_index = context_indices.shape[0] // 2
|
|
@@ -246,12 +165,6 @@ class DatasetDL3DV(IterableDataset):
|
|
| 246 |
extrinsics[context_indices][mid_index:mid_index + 1], extrinsics
|
| 247 |
)
|
| 248 |
|
| 249 |
-
if self.cfg.pose_align_first_view:
|
| 250 |
-
extrinsics = camera_normalization(extrinsics[context_indices][0:1], extrinsics)
|
| 251 |
-
|
| 252 |
-
if self.cfg.center_pose:
|
| 253 |
-
extrinsics = center_norm_pose(extrinsics)
|
| 254 |
-
|
| 255 |
# Validate extrinsics
|
| 256 |
if any(torch.isnan(torch.det(extrinsics[context_indices][:, :3, :3]))):
|
| 257 |
return None
|
|
@@ -272,20 +185,6 @@ class DatasetDL3DV(IterableDataset):
|
|
| 272 |
):
|
| 273 |
return None
|
| 274 |
|
| 275 |
-
if self.cfg.load_remain_context:
|
| 276 |
-
if any(torch.isnan(torch.det(extrinsics[remaining_indices][:, :3, :3]))):
|
| 277 |
-
return None
|
| 278 |
-
if (extrinsics[remaining_indices][:, :3, 3] > 1e3).any():
|
| 279 |
-
return None
|
| 280 |
-
if not torch.allclose(
|
| 281 |
-
torch.det(extrinsics[remaining_indices][:, :3, :3]),
|
| 282 |
-
extrinsics[remaining_indices][:, :3, :3].new_tensor(1)
|
| 283 |
-
):
|
| 284 |
-
return None
|
| 285 |
-
|
| 286 |
-
# Apply scale factor
|
| 287 |
-
extrinsics[:, :3, 3] *= scale_factor
|
| 288 |
-
|
| 289 |
# Build output
|
| 290 |
example_out = {
|
| 291 |
"context": {
|
|
@@ -307,22 +206,12 @@ class DatasetDL3DV(IterableDataset):
|
|
| 307 |
"scene": scene,
|
| 308 |
}
|
| 309 |
|
| 310 |
-
if self.cfg.load_remain_context:
|
| 311 |
-
example_out["context_remain"] = {
|
| 312 |
-
"extrinsics": extrinsics[remaining_indices],
|
| 313 |
-
"intrinsics": intrinsics[remaining_indices],
|
| 314 |
-
"image": remain_context_images,
|
| 315 |
-
"near": self.get_bound("near", len(remaining_indices)),
|
| 316 |
-
"far": self.get_bound("far", len(remaining_indices)),
|
| 317 |
-
"index": remaining_indices,
|
| 318 |
-
}
|
| 319 |
-
|
| 320 |
return example_out
|
| 321 |
|
| 322 |
def __iter__(self):
|
| 323 |
# Chunks must be shuffled here (not inside __init__) for validation to show
|
| 324 |
# random chunks.
|
| 325 |
-
if self.stage
|
| 326 |
self.chunks = self.shuffle(self.chunks)
|
| 327 |
|
| 328 |
# When testing, the data loaders alternate chunks.
|
|
@@ -350,7 +239,7 @@ class DatasetDL3DV(IterableDataset):
|
|
| 350 |
else:
|
| 351 |
chunk = item * len(chunk)
|
| 352 |
|
| 353 |
-
if self.stage
|
| 354 |
chunk = self.shuffle(chunk)
|
| 355 |
|
| 356 |
times_per_scene = (
|
|
@@ -373,8 +262,6 @@ class DatasetDL3DV(IterableDataset):
|
|
| 373 |
if (get_fov(intrinsics).rad2deg() > self.cfg.max_fov).any():
|
| 374 |
continue
|
| 375 |
|
| 376 |
-
scale_factor = self._get_scale_factor(scene)
|
| 377 |
-
|
| 378 |
try:
|
| 379 |
extra_kwargs = {}
|
| 380 |
if self.cfg.overfit_to_scene is not None and self.stage != "test":
|
|
@@ -383,16 +270,12 @@ class DatasetDL3DV(IterableDataset):
|
|
| 383 |
else self.cfg.overfit_max_views
|
| 384 |
)
|
| 385 |
|
| 386 |
-
is_re10k = self.cfg.mix_re10k and 'dl3dv' not in scene and self.stage == 'train'
|
| 387 |
-
|
| 388 |
out_data = self.view_sampler.sample(
|
| 389 |
scene,
|
| 390 |
extrinsics,
|
| 391 |
intrinsics,
|
| 392 |
min_context_views=self.cfg.min_views,
|
| 393 |
max_context_views=self.cfg.max_views,
|
| 394 |
-
min_view_dist=self.cfg.re10k_min_view_dist if is_re10k else None,
|
| 395 |
-
max_view_dist=self.cfg.re10k_max_view_dist if is_re10k else None,
|
| 396 |
**extra_kwargs,
|
| 397 |
)
|
| 398 |
|
|
@@ -421,7 +304,7 @@ class DatasetDL3DV(IterableDataset):
|
|
| 421 |
for context_indices, target_indices in zip(c_list, t_list):
|
| 422 |
example_out = self._process_example_to_batch(
|
| 423 |
example, extrinsics.clone(), intrinsics,
|
| 424 |
-
context_indices, target_indices,
|
| 425 |
)
|
| 426 |
|
| 427 |
if example_out is None:
|
|
@@ -440,13 +323,7 @@ class DatasetDL3DV(IterableDataset):
|
|
| 440 |
if self.cfg.image_shape == list(context_images.shape[2:]):
|
| 441 |
yield example_out
|
| 442 |
else:
|
| 443 |
-
|
| 444 |
-
crop_h = random.randint(self.cfg.min_size[0], self.cfg.max_size[0] + 1) // 64 * 64
|
| 445 |
-
crop_w = random.randint(self.cfg.min_size[1], self.cfg.max_size[1] + 1) // 64 * 64
|
| 446 |
-
crop_size = (crop_h, crop_w)
|
| 447 |
-
yield apply_crop_shim(example_out, crop_size)
|
| 448 |
-
else:
|
| 449 |
-
yield apply_crop_shim(example_out, tuple(self.cfg.image_shape))
|
| 450 |
|
| 451 |
def convert_poses(
|
| 452 |
self,
|
|
@@ -472,22 +349,16 @@ class DatasetDL3DV(IterableDataset):
|
|
| 472 |
w2c[:, :3] = rearrange(poses[:, 6:], "b (h w) -> b h w", h=3, w=4)
|
| 473 |
|
| 474 |
if self.cfg.opencv_pose_format:
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
c2w = torch.matmul(c2w, blender2opencv)
|
| 486 |
-
c2w[:, 2, :] *= -1
|
| 487 |
-
c2w = c2w[:, torch.tensor(np.array([1, 0, 2, 3])), :]
|
| 488 |
-
c2w[:, 0:3, 1:3] *= -1
|
| 489 |
-
|
| 490 |
-
return c2w
|
| 491 |
|
| 492 |
def convert_images(
|
| 493 |
self,
|
|
@@ -575,66 +446,3 @@ def camera_normalization(pivotal_pose: torch.Tensor, poses: torch.Tensor):
|
|
| 575 |
normalized_poses = camera_norm_matrix_manuall @ poses # [N, 4, 4]
|
| 576 |
|
| 577 |
return normalized_poses
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
def center_norm_pose(extrinsics):
|
| 581 |
-
# extrinsics: [V, 4, 4]
|
| 582 |
-
cam_centers = extrinsics[:, :3, 3] # [V, 3]
|
| 583 |
-
avg_center = cam_centers.mean(dim=0, keepdim=True) # [1, 3]
|
| 584 |
-
dist = (cam_centers - avg_center).norm(dim=1, keepdim=True) # [V, 1]
|
| 585 |
-
scale = dist.max()
|
| 586 |
-
|
| 587 |
-
# translate
|
| 588 |
-
extrinsics = extrinsics.clone()
|
| 589 |
-
extrinsics[:, :3, 3] -= avg_center
|
| 590 |
-
extrinsics[:, :3, 3] /= scale
|
| 591 |
-
|
| 592 |
-
return extrinsics
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
def get_remaining_indices(context_indices: torch.Tensor,
|
| 596 |
-
target_indices: torch.Tensor,
|
| 597 |
-
num_remain_context: int) -> torch.Tensor:
|
| 598 |
-
"""
|
| 599 |
-
Randomly selects a fixed number of remaining indices in the range [min(context), max(context)],
|
| 600 |
-
excluding those in context or target. Pads by repeating if not enough remain.
|
| 601 |
-
|
| 602 |
-
Args:
|
| 603 |
-
context_indices (torch.Tensor): 1D tensor of context indices.
|
| 604 |
-
target_indices (torch.Tensor): 1D tensor of target indices.
|
| 605 |
-
num_remain_context (int): Number of remaining indices to return.
|
| 606 |
-
|
| 607 |
-
Returns:
|
| 608 |
-
torch.Tensor: 1D tensor of length `num_remain_context`.
|
| 609 |
-
"""
|
| 610 |
-
if context_indices.numel() == 0:
|
| 611 |
-
raise ValueError("context_indices must not be empty.")
|
| 612 |
-
|
| 613 |
-
min_idx = torch.min(context_indices).item()
|
| 614 |
-
max_idx = torch.max(context_indices).item()
|
| 615 |
-
|
| 616 |
-
full_range = torch.arange(min_idx, max_idx + 1, dtype=torch.long)
|
| 617 |
-
exclude_indices = torch.cat([context_indices, target_indices])
|
| 618 |
-
mask = ~torch.isin(full_range, exclude_indices)
|
| 619 |
-
|
| 620 |
-
remaining = full_range[mask]
|
| 621 |
-
|
| 622 |
-
if remaining.numel() == 0:
|
| 623 |
-
# Nothing to sample from; repeat the first context index (or any fallback)
|
| 624 |
-
return context_indices[0].repeat(num_remain_context)
|
| 625 |
-
|
| 626 |
-
# return all
|
| 627 |
-
selected = remaining
|
| 628 |
-
|
| 629 |
-
# Randomly sample with or without replacement
|
| 630 |
-
# if remaining.numel() >= num_remain_context:
|
| 631 |
-
# selected = remaining[torch.randperm(remaining.numel())[:num_remain_context]]
|
| 632 |
-
# else:
|
| 633 |
-
# # return all
|
| 634 |
-
# selected = remaining
|
| 635 |
-
# # # Repeat with wrap-around to pad
|
| 636 |
-
# # num_repeat = (num_remain_context + remaining.numel() - 1) // remaining.numel()
|
| 637 |
-
# # padded = remaining.repeat(num_repeat)[:num_remain_context]
|
| 638 |
-
# # selected = padded[torch.randperm(num_remain_context)] # Shuffle for randomness
|
| 639 |
-
|
| 640 |
-
return selected.sort().values
|
|
|
|
| 12 |
from PIL import Image
|
| 13 |
from torch import Tensor
|
| 14 |
from torch.utils.data import IterableDataset
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
from ..geometry.projection import get_fov
|
| 17 |
from .dataset import DatasetCfgCommon
|
|
|
|
| 25 |
class DatasetDL3DVCfg(DatasetCfgCommon):
|
| 26 |
name: Literal["dl3dv"]
|
| 27 |
roots: list[Path]
|
|
|
|
| 28 |
max_fov: float
|
|
|
|
| 29 |
augment: bool
|
| 30 |
test_len: int
|
| 31 |
test_chunk_interval: int
|
| 32 |
train_times_per_scene: int
|
| 33 |
test_times_per_scene: int
|
| 34 |
ori_image_shape: list[int]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
skip_bad_shape: bool = True
|
| 37 |
near: float = -1.0
|
| 38 |
far: float = -1.0
|
|
|
|
| 39 |
shuffle_val: bool = True
|
| 40 |
no_mix_test_set: bool = True
|
|
|
|
| 41 |
min_views: int = 0
|
| 42 |
max_views: int = 0
|
|
|
|
| 43 |
sort_target_index: Optional[bool] = False
|
| 44 |
overfit_max_views: Optional[int] = None
|
| 45 |
sort_context_index: Optional[bool] = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
index_name: str = "index.json"
|
| 48 |
|
|
|
|
| 78 |
self.chunks = []
|
| 79 |
for i, root in enumerate(cfg.roots):
|
| 80 |
root = root / self.data_stage
|
| 81 |
+
root_chunks = sorted(
|
| 82 |
+
[path for path in root.iterdir() if path.suffix == ".torch"]
|
| 83 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
|
| 85 |
# mixed data training only evaluate on a single test set
|
| 86 |
if cfg.no_mix_test_set and self.data_stage in ['val', 'test'] and i > 0:
|
|
|
|
| 102 |
if self.stage == "val":
|
| 103 |
self.chunks = self.chunks * int(1e6 // len(self.chunks))
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
# Calculate actual number of scenes (keys of index file that their chunks exist).
|
| 106 |
+
chunk_set = set(self.chunks) # self.chunks can be huge (val multiplies it) -> set lookup
|
| 107 |
num_available_scenes = 0
|
| 108 |
for scene, chunk_path in self.index.items():
|
| 109 |
+
if chunk_path in chunk_set:
|
| 110 |
num_available_scenes += 1
|
| 111 |
self.num_available_scenes = num_available_scenes
|
| 112 |
|
|
|
|
| 114 |
indices = torch.randperm(len(lst))
|
| 115 |
return [lst[x] for x in indices]
|
| 116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
def _process_example_to_batch(
|
| 118 |
self,
|
| 119 |
example: dict,
|
|
|
|
| 121 |
intrinsics: Tensor,
|
| 122 |
context_indices: Tensor,
|
| 123 |
target_indices: Tensor,
|
|
|
|
| 124 |
) -> Optional[dict]:
|
| 125 |
"""
|
| 126 |
Process an example into a batch dict (original behavior).
|
|
|
|
| 128 |
"""
|
| 129 |
scene = example["key"]
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
# Load context images
|
| 132 |
context_images = [
|
| 133 |
example["images"][index.item()] for index in context_indices
|
|
|
|
| 147 |
return None
|
| 148 |
|
| 149 |
# Validate image shapes
|
| 150 |
+
expected_shape = tuple([3, *self.cfg.ori_image_shape])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
if self.cfg.skip_bad_shape:
|
| 153 |
if context_images.shape[1:] != expected_shape or target_images.shape[1:] != expected_shape:
|
|
|
|
| 158 |
)
|
| 159 |
return None
|
| 160 |
|
|
|
|
|
|
|
|
|
|
| 161 |
# Apply pose transformations
|
| 162 |
if self.cfg.pose_align_middle_view:
|
| 163 |
mid_index = context_indices.shape[0] // 2
|
|
|
|
| 165 |
extrinsics[context_indices][mid_index:mid_index + 1], extrinsics
|
| 166 |
)
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
# Validate extrinsics
|
| 169 |
if any(torch.isnan(torch.det(extrinsics[context_indices][:, :3, :3]))):
|
| 170 |
return None
|
|
|
|
| 185 |
):
|
| 186 |
return None
|
| 187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
# Build output
|
| 189 |
example_out = {
|
| 190 |
"context": {
|
|
|
|
| 206 |
"scene": scene,
|
| 207 |
}
|
| 208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
return example_out
|
| 210 |
|
| 211 |
def __iter__(self):
|
| 212 |
# Chunks must be shuffled here (not inside __init__) for validation to show
|
| 213 |
# random chunks.
|
| 214 |
+
if self.stage == "train" or (self.cfg.shuffle_val and self.stage == "val"):
|
| 215 |
self.chunks = self.shuffle(self.chunks)
|
| 216 |
|
| 217 |
# When testing, the data loaders alternate chunks.
|
|
|
|
| 239 |
else:
|
| 240 |
chunk = item * len(chunk)
|
| 241 |
|
| 242 |
+
if self.stage == "train" or (self.cfg.shuffle_val and self.stage == "val"):
|
| 243 |
chunk = self.shuffle(chunk)
|
| 244 |
|
| 245 |
times_per_scene = (
|
|
|
|
| 262 |
if (get_fov(intrinsics).rad2deg() > self.cfg.max_fov).any():
|
| 263 |
continue
|
| 264 |
|
|
|
|
|
|
|
| 265 |
try:
|
| 266 |
extra_kwargs = {}
|
| 267 |
if self.cfg.overfit_to_scene is not None and self.stage != "test":
|
|
|
|
| 270 |
else self.cfg.overfit_max_views
|
| 271 |
)
|
| 272 |
|
|
|
|
|
|
|
| 273 |
out_data = self.view_sampler.sample(
|
| 274 |
scene,
|
| 275 |
extrinsics,
|
| 276 |
intrinsics,
|
| 277 |
min_context_views=self.cfg.min_views,
|
| 278 |
max_context_views=self.cfg.max_views,
|
|
|
|
|
|
|
| 279 |
**extra_kwargs,
|
| 280 |
)
|
| 281 |
|
|
|
|
| 304 |
for context_indices, target_indices in zip(c_list, t_list):
|
| 305 |
example_out = self._process_example_to_batch(
|
| 306 |
example, extrinsics.clone(), intrinsics,
|
| 307 |
+
context_indices, target_indices,
|
| 308 |
)
|
| 309 |
|
| 310 |
if example_out is None:
|
|
|
|
| 323 |
if self.cfg.image_shape == list(context_images.shape[2:]):
|
| 324 |
yield example_out
|
| 325 |
else:
|
| 326 |
+
yield apply_crop_shim(example_out, tuple(self.cfg.image_shape))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
|
| 328 |
def convert_poses(
|
| 329 |
self,
|
|
|
|
| 349 |
w2c[:, :3] = rearrange(poses[:, 6:], "b (h w) -> b h w", h=3, w=4)
|
| 350 |
|
| 351 |
if self.cfg.opencv_pose_format:
|
| 352 |
+
# DL3DV chunk poses are already in OpenCV convention (verified: they match the
|
| 353 |
+
# COLMAP/OpenCV poses up to a world rotation), so opengl_to_opencv would corrupt
|
| 354 |
+
# them. Fail loudly instead of silently converting. NOTE: the resplat_v2 baseline
|
| 355 |
+
# in scripts/testing/_common/run_experiments.sh sets opencv_pose_format=true and
|
| 356 |
+
# will now hit this — update that path if resplat_v2 is still needed.
|
| 357 |
+
raise ValueError(
|
| 358 |
+
"opencv_pose_format=true is not supported for DL3DV: chunk poses are already "
|
| 359 |
+
"in OpenCV convention and need no conversion."
|
| 360 |
+
)
|
| 361 |
+
return w2c.inverse(), intrinsics
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
def convert_images(
|
| 364 |
self,
|
|
|
|
| 446 |
normalized_poses = camera_norm_matrix_manuall @ poses # [N, 4, 4]
|
| 447 |
|
| 448 |
return normalized_poses
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/dataset/dataset_re10k.py
CHANGED
|
@@ -8,13 +8,11 @@ from typing import Literal, Optional
|
|
| 8 |
import numpy as np
|
| 9 |
import torch
|
| 10 |
import torchvision.transforms as tf
|
| 11 |
-
import torch.nn.functional as F
|
| 12 |
from einops import rearrange, repeat
|
| 13 |
from jaxtyping import Float, UInt8
|
| 14 |
from PIL import Image
|
| 15 |
from torch import Tensor
|
| 16 |
from torch.utils.data import IterableDataset
|
| 17 |
-
import cv2
|
| 18 |
|
| 19 |
from ..geometry.projection import get_fov
|
| 20 |
from .dataset import DatasetCfgCommon
|
|
@@ -22,38 +20,25 @@ from .shims.augmentation_shim import apply_augmentation_shim
|
|
| 22 |
from .shims.crop_shim import apply_crop_shim
|
| 23 |
from .data_types import Stage
|
| 24 |
from .view_sampler import ViewSampler
|
| 25 |
-
from .dataset_dl3dv import get_remaining_indices
|
| 26 |
|
| 27 |
|
| 28 |
@dataclass
|
| 29 |
class DatasetRE10kCfg(DatasetCfgCommon):
|
| 30 |
name: Literal["re10k"]
|
| 31 |
roots: list[Path]
|
| 32 |
-
baseline_epsilon: float
|
| 33 |
max_fov: float
|
| 34 |
-
make_baseline_1: bool
|
| 35 |
augment: bool
|
| 36 |
test_len: int
|
| 37 |
test_chunk_interval: int
|
| 38 |
-
average_pose: bool
|
| 39 |
skip_bad_shape: bool = True
|
| 40 |
near: float = -1.0
|
| 41 |
far: float = -1.0
|
| 42 |
-
baseline_scale_bounds: bool = True
|
| 43 |
shuffle_val: bool = True
|
| 44 |
train_times_per_scene: int = 1
|
| 45 |
highres: bool = False
|
| 46 |
scannet: bool = False
|
| 47 |
tartanair: bool = False
|
| 48 |
-
use_index_to_load_chunk: Optional[bool] = False
|
| 49 |
load_depth: bool = False
|
| 50 |
-
pose_align_first_view: bool = False # align the camera pose to the first view
|
| 51 |
-
center_pose: bool = False # center and normalize the pose by the distance to the center
|
| 52 |
-
|
| 53 |
-
scale_extrinsics: float = 1.
|
| 54 |
-
|
| 55 |
-
# load remaining context views
|
| 56 |
-
load_remain_context: bool = False
|
| 57 |
|
| 58 |
|
| 59 |
class DatasetRE10k(IterableDataset):
|
|
@@ -86,14 +71,9 @@ class DatasetRE10k(IterableDataset):
|
|
| 86 |
self.chunks = []
|
| 87 |
for i, root in enumerate(cfg.roots):
|
| 88 |
root = root / self.data_stage
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
root_chunks = sorted(list(set(json_dict.values())))
|
| 93 |
-
else:
|
| 94 |
-
root_chunks = sorted(
|
| 95 |
-
[path for path in root.iterdir() if path.suffix == ".torch"]
|
| 96 |
-
)
|
| 97 |
|
| 98 |
self.chunks.extend(root_chunks)
|
| 99 |
if self.cfg.overfit_to_scene is not None:
|
|
@@ -110,7 +90,7 @@ class DatasetRE10k(IterableDataset):
|
|
| 110 |
def __iter__(self):
|
| 111 |
# Chunks must be shuffled here (not inside __init__) for validation to show
|
| 112 |
# random chunks.
|
| 113 |
-
if self.stage
|
| 114 |
self.chunks = self.shuffle(self.chunks)
|
| 115 |
|
| 116 |
# When testing, the data loaders alternate chunks.
|
|
@@ -134,7 +114,7 @@ class DatasetRE10k(IterableDataset):
|
|
| 134 |
assert len(item) == 1
|
| 135 |
chunk = item * len(chunk)
|
| 136 |
|
| 137 |
-
if self.stage
|
| 138 |
chunk = self.shuffle(chunk)
|
| 139 |
|
| 140 |
times_per_scene = (
|
|
@@ -163,23 +143,6 @@ class DatasetRE10k(IterableDataset):
|
|
| 163 |
if (get_fov(intrinsics).rad2deg() > self.cfg.max_fov).any():
|
| 164 |
continue
|
| 165 |
|
| 166 |
-
# load remaining context views
|
| 167 |
-
if self.cfg.load_remain_context:
|
| 168 |
-
# randomly select fixed number of remaining views such that they can be batched
|
| 169 |
-
remaining_indices = get_remaining_indices(context_indices, target_indices,
|
| 170 |
-
0)
|
| 171 |
-
|
| 172 |
-
# Load the images.
|
| 173 |
-
remain_context_images = [
|
| 174 |
-
example["images"][index.item()] for index in remaining_indices
|
| 175 |
-
]
|
| 176 |
-
|
| 177 |
-
try:
|
| 178 |
-
remain_context_images = self.convert_images(remain_context_images)
|
| 179 |
-
except OSError:
|
| 180 |
-
# some data might be corrupted
|
| 181 |
-
continue
|
| 182 |
-
|
| 183 |
# Load the images.
|
| 184 |
context_images = [
|
| 185 |
example["images"][index.item()] for index in context_indices
|
|
@@ -207,12 +170,6 @@ class DatasetRE10k(IterableDataset):
|
|
| 207 |
)
|
| 208 |
continue
|
| 209 |
|
| 210 |
-
if self.cfg.load_remain_context:
|
| 211 |
-
remain_context_invalid = remain_context_images.shape[1:] != expected_shape
|
| 212 |
-
|
| 213 |
-
if self.cfg.skip_bad_shape and remain_context_invalid:
|
| 214 |
-
continue
|
| 215 |
-
|
| 216 |
# check the extrinsics
|
| 217 |
if any(torch.isnan(torch.det(extrinsics[context_indices][:, :3, :3]))):
|
| 218 |
continue
|
|
@@ -220,9 +177,6 @@ class DatasetRE10k(IterableDataset):
|
|
| 220 |
if any(torch.isnan(torch.det(extrinsics[target_indices][:, :3, :3]))):
|
| 221 |
continue
|
| 222 |
|
| 223 |
-
if self.cfg.average_pose:
|
| 224 |
-
extrinsics = self.preprocess_poses(extrinsics)
|
| 225 |
-
|
| 226 |
# load depth
|
| 227 |
if self.cfg.load_depth:
|
| 228 |
context_depths = [
|
|
@@ -245,15 +199,12 @@ class DatasetRE10k(IterableDataset):
|
|
| 245 |
else:
|
| 246 |
raise NotImplementedError
|
| 247 |
|
| 248 |
-
# align pose to the
|
| 249 |
-
if self.cfg.
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
# scale the scene when necessary: only scale the extrinsics
|
| 256 |
-
extrinsics[:, :3, 3] *= self.cfg.scale_extrinsics
|
| 257 |
|
| 258 |
example = {
|
| 259 |
"context": {
|
|
@@ -275,19 +226,6 @@ class DatasetRE10k(IterableDataset):
|
|
| 275 |
"scene": scene,
|
| 276 |
}
|
| 277 |
|
| 278 |
-
if self.cfg.load_remain_context:
|
| 279 |
-
example.update({
|
| 280 |
-
"context_remain": {
|
| 281 |
-
"extrinsics": extrinsics[remaining_indices],
|
| 282 |
-
"intrinsics": intrinsics[remaining_indices],
|
| 283 |
-
"image": remain_context_images,
|
| 284 |
-
"near": self.get_bound("near", len(remaining_indices)),
|
| 285 |
-
"far": self.get_bound("far", len(remaining_indices)),
|
| 286 |
-
"index": remaining_indices,
|
| 287 |
-
}
|
| 288 |
-
}
|
| 289 |
-
)
|
| 290 |
-
|
| 291 |
if self.cfg.load_depth:
|
| 292 |
example['context']['depth'] = context_depths
|
| 293 |
example['target']['depth'] = target_depths
|
|
@@ -400,65 +338,20 @@ class DatasetRE10k(IterableDataset):
|
|
| 400 |
else len(self.index.keys()) * self.cfg.train_times_per_scene
|
| 401 |
)
|
| 402 |
|
| 403 |
-
def preprocess_poses(
|
| 404 |
-
self,
|
| 405 |
-
in_c2ws: torch.Tensor,
|
| 406 |
-
scene_scale_factor=1.35,
|
| 407 |
-
):
|
| 408 |
-
"""
|
| 409 |
-
Ref: https://github.com/Haian-Jin/LVSM/blob/main/data/dataset_scene.py
|
| 410 |
-
Preprocess the poses to:
|
| 411 |
-
1. translate and rotate the scene to align the average camera direction and position
|
| 412 |
-
2. rescale the whole scene to a fixed scale
|
| 413 |
-
"""
|
| 414 |
-
|
| 415 |
-
# Translation and Rotation
|
| 416 |
-
# align coordinate system (OpenCV coordinate) to the mean camera
|
| 417 |
-
# center is the average of all camera centers
|
| 418 |
-
# average direction vectors are computed from all camera direction vectors (average down and forward)
|
| 419 |
-
center = in_c2ws[:, :3, 3].mean(0)
|
| 420 |
-
avg_forward = F.normalize(in_c2ws[:, :3, 2].mean(0), dim=-1) # average forward direction (z of opencv camera)
|
| 421 |
-
avg_down = in_c2ws[:, :3, 1].mean(0) # average down direction (y of opencv camera)
|
| 422 |
-
avg_right = F.normalize(torch.cross(avg_down, avg_forward, dim=-1), dim=-1) # (x of opencv camera)
|
| 423 |
-
avg_down = F.normalize(torch.cross(avg_forward, avg_right, dim=-1), dim=-1) # (y of opencv camera)
|
| 424 |
-
|
| 425 |
-
avg_pose = torch.eye(4, device=in_c2ws.device) # average c2w matrix
|
| 426 |
-
avg_pose[:3, :3] = torch.stack([avg_right, avg_down, avg_forward], dim=-1)
|
| 427 |
-
avg_pose[:3, 3] = center
|
| 428 |
-
avg_pose = torch.linalg.inv(avg_pose) # average w2c matrix
|
| 429 |
-
in_c2ws = avg_pose @ in_c2ws
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
# Rescale the whole scene to a fixed scale
|
| 433 |
-
scene_scale = torch.max(torch.abs(in_c2ws[:, :3, 3]))
|
| 434 |
-
scene_scale = scene_scale_factor * scene_scale
|
| 435 |
-
|
| 436 |
-
in_c2ws[:, :3, 3] /= scene_scale
|
| 437 |
-
|
| 438 |
-
return in_c2ws
|
| 439 |
-
|
| 440 |
|
| 441 |
def camera_normalization(pivotal_pose: torch.Tensor, poses: torch.Tensor):
|
| 442 |
# [1, 4, 4], [N, 4, 4]
|
| 443 |
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
#
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
|
|
|
| 451 |
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
cam_centers = extrinsics[:, :3, 3] # [V, 3]
|
| 455 |
-
avg_center = cam_centers.mean(dim=0, keepdim=True) # [1, 3]
|
| 456 |
-
dist = (cam_centers - avg_center).norm(dim=1, keepdim=True) # [V, 1]
|
| 457 |
-
scale = dist.max()
|
| 458 |
-
|
| 459 |
-
# translate
|
| 460 |
-
extrinsics = extrinsics.clone()
|
| 461 |
-
extrinsics[:, :3, 3] -= avg_center
|
| 462 |
-
extrinsics[:, :3, 3] /= scale
|
| 463 |
|
| 464 |
-
return
|
|
|
|
| 8 |
import numpy as np
|
| 9 |
import torch
|
| 10 |
import torchvision.transforms as tf
|
|
|
|
| 11 |
from einops import rearrange, repeat
|
| 12 |
from jaxtyping import Float, UInt8
|
| 13 |
from PIL import Image
|
| 14 |
from torch import Tensor
|
| 15 |
from torch.utils.data import IterableDataset
|
|
|
|
| 16 |
|
| 17 |
from ..geometry.projection import get_fov
|
| 18 |
from .dataset import DatasetCfgCommon
|
|
|
|
| 20 |
from .shims.crop_shim import apply_crop_shim
|
| 21 |
from .data_types import Stage
|
| 22 |
from .view_sampler import ViewSampler
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
@dataclass
|
| 26 |
class DatasetRE10kCfg(DatasetCfgCommon):
|
| 27 |
name: Literal["re10k"]
|
| 28 |
roots: list[Path]
|
|
|
|
| 29 |
max_fov: float
|
|
|
|
| 30 |
augment: bool
|
| 31 |
test_len: int
|
| 32 |
test_chunk_interval: int
|
|
|
|
| 33 |
skip_bad_shape: bool = True
|
| 34 |
near: float = -1.0
|
| 35 |
far: float = -1.0
|
|
|
|
| 36 |
shuffle_val: bool = True
|
| 37 |
train_times_per_scene: int = 1
|
| 38 |
highres: bool = False
|
| 39 |
scannet: bool = False
|
| 40 |
tartanair: bool = False
|
|
|
|
| 41 |
load_depth: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
class DatasetRE10k(IterableDataset):
|
|
|
|
| 71 |
self.chunks = []
|
| 72 |
for i, root in enumerate(cfg.roots):
|
| 73 |
root = root / self.data_stage
|
| 74 |
+
root_chunks = sorted(
|
| 75 |
+
[path for path in root.iterdir() if path.suffix == ".torch"]
|
| 76 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
self.chunks.extend(root_chunks)
|
| 79 |
if self.cfg.overfit_to_scene is not None:
|
|
|
|
| 90 |
def __iter__(self):
|
| 91 |
# Chunks must be shuffled here (not inside __init__) for validation to show
|
| 92 |
# random chunks.
|
| 93 |
+
if self.stage == "train" or (self.cfg.shuffle_val and self.stage == "val"):
|
| 94 |
self.chunks = self.shuffle(self.chunks)
|
| 95 |
|
| 96 |
# When testing, the data loaders alternate chunks.
|
|
|
|
| 114 |
assert len(item) == 1
|
| 115 |
chunk = item * len(chunk)
|
| 116 |
|
| 117 |
+
if self.stage == "train" or (self.cfg.shuffle_val and self.stage == "val"):
|
| 118 |
chunk = self.shuffle(chunk)
|
| 119 |
|
| 120 |
times_per_scene = (
|
|
|
|
| 143 |
if (get_fov(intrinsics).rad2deg() > self.cfg.max_fov).any():
|
| 144 |
continue
|
| 145 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
# Load the images.
|
| 147 |
context_images = [
|
| 148 |
example["images"][index.item()] for index in context_indices
|
|
|
|
| 170 |
)
|
| 171 |
continue
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
# check the extrinsics
|
| 174 |
if any(torch.isnan(torch.det(extrinsics[context_indices][:, :3, :3]))):
|
| 175 |
continue
|
|
|
|
| 177 |
if any(torch.isnan(torch.det(extrinsics[target_indices][:, :3, :3]))):
|
| 178 |
continue
|
| 179 |
|
|
|
|
|
|
|
|
|
|
| 180 |
# load depth
|
| 181 |
if self.cfg.load_depth:
|
| 182 |
context_depths = [
|
|
|
|
| 199 |
else:
|
| 200 |
raise NotImplementedError
|
| 201 |
|
| 202 |
+
# align pose to the middle view
|
| 203 |
+
if self.cfg.pose_align_middle_view:
|
| 204 |
+
mid_index = context_indices.shape[0] // 2
|
| 205 |
+
extrinsics = camera_normalization(
|
| 206 |
+
extrinsics[context_indices][mid_index:mid_index + 1], extrinsics
|
| 207 |
+
)
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
example = {
|
| 210 |
"context": {
|
|
|
|
| 226 |
"scene": scene,
|
| 227 |
}
|
| 228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
if self.cfg.load_depth:
|
| 230 |
example['context']['depth'] = context_depths
|
| 231 |
example['target']['depth'] = target_depths
|
|
|
|
| 338 |
else len(self.index.keys()) * self.cfg.train_times_per_scene
|
| 339 |
)
|
| 340 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
|
| 342 |
def camera_normalization(pivotal_pose: torch.Tensor, poses: torch.Tensor):
|
| 343 |
# [1, 4, 4], [N, 4, 4]
|
| 344 |
|
| 345 |
+
# Manually calculate the inverse of SE(3) to avoid numerical issues
|
| 346 |
+
R = pivotal_pose[:, :3, :3] # [1, 3, 3]
|
| 347 |
+
t = pivotal_pose[:, :3, 3:] # [1, 3, 1]
|
| 348 |
+
R_inv = R.transpose(-1, -2) # [1, 3, 3]
|
| 349 |
+
t_inv = -R_inv @ t # [1, 3, 1]
|
| 350 |
+
camera_norm_matrix_manuall = torch.eye(4, dtype=poses.dtype, device=poses.device).unsqueeze(0) # [1, 4, 4]
|
| 351 |
+
camera_norm_matrix_manuall[:, :3, :3] = R_inv
|
| 352 |
+
camera_norm_matrix_manuall[:, :3, 3:] = t_inv
|
| 353 |
|
| 354 |
+
# normalize all views
|
| 355 |
+
normalized_poses = camera_norm_matrix_manuall @ poses # [N, 4, 4]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
|
| 357 |
+
return normalized_poses
|
optgs/dataset/shims/augmentation_shim.py
CHANGED
|
@@ -22,25 +22,26 @@ def reflect_views(views: AnyViews) -> AnyViews:
|
|
| 22 |
}
|
| 23 |
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def apply_augmentation_shim(
|
| 26 |
example: AnyExample,
|
| 27 |
generator: torch.Generator | None = None,
|
| 28 |
) -> AnyExample:
|
| 29 |
-
"""Randomly
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
if "context_remain" in example:
|
| 35 |
-
return {
|
| 36 |
-
**example,
|
| 37 |
-
"context": reflect_views(example["context"]),
|
| 38 |
-
"target": reflect_views(example["target"]),
|
| 39 |
-
"context_remain": reflect_views(example["context_remain"]),
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
return {
|
| 43 |
**example,
|
| 44 |
-
"context":
|
| 45 |
-
"target":
|
| 46 |
}
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
}
|
| 23 |
|
| 24 |
|
| 25 |
+
def mark_unflipped(views: AnyViews) -> AnyViews:
|
| 26 |
+
"""No-op augmentation that still records x_flipped=False, so the key is
|
| 27 |
+
always present and downstream code (e.g. BatchedViews.from_dict) need not
|
| 28 |
+
fall back to a default."""
|
| 29 |
+
return {**views, "x_flipped": False}
|
| 30 |
+
|
| 31 |
+
|
| 32 |
def apply_augmentation_shim(
|
| 33 |
example: AnyExample,
|
| 34 |
generator: torch.Generator | None = None,
|
| 35 |
) -> AnyExample:
|
| 36 |
+
"""Randomly horizontally-flip the scene (50% chance). Either way, x_flipped
|
| 37 |
+
is recorded on every view set so the key is always present."""
|
| 38 |
+
flip = torch.rand(tuple(), generator=generator) >= 0.5
|
| 39 |
+
transform = reflect_views if flip else mark_unflipped
|
| 40 |
+
out = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
**example,
|
| 42 |
+
"context": transform(example["context"]),
|
| 43 |
+
"target": transform(example["target"]),
|
| 44 |
}
|
| 45 |
+
if "context_remain" in example:
|
| 46 |
+
out["context_remain"] = transform(example["context_remain"])
|
| 47 |
+
return out
|
optgs/dataset/shims/bounds_shim.py
DELETED
|
@@ -1,80 +0,0 @@
|
|
| 1 |
-
import torch
|
| 2 |
-
from einops import einsum, reduce, repeat
|
| 3 |
-
from jaxtyping import Float
|
| 4 |
-
from torch import Tensor
|
| 5 |
-
|
| 6 |
-
from ..data_types import BatchedExample
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
def compute_depth_for_disparity(
|
| 10 |
-
extrinsics: Float[Tensor, "batch view 4 4"],
|
| 11 |
-
intrinsics: Float[Tensor, "batch view 3 3"],
|
| 12 |
-
image_shape: tuple[int, int],
|
| 13 |
-
disparity: float,
|
| 14 |
-
delta_min: float = 1e-6, # This prevents motionless scenes from lacking depth.
|
| 15 |
-
) -> Float[Tensor, " batch"]:
|
| 16 |
-
"""Compute the depth at which moving the maximum distance between cameras
|
| 17 |
-
corresponds to the specified disparity (in pixels).
|
| 18 |
-
"""
|
| 19 |
-
|
| 20 |
-
# Use the furthest distance between cameras as the baseline.
|
| 21 |
-
origins = extrinsics[:, :, :3, 3]
|
| 22 |
-
deltas = (origins[:, None, :, :] - origins[:, :, None, :]).norm(dim=-1)
|
| 23 |
-
deltas = deltas.clip(min=delta_min)
|
| 24 |
-
baselines = reduce(deltas, "b v ov -> b", "max")
|
| 25 |
-
|
| 26 |
-
# Compute a single pixel's size at depth 1.
|
| 27 |
-
h, w = image_shape
|
| 28 |
-
pixel_size = 1 / torch.tensor((w, h), dtype=torch.float32, device=extrinsics.device)
|
| 29 |
-
pixel_size = einsum(
|
| 30 |
-
intrinsics[..., :2, :2].inverse(), pixel_size, "... i j, j -> ... i"
|
| 31 |
-
)
|
| 32 |
-
|
| 33 |
-
# This wouldn't make sense with non-square pixels, but then again, non-square pixels
|
| 34 |
-
# don't make much sense anyway.
|
| 35 |
-
mean_pixel_size = reduce(pixel_size, "b v xy -> b", "mean")
|
| 36 |
-
|
| 37 |
-
return baselines / (disparity * mean_pixel_size)
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
def apply_bounds_shim(
|
| 41 |
-
batch: BatchedExample,
|
| 42 |
-
near_disparity: float,
|
| 43 |
-
far_disparity: float,
|
| 44 |
-
) -> BatchedExample:
|
| 45 |
-
"""Compute reasonable near and far planes (lower and upper bounds on depth). This
|
| 46 |
-
assumes that all of an example's views are of roughly the same thing.
|
| 47 |
-
"""
|
| 48 |
-
|
| 49 |
-
context = batch["context"]
|
| 50 |
-
_, cv, _, h, w = context["image"].shape
|
| 51 |
-
|
| 52 |
-
# Compute near and far planes using the context views.
|
| 53 |
-
near = compute_depth_for_disparity(
|
| 54 |
-
context["extrinsics"],
|
| 55 |
-
context["intrinsics"],
|
| 56 |
-
(h, w),
|
| 57 |
-
near_disparity,
|
| 58 |
-
)
|
| 59 |
-
far = compute_depth_for_disparity(
|
| 60 |
-
context["extrinsics"],
|
| 61 |
-
context["intrinsics"],
|
| 62 |
-
(h, w),
|
| 63 |
-
far_disparity,
|
| 64 |
-
)
|
| 65 |
-
|
| 66 |
-
target = batch["target"]
|
| 67 |
-
_, tv, _, _, _ = target["image"].shape
|
| 68 |
-
return {
|
| 69 |
-
**batch,
|
| 70 |
-
"context": {
|
| 71 |
-
**context,
|
| 72 |
-
"near": repeat(near, "b -> b v", v=cv),
|
| 73 |
-
"far": repeat(far, "b -> b v", v=cv),
|
| 74 |
-
},
|
| 75 |
-
"target": {
|
| 76 |
-
**target,
|
| 77 |
-
"near": repeat(near, "b -> b v", v=tv),
|
| 78 |
-
"far": repeat(far, "b -> b v", v=tv),
|
| 79 |
-
},
|
| 80 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/dataset/view_sampler/view_sampler.py
CHANGED
|
@@ -44,10 +44,6 @@ class ViewSampler(ABC, Generic[T]):
|
|
| 44 |
self._all_context_indices = None
|
| 45 |
self._all_target_indices = None
|
| 46 |
|
| 47 |
-
@property
|
| 48 |
-
def all_context_indices(self) -> Int64[Tensor, " context_view"]:
|
| 49 |
-
return self._all_context_indices
|
| 50 |
-
|
| 51 |
@property
|
| 52 |
def context_indices(self) -> Int64[Tensor, " target_view"]:
|
| 53 |
return self._all_context_indices
|
|
@@ -70,9 +66,6 @@ class ViewSampler(ABC, Generic[T]):
|
|
| 70 |
else:
|
| 71 |
raise RuntimeError("Target indices have already been set.")
|
| 72 |
|
| 73 |
-
def sample_subset(self, extrinsics, intrinsics, device):
|
| 74 |
-
pass
|
| 75 |
-
|
| 76 |
@abstractmethod
|
| 77 |
def _sample_impl(
|
| 78 |
self,
|
|
@@ -123,13 +116,3 @@ class ViewSampler(ABC, Generic[T]):
|
|
| 123 |
@property
|
| 124 |
def global_step(self) -> int:
|
| 125 |
return 0 if self.step_tracker is None else self.step_tracker.get_step()
|
| 126 |
-
|
| 127 |
-
def new_instance(self) -> "ViewSampler":
|
| 128 |
-
"""Create a new instance of the same ViewSampler class with the same configuration."""
|
| 129 |
-
return value(self.__class__)(
|
| 130 |
-
cfg=self.cfg,
|
| 131 |
-
stage=self.stage,
|
| 132 |
-
is_overfitting=self.is_overfitting,
|
| 133 |
-
cameras_are_circular=self.cameras_are_circular,
|
| 134 |
-
step_tracker=self.step_tracker,
|
| 135 |
-
)
|
|
|
|
| 44 |
self._all_context_indices = None
|
| 45 |
self._all_target_indices = None
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
@property
|
| 48 |
def context_indices(self) -> Int64[Tensor, " target_view"]:
|
| 49 |
return self._all_context_indices
|
|
|
|
| 66 |
else:
|
| 67 |
raise RuntimeError("Target indices have already been set.")
|
| 68 |
|
|
|
|
|
|
|
|
|
|
| 69 |
@abstractmethod
|
| 70 |
def _sample_impl(
|
| 71 |
self,
|
|
|
|
| 116 |
@property
|
| 117 |
def global_step(self) -> int:
|
| 118 |
return 0 if self.step_tracker is None else self.step_tracker.get_step()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/evaluation/metric_computer.py
DELETED
|
@@ -1,115 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
from pathlib import Path
|
| 3 |
-
|
| 4 |
-
import torch
|
| 5 |
-
from pytorch_lightning import LightningModule
|
| 6 |
-
from ..misc.console import metrics_table
|
| 7 |
-
from ..misc.image_io import load_image, save_image
|
| 8 |
-
from ..visualization.annotation import add_label
|
| 9 |
-
from ..visualization.layout import add_border, hcat
|
| 10 |
-
from .evaluation_cfg import EvaluationCfg
|
| 11 |
-
from .metrics import compute_lpips, compute_psnr, compute_ssim
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
class MetricComputer(LightningModule):
|
| 15 |
-
cfg: EvaluationCfg
|
| 16 |
-
|
| 17 |
-
def __init__(self, cfg: EvaluationCfg) -> None:
|
| 18 |
-
super().__init__()
|
| 19 |
-
self.cfg = cfg
|
| 20 |
-
|
| 21 |
-
def test_step(self, batch, batch_idx):
|
| 22 |
-
scene = batch["scene"][0]
|
| 23 |
-
b, cv, _, _, _ = batch["context"]["image"].shape
|
| 24 |
-
assert b == 1 and cv == 2
|
| 25 |
-
_, v, _, _, _ = batch["target"]["image"].shape
|
| 26 |
-
|
| 27 |
-
# Skip scenes.
|
| 28 |
-
for method in self.cfg.methods:
|
| 29 |
-
if not (method.path / scene).exists():
|
| 30 |
-
print(f'Skipping "{scene}".')
|
| 31 |
-
return
|
| 32 |
-
|
| 33 |
-
# Load the images.
|
| 34 |
-
all_images = {}
|
| 35 |
-
try:
|
| 36 |
-
for method in self.cfg.methods:
|
| 37 |
-
images = [
|
| 38 |
-
load_image(method.path / scene / f"color/{index.item():0>6}.png")
|
| 39 |
-
for index in batch["target"]["index"][0]
|
| 40 |
-
]
|
| 41 |
-
all_images[method.key] = torch.stack(images).to(self.device)
|
| 42 |
-
except FileNotFoundError:
|
| 43 |
-
print(f'Skipping "{scene}".')
|
| 44 |
-
return
|
| 45 |
-
|
| 46 |
-
# Compute metrics.
|
| 47 |
-
all_metrics = {}
|
| 48 |
-
rgb_gt = batch["target"]["image"][0]
|
| 49 |
-
for key, images in all_images.items():
|
| 50 |
-
alex_lpips, vgg_lpips = compute_lpips(rgb_gt, images)
|
| 51 |
-
all_metrics = {
|
| 52 |
-
**all_metrics,
|
| 53 |
-
f"alex_lpips_{key}": alex_lpips,
|
| 54 |
-
f"vgg_lpips_{key}": vgg_lpips,
|
| 55 |
-
f"ssim_{key}": compute_ssim(rgb_gt, images),
|
| 56 |
-
f"psnr_{key}": compute_psnr(rgb_gt, images),
|
| 57 |
-
}
|
| 58 |
-
self.log_dict(all_metrics)
|
| 59 |
-
self.print_preview_metrics(all_metrics)
|
| 60 |
-
|
| 61 |
-
# Skip the rest if no side-by-side is needed.
|
| 62 |
-
if self.cfg.side_by_side_path is None:
|
| 63 |
-
return
|
| 64 |
-
|
| 65 |
-
# Create side-by-side.
|
| 66 |
-
scene_key = f"{batch_idx:0>6}_{scene}"
|
| 67 |
-
for i in range(v):
|
| 68 |
-
true_index = batch["target"]["index"][0, i]
|
| 69 |
-
row = [add_label(batch["target"]["image"][0, i], "Ground Truth")]
|
| 70 |
-
for method in self.cfg.methods:
|
| 71 |
-
image = all_images[method.key][i]
|
| 72 |
-
image = add_label(image, method.name)
|
| 73 |
-
row.append(image)
|
| 74 |
-
start_frame = batch["target"]["index"][0, 0]
|
| 75 |
-
end_frame = batch["target"]["index"][0, -1]
|
| 76 |
-
label = f"Scene {batch['scene'][0]} (frames {start_frame} to {end_frame})"
|
| 77 |
-
row = add_border(add_label(hcat(*row), label, font_size=16))
|
| 78 |
-
save_image(
|
| 79 |
-
row,
|
| 80 |
-
self.cfg.side_by_side_path / scene_key / f"{true_index:0>6}.png",
|
| 81 |
-
)
|
| 82 |
-
|
| 83 |
-
# Create an animation.
|
| 84 |
-
if self.cfg.animate_side_by_side:
|
| 85 |
-
(self.cfg.side_by_side_path / "videos").mkdir(exist_ok=True, parents=True)
|
| 86 |
-
command = (
|
| 87 |
-
'ffmpeg -y -framerate 30 -pattern_type glob -i "*.png" -c:v libx264 '
|
| 88 |
-
'-pix_fmt yuv420p -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2"'
|
| 89 |
-
)
|
| 90 |
-
os.system(
|
| 91 |
-
f"cd {self.cfg.side_by_side_path / scene_key} && {command} "
|
| 92 |
-
f"{Path.cwd()}/{self.cfg.side_by_side_path}/videos/{scene_key}.mp4"
|
| 93 |
-
)
|
| 94 |
-
|
| 95 |
-
def print_preview_metrics(self, metrics: dict[str, float]) -> None:
|
| 96 |
-
if getattr(self, "running_metrics", None) is None:
|
| 97 |
-
self.running_metrics = metrics
|
| 98 |
-
self.running_metric_steps = 1
|
| 99 |
-
else:
|
| 100 |
-
s = self.running_metric_steps
|
| 101 |
-
self.running_metrics = {
|
| 102 |
-
k: ((s * v) + metrics[k]) / (s + 1)
|
| 103 |
-
for k, v in self.running_metrics.items()
|
| 104 |
-
}
|
| 105 |
-
self.running_metric_steps += 1
|
| 106 |
-
|
| 107 |
-
rows = []
|
| 108 |
-
for method in self.cfg.methods:
|
| 109 |
-
row = [
|
| 110 |
-
f"{self.running_metrics[f'{metric}_{method.key}']:.3f}"
|
| 111 |
-
for metric in ("psnr", "lpips", "ssim")
|
| 112 |
-
]
|
| 113 |
-
rows.append((method.key, *row))
|
| 114 |
-
|
| 115 |
-
metrics_table(rows, ["Method", "PSNR (dB)", "LPIPS", "SSIM"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/evaluation/metrics.py
CHANGED
|
@@ -6,7 +6,7 @@ from jaxtyping import Float
|
|
| 6 |
# from lpips import LPIPS
|
| 7 |
# from skimage.metrics import structural_similarity
|
| 8 |
from torch import Tensor
|
| 9 |
-
from torchmetrics.image import
|
| 10 |
from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity
|
| 11 |
from tqdm import tqdm
|
| 12 |
|
|
|
|
| 6 |
# from lpips import LPIPS
|
| 7 |
# from skimage.metrics import structural_similarity
|
| 8 |
from torch import Tensor
|
| 9 |
+
from torchmetrics.image import StructuralSimilarityIndexMeasure
|
| 10 |
from torchmetrics.image.lpip import LearnedPerceptualImagePatchSimilarity
|
| 11 |
from tqdm import tqdm
|
| 12 |
|
optgs/experimental/api/api.py
CHANGED
|
@@ -21,7 +21,7 @@ For non-inria codebases use :meth:`OptGS.initialize_from_ply` /
|
|
| 21 |
|
| 22 |
External SfM scenes carry no optgs encoder features, so checkpoints trained
|
| 23 |
with ``init_state_wo_features=False`` are coerced at construction (with a
|
| 24 |
-
warning): the feature-conditioned ``
|
| 25 |
optimizer state is initialized standard-normal.
|
| 26 |
"""
|
| 27 |
|
|
@@ -105,7 +105,7 @@ class OptGS:
|
|
| 105 |
"(scene_trainer.scene_optimizer.init_state_wo_features=False). "
|
| 106 |
"External SfM/inria scenes carry no optgs encoder features; "
|
| 107 |
"proceeding with init_state_wo_features=True — the "
|
| 108 |
-
"feature-conditioned
|
| 109 |
"initial optimizer state is set to a standard-normal random "
|
| 110 |
"vector (init_state_type='random', init_state_scale=1.0)."
|
| 111 |
)
|
|
@@ -348,7 +348,6 @@ class OptGS:
|
|
| 348 |
context=self._context,
|
| 349 |
renderer=self.decoder,
|
| 350 |
prev_output=self._init_output,
|
| 351 |
-
num_refine=self.num_refine,
|
| 352 |
iter_batch_size=self.iter_batch_size,
|
| 353 |
target=self._context,
|
| 354 |
)
|
|
@@ -416,27 +415,36 @@ class OptGS:
|
|
| 416 |
OptimizerPreviousOutput,
|
| 417 |
)
|
| 418 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
with torch.no_grad():
|
| 420 |
-
inp = OptimizerInput(
|
| 421 |
-
context=self._context,
|
| 422 |
-
renderer=self.decoder,
|
| 423 |
-
prev_output=self._init_output,
|
| 424 |
-
num_refine=self.num_refine,
|
| 425 |
-
iter_batch_size=self.iter_batch_size,
|
| 426 |
-
target=self._context,
|
| 427 |
-
)
|
| 428 |
opt.validate_input(inp)
|
| 429 |
opt.on_scene_start(inp) # InitializerOutput -> OptimizerPreviousOutput
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
|
|
|
| 440 |
# Fresh view minibatch each step (the regime the optimizer
|
| 441 |
# was trained with); full_context/target stay the whole scene.
|
| 442 |
batch = self._view_minibatch(self._context)
|
|
@@ -447,12 +455,14 @@ class OptGS:
|
|
| 447 |
full_context=self._context, full_target=self._context,
|
| 448 |
)
|
| 449 |
out.t = (out.t or 0) + 1
|
| 450 |
-
|
| 451 |
-
|
|
|
|
|
|
|
| 452 |
if torch.cuda.is_available():
|
| 453 |
torch.cuda.synchronize()
|
| 454 |
opt.on_scene_end()
|
| 455 |
-
|
| 456 |
|
| 457 |
def export_ply(self, path: str) -> None:
|
| 458 |
"""Write the most recently refined Gaussians to a 3DGS PLY."""
|
|
|
|
| 21 |
|
| 22 |
External SfM scenes carry no optgs encoder features, so checkpoints trained
|
| 23 |
with ``init_state_wo_features=False`` are coerced at construction (with a
|
| 24 |
+
warning): the feature-conditioned ``state_proj`` weights are dropped and the
|
| 25 |
optimizer state is initialized standard-normal.
|
| 26 |
"""
|
| 27 |
|
|
|
|
| 105 |
"(scene_trainer.scene_optimizer.init_state_wo_features=False). "
|
| 106 |
"External SfM/inria scenes carry no optgs encoder features; "
|
| 107 |
"proceeding with init_state_wo_features=True — the "
|
| 108 |
+
"feature-conditioned state_proj weights are dropped and the "
|
| 109 |
"initial optimizer state is set to a standard-normal random "
|
| 110 |
"vector (init_state_type='random', init_state_scale=1.0)."
|
| 111 |
)
|
|
|
|
| 348 |
context=self._context,
|
| 349 |
renderer=self.decoder,
|
| 350 |
prev_output=self._init_output,
|
|
|
|
| 351 |
iter_batch_size=self.iter_batch_size,
|
| 352 |
target=self._context,
|
| 353 |
)
|
|
|
|
| 415 |
OptimizerPreviousOutput,
|
| 416 |
)
|
| 417 |
|
| 418 |
+
# torch's grad mode is thread-local, and a generator consumer (e.g.
|
| 419 |
+
# gradio) may resume this generator on a *different* worker thread each
|
| 420 |
+
# step — so a single ``with torch.no_grad()`` spanning the ``yield`` does
|
| 421 |
+
# not reliably hold across steps. Enter no_grad *per step* and yield
|
| 422 |
+
# outside it: the Adam baseline's in-place parameter updates require
|
| 423 |
+
# no_grad ambient (calc_input_gradients re-enables grad internally just
|
| 424 |
+
# for the gradient computation), so a step that resumes on a grad-enabled
|
| 425 |
+
# thread would otherwise raise "a leaf Variable that requires grad is
|
| 426 |
+
# being used in an in-place operation" and leak the autograd graph.
|
| 427 |
+
inp = OptimizerInput(
|
| 428 |
+
context=self._context,
|
| 429 |
+
renderer=self.decoder,
|
| 430 |
+
prev_output=self._init_output,
|
| 431 |
+
iter_batch_size=self.iter_batch_size,
|
| 432 |
+
target=self._context,
|
| 433 |
+
)
|
| 434 |
with torch.no_grad():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 435 |
opt.validate_input(inp)
|
| 436 |
opt.on_scene_start(inp) # InitializerOutput -> OptimizerPreviousOutput
|
| 437 |
+
if not isinstance(inp.prev_output, OptimizerPreviousOutput):
|
| 438 |
+
raise OptGSError(
|
| 439 |
+
"optimizer.on_scene_start did not produce an "
|
| 440 |
+
f"OptimizerPreviousOutput (got {type(inp.prev_output)})."
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
out = OptimizerOutput.empty(t=0)
|
| 444 |
+
out.T = self.num_refine
|
| 445 |
+
try:
|
| 446 |
+
for step in range(self.num_refine):
|
| 447 |
+
with torch.no_grad():
|
| 448 |
# Fresh view minibatch each step (the regime the optimizer
|
| 449 |
# was trained with); full_context/target stay the whole scene.
|
| 450 |
batch = self._view_minibatch(self._context)
|
|
|
|
| 455 |
full_context=self._context, full_target=self._context,
|
| 456 |
)
|
| 457 |
out.t = (out.t or 0) + 1
|
| 458 |
+
g = inp.prev_output.gaussians
|
| 459 |
+
yield step, g
|
| 460 |
+
finally:
|
| 461 |
+
with torch.no_grad():
|
| 462 |
if torch.cuda.is_available():
|
| 463 |
torch.cuda.synchronize()
|
| 464 |
opt.on_scene_end()
|
| 465 |
+
self._refined = inp.prev_output.gaussians
|
| 466 |
|
| 467 |
def export_ply(self, path: str) -> None:
|
| 468 |
"""Write the most recently refined Gaussians to a 3DGS PLY."""
|
optgs/experimental/api/integration/config_bridge.py
CHANGED
|
@@ -190,22 +190,21 @@ def build_optimizer_cfg(cfg_path: Path) -> tuple["KnnBasedOptimizerCfg", int | N
|
|
| 190 |
|
| 191 |
# Mirror SceneTrainerCfg (scene_trainer_cfg.py: scene_optimizer.update(
|
| 192 |
# scene_initializer)): wire the checkpoint's initializer cfg into the
|
| 193 |
-
# optimizer cfg so the runtime-only fields
|
| 194 |
-
#
|
| 195 |
-
# the optimizer nn.Module is built.
|
| 196 |
si = OmegaConf.select(cfg, "scene_trainer.scene_initializer")
|
| 197 |
si_name = OmegaConf.select(cfg, "scene_trainer.scene_initializer.name")
|
| 198 |
if si is None or si_name in (None, "none"):
|
| 199 |
raise OptGSError(
|
| 200 |
f"checkpoint config at {cfg_path} has no scene_initializer "
|
| 201 |
-
f"(name={si_name!r}); cannot derive
|
| 202 |
-
f"required to build
|
| 203 |
)
|
| 204 |
init_cls = _initializer_cfg_class(str(si_name))
|
| 205 |
if init_cls is None:
|
| 206 |
raise OptGSError(
|
| 207 |
f"unsupported scene_initializer.name={si_name!r} in {cfg_path}; "
|
| 208 |
-
f"cannot derive
|
| 209 |
)
|
| 210 |
default_si = _compose_default_group("scene_initializer", str(si_name))
|
| 211 |
if default_si is not None:
|
|
@@ -215,7 +214,7 @@ def build_optimizer_cfg(cfg_path: Path) -> tuple["KnnBasedOptimizerCfg", int | N
|
|
| 215 |
merged_si = si
|
| 216 |
try:
|
| 217 |
init_cfg = load_typed_config(merged_si, init_cls)
|
| 218 |
-
opt_cfg.update(init_cfg) # sets
|
| 219 |
except Exception as e:
|
| 220 |
raise OptGSError(
|
| 221 |
f"failed to wire scene_initializer ({si_name!r}) into the "
|
|
@@ -335,7 +334,7 @@ def build_adam_baseline(num_refine: int) -> "nn.Module":
|
|
| 335 |
)
|
| 336 |
OmegaConf.set_struct(composed, False)
|
| 337 |
# gsplat decays the means LR over the full step budget.
|
| 338 |
-
composed.
|
| 339 |
# Disable densification — the baseline refines the same fixed Gaussian set
|
| 340 |
# as the learned optimizer (a like-for-like comparison of the update rule).
|
| 341 |
for flag in ("do_densify", "do_prune", "do_opacity_reset"):
|
|
@@ -357,14 +356,6 @@ def build_adam_baseline(num_refine: int) -> "nn.Module":
|
|
| 357 |
return optimizer
|
| 358 |
|
| 359 |
|
| 360 |
-
# Module-attribute renames applied when the legacy Resplat encoder was split
|
| 361 |
-
# into separate initializer/optimizer modules (transcribed from
|
| 362 |
-
# optgs/main.py:load_optimizer).
|
| 363 |
-
_ORIG_OPTIMIZER_ATTR_RENAMES = {
|
| 364 |
-
"render_error_mv_attn": "update_error_attn",
|
| 365 |
-
}
|
| 366 |
-
|
| 367 |
-
|
| 368 |
def load_optimizer_state(
|
| 369 |
optimizer: "nn.Module",
|
| 370 |
ckpt_path: str,
|
|
@@ -379,6 +370,8 @@ def load_optimizer_state(
|
|
| 379 |
"""
|
| 380 |
import torch
|
| 381 |
|
|
|
|
|
|
|
| 382 |
state = torch.load(ckpt_path, map_location="cpu")
|
| 383 |
if isinstance(state, dict) and "state_dict" in state:
|
| 384 |
state = state["state_dict"]
|
|
@@ -399,14 +392,10 @@ def load_optimizer_state(
|
|
| 399 |
for k, v in state.items()
|
| 400 |
if k.startswith("encoder.")
|
| 401 |
}
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
k = new + k[len(old):]
|
| 407 |
-
break
|
| 408 |
-
renamed[k] = v
|
| 409 |
-
osd = renamed
|
| 410 |
|
| 411 |
if not osd:
|
| 412 |
raise OptGSError(
|
|
@@ -415,6 +404,6 @@ def load_optimizer_state(
|
|
| 415 |
)
|
| 416 |
|
| 417 |
if init_state_wo_features:
|
| 418 |
-
osd = {k: v for k, v in osd.items() if "
|
| 419 |
|
| 420 |
optimizer.load_state_dict(osd, strict=strict)
|
|
|
|
| 190 |
|
| 191 |
# Mirror SceneTrainerCfg (scene_trainer_cfg.py: scene_optimizer.update(
|
| 192 |
# scene_initializer)): wire the checkpoint's initializer cfg into the
|
| 193 |
+
# optimizer cfg so the runtime-only fields init_sh_d / sh_d — absent from
|
| 194 |
+
# every config file — are populated before the optimizer nn.Module is built.
|
|
|
|
| 195 |
si = OmegaConf.select(cfg, "scene_trainer.scene_initializer")
|
| 196 |
si_name = OmegaConf.select(cfg, "scene_trainer.scene_initializer.name")
|
| 197 |
if si is None or si_name in (None, "none"):
|
| 198 |
raise OptGSError(
|
| 199 |
f"checkpoint config at {cfg_path} has no scene_initializer "
|
| 200 |
+
f"(name={si_name!r}); cannot derive the optimizer's initializer "
|
| 201 |
+
f"settings required to build it."
|
| 202 |
)
|
| 203 |
init_cls = _initializer_cfg_class(str(si_name))
|
| 204 |
if init_cls is None:
|
| 205 |
raise OptGSError(
|
| 206 |
f"unsupported scene_initializer.name={si_name!r} in {cfg_path}; "
|
| 207 |
+
f"cannot derive the optimizer's initializer settings."
|
| 208 |
)
|
| 209 |
default_si = _compose_default_group("scene_initializer", str(si_name))
|
| 210 |
if default_si is not None:
|
|
|
|
| 214 |
merged_si = si
|
| 215 |
try:
|
| 216 |
init_cfg = load_typed_config(merged_si, init_cls)
|
| 217 |
+
opt_cfg.update(init_cfg) # sets init_sh_d/sh_d
|
| 218 |
except Exception as e:
|
| 219 |
raise OptGSError(
|
| 220 |
f"failed to wire scene_initializer ({si_name!r}) into the "
|
|
|
|
| 334 |
)
|
| 335 |
OmegaConf.set_struct(composed, False)
|
| 336 |
# gsplat decays the means LR over the full step budget.
|
| 337 |
+
composed.lr_scheduler.max_steps = int(num_refine)
|
| 338 |
# Disable densification — the baseline refines the same fixed Gaussian set
|
| 339 |
# as the learned optimizer (a like-for-like comparison of the update rule).
|
| 340 |
for flag in ("do_densify", "do_prune", "do_opacity_reset"):
|
|
|
|
| 356 |
return optimizer
|
| 357 |
|
| 358 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
def load_optimizer_state(
|
| 360 |
optimizer: "nn.Module",
|
| 361 |
ckpt_path: str,
|
|
|
|
| 370 |
"""
|
| 371 |
import torch
|
| 372 |
|
| 373 |
+
from optgs.misc.checkpointing import _rename_optimizer_attrs
|
| 374 |
+
|
| 375 |
state = torch.load(ckpt_path, map_location="cpu")
|
| 376 |
if isinstance(state, dict) and "state_dict" in state:
|
| 377 |
state = state["state_dict"]
|
|
|
|
| 392 |
for k, v in state.items()
|
| 393 |
if k.startswith("encoder.")
|
| 394 |
}
|
| 395 |
+
|
| 396 |
+
# Rename module attributes renamed in the update_/refine_ cleanup pass
|
| 397 |
+
# (and the resplat-era render_error_mv_attn).
|
| 398 |
+
osd = _rename_optimizer_attrs(osd)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
|
| 400 |
if not osd:
|
| 401 |
raise OptGSError(
|
|
|
|
| 404 |
)
|
| 405 |
|
| 406 |
if init_state_wo_features:
|
| 407 |
+
osd = {k: v for k, v in osd.items() if "state_proj" not in k}
|
| 408 |
|
| 409 |
optimizer.load_state_dict(osd, strict=strict)
|
optgs/global_cfg.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
from typing import Optional
|
| 2 |
-
|
| 3 |
-
from omegaconf import DictConfig
|
| 4 |
-
|
| 5 |
-
cfg: Optional[DictConfig] = None
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
def get_cfg() -> DictConfig:
|
| 9 |
-
global cfg
|
| 10 |
-
return cfg
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def set_cfg(new_cfg: DictConfig) -> None:
|
| 14 |
-
global cfg
|
| 15 |
-
cfg = new_cfg
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def get_seed() -> int:
|
| 19 |
-
return cfg.seed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/loss/loss_deltas.py
CHANGED
|
@@ -24,6 +24,13 @@ class LossDeltasCfgWrapper:
|
|
| 24 |
|
| 25 |
|
| 26 |
class LossDeltas(Loss[LossDeltasCfg, LossDeltasCfgWrapper]):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
def forward(
|
| 28 |
self,
|
| 29 |
prediction: DecoderOutput,
|
|
@@ -32,8 +39,6 @@ class LossDeltas(Loss[LossDeltasCfg, LossDeltasCfgWrapper]):
|
|
| 32 |
gt_rgb: Tensor,
|
| 33 |
pred_rgb: Tensor,
|
| 34 |
valid_depth_mask: Tensor | None,
|
| 35 |
-
l1_loss: bool,
|
| 36 |
-
clamp_large_error: float,
|
| 37 |
**kwargs,
|
| 38 |
) -> Float[Tensor, ""]:
|
| 39 |
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
class LossDeltas(Loss[LossDeltasCfg, LossDeltasCfgWrapper]):
|
| 27 |
+
"""L1 regularization on the optimizer's predicted per-Gaussian deltas, keeping the updates small.
|
| 28 |
+
|
| 29 |
+
With cfg.exclude_by_norm_grad the penalty is restricted to Gaussians with small gradients (and,
|
| 30 |
+
with exclude_by_norm_grad_opposite, those whose delta already agrees in sign with the gradient).
|
| 31 |
+
Returns 0 until global_step >= cfg.apply_after_step.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
def forward(
|
| 35 |
self,
|
| 36 |
prediction: DecoderOutput,
|
|
|
|
| 39 |
gt_rgb: Tensor,
|
| 40 |
pred_rgb: Tensor,
|
| 41 |
valid_depth_mask: Tensor | None,
|
|
|
|
|
|
|
| 42 |
**kwargs,
|
| 43 |
) -> Float[Tensor, ""]:
|
| 44 |
|
optgs/loss/loss_lpips.py
CHANGED
|
@@ -20,6 +20,7 @@ class LossLpipsCfg:
|
|
| 20 |
weight: float
|
| 21 |
apply_after_step: int
|
| 22 |
perceptual_loss: bool
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
@dataclass
|
|
@@ -28,6 +29,12 @@ class LossLpipsCfgWrapper:
|
|
| 28 |
|
| 29 |
|
| 30 |
class LossLpips(Loss[LossLpipsCfg, LossLpipsCfgWrapper]):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
lpips: LPIPS
|
| 32 |
|
| 33 |
def __init__(self, cfg: LossLpipsCfgWrapper) -> None:
|
|
@@ -48,7 +55,6 @@ class LossLpips(Loss[LossLpipsCfg, LossLpipsCfgWrapper]):
|
|
| 48 |
gt_rgb: Tensor,
|
| 49 |
pred_rgb: Tensor,
|
| 50 |
valid_depth_mask: Tensor | None,
|
| 51 |
-
half_res_lpips: bool = False,
|
| 52 |
**kwargs,
|
| 53 |
) -> Float[Tensor, ""]:
|
| 54 |
|
|
@@ -64,7 +70,7 @@ class LossLpips(Loss[LossLpipsCfg, LossLpipsCfgWrapper]):
|
|
| 64 |
pred = rearrange(pred_rgb, "b v c h w -> (b v) c h w")
|
| 65 |
gt = rearrange(gt_rgb, "b v c h w -> (b v) c h w")
|
| 66 |
|
| 67 |
-
if
|
| 68 |
pred = F.interpolate(pred, scale_factor=0.5, mode="bilinear", align_corners=True)
|
| 69 |
gt = F.interpolate(gt, scale_factor=0.5, mode="bilinear", align_corners=True)
|
| 70 |
|
|
|
|
| 20 |
weight: float
|
| 21 |
apply_after_step: int
|
| 22 |
perceptual_loss: bool
|
| 23 |
+
half_res: bool # downsample to half resolution before LPIPS to save memory
|
| 24 |
|
| 25 |
|
| 26 |
@dataclass
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
class LossLpips(Loss[LossLpipsCfg, LossLpipsCfgWrapper]):
|
| 32 |
+
"""LPIPS perceptual distance between the rendered and ground-truth views.
|
| 33 |
+
|
| 34 |
+
Returns 0 until global_step >= cfg.apply_after_step. Uses VGG-LPIPS, or the custom
|
| 35 |
+
PerceptualLoss when cfg.perceptual_loss.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
lpips: LPIPS
|
| 39 |
|
| 40 |
def __init__(self, cfg: LossLpipsCfgWrapper) -> None:
|
|
|
|
| 55 |
gt_rgb: Tensor,
|
| 56 |
pred_rgb: Tensor,
|
| 57 |
valid_depth_mask: Tensor | None,
|
|
|
|
| 58 |
**kwargs,
|
| 59 |
) -> Float[Tensor, ""]:
|
| 60 |
|
|
|
|
| 70 |
pred = rearrange(pred_rgb, "b v c h w -> (b v) c h w")
|
| 71 |
gt = rearrange(gt_rgb, "b v c h w -> (b v) c h w")
|
| 72 |
|
| 73 |
+
if self.cfg.half_res:
|
| 74 |
pred = F.interpolate(pred, scale_factor=0.5, mode="bilinear", align_corners=True)
|
| 75 |
gt = F.interpolate(gt, scale_factor=0.5, mode="bilinear", align_corners=True)
|
| 76 |
|
optgs/loss/loss_monodepth.py
CHANGED
|
@@ -1,11 +1,6 @@
|
|
| 1 |
import torch
|
| 2 |
|
| 3 |
-
|
| 4 |
-
import cv2
|
| 5 |
-
import torch
|
| 6 |
-
|
| 7 |
-
from optgs.model.encoder.depth_anything_v2.dpt import DepthAnythingV2
|
| 8 |
-
|
| 9 |
|
| 10 |
|
| 11 |
def get_monodepth_model():
|
|
@@ -16,7 +11,7 @@ def get_monodepth_model():
|
|
| 16 |
'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': [1536, 1536, 1536, 1536]}
|
| 17 |
}
|
| 18 |
|
| 19 |
-
encoder = 'vitl'
|
| 20 |
|
| 21 |
model = DepthAnythingV2(**model_configs[encoder])
|
| 22 |
model.load_state_dict(torch.load(f'pretrained/depth_anything_v2_{encoder}.pth', map_location='cpu'))
|
|
@@ -26,17 +21,3 @@ def get_monodepth_model():
|
|
| 26 |
param.requires_grad = False
|
| 27 |
|
| 28 |
return model
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def get_monodepth_pred(img, model):
|
| 33 |
-
|
| 34 |
-
with torch.no_grad():
|
| 35 |
-
pass
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
def get_monodepth_loss(pred_depth, img):
|
| 39 |
-
pass
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
| 1 |
import torch
|
| 2 |
|
| 3 |
+
from optgs.model.backbones.depth_anything_v2.dpt import DepthAnythingV2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
|
| 6 |
def get_monodepth_model():
|
|
|
|
| 11 |
'vitg': {'encoder': 'vitg', 'features': 384, 'out_channels': [1536, 1536, 1536, 1536]}
|
| 12 |
}
|
| 13 |
|
| 14 |
+
encoder = 'vitl' # or 'vits', 'vitb', 'vitg'
|
| 15 |
|
| 16 |
model = DepthAnythingV2(**model_configs[encoder])
|
| 17 |
model.load_state_dict(torch.load(f'pretrained/depth_anything_v2_{encoder}.pth', map_location='cpu'))
|
|
|
|
| 21 |
param.requires_grad = False
|
| 22 |
|
| 23 |
return model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
optgs/loss/loss_mse.py
CHANGED
|
@@ -12,6 +12,8 @@ from .loss import Loss
|
|
| 12 |
@dataclass
|
| 13 |
class LossMseCfg:
|
| 14 |
weight: float
|
|
|
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
@dataclass
|
|
@@ -20,6 +22,8 @@ class LossMseCfgWrapper:
|
|
| 20 |
|
| 21 |
|
| 22 |
class LossMse(Loss[LossMseCfg, LossMseCfgWrapper]):
|
|
|
|
|
|
|
| 23 |
def forward(
|
| 24 |
self,
|
| 25 |
prediction: DecoderOutput,
|
|
@@ -28,8 +32,6 @@ class LossMse(Loss[LossMseCfg, LossMseCfgWrapper]):
|
|
| 28 |
gt_rgb: Tensor,
|
| 29 |
pred_rgb: Tensor,
|
| 30 |
valid_depth_mask: Tensor | None,
|
| 31 |
-
l1_loss: bool,
|
| 32 |
-
clamp_large_error: float,
|
| 33 |
**kwargs,
|
| 34 |
) -> Float[Tensor, ""]:
|
| 35 |
|
|
@@ -38,15 +40,15 @@ class LossMse(Loss[LossMseCfg, LossMseCfgWrapper]):
|
|
| 38 |
if valid_depth_mask is not None and valid_depth_mask.max() > 0.5 and valid_depth_mask.min() < 0.5:
|
| 39 |
error = error[~valid_depth_mask]
|
| 40 |
|
| 41 |
-
if l1_loss:
|
| 42 |
# l1 loss
|
| 43 |
error = error.abs()
|
| 44 |
else:
|
| 45 |
# l2 loss
|
| 46 |
error = error ** 2
|
| 47 |
|
| 48 |
-
if clamp_large_error > 0:
|
| 49 |
-
valid_mask = error < clamp_large_error
|
| 50 |
error = error[valid_mask]
|
| 51 |
|
| 52 |
error = error.mean()
|
|
|
|
| 12 |
@dataclass
|
| 13 |
class LossMseCfg:
|
| 14 |
weight: float
|
| 15 |
+
l1_loss: bool # use L1 instead of L2 on the RGB error
|
| 16 |
+
clamp_large_error: float # if > 0, drop per-pixel errors above this threshold
|
| 17 |
|
| 18 |
|
| 19 |
@dataclass
|
|
|
|
| 22 |
|
| 23 |
|
| 24 |
class LossMse(Loss[LossMseCfg, LossMseCfgWrapper]):
|
| 25 |
+
"""RGB reconstruction loss between the rendered and ground-truth views (L2, or L1 if cfg.l1_loss)."""
|
| 26 |
+
|
| 27 |
def forward(
|
| 28 |
self,
|
| 29 |
prediction: DecoderOutput,
|
|
|
|
| 32 |
gt_rgb: Tensor,
|
| 33 |
pred_rgb: Tensor,
|
| 34 |
valid_depth_mask: Tensor | None,
|
|
|
|
|
|
|
| 35 |
**kwargs,
|
| 36 |
) -> Float[Tensor, ""]:
|
| 37 |
|
|
|
|
| 40 |
if valid_depth_mask is not None and valid_depth_mask.max() > 0.5 and valid_depth_mask.min() < 0.5:
|
| 41 |
error = error[~valid_depth_mask]
|
| 42 |
|
| 43 |
+
if self.cfg.l1_loss:
|
| 44 |
# l1 loss
|
| 45 |
error = error.abs()
|
| 46 |
else:
|
| 47 |
# l2 loss
|
| 48 |
error = error ** 2
|
| 49 |
|
| 50 |
+
if self.cfg.clamp_large_error > 0:
|
| 51 |
+
valid_mask = error < self.cfg.clamp_large_error
|
| 52 |
error = error[valid_mask]
|
| 53 |
|
| 54 |
error = error.mean()
|
optgs/loss/loss_sgd.py
CHANGED
|
@@ -3,7 +3,6 @@ from dataclasses import dataclass
|
|
| 3 |
from jaxtyping import Float
|
| 4 |
from torch import Tensor
|
| 5 |
|
| 6 |
-
from optgs.dataset.data_types import BatchedExample
|
| 7 |
from optgs.loss import Loss
|
| 8 |
from optgs.model.decoder.decoder import DecoderOutput
|
| 9 |
from optgs.model.types import Gaussians
|
|
@@ -12,7 +11,8 @@ from optgs.scene_trainer.gaussian_module import GaussiansModule
|
|
| 12 |
|
| 13 |
@dataclass
|
| 14 |
class LossSGDCfg:
|
| 15 |
-
|
|
|
|
| 16 |
|
| 17 |
@dataclass
|
| 18 |
class LossSGDCfgWrapper:
|
|
@@ -20,20 +20,21 @@ class LossSGDCfgWrapper:
|
|
| 20 |
|
| 21 |
|
| 22 |
class LossSGD(Loss[LossSGDCfg, LossSGDCfgWrapper]):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
def forward(
|
| 24 |
self,
|
| 25 |
prediction: DecoderOutput,
|
| 26 |
-
batch: BatchedExample,
|
| 27 |
gaussians: Gaussians | GaussiansModule | None,
|
| 28 |
global_step: int,
|
| 29 |
-
l1_loss: bool,
|
| 30 |
-
clamp_large_error: float,
|
| 31 |
valid_depth_mask: Tensor | None,
|
| 32 |
**kwargs,
|
| 33 |
) -> Float[Tensor, ""]:
|
| 34 |
-
|
| 35 |
if gaussians is None:
|
| 36 |
-
raise ValueError("Gaussians must be provided for
|
| 37 |
|
| 38 |
predicted_deltas = gaussians.deltas
|
| 39 |
gt_gradients = gaussians.gradients
|
|
@@ -42,10 +43,10 @@ class LossSGD(Loss[LossSGDCfg, LossSGDCfgWrapper]):
|
|
| 42 |
if predicted_deltas.dtype != gt_gradients.dtype:
|
| 43 |
gt_gradients = gt_gradients.to(predicted_deltas.dtype)
|
| 44 |
|
| 45 |
-
if l1_loss:
|
| 46 |
loss = (predicted_deltas - gt_gradients).abs().mean()
|
| 47 |
else:
|
| 48 |
loss = ((predicted_deltas - gt_gradients) ** 2).mean()
|
| 49 |
-
if clamp_large_error > 0:
|
| 50 |
-
loss = loss.clamp(max=clamp_large_error)
|
| 51 |
return loss
|
|
|
|
| 3 |
from jaxtyping import Float
|
| 4 |
from torch import Tensor
|
| 5 |
|
|
|
|
| 6 |
from optgs.loss import Loss
|
| 7 |
from optgs.model.decoder.decoder import DecoderOutput
|
| 8 |
from optgs.model.types import Gaussians
|
|
|
|
| 11 |
|
| 12 |
@dataclass
|
| 13 |
class LossSGDCfg:
|
| 14 |
+
l1_loss: bool # use L1 instead of L2 on the delta-vs-gradient error
|
| 15 |
+
clamp_large_error: float # if > 0, clamp the loss to this maximum
|
| 16 |
|
| 17 |
@dataclass
|
| 18 |
class LossSGDCfgWrapper:
|
|
|
|
| 20 |
|
| 21 |
|
| 22 |
class LossSGD(Loss[LossSGDCfg, LossSGDCfgWrapper]):
|
| 23 |
+
"""Debugging / sanity-check loss: supervises the optimizer's predicted per-Gaussian deltas to match
|
| 24 |
+
the true gradients, i.e. checks the learned optimizer can reproduce a plain gradient-descent step
|
| 25 |
+
(L2, or L1 if cfg.l1_loss)."""
|
| 26 |
+
|
| 27 |
def forward(
|
| 28 |
self,
|
| 29 |
prediction: DecoderOutput,
|
|
|
|
| 30 |
gaussians: Gaussians | GaussiansModule | None,
|
| 31 |
global_step: int,
|
|
|
|
|
|
|
| 32 |
valid_depth_mask: Tensor | None,
|
| 33 |
**kwargs,
|
| 34 |
) -> Float[Tensor, ""]:
|
| 35 |
+
|
| 36 |
if gaussians is None:
|
| 37 |
+
raise ValueError("Gaussians must be provided for LossSGD.")
|
| 38 |
|
| 39 |
predicted_deltas = gaussians.deltas
|
| 40 |
gt_gradients = gaussians.gradients
|
|
|
|
| 43 |
if predicted_deltas.dtype != gt_gradients.dtype:
|
| 44 |
gt_gradients = gt_gradients.to(predicted_deltas.dtype)
|
| 45 |
|
| 46 |
+
if self.cfg.l1_loss:
|
| 47 |
loss = (predicted_deltas - gt_gradients).abs().mean()
|
| 48 |
else:
|
| 49 |
loss = ((predicted_deltas - gt_gradients) ** 2).mean()
|
| 50 |
+
if self.cfg.clamp_large_error > 0:
|
| 51 |
+
loss = loss.clamp(max=self.cfg.clamp_large_error)
|
| 52 |
return loss
|
optgs/loss/loss_sh0.py
CHANGED
|
@@ -18,6 +18,9 @@ class LossSh0CfgWrapper:
|
|
| 18 |
sh0: LossSh0Cfg
|
| 19 |
|
| 20 |
class LossSh0(Loss[LossSh0Cfg, LossSh0CfgWrapper]):
|
|
|
|
|
|
|
|
|
|
| 21 |
def forward(
|
| 22 |
self,
|
| 23 |
prediction: DecoderOutput,
|
|
|
|
| 18 |
sh0: LossSh0Cfg
|
| 19 |
|
| 20 |
class LossSh0(Loss[LossSh0Cfg, LossSh0CfgWrapper]):
|
| 21 |
+
"""Supervises each Gaussian's SH degree-0 (base) color against the ground-truth image colour
|
| 22 |
+
sampled at the Gaussian's projected 2D location, averaged over the views where it is visible."""
|
| 23 |
+
|
| 24 |
def forward(
|
| 25 |
self,
|
| 26 |
prediction: DecoderOutput,
|