Newer
Older
SmTIAS-LightSim / src / sm_tias_lightsim / cli / evaluate.py
"""sm-evaluate コマンドラインエントリポイント.

PNG または luminance.npy を入力として均一性評価を実行し,
metrics.csv と可視化 PNG を出力ディレクトリに保存する.

処理本体は `evaluation/pipeline.py` の `evaluate_image()` にある(スイープ等から
プロセス内で直接呼べるようにするため).本モジュールは引数のパースと,
例外 → 「エラー: …」表示 + 終了コード 1 への変換だけを担う.

matplotlib のバックエンドは Agg を強制する(headless 環境対応).
evaluate は Mitsuba を使わないため,render.py と異なり遅延 import は不要.
ただし Agg バックエンドの設定順序を保つため,matplotlib.use() は pyplot import より前に置く.
"""

from __future__ import annotations

import argparse
import sys
from pathlib import Path

import matplotlib

# headless 環境(サーバ・CI 等)でも動くよう Agg バックエンドを強制する.
# pipeline(→ visualize.py)より先に設定することで,pyplot import 時に Agg が有効になる.
matplotlib.use("Agg")

# evaluate_image / EvaluationStepError 以外は,補助関数が本モジュールにあった頃の
# 参照互換のための再エクスポート(テスト・スクリプトが cli.evaluate.<名前> を参照している).
from sm_tias_lightsim.evaluation.pipeline import (  # noqa: E402, F401
    _FIGURES_DIRNAME,
    _HISTOGRAM_FILENAME,
    _LUMINANCE_MAP_FILENAME,
    _METRICS_FILENAME,
    _RADIAL_PROFILE_FILENAME,
    _ZONE_MAP_FILENAME,
    EvaluationStepError,
    _apply_normalization_to_luminance,
    _apply_oetf_to_luminance,
    _load_eval_config,
    _load_luminance,
    _load_reference_luminance,
    _print_summary,
    _run_radial_min_max,
    _run_radial_profile,
    _run_shape_metrics,
    _run_zone_analysis,
    _save_figures,
    _save_metrics_csv,
    evaluate_image,
)
from sm_tias_lightsim.evaluation.radial import (  # noqa: E402, F401
    calc_radial_min_max_ratio,
    calc_radial_profile,
)


def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    """コマンドライン引数をパースして返す.

    Args:
        argv: 引数リスト.None のとき sys.argv[1:] を使用.

    Returns:
        パース済み Namespace.
    """
    parser = argparse.ArgumentParser(
        prog="sm-evaluate",
        description=(
            "PNG または luminance.npy を読み込み,均一性指標 CSV と可視化 PNG を出力する."
        ),
    )
    parser.add_argument(
        "--image",
        "-i",
        required=True,
        type=Path,
        metavar="IMAGE_PATH",
        help="評価対象の画像ファイル(PNG または .npy).",
    )
    parser.add_argument(
        "--config",
        "-c",
        required=True,
        type=Path,
        metavar="YAML_PATH",
        help="評価設定 YAML ファイルのパス(SceneConfig または EvaluationConfig 互換).",
    )
    parser.add_argument(
        "--output",
        "-o",
        required=True,
        type=Path,
        metavar="OUTPUT_DIR",
        help="出力先ディレクトリ(存在しない場合は親ごと作成する).",
    )
    parser.add_argument(
        "--no-figures",
        action="store_true",
        help="figures/ の可視化 PNG を作らない(指標だけ欲しいスイープ用.描画分だけ速い).",
    )
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    """sm-evaluate コマンドのメインエントリポイント.

    引数をパースして `evaluate_image()` を呼び,段階別のエラーを終了コードに変換する.
    評価そのものの処理フローは `evaluation/pipeline.py` の `evaluate_image()` を参照.

    Args:
        argv: 引数リスト.None のとき sys.argv[1:] を使用.

    Returns:
        終了コード(成功時 0,失敗時 1).
    """
    # argparse 自体のエラー(必須引数欠落等)は ArgumentParser が直接 sys.exit するため
    # ここでは ValueError / FileNotFoundError 等の実行時エラーのみ捕捉する
    args = _parse_args(argv)

    try:
        evaluate_image(
            args.image.resolve(),
            args.config.resolve(),
            args.output.resolve(),
            save_figures=not args.no_figures,
        )
    except EvaluationStepError as exc:
        print(f"エラー: {exc}", file=sys.stderr)
        return 1

    return 0