Newer
Older
SmTIAS-LightSim / tests / scene / test_metrics_radial_min_max_schema.py
@rintoHasegawa rintoHasegawa on 1 Jun 8 KB [add] S3 Step D sm-evaluate に動径 min/max 比を統合 — S3 方針見直し: 「sm-evaluate に DNG 入力経路を追加」当初案は不採用 (実機 DNG 評価は MiniTias-Evaluation の本業で完結.SmTIAS-LightSim 側で重複実装する必要なし).S3 を 3 項目に絞り込み: Step D (sm-evaluate に動径 min/max 比統合) + Step C' (新規シミュ baseline config 作成) + Step G (シミュ再評価 → Step 8-13 として TEST_01 記録).本コミットは Step D.MetricsConfig.radial_min_max: bool = False をスキーマ追加 (schema.py),cli/evaluate.py で _run_radial_min_max() 関数追加・main() 内で算出ステップ統合・_print_summary に代表 2 行追加.radial_profile を再利用 (重複算出回避),未算出時 (radial_profile: false) は関数内部で calc_radial_profile を呼んで自力算出 → 独立有効化可能.metrics.radial_min_max: true 時に metrics.csv 末尾に 4 列 (radial_min_max_ratio / radial_min_distance / radial_min / radial_max) を追加,既存列順序は不変で後方互換維持.既定 False で既存 3 configs (eval_baseline.yaml / eval_baseline_linear.yaml / eval_real.yaml) は無修正で動作.新規テスト 39 件追加 (tests/scene/test_metrics_radial_min_max_schema.py 250 行: A1-A4 スキーマ仕様 + tests/cli/test_evaluate_radial_min_max.py 740 行: B1-B5 関数仕様 + C1-C5 CLI 統合 + D2 slow 統合).既存 569 + 新規 39 = 608 件全通過.Refactorer 判断: プロダクションコード変更なし (既存パターン一貫性あり),テストコードのみ軽微な lint 修正 (未使用 import 削除・I001/E501).SPEC_04 §動径 min/max 比 (オプション) セクション新規追加 (4 列出力・nan 除外・独立有効化の仕様明記.evaluation/radial.py:calc_radial_min_max_ratio は MiniTias-Evaluation src/analysis/spatial.py からのマージと明記).CLAUDE.md 進捗欄に S3 方針見直しと Step D 完了 (608 件全通過) を追記.handoff_smtias_lightsim/ ディレクトリは S3 全体完了後に削除予定で本コミットには含めない.次フェーズ: Step C' (configs/eval_baseline_dng_target.yaml 作成: 既存 eval_baseline_linear.yaml ベース,target_mean=207.82,radial_min_max=true) → Step G (Step 8-13 計測)
# pyright: reportArgumentType=none, reportCallIssue=none
"""scene/schema.py の MetricsConfig.radial_min_max フィールド仕様ベーステスト.

仕様出典:
- docs/04_SPEC/SPEC_04_評価仕様.md(動径指標・評価パイプライン)
- docs/04_SPEC/SPEC_02_シーン定義仕様.md(evaluation/metrics セクション)
- src/sm_tias_lightsim/scene/schema.py(MetricsConfig 定義)

テスト方針:
  - Pydantic モデルを直接構築して振る舞いを検証する(YAML パースは別途)
  - 既存フィールド(cov/std/max_min/radial_profile)への非干渉を確認する
  - YAML ロード経由の動作も確認する
"""

from __future__ import annotations

import pytest
import yaml
from pydantic import ValidationError

from sm_tias_lightsim.scene.schema import EvaluationConfig, MetricsConfig

# ---------------------------------------------------------------------------
# A1. 既定値: MetricsConfig() で radial_min_max == False
# ---------------------------------------------------------------------------


def test_metrics_radial_min_max_default_is_false():
    """MetricsConfig() のインスタンスで radial_min_max の既定値が False であること.

    仕様: MetricsConfig.radial_min_max: bool = Field(default=False, ...)
    (schema.py MetricsConfig)
    """
    cfg = MetricsConfig()
    assert cfg.radial_min_max is False


# ---------------------------------------------------------------------------
# A2. 明示 True: MetricsConfig(radial_min_max=True) で True になること
# ---------------------------------------------------------------------------


def test_metrics_radial_min_max_explicit_true():
    """MetricsConfig(radial_min_max=True) で radial_min_max が True になること.

    仕様: MetricsConfig.radial_min_max: bool = Field(default=False, ...)
    True を明示指定すると True が設定される(schema.py MetricsConfig)
    """
    cfg = MetricsConfig(radial_min_max=True)
    assert cfg.radial_min_max is True


def test_metrics_radial_min_max_explicit_false():
    """MetricsConfig(radial_min_max=False) で radial_min_max が False になること.

    仕様: MetricsConfig.radial_min_max: bool = Field(default=False, ...)
    """
    cfg = MetricsConfig(radial_min_max=False)
    assert cfg.radial_min_max is False


# ---------------------------------------------------------------------------
# A3. 既存フィールドへの非干渉
# ---------------------------------------------------------------------------


def test_metrics_radial_min_max_does_not_affect_cov_default():
    """radial_min_max 指定有無で cov の既定値が変わらないこと.

    仕様: MetricsConfig.cov: bool = Field(default=True, ...) — 非干渉
    """
    cfg_default = MetricsConfig()
    cfg_true = MetricsConfig(radial_min_max=True)

    assert cfg_default.cov == cfg_true.cov
    assert cfg_default.cov is True


