Source: src/mcss/data/collate.py · Plain text

src/mcss/data/collate.py

"""Strict collation for sparse-view scene examples."""

from __future__ import annotations

from collections.abc import Sequence

import torch
from torch import Tensor

from mcss.types import Cameras, SceneBatch, SceneExample


def collate_scene_examples(examples: Sequence[SceneExample]) -> SceneBatch:
    if not examples:
        raise ValueError("cannot collate an empty example sequence")
    context_size = examples[0].context_cameras.image_size
    target_size = examples[0].target_cameras.image_size
    if any(example.context_cameras.image_size != context_size for example in examples):
        raise ValueError("all context image sizes in a batch must match")
    if any(example.target_cameras.image_size != target_size for example in examples):
        raise ValueError("all target image sizes in a batch must match")
    return SceneBatch(
        context_rgb=torch.stack([example.context_rgb for example in examples]),
        context_cameras=Cameras(
            torch.stack([example.context_cameras.intrinsics for example in examples]),
            torch.stack([example.context_cameras.c2w for example in examples]),
            context_size,
        ),
        target_rgb=torch.stack([example.target_rgb for example in examples]),
        target_cameras=Cameras(
            torch.stack([example.target_cameras.intrinsics for example in examples]),
            torch.stack([example.target_cameras.c2w for example in examples]),
            target_size,
        ),
        bounds=torch.stack([example.bounds for example in examples]),
        target_depth=_stack_optional(examples, "target_depth"),
        target_normal=_stack_optional(examples, "target_normal"),
        target_point=_stack_optional(examples, "target_point"),
        target_visibility=_stack_optional(examples, "target_visibility"),
        target_support=_stack_optional(examples, "target_support"),
        scene_ids=tuple(example.scene_id for example in examples),
    )


def _stack_optional(examples: Sequence[SceneExample], name: str) -> Tensor | None:
    values = [getattr(example, name) for example in examples]
    if all(value is None for value in values):
        return None
    if any(value is None for value in values):
        raise ValueError(f"cannot mix missing and present values for {name}")
    return torch.stack(values)  # type: ignore[arg-type]