Newer
Older
SmTIAS-LightSim / scripts / build_full_vs_singles_panel.py
"""全灯と単灯 3 灯の形比較パネル生成(面法線修正後).

行 = シミュ(面法線修正後)/ 実機,列 = 全灯 / bottom / left / right の 2×4 グリッド.
単灯シミュ(spp=1024)はノイズ 5.3% > 構造振幅 4% のため,全セルに同一の平滑化
(median 5px + gaussian σ=4)を適用した「形の比較専用」表示とする.
各セルは mean 正規化+独立 1–99% 正規化,シミュセルには対応する実機マップとの
NCC(平滑後)を併記する.

生成するファイル:
    output/_slide_panels/full_vs_singles_fix.png

使い方:
    uv run python scripts/build_full_vs_singles_panel.py

前提:
    - output/full_base_fix/luminance.npy・output/singles_fix/{bottom,left,right}/luminance.npy
    - lightsim/luminance_maps/{full,bottom,left,right}_*.npy(実機)
"""

from __future__ import annotations

import sys
from pathlib import Path

import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from scipy.ndimage import gaussian_filter, median_filter
from skimage.transform import resize

from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh
from sm_tias_lightsim.evaluation.shape_metrics import calc_ncc

matplotlib.rcParams["font.family"] = ["Meiryo", "Yu Gothic", "DejaVu Sans"]

ROOT = Path(__file__).resolve().parents[1]
PANEL_PATH = ROOT / "output" / "_slide_panels" / "full_vs_singles_fix.png"

COLS = ["full", "bottom", "left", "right"]
COL_LABELS = {
    "full": "全灯 18 灯",
    "bottom": "bottom 単灯",
    "left": "left 単灯",
    "right": "right 単灯",
}
SIM = {
    "full": ROOT / "output" / "full_base_fix" / "luminance.npy",
    "bottom": ROOT / "output" / "singles_fix" / "bottom" / "luminance.npy",
    "left": ROOT / "output" / "singles_fix" / "left" / "luminance.npy",
    "right": ROOT / "output" / "singles_fix" / "right" / "luminance.npy",
}
REAL = {
    "full": ROOT / "lightsim" / "luminance_maps" / "full_MiniTIAS_QM_20260601_124500.npy",
    "bottom": ROOT / "lightsim" / "luminance_maps" / "bottom_SmTIAS_QM_20260615_141420.npy",
    "left": ROOT / "lightsim" / "luminance_maps" / "left_SmTIAS_QM_20260615_150300.npy",
    "right": ROOT / "lightsim" / "luminance_maps" / "right_SmTIAS_QM_20260615_170948.npy",
}

CELL_SHAPE = (450, 464)


def _load_smooth(path: Path, is_sim: bool) -> np.ndarray:
    """luminance を読み込み(シミュは SIM_ROI 抽出後)平滑化して返す."""
    arr = np.load(path)
    if arr.ndim == 3:
        arr = 0.2126 * arr[..., 0] + 0.7152 * arr[..., 1] + 0.0722 * arr[..., 2]
    arr = arr.astype(np.float64)
    if is_sim:
        arr = extract_roi_xywh(arr, SIM_ROI)
    return gaussian_filter(median_filter(arr, size=5), sigma=4)


def main() -> int:
    """2×4 形比較パネルを生成するメインエントリポイント.

    Returns:
        終了コード(成功なら 0,入力不足なら 1).
    """
    for paths in (SIM, REAL):
        for p in paths.values():
            if not p.exists():
                print(f"エラー: {p} が見つかりません", file=sys.stderr)
                return 1

    sim = {c: _load_smooth(SIM[c], True) for c in COLS}
    real = {c: _load_smooth(REAL[c], False) for c in COLS}

    fig, axes = plt.subplots(2, 4, figsize=(4 * 4.2, 2 * 4.4), constrained_layout=True)
    for r, (row_label, maps) in enumerate((("シミュ(面法線修正後)", sim), ("実機", real))):
        for c, col in enumerate(COLS):
            ax = axes[r][c]
            img = maps[col]
            disp = resize(img / img.mean(), CELL_SHAPE, anti_aliasing=True)
            ax.imshow(
                disp,
                cmap="hot",
                vmin=float(np.percentile(disp, 1.0)),
                vmax=float(np.percentile(disp, 99.0)),
                aspect="equal",
            )
            h = img.shape[0]
            pm = img.max() / img.mean()
            tb = img[: h // 2].mean() / img[h // 2 :].mean()
            title = f"p/m={pm:.3f}  t/b={tb:.3f}"
            if r == 0:
                ncc = calc_ncc(resize(img, real[col].shape, anti_aliasing=True), real[col])
                title = f"{COL_LABELS[col]}\n{title}  NCC対実機={ncc:.3f}"
            ax.set_title(title, fontsize=10, pad=6)
            ax.set_axis_off()
        axes[r][0].text(
            -0.06, 0.5, row_label, ha="right", va="center", rotation=90,
            transform=axes[r][0].transAxes, fontsize=12, fontweight="bold",
        )
    fig.suptitle(
        "全灯 vs 単灯 3 灯の形比較(面法線修正後)— blend w0.05_a0.1, spp=1024, d=16, ROI 揃え\n"
        "全セル同一平滑化(median5+gauss σ4)・mean 正規化・独立 1–99% 表示(形の比較専用)",
        fontsize=12,
    )
    PANEL_PATH.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(PANEL_PATH, dpi=150, bbox_inches="tight")
    plt.close(fig)
    print(f"パネル保存: {PANEL_PATH}")
    return 0


if __name__ == "__main__":
    sys.exit(main())