"""w1p4 全灯 2 撮影(初回 / 撮り直し)の中心凹みの再現性を比較するスクリプト.
後側ロフト w=1.4 筐体の全灯白板は,輝度マップ中心部が周囲より僅かに暗い
「中心凹み(ドーナツ状)」を示す.これが特定撮影のアーティファクトか,
筐体由来で再現する特徴かを切り分けるため,独立した 2 撮影
(2026-07-26 初回・2026-07-27 撮り直し)を共通スケール並置と
自ピーク正規化の動径プロファイルで比較する.参照として従来筐体 w=0.5 も重ねる.
すべて linear + LSC 補正・ROI=smtias.whiteboard で揃えて算出する.
使い方:
python scripts/compare_w1p4_shots.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 calc_distance_map, calc_radial_profile # noqa: E402
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
QUANT_DIR = PROJECT_ROOT / "data" / "smtias" / "quantitative"
SHOTS = [
("w1.4 #1 (07-26, dirty board)", QUANT_DIR / "SmTIAS_QM_20260726_183416.dng", "darkorange"),
("w1.4 #2 (07-27, cleaned)", QUANT_DIR / "SmTIAS_QM_20260727_124257.dng", "crimson"),
("w0.5 reference (06-01)", QUANT_DIR / "SmTIAS_QM_20260601_124500.dng", "gray"),
]
ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json"
ROI_KEY = "smtias.whiteboard"
OUTPUT_PATH = PROJECT_ROOT / "output" / "smtias" / "figures" / "compare_w1p4_shots.png"
# 共通絶対カラースケールの設定(外れ値に強いパーセンタイルで vmin/vmax を決める)
SCALE_PERCENTILES = (1.0, 99.0)
def main() -> None:
"""w1p4 2 撮影の並置+正規化動径プロファイル図を生成して保存する."""
roi = load_roi_config(str(ROI_CONFIG), ROI_KEY)
if roi is None:
raise SystemExit(f"ROI '{ROI_KEY}' が config に見つかりません")
lumas = [
extract_roi(
dng_luminance(str(path), apply_lsc=True),
roi["x"], roi["y"], roi["width"], roi["height"],
)
for _, path, _ in SHOTS
]
profiles = []
for (label, _, _), luma in zip(SHOTS, lumas):
distance_map = calc_distance_map(*luma.shape)
profile = calc_radial_profile(luma, distance_map)
profiles.append(profile)
center_val = profile[0, 1]
peak_idx = int(np.argmax(profile[:, 1]))
peak_r, peak_val = profile[peak_idx, 0], profile[peak_idx, 1]
dip = 1.0 - center_val / peak_val
print(f" {label}: center={center_val:.2f}, peak={peak_val:.2f} @r={peak_r:.2f}, "
f"center dip={dip * 100:.2f}%")
fig = plt.figure(figsize=(15, 5))
gs = fig.add_gridspec(1, 3, wspace=0.25)
# 左・中央: w1.4 の 2 撮影を共通絶対スケールで並置
flat = np.concatenate([lumas[0].ravel(), lumas[1].ravel()])
vmin, vmax = np.percentile(flat, SCALE_PERCENTILES)
map_axes = []
im = None
for col in range(2):
ax = fig.add_subplot(gs[0, col])
im = ax.imshow(lumas[col], cmap="hot", interpolation="nearest", vmin=vmin, vmax=vmax)
ax.set_title(SHOTS[col][0], fontsize=10)
ax.set_xlabel("X (px)")
if col == 0:
ax.set_ylabel("Y (px)")
map_axes.append(ax)
cbar = fig.colorbar(im, ax=map_axes, fraction=0.030, pad=0.02)
cbar.set_label(
f"Luminance (common scale, p{SCALE_PERCENTILES[0]:g}-p{SCALE_PERCENTILES[1]:g})"
)
# 右: 自ピーク正規化の動径プロファイル(明るさ差を除いて形だけ比較)
ax_prof = fig.add_subplot(gs[0, 2])
for (label, _, color), profile in zip(SHOTS, profiles):
ax_prof.plot(
profile[:, 0], profile[:, 1] / profile[:, 1].max(),
marker="o", ms=3, color=color, label=label,
)
ax_prof.set_title("Radial profile (normalized to own peak)", fontsize=10)
ax_prof.set_xlabel("Normalized distance from center")
ax_prof.set_ylabel("Luminance / peak")
ax_prof.grid(True, linestyle="--", alpha=0.5)
ax_prof.legend(fontsize=8, loc="lower left")
fig.suptitle("Center-dip reproducibility: two independent w=1.4 all-LED shots", fontsize=13)
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(str(OUTPUT_PATH), dpi=150, bbox_inches="tight")
plt.close(fig)
print(f"w1p4 2 撮影の比較図を保存しました: {OUTPUT_PATH}")
if __name__ == "__main__":
main()