"""全灯と単灯 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())