"""TEST_01 用「輝度マップ形状の追跡」比較パネルを生成する.
各改善 Step での輝度マップの「形」を 2×2 グリッドで並べ,
タイトルに主要指標 (mean / CoV / 勾配 % / C/P) を併記する.
形を見せる目的なので各画像は個別正規化 (sm-evaluate デフォルト) のまま使う.
共有 colorbar にすると絶対値の差にばかり目が行き「形の異同」が見えにくくなる.
使い方:
uv run python scripts/build_luminance_evolution_panel.py
出力:
docs/06_TEST/figures/luminance_map_evolution.png
新しい改善 Step を追加する場合は PANELS リストに 1 エントリ追加するだけ.
"""
from __future__ import annotations
import csv
from pathlib import Path
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
ROOT = Path(__file__).resolve().parents[1]
def _load_metrics(csv_path: Path) -> dict[str, float]:
"""sm-evaluate が書き出す metrics.csv から主要指標を取り出す."""
with csv_path.open(encoding="utf-8") as f:
row = next(csv.DictReader(f))
return {
"mean": float(row["mean"]),
"CoV": float(row["cov"]),
"grad": float(row["gradient_magnitude"]),
"CP": float(row["center_periphery_ratio"]),
}
# パネル定義. ここに新規 Step を追加すれば自動で並ぶ.
PANELS = [
{
"image": ROOT / "MiniTias-Evaluation" / "figures" / "luminance_map_sample.png",
"title": "Real Measurement (TARGET)",
"subtitle": "central peak, near-symmetric (camera optics anisotropy)",
"metrics": {"mean": 146.71, "CoV": 0.0427, "grad": 6.10, "CP": 1.065},
},
{
"image": ROOT / "output" / "baseline_linear_npy" / "figures" / "luminance_map.png",
"title": "Step 8-1: Starting Point",
"subtitle": "R=0.85, d=8, spp=1024, linear (no OETF)",
"metrics_csv": ROOT / "output" / "baseline_linear_npy" / "metrics.csv",
},
{
"image": ROOT / "output" / "eval_oetf_r085" / "figures" / "luminance_map.png",
"title": "Step 8-3: After OETF Forward Application",
"subtitle": "R=0.85, d=8, spp=1024, sRGB domain",
"metrics_csv": ROOT / "output" / "eval_oetf_r085" / "metrics.csv",
},
{
"image": ROOT / "output" / "emitter_real" / "figures" / "luminance_map.png",
"title": "Step 8-7: Real-chip Emitter Size (Φe preserved)",
"subtitle": "size 1.5x1.5 mm, linear",
"metrics_csv": ROOT / "output" / "emitter_real" / "metrics.csv",
},
]
def main() -> None:
fig, axes = plt.subplots(2, 2, figsize=(14, 13))
for ax, panel in zip(axes.flatten(), PANELS):
img_path = panel["image"]
if not img_path.is_file():
ax.text(0.5, 0.5, f"MISSING\n{img_path}", ha="center", va="center")
ax.set_axis_off()
continue
ax.imshow(mpimg.imread(img_path))
ax.set_axis_off()
metrics = panel.get("metrics")
if metrics is None and "metrics_csv" in panel:
metrics = _load_metrics(panel["metrics_csv"])
m = metrics
title = (
f"{panel['title']}\n"
f"{panel['subtitle']}\n"
f"mean={m['mean']:.2f} CoV={m['CoV']:.4f} "
f"grad={m['grad']:.2f}% C/P={m['CP']:.3f}"
)
ax.set_title(title, fontsize=10, loc="left")
fig.suptitle(
"Luminance Map Shape Tracking across Calibration Steps",
fontsize=14,
fontweight="bold",
)
plt.tight_layout(rect=(0, 0, 1, 0.97))
out_dir = ROOT / "docs" / "06_TEST" / "figures"
out_dir.mkdir(parents=True, exist_ok=True)
out_path = out_dir / "luminance_map_evolution.png"
plt.savefig(out_path, dpi=120, bbox_inches="tight")
print(f"Saved: {out_path}")
if __name__ == "__main__":
main()