diff --git a/scripts/build_blend_bsdf_panel.py b/scripts/build_blend_bsdf_panel.py index f0a4c4a..db92d5d 100644 --- a/scripts/build_blend_bsdf_panel.py +++ b/scripts/build_blend_bsdf_panel.py @@ -32,6 +32,7 @@ import numpy as np from sm_tias_lightsim.evaluation.normalize import apply_mean_anchor +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh # --------------------------------------------------------------------------- # 定数 @@ -44,9 +45,6 @@ BASELINE_NPY = ROOT / "output" / "baseline" / "luminance.npy" BLEND_OUTPUT_BASE = ROOT / "output" / "blend_sweep" -# ROI 設定(configs/eval_baseline.yaml / eval_baseline_dng_target.yaml と整合) -SIM_ROI = {"x": 24, "y": 37, "w": 464, "h": 450} - # mean 正規化の target_mean(eval_baseline_dng_target.yaml の target_mean と整合) TARGET_MEAN = 207.82 @@ -82,12 +80,6 @@ return f"w{_format_param(weight)}_a{_format_param(alpha)}" -def _extract_roi(arr: np.ndarray, roi: dict) -> np.ndarray: - """ROI 矩形を切り出して float64 に変換する.""" - x, y, w, h = roi["x"], roi["y"], roi["w"], roi["h"] - return arr[y: y + h, x: x + w].astype(np.float64) - - def _rec709_luma(rgb: np.ndarray) -> np.ndarray: """RGB 配列を Rec.709 luma に変換する (H, W, 3) → (H, W).""" return 0.2126 * rgb[..., 0] + 0.7152 * rgb[..., 1] + 0.0722 * rgb[..., 2] @@ -112,7 +104,7 @@ arr = np.load(npy_path) if arr.ndim == 3: arr = _rec709_luma(arr) - roi = _extract_roi(arr, SIM_ROI) + roi = extract_roi_xywh(arr, SIM_ROI) normalized, _ = apply_mean_anchor(roi, TARGET_MEAN) return normalized diff --git a/scripts/build_led_distribution_panel.py b/scripts/build_led_distribution_panel.py index 2b579af..455d88d 100644 --- a/scripts/build_led_distribution_panel.py +++ b/scripts/build_led_distribution_panel.py @@ -22,6 +22,8 @@ import matplotlib.font_manager as fm import matplotlib.pyplot as plt +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh + ROOT = Path(__file__).resolve().parents[1] SWEEP_BASE = ROOT / "output" / "led_distribution_sweep" SUMMARY_CSV = SWEEP_BASE / "summary.csv" @@ -66,7 +68,7 @@ if not npy.exists(): lums.append(None) continue - arr = np.load(npy) + arr = extract_roi_xywh(np.load(npy), SIM_ROI) lums.append(arr.mean(axis=-1) if arr.ndim == 3 else arr) valid = [x for x in lums if x is not None] diff --git a/scripts/build_led_group_sanity_panel.py b/scripts/build_led_group_sanity_panel.py index 0d40937..dcca696 100644 --- a/scripts/build_led_group_sanity_panel.py +++ b/scripts/build_led_group_sanity_panel.py @@ -32,6 +32,8 @@ import matplotlib.font_manager as fm import matplotlib.pyplot as plt +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh + # --------------------------------------------------------------------------- # 定数 # --------------------------------------------------------------------------- @@ -63,13 +65,13 @@ if not npy_path.exists(): print(f"エラー: {npy_path} が見つかりません", file=sys.stderr) return 1 - lums[g] = np.load(npy_path) + lums[g] = extract_roi_xywh(np.load(npy_path), SIM_ROI) # ベースライン (全 ON) 読み込み if not BASELINE_NPY.exists(): print(f"エラー: ベースライン {BASELINE_NPY} が見つかりません", file=sys.stderr) return 1 - baseline = np.load(BASELINE_NPY) + baseline = extract_roi_xywh(np.load(BASELINE_NPY), SIM_ROI) # スカラ輝度に変換 (RGB なら平均,2D ならそのまま) def to_2d(arr: np.ndarray) -> np.ndarray: diff --git a/scripts/build_luminance_evolution_panel.py b/scripts/build_luminance_evolution_panel.py index 5d4a809..689a0c4 100644 --- a/scripts/build_luminance_evolution_panel.py +++ b/scripts/build_luminance_evolution_panel.py @@ -32,14 +32,11 @@ from sm_tias_lightsim.evaluation.normalize import apply_mean_anchor from sm_tias_lightsim.evaluation.oetf import apply_srgb_oetf, solve_exposure +from sm_tias_lightsim.evaluation.roi_presets import REAL_ROI, SIM_ROI, extract_roi_xywh ROOT = Path(__file__).resolve().parents[1] TARGET_MEAN = 146.7146 -# ROI 設定(configs と整合) -SIM_ROI = {"x": 24, "y": 37, "w": 464, "h": 450} # configs/eval_baseline.yaml -REAL_ROI = {"x": 539, "y": 1015, "w": 1400, "h": 1409} # configs/eval_real.yaml - # 共有 colorbar の値域決定方針: 実機 ROI のパーセンタイル # 5〜95 パーセンタイルで外れ値(センサノイズ・最暗最明画素)を除く. REAL_VMIN_PERCENTILE = 5.0 @@ -51,12 +48,6 @@ return 0.2126 * rgb[..., 0] + 0.7152 * rgb[..., 1] + 0.0722 * rgb[..., 2] -def _extract_roi(arr: np.ndarray, roi: dict) -> np.ndarray: - """ROI 矩形を切り出して float64 に変換する.""" - x, y, w, h = roi["x"], roi["y"], roi["w"], roi["h"] - return arr[y : y + h, x : x + w].astype(np.float64) - - def _load_real() -> np.ndarray: """実機 PNG → Rec.709 luma → ROI 抽出 → mean アンカー(恒等近い).""" png = ( @@ -68,7 +59,7 @@ luma = _rec709_luma(img.astype(np.float64)) else: luma = img.astype(np.float64) - roi = _extract_roi(luma, REAL_ROI) + roi = extract_roi_xywh(luma, REAL_ROI) # 実機は既に mean ≈ 146.71 なので alpha ≈ 1.0(揺れ吸収) normalized, _ = apply_mean_anchor(roi, TARGET_MEAN) return normalized @@ -79,7 +70,7 @@ arr = np.load(npy_path) if arr.ndim == 3: arr = _rec709_luma(arr) - roi = _extract_roi(arr, SIM_ROI) + roi = extract_roi_xywh(arr, SIM_ROI) normalized, _ = apply_mean_anchor(roi, TARGET_MEAN) return normalized @@ -89,7 +80,7 @@ arr = np.load(npy_path) if arr.ndim == 3: arr = _rec709_luma(arr) - roi = _extract_roi(arr, SIM_ROI) + roi = extract_roi_xywh(arr, SIM_ROI) scale, _ = solve_exposure(roi, TARGET_MEAN) return apply_srgb_oetf(roi, scale, quantize_uint8=True) diff --git a/scripts/build_max_depth_panel.py b/scripts/build_max_depth_panel.py index e4cced1..99c785e 100644 --- a/scripts/build_max_depth_panel.py +++ b/scripts/build_max_depth_panel.py @@ -29,6 +29,8 @@ import matplotlib.font_manager as fm import matplotlib.pyplot as plt +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh + ROOT = Path(__file__).resolve().parents[1] SWEEP_BASE = ROOT / "output" / "max_depth_sweep" SUMMARY_CSV = SWEEP_BASE / "summary.csv" @@ -75,7 +77,7 @@ print(f"警告: {npy} 見つからずスキップ", file=sys.stderr) lums.append(None) continue - arr = np.load(npy) + arr = extract_roi_xywh(np.load(npy), SIM_ROI) lums.append(arr.mean(axis=-1) if arr.ndim == 3 else arr) # 共有 vmin/vmax diff --git a/scripts/build_reflectance_panel.py b/scripts/build_reflectance_panel.py index bd7da9b..fed3750 100644 --- a/scripts/build_reflectance_panel.py +++ b/scripts/build_reflectance_panel.py @@ -28,6 +28,8 @@ import matplotlib.font_manager as fm import matplotlib.pyplot as plt +from sm_tias_lightsim.evaluation.roi_presets import SIM_ROI, extract_roi_xywh + ROOT = Path(__file__).resolve().parents[1] SWEEP_BASE = ROOT / "output" / "reflectance_sweep" SUMMARY_CSV = SWEEP_BASE / "summary.csv" @@ -74,7 +76,7 @@ if not npy.exists(): lums.append(None) continue - arr = np.load(npy) + arr = extract_roi_xywh(np.load(npy), SIM_ROI) lums.append(arr.mean(axis=-1) if arr.ndim == 3 else arr) valid = [x for x in lums if x is not None] diff --git a/scripts/build_sim_vs_darkroom_panel.py b/scripts/build_sim_vs_darkroom_panel.py index 2b34419..ed1f090 100644 --- a/scripts/build_sim_vs_darkroom_panel.py +++ b/scripts/build_sim_vs_darkroom_panel.py @@ -46,6 +46,7 @@ calc_radial_min_max_ratio, calc_radial_profile, ) +from sm_tias_lightsim.evaluation.roi_presets import REAL_ROI, SIM_ROI, extract_roi_xywh from sm_tias_lightsim.evaluation.zones import ( calc_zone_stats, classify_zones, @@ -62,12 +63,10 @@ REAL_META = ( ROOT / "reference_measurement" / "quantitative" / "MiniTIAS_QM_20260601_124500.meta.json" ) -# 白板 ROI (portrait, 旧 PNG と同一座標. DNG portrait は 2448x3264 で同一フレーミング) -REAL_ROI = {"x": 539, "y": 1015, "w": 1400, "h": 1409} +# REAL_ROI(白板 portrait)/ SIM_ROI は roi_presets に集約(configs と整合). # シミュ baseline (max_depth=16, R=0.85, spp=1024) SIM_NPY = ROOT / "output" / "baseline" / "luminance.npy" -SIM_ROI = {"x": 24, "y": 37, "w": 464, "h": 450} # configs/eval_baseline_dng_target.yaml OUT_PATH = ROOT / "output" / "_slide_panels" / "sim_vs_darkroom_normalized.png" @@ -77,12 +76,6 @@ return _REC709[0] * rgb[..., 0] + _REC709[1] * rgb[..., 1] + _REC709[2] * rgb[..., 2] -def _extract_roi(arr: np.ndarray, roi: dict) -> np.ndarray: - """ROI 矩形を float64 で切り出す.""" - x, y, w, h = roi["x"], roi["y"], roi["w"], roi["h"] - return arr[y : y + h, x : x + w].astype(np.float64) - - def _upsample(grid: np.ndarray, shape: tuple[int, int]) -> np.ndarray: """(rows, cols) グリッドを (H, W) へバイリニア補間する (run_uniformity_dng.py と同一).""" rows, cols = grid.shape @@ -117,7 +110,7 @@ rgb = rgb * _lsc_gain_rgb(REAL_META, rgb.shape[:2]) # LSC 順方向適用 (ビネット除去) luma = _rec709_luma(rgb) * 255.0 # 0〜255 linear スケール luma = np.rot90(luma, -1) # portrait (PNG / ROI と同じ向き) - return _extract_roi(luma, REAL_ROI) + return extract_roi_xywh(luma, REAL_ROI) def _load_sim() -> np.ndarray: @@ -125,7 +118,7 @@ arr = np.load(SIM_NPY) if arr.ndim == 3: arr = _rec709_luma(arr) - return _extract_roi(arr, SIM_ROI) + return extract_roi_xywh(arr, SIM_ROI) def _minmax_normalize(roi: np.ndarray) -> np.ndarray: diff --git a/src/sm_tias_lightsim/evaluation/roi_presets.py b/src/sm_tias_lightsim/evaluation/roi_presets.py new file mode 100644 index 0000000..e0c3332 --- /dev/null +++ b/src/sm_tias_lightsim/evaluation/roi_presets.py @@ -0,0 +1,36 @@ +"""キャリブレーション基準 ROI のプリセットと dict ベース抽出ヘルパ. + +スライド/比較パネル生成スクリプト(scripts/build_*_panel.py 等)が共通で使う +ROI 座標を一元管理する.座標は評価パイプライン(sm-evaluate)の +configs/eval_baseline*.yaml / eval_real.yaml と同一. + +評価本体(シーンスキーマ ``RoiConfig`` を使う側)は +``sm_tias_lightsim.evaluation.roi.extract_roi`` を使うこと. +本モジュールは ``{"x", "y", "w", "h"}`` dict を扱うスクリプト向けの軽量ヘルパ. +""" + +from __future__ import annotations + +import numpy as np + +# シミュ luminance.npy 用 ROI(configs/eval_baseline*.yaml と整合). +SIM_ROI = {"x": 24, "y": 37, "w": 464, "h": 450} + +# 実機測定画像用 ROI(configs/eval_real.yaml と整合). +REAL_ROI = {"x": 539, "y": 1015, "w": 1400, "h": 1409} + + +def extract_roi_xywh(arr: np.ndarray, roi: dict) -> np.ndarray: + """ROI 矩形(``{"x", "y", "w", "h"}``)を切り出して float64 で返す. + + 先頭 2 軸を (H, W) とみなすため,2D(輝度)・3D(RGB)どちらの配列も扱える. + + Args: + arr: 先頭 2 軸が (H, W) の配列. + roi: ``{"x", "y", "w", "h"}`` 形式の ROI 辞書(``SIM_ROI`` / ``REAL_ROI``). + + Returns: + ROI でクロップした float64 配列(入力と同じ次元数). + """ + x, y, w, h = roi["x"], roi["y"], roi["w"], roi["h"] + return arr[y : y + h, x : x + w].astype(np.float64)