Files
hao-ai-lab-FastVideo/fastvideo/tests/eval/test_evaluator_single.py
T

137 lines
4.7 KiB
Python

"""End-to-end tests for single-replica eval through the public API.
Runs the lightweight pixel-space metrics — ``common.psnr`` and
``common.ssim`` — under both shapes that real callers use:
* one-shot ``evaluate(video=..., reference=...)`` (the helper in
``fastvideo.eval.api``);
* a long-lived ``Evaluator``, called once per sample;
* a long-lived ``Evaluator``, called with a list of sample dicts to
fan out (``samples=[...]``).
GPU-only metrics live in separate test modules / classes; everything
here runs on CPU so the suite stays cheap to invoke.
"""
from __future__ import annotations
import pytest
import torch
from fastvideo.eval import MetricResult, create_evaluator, evaluate
# Tight resolution + frame count so CPU SSIM stays under a couple of seconds.
_T, _C, _H, _W = 6, 3, 32, 32
@pytest.fixture
def gen_ref():
"""Reproducible (gen, ref) pair shaped (T, C, H, W)."""
torch.manual_seed(0)
gen = torch.rand(_T, _C, _H, _W)
ref = torch.rand(_T, _C, _H, _W)
return gen, ref
@pytest.fixture
def evaluator():
ev = create_evaluator(
metrics=["common.psnr", "common.ssim"],
device="cpu",
)
yield ev
ev.shutdown()
def _assert_well_formed(result: MetricResult, name: str) -> None:
assert isinstance(result, MetricResult)
assert result.name == name
assert result.score is not None
assert isinstance(result.score, float)
# PSNR / SSIM both populate per-frame details.
assert "per_frame" in result.details
assert len(result.details["per_frame"]) == _T
# ---------------------------------------------------------------------------
# One-shot helper
# ---------------------------------------------------------------------------
def test_evaluate_one_shot_returns_dict_of_metric_results(gen_ref):
gen, ref = gen_ref
out = evaluate(generated=gen, reference=ref,
metrics=["common.psnr", "common.ssim"], device="cpu")
assert isinstance(out, dict)
assert set(out.keys()) == {"common.psnr", "common.ssim"}
_assert_well_formed(out["common.psnr"], "common.psnr")
_assert_well_formed(out["common.ssim"], "common.ssim")
# ---------------------------------------------------------------------------
# Long-lived Evaluator, single-sample form
# ---------------------------------------------------------------------------
def test_evaluator_single_sample_returns_dict(evaluator, gen_ref):
gen, ref = gen_ref
out = evaluator.evaluate(video=gen, reference=ref)
assert isinstance(out, dict)
assert set(out.keys()) == {"common.psnr", "common.ssim"}
for name, mr in out.items():
_assert_well_formed(mr, name)
def test_evaluator_accepts_legacy_5d_input(evaluator, gen_ref):
"""Callers that still pass ``(1, T, C, H, W)`` should get unwrapped."""
gen, ref = gen_ref
out = evaluator.evaluate(video=gen.unsqueeze(0), reference=ref.unsqueeze(0))
_assert_well_formed(out["common.psnr"], "common.psnr")
def test_evaluator_score_is_deterministic(evaluator, gen_ref):
gen, ref = gen_ref
a = evaluator.evaluate(video=gen, reference=ref)
b = evaluator.evaluate(video=gen.clone(), reference=ref.clone())
assert a["common.psnr"].score == pytest.approx(b["common.psnr"].score)
assert a["common.ssim"].score == pytest.approx(b["common.ssim"].score)
def test_evaluator_psnr_identical_videos_is_high(evaluator, gen_ref):
"""PSNR(x, x) is unbounded above; with our clamp it caps near 100 dB."""
gen, _ = gen_ref
out = evaluator.evaluate(video=gen, reference=gen)
assert out["common.psnr"].score > 50.0
assert out["common.ssim"].score == pytest.approx(1.0, abs=1e-5)
# ---------------------------------------------------------------------------
# Long-lived Evaluator, list (fan-out) form
# ---------------------------------------------------------------------------
def test_evaluator_samples_list_preserves_input_order(evaluator):
"""When ``samples=[...]`` is passed, results must come back per sample."""
torch.manual_seed(1)
samples = []
for i in range(4):
# Vary the reference enough that scores differ across rows.
gen = torch.rand(_T, _C, _H, _W)
ref = gen + 0.01 * (i + 1) * torch.rand_like(gen)
samples.append({"video": gen, "reference": ref})
out = evaluator.evaluate(samples=samples)
assert isinstance(out, list)
assert len(out) == len(samples)
for row in out:
assert set(row.keys()) == {"common.psnr", "common.ssim"}
_assert_well_formed(row["common.psnr"], "common.psnr")
# Re-running should give bit-identical scores (no nondeterministic
# scheduling effects under single-GPU dispatch).
out2 = evaluator.evaluate(samples=samples)
for a, b in zip(out, out2):
assert a["common.psnr"].score == pytest.approx(b["common.psnr"].score)