Newer
Older
SmTIAS-Evaluation / scripts / compare_ambient_radial.py
"""暗闇 vs 室内(外乱光)の動径プロファイル重ね描きスライドを生成する.

REPORT_03(外乱光影響評価)に対応.同一 DNG 定量モード(linear + LSC 補正,露出/ISO 固定)で
撮った 2 枚を,動径プロファイルを **絶対輝度のまま** 1 枚に重ねて比較する.

軸を絶対輝度にする理由(正規化しない):
- 両者は同じ linear 単位・同じ露出/ISO・同じ LSC 補正なので絶対値が正当に比較可能.
- 「外光を足したのに暗い」(暗闇 raw mean 441.2 > 室内 431.4)という逆転は絶対値でしか見えない.
  各自の最大で正規化するとこの"レベルも含めてほぼ同一"という情報が消える.
- 2 本がほぼ重なること自体が「外乱光にロバスト」というメッセージになる.

対象(REPORT_03 の条件 A/B):
- 暗闇 (lights OFF) : SmTIAS_QM_20260601_124500  → min/max 0.860
- 室内 (lights ON)  : SmTIAS_QM_20260601_102753  → min/max 0.853

使い方:
    python scripts/compare_ambient_radial.py
"""

import sys
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np

PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

from src.analysis.spatial import (  # noqa: E402
    calc_distance_map,
    calc_radial_min_max_ratio,
    calc_radial_profile,
)
from src.config import load_roi_config  # noqa: E402
from src.io.dng_pipeline import dng_luminance  # noqa: E402
from src.io.loader import extract_roi  # noqa: E402

# REPORT_03 の条件 A(暗闇=室内灯OFF)/ 条件 B(室内=室内灯ON)
DARK_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_124500.dng"
INDOOR_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_102753.dng"
ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json"
ROI_KEY = "smtias.whiteboard"
OUTPUT_PATH = PROJECT_ROOT / "output" / "figures" / "compare_ambient_radial.png"


def _radial(path: Path, roi: dict) -> tuple[np.ndarray, dict]:
    """DNG(linear + LSC)の ROI 動径プロファイルと min/max 統計を返す."""
    luma = dng_luminance(str(path), apply_lsc=True)
    luma = extract_roi(luma, roi["x"], roi["y"], roi["width"], roi["height"])
    distance_map = calc_distance_map(*luma.shape)
    profile = calc_radial_profile(luma, distance_map)
    radial = calc_radial_min_max_ratio(profile)
    return profile, radial


def main() -> None:
    """重ね描きスライドを生成して保存する."""
    roi = load_roi_config(str(ROI_CONFIG), ROI_KEY)
    if roi is None:
        raise SystemExit(f"ROI '{ROI_KEY}' が config に見つかりません")

    dark_prof, dark_r = _radial(DARK_PATH, roi)
    indoor_prof, indoor_r = _radial(INDOOR_PATH, roi)

    fig, ax = plt.subplots(figsize=(9, 6))

    ax.plot(
        dark_prof[:, 0], dark_prof[:, 1],
        marker="o", ms=5, color="#1f3b73",
        label=f"Dark (lights OFF)   min/max = {dark_r['radial_min_max_ratio']:.3f}",
    )
    ax.plot(
        indoor_prof[:, 0], indoor_prof[:, 1],
        marker="s", ms=5, color="#e08214",
        label=f"Indoor (lights ON)  min/max = {indoor_r['radial_min_max_ratio']:.3f}",
    )

    ax.set_title("Ambient light effect on radial profile (DNG, linear + LSC)", fontsize=12)
    ax.set_xlabel("Normalized distance from center")
    ax.set_ylabel("Mean luminance (absolute, linear 0-255 scale)")
    ax.grid(True, linestyle="--", alpha=0.5)
    ax.legend(loc="upper right", fontsize=10)
    fig.tight_layout()

    OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(str(OUTPUT_PATH), dpi=150)
    plt.close(fig)
    print(f"重ね描きスライドを保存しました: {OUTPUT_PATH}")
    print(f"  Dark   (124500) min/max = {dark_r['radial_min_max_ratio']:.4f}")
    print(f"  Indoor (102753) min/max = {indoor_r['radial_min_max_ratio']:.4f}")


if __name__ == "__main__":
    main()