"""cli/evaluate.py の normalization(linear mean anchor 正規化)に関する仕様ベーステスト.
仕様出典:
- 実装サマリー(CLI 後方互換・ログ出力・metrics.csv mean 一致・形状指標不変性)
- docs/04_SPEC/SPEC_04_評価仕様.md(評価パイプライン Step 6: normalization)
- docs/04_SPEC/SPEC_03_基準設計の物理構成.md(「形状指標への影響(重要)」節)
- docs/01_GUIDE/GUIDE_08_テスト方針.md(テスト方針)
テスト方針:
- subprocess ではなく main() を直接呼ぶ(速度・カバレッジのため)
- ファイル I/O はモックせず tmp_path フィクスチャで実ファイルを使う (GUIDE_08)
- 乱数を使う場合は seed を必ず固定する (GUIDE_08)
- CLI 統合テストとして「パイプライン全体を通した振る舞い」を検証する
注意:
matplotlib の Agg バックエンドは evaluate.py 内部で設定される.
テスト側でも安全のため import 前に設定する.
"""
from __future__ import annotations
import matplotlib # noqa: I001
# headless 環境でも動くよう Agg バックエンドを pyplot より前に設定する
matplotlib.use("Agg")
from pathlib import Path
import numpy
import pytest
from sm_tias_lightsim.cli.evaluate import main
# ---------------------------------------------------------------------------
# テスト用定数
# ---------------------------------------------------------------------------
#: output/baseline/luminance.npy の絶対パス(実際に存在する前提)
BASELINE_LUMINANCE_NPY = (
Path(__file__).parent.parent.parent / "output" / "baseline" / "luminance.npy"
)
#: configs/eval_baseline_linear.yaml の絶対パス
EVAL_BASELINE_LINEAR_YAML = (
Path(__file__).parent.parent.parent / "configs" / "eval_baseline_linear.yaml"
)
#: configs/eval_baseline.yaml の絶対パス(OETF on, normalization なし)
EVAL_BASELINE_YAML = (
Path(__file__).parent.parent.parent / "configs" / "eval_baseline.yaml"
)
#: 基準 baseline 絶対値スナップショット(CoV / max-min / C/P / 勾配)の xfail 理由.
# これらの期待値は特定時点の output/baseline/luminance.npy(.gitignore 対象・キャリブ中に
# 再生成される一時成果物)に紐づくため,キャリブレーション進行中は実値とドリフトする.
# 評価パイプライン自体の正しさは本ファイルの「正規化不変性 / mean 一致」テスト群が担保する
# (絶対値スナップショットはそれとは別の脆い検証).キャリブ確定時に期待値を更新するか撤去する.
_CALIBRATION_XFAIL_REASON = (
"基準 baseline の絶対値スナップショット.期待値は特定時点の render 成果物"
"(output/baseline/luminance.npy・gitignore 対象)に依存し,キャリブレーション進行中は"
"ドリフトする.キャリブ確定時に期待値更新 or 撤去する."
)
# ---------------------------------------------------------------------------
# テスト用ヘルパー
# ---------------------------------------------------------------------------
def _make_luminance_array(seed: int = 0, size: int = 256) -> numpy.ndarray:
"""ゼロを含まない合成輝度配列を返す(seed 固定).
max/min 計算が走るようゼロを含まない値にする (GUIDE_08 補助ガイダンス)
"""
rng = numpy.random.RandomState(seed)
return numpy.full((size, size), 100.0) + rng.normal(0, 5, (size, size))
def _make_normalization_yaml(
tmp_path: Path,
target_mean: float = 146.7146,
x: int = 32,
y: int = 32,
width: int = 192,
height: int = 192,
zones_enabled: bool = True,
radial_profile: bool = True,
) -> Path:
"""normalization.enabled=true を含む評価 YAML を tmp_path に書き出す.
仕様: eval_baseline_linear.yaml の normalization セクション相当
"""
content = f"""\
roi:
type: image_pixel
x: {x}
y: {y}
width: {width}
height: {height}
zones:
enabled: {'true' if zones_enabled else 'false'}
boundaries: [0.33, 0.66]
metrics:
cov: true
std: true
max_min: true
radial_profile: {'true' if radial_profile else 'false'}
normalization:
enabled: true
type: mean_anchor
target_mean: {target_mean}
"""
yaml_path = tmp_path / "eval_normalization.yaml"
yaml_path.write_text(content, encoding="utf-8")
return yaml_path
def _make_plain_yaml(
tmp_path: Path,
x: int = 32,
y: int = 32,
width: int = 192,
height: int = 192,
) -> Path:
"""normalization なし(後方互換)の評価 YAML を tmp_path に書き出す."""
content = f"""\
roi:
type: image_pixel
x: {x}
y: {y}
width: {width}
height: {height}
zones:
enabled: true
boundaries: [0.33, 0.66]
metrics:
cov: true
std: true
max_min: true
radial_profile: true
"""
yaml_path = tmp_path / "eval_plain.yaml"
yaml_path.write_text(content, encoding="utf-8")
return yaml_path
def _run_main(npy_path: Path, config_path: Path, output_dir: Path) -> int:
"""main() を呼ぶ共通ラッパー(行長削減のため)."""
return main(
["--image", str(npy_path), "--config", str(config_path), "--output", str(output_dir)]
)
# ---------------------------------------------------------------------------
# 1. 正常系 — normalization.enabled=true で main() が 0 を返すこと
# ---------------------------------------------------------------------------
def test_normalization_enabled_returns_0(tmp_path: Path):
"""normalization.enabled=true を含む YAML で main() が 0 を返すこと.
仕様: normalization.enabled=True のとき ROI 抽出後・OETF 前に apply_mean_anchor を適用する
(cli/evaluate.py main 処理フロー)
"""
luminance = _make_luminance_array(seed=300)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
config_path = _make_normalization_yaml(tmp_path, target_mean=80.0)
output_dir = tmp_path / "output"
result = _run_main(npy_path, config_path, output_dir)
assert result == 0
def test_normalization_enabled_creates_metrics_csv(tmp_path: Path):
"""normalization.enabled=true のとき metrics.csv が生成されること.
仕様: normalization 後の ROI で均一性指標を算出し CSV に保存する
"""
luminance = _make_luminance_array(seed=301)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
config_path = _make_normalization_yaml(tmp_path, target_mean=100.0)
output_dir = tmp_path / "output"
_run_main(npy_path, config_path, output_dir)
assert (output_dir / "metrics.csv").exists()
# ---------------------------------------------------------------------------
# 2. mean が target_mean に一致すること
# ---------------------------------------------------------------------------
def test_normalization_mean_equals_target(tmp_path: Path):
"""normalization.enabled=true のとき metrics.csv の mean が target_mean に一致すること.
仕様(実装サマリー「線形ゲインの不変性」,CLI 後方互換):
metrics.csv の mean が target_mean に一致する
NOTE: 合成データは ROI = 配列全体と一致する設定にする
(size=256, ROI x=0,y=0,w=256,h=256 → ROI mean == 配列 mean)
"""
import pandas
rng = numpy.random.RandomState(302)
luminance = numpy.full((256, 256), 100.0) + rng.normal(0, 5, (256, 256))
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
target_mean = 146.7146
# ROI = 配列全体にして ROI mean が配列 mean に一致するようにする
content = f"""\
roi:
type: image_pixel
x: 0
y: 0
width: 256
height: 256
normalization:
enabled: true
type: mean_anchor
target_mean: {target_mean}
"""
config_path = tmp_path / "eval_full_roi.yaml"
config_path.write_text(content, encoding="utf-8")
output_dir = tmp_path / "output"
_run_main(npy_path, config_path, output_dir)
df = pandas.read_csv(output_dir / "metrics.csv")
mean_val = float(df["mean"].iloc[0])
# apply_mean_anchor の後 mean は target_mean に厳密一致するはず
assert mean_val == pytest.approx(target_mean, rel=1e-6)
# ---------------------------------------------------------------------------
# 3. 形状指標の不変性(CoV / max-min / C/P / 勾配)
# ---------------------------------------------------------------------------
def test_normalization_cov_same_as_without_normalization(tmp_path: Path):
"""normalization 適用前後で CoV が不変であること(CLI レベル検証).
仕様(実装サマリー「線形ゲインの不変性」):
線形ゲインは CoV に不変
normalization 後の CoV == normalization 前の CoV
"""
import pandas
rng = numpy.random.RandomState(303)
luminance = numpy.full((256, 256), 80.0) + rng.normal(0, 5, (256, 256))
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
roi_kwargs = {"x": 0, "y": 0, "width": 256, "height": 256}
# normalization なし
config_no_norm = _make_plain_yaml(tmp_path, **roi_kwargs)
out_no_norm = tmp_path / "output_no_norm"
_run_main(npy_path, config_no_norm, out_no_norm)
df_no_norm = pandas.read_csv(out_no_norm / "metrics.csv")
cov_no_norm = float(df_no_norm["cov"].iloc[0])
# normalization あり
content_norm = """\
roi:
type: image_pixel
x: 0
y: 0
width: 256
height: 256
normalization:
enabled: true
type: mean_anchor
target_mean: 200.0
"""
config_norm = tmp_path / "eval_norm.yaml"
config_norm.write_text(content_norm, encoding="utf-8")
out_norm = tmp_path / "output_norm"
_run_main(npy_path, config_norm, out_norm)
df_norm = pandas.read_csv(out_norm / "metrics.csv")
cov_norm = float(df_norm["cov"].iloc[0])
# 線形ゲインで CoV は変わらない
assert cov_norm == pytest.approx(cov_no_norm, rel=1e-8)
def test_normalization_max_min_ratio_same_as_without(tmp_path: Path):
"""normalization 適用前後で max/min 比が不変であること(CLI レベル検証).
仕様(実装サマリー「線形ゲインの不変性」):
線形ゲインは max/min 比に不変
"""
import pandas
rng = numpy.random.RandomState(304)
luminance = numpy.full((256, 256), 80.0) + rng.uniform(1.0, 20.0, (256, 256))
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
roi_kwargs = {"x": 0, "y": 0, "width": 256, "height": 256}
config_no_norm = _make_plain_yaml(tmp_path, **roi_kwargs)
out_no_norm = tmp_path / "output_no_norm"
_run_main(npy_path, config_no_norm, out_no_norm)
df_no_norm = pandas.read_csv(out_no_norm / "metrics.csv")
ratio_before = float(df_no_norm["max_min_ratio"].iloc[0])
content_norm = """\
roi:
type: image_pixel
x: 0
y: 0
width: 256
height: 256
normalization:
enabled: true
type: mean_anchor
target_mean: 50.0
"""
config_norm = tmp_path / "eval_norm.yaml"
config_norm.write_text(content_norm, encoding="utf-8")
out_norm = tmp_path / "output_norm"
_run_main(npy_path, config_norm, out_norm)
df_norm = pandas.read_csv(out_norm / "metrics.csv")
ratio_after = float(df_norm["max_min_ratio"].iloc[0])
assert ratio_after == pytest.approx(ratio_before, rel=1e-8)
def test_normalization_gradient_same_as_without(tmp_path: Path):
"""normalization 適用前後で勾配(gradient_magnitude)が不変であること.
仕様(実装サマリー「線形ゲインの不変性」):
線形ゲインは勾配 % に不変
"""
import pandas
rng = numpy.random.RandomState(305)
luminance = numpy.full((256, 256), 80.0) + rng.normal(0, 5, (256, 256))
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
roi_kwargs = {"x": 0, "y": 0, "width": 256, "height": 256}
config_no_norm = _make_plain_yaml(tmp_path, **roi_kwargs)
out_no_norm = tmp_path / "output_no_norm"
_run_main(npy_path, config_no_norm, out_no_norm)
df_no_norm = pandas.read_csv(out_no_norm / "metrics.csv")
grad_before = float(df_no_norm["gradient_magnitude"].iloc[0])
content_norm = """\
roi:
type: image_pixel
x: 0
y: 0
width: 256
height: 256
zones:
enabled: true
boundaries: [0.33, 0.66]
normalization:
enabled: true
type: mean_anchor
target_mean: 200.0
"""
config_norm = tmp_path / "eval_norm.yaml"
config_norm.write_text(content_norm, encoding="utf-8")
out_norm = tmp_path / "output_norm"
_run_main(npy_path, config_norm, out_norm)
df_norm = pandas.read_csv(out_norm / "metrics.csv")
grad_after = float(df_norm["gradient_magnitude"].iloc[0])
assert grad_after == pytest.approx(grad_before, rel=1e-8)
def test_normalization_cp_ratio_same_as_without(tmp_path: Path):
"""normalization 適用前後で C/P 比(center_periphery_ratio)が不変であること.
仕様(実装サマリー「線形ゲインの不変性」):
線形ゲインは C/P 比に不変
"""
import pandas
rng = numpy.random.RandomState(306)
luminance = numpy.full((256, 256), 80.0) + rng.normal(0, 5, (256, 256))
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
roi_kwargs = {"x": 0, "y": 0, "width": 256, "height": 256}
config_no_norm = _make_plain_yaml(tmp_path, **roi_kwargs)
out_no_norm = tmp_path / "output_no_norm"
_run_main(npy_path, config_no_norm, out_no_norm)
df_no_norm = pandas.read_csv(out_no_norm / "metrics.csv")
cp_before = float(df_no_norm["center_periphery_ratio"].iloc[0])
content_norm = """\
roi:
type: image_pixel
x: 0
y: 0
width: 256
height: 256
zones:
enabled: true
boundaries: [0.33, 0.66]
normalization:
enabled: true
type: mean_anchor
target_mean: 150.0
"""
config_norm = tmp_path / "eval_norm.yaml"
config_norm.write_text(content_norm, encoding="utf-8")
out_norm = tmp_path / "output_norm"
_run_main(npy_path, config_norm, out_norm)
df_norm = pandas.read_csv(out_norm / "metrics.csv")
cp_after = float(df_norm["center_periphery_ratio"].iloc[0])
assert cp_after == pytest.approx(cp_before, rel=1e-8)
# ---------------------------------------------------------------------------
# 4. ログ出力の確認
# ---------------------------------------------------------------------------
def test_normalization_prints_alpha_and_target_mean(tmp_path: Path, capsys):
"""normalization.enabled=true のとき標準出力に alpha と target_mean が表示されること.
仕様(実装サマリー「CLI 後方互換」):
`normalization : alpha=... target_mean=...` のログが出る
"""
luminance = _make_luminance_array(seed=307)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
config_path = _make_normalization_yaml(tmp_path, target_mean=100.0)
output_dir = tmp_path / "output"
_run_main(npy_path, config_path, output_dir)
captured = capsys.readouterr()
# cli/evaluate.py の print フォーマット: " normalization : alpha=... target_mean=..."
assert "normalization : alpha=" in captured.out
assert "target_mean=" in captured.out
def test_normalization_disabled_no_normalization_log(tmp_path: Path, capsys):
"""normalization.enabled=false(または省略)のとき normalization ログが出ないこと.
仕様: normalization が無効なとき _apply_normalization_to_luminance は呼ばれない
(cli/evaluate.py main: `if norm_cfg is not None and norm_cfg.enabled`)
NOTE: ログフォーマット "normalization : alpha=" を検索する(パス名との混同を避ける)
"""
luminance = _make_luminance_array(seed=308)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
config_path = _make_plain_yaml(tmp_path)
output_dir = tmp_path / "output"
_run_main(npy_path, config_path, output_dir)
captured = capsys.readouterr()
# "normalization : alpha=" はログとして出力されるフォーマット(cli/evaluate.py)
# normalization が無効なときはこのログは出ない
assert "normalization : alpha=" not in captured.out
# ---------------------------------------------------------------------------
# 5. 後方互換性 — normalization なし既存 YAML は動作変更なし
# ---------------------------------------------------------------------------
def test_backward_compat_no_normalization_key(tmp_path: Path):
"""normalization キーなしの既存 YAML が後方互換で動くこと.
仕様(実装サマリー「CLI 後方互換」):
normalization セクションがない既存 config(eval_baseline.yaml, eval_real.yaml)は
動作変更なし
"""
luminance = _make_luminance_array(seed=309)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
config_path = _make_plain_yaml(tmp_path)
output_dir = tmp_path / "output"
result = _run_main(npy_path, config_path, output_dir)
assert result == 0
assert (output_dir / "metrics.csv").exists()
def test_backward_compat_oetf_yaml_unaffected(tmp_path: Path):
"""oetf.enabled=true の既存 YAML(eval_baseline.yaml 相当)が動作変更なしであること.
仕様(実装サマリー「CLI 後方互換」):
eval_baseline.yaml (OETF on) は動作変更なし
"""
luminance = _make_luminance_array(seed=310)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
# eval_baseline.yaml 相当(OETF on, normalization なし)
content = """\
roi:
type: image_pixel
x: 32
y: 32
width: 192
height: 192
oetf:
enabled: true
transfer: srgb
exposure:
mode: anchor_mean
target_mean: 146.7146
quantize_uint8: true
"""
config_path = tmp_path / "eval_oetf_only.yaml"
config_path.write_text(content, encoding="utf-8")
output_dir = tmp_path / "output"
result = _run_main(npy_path, config_path, output_dir)
assert result == 0
assert (output_dir / "metrics.csv").exists()
# ---------------------------------------------------------------------------
# 6. 異常系 — OETF + normalization 同時有効化で main() が 1 を返すこと
# ---------------------------------------------------------------------------
def test_oetf_and_normalization_both_enabled_returns_1(tmp_path: Path):
"""oetf.enabled=true かつ normalization.enabled=true のとき main() が 1 を返すこと.
仕様(実装サマリー「スキーマ排他性」):
oetf.enabled=True かつ normalization.enabled=True は ValidationError
→ cli/evaluate.py は return 1
"""
luminance = _make_luminance_array(seed=311)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
content = """\
roi:
type: image_pixel
x: 32
y: 32
width: 192
height: 192
oetf:
enabled: true
transfer: srgb
exposure:
mode: anchor_mean
target_mean: 146.7146
quantize_uint8: true
normalization:
enabled: true
type: mean_anchor
target_mean: 146.7146
"""
config_path = tmp_path / "eval_both_enabled.yaml"
config_path.write_text(content, encoding="utf-8")
output_dir = tmp_path / "output"
result = _run_main(npy_path, config_path, output_dir)
assert result == 1
def test_oetf_and_normalization_both_enabled_stderr_message(tmp_path: Path, capsys):
"""oetf + normalization 同時有効化時にエラーメッセージが stderr に出ること."""
luminance = _make_luminance_array(seed=312)
npy_path = tmp_path / "luminance.npy"
numpy.save(npy_path, luminance)
content = """\
roi:
type: image_pixel
x: 32
y: 32
width: 192
height: 192
oetf:
enabled: true
transfer: srgb
exposure:
mode: anchor_mean
target_mean: 146.7146
normalization:
enabled: true
type: mean_anchor
target_mean: 146.7146
"""
config_path = tmp_path / "eval_both_enabled.yaml"
config_path.write_text(content, encoding="utf-8")
output_dir = tmp_path / "output"
main(["--image", str(npy_path), "--config", str(config_path), "--output", str(output_dir)])
captured = capsys.readouterr()
assert "エラー" in captured.err
# ---------------------------------------------------------------------------
# 7. 統合テスト — output/baseline/luminance.npy + eval_baseline_linear.yaml
# ---------------------------------------------------------------------------
@pytest.mark.skipif(
not BASELINE_LUMINANCE_NPY.exists(),
reason="output/baseline/luminance.npy が存在しない場合はスキップ",
)
def test_baseline_linear_yaml_returns_0():
"""output/baseline/luminance.npy + configs/eval_baseline_linear.yaml で main() が 0 を返すこと.
仕様(実装サマリー「動作確認済み(参考)」):
入力: output/baseline/luminance.npy
設定: configs/eval_baseline_linear.yaml(normalization.enabled=true, target_mean=146.7146)
期待: 正常終了
"""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir) / "output"
result = main(
[
"--image", str(BASELINE_LUMINANCE_NPY),
"--config", str(EVAL_BASELINE_LINEAR_YAML),
"--output", str(output_dir),
]
)
assert result == 0
@pytest.mark.skipif(
not BASELINE_LUMINANCE_NPY.exists(),
reason="output/baseline/luminance.npy が存在しない場合はスキップ",
)
def test_baseline_linear_yaml_mean_close_to_target():
"""eval_baseline_linear.yaml 実行後 metrics.csv の mean が target_mean に一致.
仕様(実装サマリー「動作確認済み(参考)」):
target_mean=146.7146
期待出力: mean=146.7146
apply_mean_anchor の後 mean は target_mean に厳密一致する
"""
import tempfile
import pandas
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir) / "output"
main(
[
"--image", str(BASELINE_LUMINANCE_NPY),
"--config", str(EVAL_BASELINE_LINEAR_YAML),
"--output", str(output_dir),
]
)
df = pandas.read_csv(output_dir / "metrics.csv")
mean_val = float(df["mean"].iloc[0])
# apply_mean_anchor は厳密に mean = target_mean にする
assert mean_val == pytest.approx(146.7146, rel=1e-5)
@pytest.mark.xfail(reason=_CALIBRATION_XFAIL_REASON, strict=False)
@pytest.mark.skipif(
not BASELINE_LUMINANCE_NPY.exists(),
reason="output/baseline/luminance.npy が存在しない場合はスキップ",
)
def test_baseline_linear_yaml_cov_close_to_expected():
"""eval_baseline_linear.yaml 実行後,metrics.csv の CoV が仕様値 0.182577 に近いこと.
仕様(実装サマリー「動作確認済み(参考)」):
期待出力: CoV=0.182577
形状指標は linear ゲインに不変なので normalization 前後で同一
"""
import tempfile
import pandas
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir) / "output"
main(
[
"--image", str(BASELINE_LUMINANCE_NPY),
"--config", str(EVAL_BASELINE_LINEAR_YAML),
"--output", str(output_dir),
]
)
df = pandas.read_csv(output_dir / "metrics.csv")
cov_val = float(df["cov"].iloc[0])
# 仕様値 CoV=0.182577 に ±0.005 程度の許容範囲で一致すること
assert cov_val == pytest.approx(0.182577, abs=0.005)
@pytest.mark.xfail(reason=_CALIBRATION_XFAIL_REASON, strict=False)
@pytest.mark.skipif(
not BASELINE_LUMINANCE_NPY.exists(),
reason="output/baseline/luminance.npy が存在しない場合はスキップ",
)
def test_baseline_linear_yaml_max_min_close_to_expected():
"""eval_baseline_linear.yaml 実行後,metrics.csv の max/min 比が仕様値 4.2759 に近いこと.
仕様(実装サマリー「動作確認済み(参考)」):
期待出力: max/min=4.2759
"""
import tempfile
import pandas
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir) / "output"
main(
[
"--image", str(BASELINE_LUMINANCE_NPY),
"--config", str(EVAL_BASELINE_LINEAR_YAML),
"--output", str(output_dir),
]
)
df = pandas.read_csv(output_dir / "metrics.csv")
ratio_val = float(df["max_min_ratio"].iloc[0])
# 仕様値 max/min=4.2759 に ±0.05 の許容範囲
assert ratio_val == pytest.approx(4.2759, abs=0.05)
@pytest.mark.xfail(reason=_CALIBRATION_XFAIL_REASON, strict=False)
@pytest.mark.skipif(
not BASELINE_LUMINANCE_NPY.exists(),
reason="output/baseline/luminance.npy が存在しない場合はスキップ",
)
def test_baseline_linear_yaml_cp_ratio_close_to_expected():
"""eval_baseline_linear.yaml 実行後,metrics.csv の C/P 比が仕様値 1.3271 に近いこと.
仕様(実装サマリー「動作確認済み(参考)」):
期待出力: C/P=1.3271
"""
import tempfile
import pandas
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir) / "output"
main(
[
"--image", str(BASELINE_LUMINANCE_NPY),
"--config", str(EVAL_BASELINE_LINEAR_YAML),
"--output", str(output_dir),
]
)
df = pandas.read_csv(output_dir / "metrics.csv")
cp_val = float(df["center_periphery_ratio"].iloc[0])
# 仕様値 C/P=1.3271 に ±0.02 の許容範囲
assert cp_val == pytest.approx(1.3271, abs=0.02)
@pytest.mark.xfail(reason=_CALIBRATION_XFAIL_REASON, strict=False)
@pytest.mark.skipif(
not BASELINE_LUMINANCE_NPY.exists(),
reason="output/baseline/luminance.npy が存在しない場合はスキップ",
)
def test_baseline_linear_yaml_gradient_close_to_expected():
"""eval_baseline_linear.yaml 実行後,metrics.csv の勾配が仕様値 24.65 に近いこと.
仕様(実装サマリー「動作確認済み(参考)」):
期待出力: 勾配=24.65
"""
import tempfile
import pandas
with tempfile.TemporaryDirectory() as tmpdir:
output_dir = Path(tmpdir) / "output"
main(
[
"--image", str(BASELINE_LUMINANCE_NPY),
"--config", str(EVAL_BASELINE_LINEAR_YAML),
"--output", str(output_dir),
]
)
df = pandas.read_csv(output_dir / "metrics.csv")
grad_val = float(df["gradient_magnitude"].iloc[0])
# 仕様値 勾配=24.65 に ±0.5 の許容範囲
assert grad_val == pytest.approx(24.65, abs=0.5)