Source: tests/dynamic/test_training_supervision.py · Plain text

tests/dynamic/test_training_supervision.py

import json
import math

import numpy as np
import pytest
import torch
from PIL import Image

from mcss.training.smoke import DynamicTrainingSmokeConfig
from mcss.training.supervision import TrainingSupervision


def _write_query_manifest(tmp_path, *, split_id: str = "train", frame_id: int = 9):
    rgb_path = tmp_path / "query.png"
    depth_path = tmp_path / "query.npy"
    Image.fromarray(np.full((4, 6, 3), 127, dtype=np.uint8)).save(rgb_path)
    np.save(depth_path, np.full((4, 6), 2.0, dtype=np.float32))
    manifest = {
        "schema_version": "mcss.dynamic.query_vault.v1",
        "episode_id": "train--scene-a--cam-00",
        "scene_id": "scene-a",
        "split_id": split_id,
        "query_vault_id": "qv-training",
        "image_size": [4, 6],
        "query": [
            {
                "frame_id": frame_id,
                "intrinsics": [[4.0, 0.0, 2.5], [0.0, 4.0, 1.5], [0.0, 0.0, 1.0]],
                "c2w": np.eye(4).tolist(),
                "rgb": str(rgb_path),
                "depth": str(depth_path),
            }
        ],
    }
    path = tmp_path / "query.json"
    path.write_text(json.dumps(manifest), encoding="utf-8")
    return path


def test_training_supervision_reads_only_matching_train_query(tmp_path) -> None:
    path = _write_query_manifest(tmp_path)

    supervision = TrainingSupervision.from_query_file(
        path,
        episode_id="train--scene-a--cam-00",
        scene_id="scene-a",
        query_vault_id="qv-training",
        context_frame_ids=(0, 1, 2, 3),
    )

    assert supervision.frame_ids == (9,)
    assert supervision.query_cameras.leading_shape == (1, 1)
    assert supervision.query_rgb.shape == (1, 1, 3, 4, 6)
    assert supervision.query_depth.shape == (1, 1, 1, 4, 6)
    torch.testing.assert_close(supervision.query_depth, torch.full((1, 1, 1, 4, 6), 2.0))


@pytest.mark.parametrize(
    ("split_id", "context_frame_ids", "error"),
    [
        ("dev", (), "training split"),
        ("train", (9,), "disjoint"),
    ],
)
def test_training_supervision_rejects_nontrain_or_overlapping_query(
    tmp_path, split_id, context_frame_ids, error
) -> None:
    path = _write_query_manifest(tmp_path, split_id=split_id)

    with pytest.raises(ValueError, match=error):
        TrainingSupervision.from_query_file(
            path,
            episode_id="train--scene-a--cam-00",
            scene_id="scene-a",
            query_vault_id="qv-training",
            context_frame_ids=context_frame_ids,
        )


@pytest.mark.parametrize("learning_rate", [math.nan, math.inf, -math.inf, 0.0])
def test_training_smoke_config_rejects_nonfinite_or_nonpositive_learning_rate(
    learning_rate,
) -> None:
    with pytest.raises(ValueError, match="finite and positive"):
        DynamicTrainingSmokeConfig(learning_rate=learning_rate)