def test_metrics_radial_min_max_does_not_affect_std_default():
    """radial_min_max 指定有無で std の既定値が変わらないこと.

    仕様: MetricsConfig.std: bool = Field(default=True, ...) — 非干渉
    """
    cfg_default = MetricsConfig()
    cfg_true = MetricsConfig(radial_min_max=True)

    assert cfg_default.std == cfg_true.std
    assert cfg_default.std is True


def test_metrics_radial_min_max_does_not_affect_max_min_default():
    """radial_min_max 指定有無で max_min の既定値が変わらないこと.

    仕様: MetricsConfig.max_min: bool = Field(default=True, ...) — 非干渉
    """
    cfg_default = MetricsConfig()
    cfg_true = MetricsConfig(radial_min_max=True)

    assert cfg_default.max_min == cfg_true.max_min
    assert cfg_default.max_min is True


def test_metrics_radial_min_max_does_not_affect_radial_profile_default():
    """radial_min_max 指定有無で radial_profile の既定値が変わらないこと.

    仕様: MetricsConfig.radial_profile: bool = Field(default=True, ...) — 非干渉
    radial_min_max とは独立したフィールド(schema.py MetricsConfig)
    """
    cfg_default = MetricsConfig()
    cfg_true = MetricsConfig(radial_min_max=True)

    assert cfg_default.radial_profile == cfg_true.radial_profile
    assert cfg_default.radial_profile is True


def test_metrics_config_all_fields_together():
    """radial_min_max=True を含む全フィールド指定で正常にパースされること.

    仕様: MetricsConfig の全フィールドが独立して指定可能(schema.py MetricsConfig)
    """
    cfg = MetricsConfig(
        cov=True, std=False, max_min=True, radial_profile=False, radial_min_max=True
    )
    assert cfg.cov is True
    assert cfg.std is False
    assert cfg.max_min is True
    assert cfg.radial_profile is False
    assert cfg.radial_min_max is True


def test_metrics_config_is_frozen_for_radial_min_max():
    """MetricsConfig が frozen(イミュータブル)で radial_min_max も変更不可であること.

    仕様: _FrozenBase → frozen=True(schema.py ConfigDict)
    """
    cfg = MetricsConfig()
    with pytest.raises(ValidationError):
        cfg.radial_min_max = True  # type: ignore[misc]


# ---------------------------------------------------------------------------
# A4. YAML ロード: YAML で metrics.radial_min_max: true を指定すると True
# ---------------------------------------------------------------------------


def test_yaml_load_metrics_radial_min_max_true():
    """YAML で metrics.radial_min_max: true を指定したとき True が反映されること.

    仕様: EvaluationConfig.metrics.radial_min_max が YAML 経由で設定される
    (schema.py MetricsConfig / EvaluationConfig)
    """
    yaml_content = """\
roi:
  type: image_pixel
  x: 0
  y: 0
  width: 100
  height: 100
metrics:
  cov: true
  std: true
  max_min: true
  radial_profile: true
  radial_min_max: true
"""
    data = yaml.safe_load(yaml_content)
    cfg = EvaluationConfig.model_validate(data)
    assert cfg.metrics is not None
    assert cfg.metrics.radial_min_max is True


def test_yaml_load_metrics_radial_min_max_false():
    """YAML で metrics.radial_min_max: false を指定したとき False が反映されること.

    仕様: MetricsConfig.radial_min_max: bool = Field(default=False, ...)
    """
    yaml_content = """\
roi:
  type: image_pixel
  x: 0
  y: 0
  width: 100
  height: 100
metrics:
  cov: true
  radial_min_max: false
"""
    data = yaml.safe_load(yaml_content)
    cfg = EvaluationConfig.model_validate(data)
    assert cfg.metrics is not None
    assert cfg.metrics.radial_min_max is False


def test_yaml_load_metrics_radial_min_max_omitted_defaults_false():
    """YAML で metrics.radial_min_max を省略したとき既定値 False が設定されること.

    仕様: MetricsConfig.radial_min_max: bool = Field(default=False, ...)
    既存 YAML(eval_baseline.yaml / eval_baseline_linear.yaml / eval_real.yaml 相当)は
    radial_min_max を含まないため後方互換として False になる必要がある
    """
    yaml_content = """\
roi:
  type: image_pixel
  x: 0
  y: 0
  width: 100
  height: 100
metrics:
  cov: true
  std: true
  max_min: true
  radial_profile: true
"""
    data = yaml.safe_load(yaml_content)
    cfg = EvaluationConfig.model_validate(data)
    assert cfg.metrics is not None
    assert cfg.metrics.radial_min_max is False


def test_yaml_load_eval_baseline_linear_equivalent_has_radial_min_max_false():
    """eval_baseline_linear.yaml 相当の YAML では radial_min_max が False であること.

    仕様: 既存 configs(eval_baseline.yaml / eval_baseline_linear.yaml)は
    radial_min_max を含まず,未指定時は False(後方互換)
    """
    yaml_content = """\
roi:
  type: image_pixel
  x: 24
  y: 37
  width: 464
  height: 450
zones:
  enabled: true
  boundaries:
  - 0.33
  - 0.66
metrics:
  cov: true
  std: true
  max_min: true
  radial_profile: true
normalization:
  enabled: true
  type: mean_anchor
  target_mean: 146.7146
"""
    data = yaml.safe_load(yaml_content)
    cfg = EvaluationConfig.model_validate(data)
    assert cfg.metrics is not None
    assert cfg.metrics.radial_min_max is False