Source: src/mcss/model/system.py · Plain text

src/mcss/model/system.py

"""Top-level scene-state systems and controlled model construction."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

from torch import Tensor, nn

from mcss.measurements import FixedMeasurementRenderer
from mcss.model.controls import FreeDecoderControl, LearnedHeadsControl
from mcss.model.evidence_encoder import (
    DualEvidenceStateEncoder,
    EvidenceResidualStateEncoder,
    TransportEvidenceStateEncoder,
)
from mcss.model.state_encoder import UnprojectiveStateEncoder
from mcss.types import Cameras, SceneState

ModelMode = Literal["fixed", "learned_heads", "free_decoder"]
StateArchitecture = Literal[
    "legacy",
    "evidence_residual",
    "dual_evidence",
    "dual_evidence_transport",
]
AppearanceResolutionScale = Literal[1, 2]


@dataclass(frozen=True)
class ModelConfig:
    mode: ModelMode = "fixed"
    state_architecture: StateArchitecture = "legacy"
    voxel_resolution: int | tuple[int, int, int] = 32
    image_feature_dim: int = 32
    state_feature_dim: int = 32
    refinement_blocks: int = 3
    evidence_temperature: float = 0.1
    observed_residual_floor: float = 0.1
    completion_residual_scale: float = 1.5
    surface_peak_temperature: float = 0.05
    appearance_resolution_scale: AppearanceResolutionScale = 1
    n_samples: int = 64
    ray_chunk_size: int = 16_384

    def __post_init__(self) -> None:
        if self.mode not in {"fixed", "learned_heads", "free_decoder"}:
            raise ValueError(f"unsupported model mode: {self.mode}")
        if self.state_architecture not in {
            "legacy",
            "evidence_residual",
            "dual_evidence",
            "dual_evidence_transport",
        }:
            raise ValueError(f"unsupported state architecture: {self.state_architecture}")
        if self.evidence_temperature <= 0:
            raise ValueError("evidence_temperature must be positive")
        if not 0.0 <= self.observed_residual_floor <= 1.0:
            raise ValueError("observed_residual_floor must be within [0, 1]")
        if self.completion_residual_scale <= 0:
            raise ValueError("completion_residual_scale must be positive")
        if self.surface_peak_temperature <= 0:
            raise ValueError("surface_peak_temperature must be positive")
        if self.appearance_resolution_scale not in {1, 2}:
            raise ValueError("appearance_resolution_scale must be one or two")
        if self.state_architecture == "legacy" and self.appearance_resolution_scale != 1:
            raise ValueError(
                "appearance_resolution_scale=2 requires state_architecture: evidence_residual"
            )
        if isinstance(self.voxel_resolution, int):
            resolution = (self.voxel_resolution,) * 3
        else:
            resolution = tuple(self.voxel_resolution)
        if len(resolution) != 3 or min(resolution) < 4:
            raise ValueError("voxel_resolution must contain D, H, W values of at least four")
        object.__setattr__(self, "voxel_resolution", resolution)


@dataclass
class ModelOutput:
    predictions: dict[str, Tensor]
    state: SceneState


class MeasurementCompleteSystem(nn.Module):
    """Construct one state from context RGB and answer target camera queries."""

    def __init__(self, config: ModelConfig) -> None:
        super().__init__()
        self.config = config
        if config.state_architecture == "legacy":
            self.state_encoder = UnprojectiveStateEncoder(
                voxel_resolution=config.voxel_resolution,
                image_feature_dim=config.image_feature_dim,
                state_feature_dim=config.state_feature_dim,
                refinement_blocks=config.refinement_blocks,
            )
        elif config.state_architecture == "evidence_residual":
            self.state_encoder = EvidenceResidualStateEncoder(
                voxel_resolution=config.voxel_resolution,
                image_feature_dim=config.image_feature_dim,
                state_feature_dim=config.state_feature_dim,
                refinement_blocks=config.refinement_blocks,
                evidence_temperature=config.evidence_temperature,
                observed_residual_floor=config.observed_residual_floor,
                completion_residual_scale=config.completion_residual_scale,
                appearance_resolution_scale=config.appearance_resolution_scale,
            )
        elif config.state_architecture == "dual_evidence":
            self.state_encoder = DualEvidenceStateEncoder(
                voxel_resolution=config.voxel_resolution,
                image_feature_dim=config.image_feature_dim,
                state_feature_dim=config.state_feature_dim,
                refinement_blocks=config.refinement_blocks,
                evidence_temperature=config.evidence_temperature,
                surface_peak_temperature=config.surface_peak_temperature,
                observed_residual_floor=config.observed_residual_floor,
                completion_residual_scale=config.completion_residual_scale,
                appearance_resolution_scale=config.appearance_resolution_scale,
            )
        else:
            self.state_encoder = TransportEvidenceStateEncoder(
                voxel_resolution=config.voxel_resolution,
                image_feature_dim=config.image_feature_dim,
                state_feature_dim=config.state_feature_dim,
                refinement_blocks=config.refinement_blocks,
                evidence_temperature=config.evidence_temperature,
                surface_peak_temperature=config.surface_peak_temperature,
                observed_residual_floor=config.observed_residual_floor,
                completion_residual_scale=config.completion_residual_scale,
                appearance_resolution_scale=config.appearance_resolution_scale,
            )
        self.renderer = FixedMeasurementRenderer(
            n_samples=config.n_samples, ray_chunk_size=config.ray_chunk_size
        )
        if config.mode == "fixed":
            self.control: nn.Module | None = None
        elif config.mode == "learned_heads":
            self.control = LearnedHeadsControl(config.state_feature_dim, self.renderer)
        else:
            self.control = FreeDecoderControl(config.state_feature_dim, self.renderer)

    def forward(
        self,
        context_rgb: Tensor,
        context_cameras: Cameras,
        target_cameras: Cameras,
        bounds: Tensor,
    ) -> ModelOutput:
        state = self.state_encoder(context_rgb, context_cameras, bounds)
        if self.control is None:
            predictions = self.renderer(state, target_cameras)
        else:
            predictions = self.control(state, target_cameras)
        return ModelOutput(predictions, state)


def build_model(config: ModelConfig) -> MeasurementCompleteSystem:
    return MeasurementCompleteSystem(config)