File size: 3,759 Bytes
78d2329
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""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)."
        )