Learn2Splat / optgs /experimental /api /integration /scene_protocol.py
SteEsp's picture
Add Docker-based Learn2Splat demo (viser GUI)
78d2329 verified
"""Structural contracts the external scene must satisfy.
These ``runtime_checkable`` Protocols are written so that inria
gaussian-splatting's own ``Scene`` / ``GaussianModel`` / ``Camera`` classes
satisfy them **with no changes** (graphdeco-inria/gaussian-splatting and the
3DGS-LM fork in ``baselines/3DGS-LM``). Non-inria codebases can either expose
the same attribute names or use the low-level ``OptGS.initialize_from_ply`` /
``initialize_from_tensors`` entrypoints.
"""
from __future__ import annotations
from typing import Any, Protocol, Sequence, runtime_checkable
class OptGSError(RuntimeError):
"""Raised for all OptGS API misuse / unsupported-checkpoint conditions."""
@runtime_checkable
class GaussiansLike(Protocol):
"""An inria ``GaussianModel`` (raw, pre-activation parameter storage)."""
active_sh_degree: int
max_sh_degree: int
_xyz: Any
_features_dc: Any
_features_rest: Any
_scaling: Any
_rotation: Any
_opacity: Any
def save_ply(self, path: str) -> None: ...
def load_ply(self, path: str) -> None: ...
@runtime_checkable
class CameraLike(Protocol):
"""An inria ``Camera`` (world->camera ``R,T`` + FoV, COLMAP convention)."""
R: Any
T: Any
FoVx: float
FoVy: float
image_width: int
image_height: int
original_image: Any # [3, H, W] in [0, 1]
@runtime_checkable
class SceneLike(Protocol):
"""An inria ``Scene`` holding a ``GaussianModel`` and posed cameras."""
cameras_extent: float
gaussians: GaussiansLike
def getTrainCameras(self, scale: float = 1.0) -> Sequence[CameraLike]: ...
# Explicit attribute lists drive precise error messages (clearer than a bare
# isinstance failure, which does not say *which* member is missing).
_SCENE_ATTRS = ("cameras_extent", "gaussians")
_SCENE_METHODS = ("getTrainCameras",)
_GAUSSIAN_ATTRS = (
"active_sh_degree", "max_sh_degree",
"_xyz", "_features_dc", "_features_rest", "_scaling", "_rotation", "_opacity",
)
_GAUSSIAN_METHODS = ("save_ply", "load_ply")
_CAMERA_ATTRS = (
"R", "T", "FoVx", "FoVy", "image_width", "image_height", "original_image",
)
def _missing(obj: object, attrs: tuple[str, ...], methods: tuple[str, ...] = ()) -> list[str]:
miss = [a for a in attrs if not hasattr(obj, a)]
miss += [f"{m}()" for m in methods if not callable(getattr(obj, m, None))]
return miss
def assert_scene_protocol(scene: object) -> None:
"""Validate ``scene`` against :class:`SceneLike`; raise a precise error.
Checked structurally (duck-typed) so inria's classes pass unchanged.
"""
miss = _missing(scene, _SCENE_ATTRS, _SCENE_METHODS)
if miss:
raise OptGSError(
f"scene is missing required attribute(s)/method(s): {', '.join(miss)}. "
f"Expected an inria-style Scene (cameras_extent, gaussians, "
f"getTrainCameras()). Use OptGS.initialize_from_ply/"
f"initialize_from_tensors for non-inria codebases."
)
gm = scene.gaussians
gmiss = _missing(gm, _GAUSSIAN_ATTRS, _GAUSSIAN_METHODS)
if gmiss:
raise OptGSError(
f"scene.gaussians is missing: {', '.join(gmiss)}. Expected an inria "
f"GaussianModel (raw _xyz/_features_dc/_features_rest/_scaling/"
f"_rotation/_opacity + save_ply/load_ply)."
)
cams = scene.getTrainCameras()
if cams is None or len(cams) == 0:
raise OptGSError("scene.getTrainCameras() returned no cameras.")
cmiss = _missing(cams[0], _CAMERA_ATTRS)
if cmiss:
raise OptGSError(
f"train camera is missing: {', '.join(cmiss)}. Expected an inria "
f"Camera (R, T, FoVx, FoVy, image_width, image_height, "
f"original_image)."
)