diff --git a/CLAUDE.md b/CLAUDE.md index 7e348a1..5f92d2f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,6 +66,7 @@ - アーキテクチャ設計: docs/04_SPEC/SPEC_01_アーキテクチャ設計.md - 照明均一性評価アルゴリズム: docs/04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md - TIAS 白板撮影プロトコル: docs/04_SPEC/SPEC_03_TIAS白板撮影プロトコル.md +- 白板撮影データ仕様: docs/04_SPEC/SPEC_04_白板撮影データ仕様.md ### 05_TECH(技術設計) diff --git a/config/roi_config.json b/config/roi_config.json index 1706232..2d387e4 100644 --- a/config/roi_config.json +++ b/config/roi_config.json @@ -6,5 +6,13 @@ "width": 1400, "height": 1409 } + }, + "tias": { + "whiteboard": { + "x": 163, + "y": 469, + "width": 787, + "height": 734 + } } } \ No newline at end of file diff --git "a/docs/04_SPEC/SPEC_04_\347\231\275\346\235\277\346\222\256\345\275\261\343\203\207\343\203\274\343\202\277\344\273\225\346\247\230.md" "b/docs/04_SPEC/SPEC_04_\347\231\275\346\235\277\346\222\256\345\275\261\343\203\207\343\203\274\343\202\277\344\273\225\346\247\230.md" new file mode 100644 index 0000000..da4edac --- /dev/null +++ "b/docs/04_SPEC/SPEC_04_\347\231\275\346\235\277\346\222\256\345\275\261\343\203\207\343\203\274\343\202\277\344\273\225\346\247\230.md" @@ -0,0 +1,91 @@ +# 白板撮影データ仕様(SmTIAS-Evaluation 引き渡し用) (White-Board Capture Data Specification) + +本書は,TIASshot の白板撮影モードで取得し **SmTIAS-Evaluation 側へ引き渡す白板撮影データ**について,**実際の撮影設定値・ファイル構成・メタデータの意味・評価時の注意**をまとめたものである.撮影プロトコルの目的・基準値・質疑応答(Q&A)は [SPEC_03 TIAS 白板撮影プロトコル](SPEC_03_TIAS白板撮影プロトコル.md) を参照する(本書は実データの説明,SPEC_03 は仕様・根拠という役割分担). + +## 概要 (Overview) + +- TIASshot の **白板撮影モード(照明均一性評価用・DEBUG 限定)** で取得した素の RGB 画像データ. +- カメラ: **ImagingSource DFK 23UX249**(USB3 カラー産業用,Sony IMX249). +- **校正なし**で撮影する(チャート検出・ホワイトバランス自動調整・TCC 色補正を一切行わない).得られるのはカメラ素の RGB. +- 1 撮影セッション = 1 フォルダ(例: `<時刻>-白板/`). + +## 撮影設定(実際の値) (Capture Settings — Actual Values) + +各撮影の正確な値は同梱の `whiteboard_meta.json` に記録される.代表値(DFK 23UX249)は以下のとおり. + +| 項目 | 値 | 備考 | +| --- | --- | --- | +| カメラ | DFK 23UX249(SN 45610026) | | +| 撮影モード | 白板(校正なし・素 RGB) | チャート検出/WB 自動調整/TCC なし | +| 露出 | 16.6 ms(手動固定,≈1/60 s) | 自動露出なし(※ Q6 で実機確定) | +| ゲイン | 0(Auto OFF) | | +| ホワイトバランス | Auto OFF・固定 G 64 / B 140 / R 100 | 校正しないため device-state の固定値のまま | +| ガンマ | 100(= 1.0,線形)・トーンマップ OFF | | +| ノイズ低減 / シャープネス | Denoise 0 / Sharpness 0 | | +| Color Enhancement | **OFF**(撮影前に無効化) | 非線形の色強調を排除 | +| 明るさ (Brightness) | 50 | 黒レベル/オフセット相当か要確認(Q7) | +| 色補正行列 (CCM) | なし | ImagingSource は Color Matrix を持たない | +| シェーディング補正 (LSC) | **なし(ビネット未除去)** | DFK23 は在カメラ LSC を持たず補正データも無い(確認済) | +| 出力形式 | RGB24(8-bit/ch) | RAW ではない | +| 撮影枚数 | 既定 10(「連続撮影枚数」設定値) | SmTIAS と揃えるなら 12 に変更可 | + +## データ構成・ファイル (Files) + +1 セッションフォルダにフレーム数 × 2 枚の画像+メタデータが入る. + +| ファイル | 内容 | 解像度 | +| --- | --- | --- | +| `WhiteBoard_{NNNN}.png` | **ROI 切り出し画像**(評価対象の白板領域) | 1136 × 1528 | +| `WhiteBoard_full_{NNNN}.png` | **クロップなしフルフレーム**(四隅まで含む) | 1200 × 1920 | +| `whiteboard_meta.json` | 撮影条件+飽和統計(後述) | — | + +- ROI: config の `X0 / Y180 / W1136 / H1528` で切り出した白板領域. +- フル: センサ 1920 × 1200 を転置した **1200 × 1920**(クロップなし).ROI で切り捨てた周辺部(上 180px・下 212px・右 64px)を含むため,**レンズビネットの確認・フラットフィールド補正用**に使う. +- 画像はいずれも RGB24(8-bit,OpenCV の BGR 並びで PNG 保存).`{NNNN}` は 1 始まりの連番. + +## メタデータ(whiteboard_meta.json) (Metadata Schema) + +| フィールド | 意味 | +| --- | --- | +| `capture_datetime` | 撮影日時 | +| `device_name` / `serial_number` | カメラ機種名・シリアル | +| `roi_x` / `roi_y` / `roi_width` / `roi_height` | ROI 座標・サイズ | +| `full_frame_saved` / `full_width` / `full_height` | フルフレーム保存有無・サイズ | +| `num_frames` | 撮影枚数 | +| `exposure_ms` | 露出時間(ミリ秒) | +| `gain` | ゲイン(device ネイティブ値) | +| `brightness` | 明るさ(黒レベル/オフセット相当・要確認) | +| `gamma` | ガンマ(100 = 1.0 線形) | +| `wb_blue` / `wb_green` / `wb_red` | ホワイトバランスのチャンネルゲイン(固定値) | +| `color_enhancement_enabled` / `color_enhancement_note` | Color Enhancement の状態(false = 無効化済み) | +| `gamma_setting` / `tone_mapping` / `denoise` / `sharpness` / `color_matrix` | 固定状態の記録(device-state 由来の決め打ち文字列) | +| `shading_correction` / `shading_correction_note` | シェーディング補正状態("未補正"・ビネット混入の注記) | +| `frame_stats[]` | フレームごとの統計(下記) | + +`frame_stats[]` の各要素: + +| フィールド | 意味 | +| --- | --- | +| `frame` | フレーム番号(1 始まり) | +| `saturated_ratio` | 飽和画素率(輝度 ≥ 254 の割合) | +| `p99_luminance` | 輝度の 99 パーセンタイル | +| `mean_B` / `mean_G` / `mean_R` | チャンネル別 平均値 | +| `max_B` / `max_G` / `max_R` | チャンネル別 最大値 | + +※ `frame_stats` は **ROI 画像に対する統計**(白板領域の露出・飽和チェック用).フルフレーム側の統計は含まない.代表的な実測値: 飽和率 0・p99 ≒ 181(レンジの約 71%=飽和なしで十分な信号量). + +## 評価時の注意・留保 (Notes for Evaluation) + +SPEC_03 の Q&A に基づく留保事項. + +- **8-bit RGB(RAW でない)**: Q1 で許容(線形性は確認済み: γ1.0・トーンマップ OFF).主指標は CoV・中心周辺勾配・動径 min/max(輪帯平均でロバスト)を推奨. +- **ビネット未除去(最重要)**: DFK 23UX249 は在カメラのレンズシェーディング補正を持たず,補正データも付加されない(device-state・公式仕様で確認済).したがって観測ムラには**レンズビネットが含まれる**.V の除去には **TIAS 照明とは独立な一様光源でのフラットフィールド撮影**が必要(Q5).**`WhiteBoard_full_*`(四隅を含むフルフレーム)はこの V 特性化・目視確認のために提供**している.補正しないまま評価する場合は「照明×レンズの系全体の均一性」として留保すること. +- **固定・非 identity の WB**: 校正しないためチャンネルゲインは固定([1,1,1] ではない).相対ばらつき指標には不変だが(Q3),ロバストチェックとして**緑チャンネル単体でも均一性を算出**することを推奨. +- **黒レベル(Brightness=50)**: 加算オフセットか別スケールかが未確定(Q7).機種間比較の前に**ダークフレーム減算**で真の黒レベルを実測・減算することを推奨. +- **露出の手動固定**: device-state の自動フラグが二重で,手動固定を実機で確定する必要がある(Q6). + +## 関連 (Related) + +- [SPEC_03 TIAS 白板撮影プロトコル](SPEC_03_TIAS白板撮影プロトコル.md) — プロトコル・基準値・SmTIAS 回答(詳細・根拠) +- [TECH_02 TIAS 撮影条件](../05_TECH/TECH_02_TIAS撮影条件.md) — TIASshot 実機の撮影条件と SPEC_03 基準値との対照 +- 実装: `TIASshot/Cameras/IScam.cs`(`ShotWhiteBoard`),`TIASshot/Utils/ImageStats.cs`(飽和統計・JSON 生成) diff --git a/scripts/compare_smtias_tias_radial.py b/scripts/compare_smtias_tias_radial.py new file mode 100644 index 0000000..bff86fe --- /dev/null +++ b/scripts/compare_smtias_tias_radial.py @@ -0,0 +1,122 @@ +"""SmTIAS(DNG)と TIAS DFK23(PNG)の動径プロファイルを 1 枚に重ね描きする. + +compare_smtias_tias_relative.png(輝度マップ 2 枚を共通相対スケールで並置した図)の +**動径プロファイル版**.いまは SmTIAS と TIAS で別々の radial_profile.png が 2 枚あるのを, +**各自のピーク=1.0 で正規化** して 1 枚に重ね,中心からの減衰カーブの形を直接比較できるようにする. + +各自ピーク=1.0 で正規化する理由(共通スケールにしない): +- SmTIAS(DNG linear+LSC, 8bit→linear)と TIAS(PNG sRGB, DFK23)は + ビット深度・色空間・センサが異なり,絶対輝度は比較できない. +- 各自の最内ピークを 1.0 に揃えると「中心からどれだけ落ちるか(減衰カーブの形)」だけが残り, + 谷の底がそのまま動径 min/max 比に一致する(compare_png_dng_slide.py 下段と同方式). + +対象(compare_smtias_tias_relative.png と同一入力): +- SmTIAS : DNG (linear + LSC 補正) SmTIAS_QM_20260601_124500 → 動径 min/max 0.860 +- TIAS : PNG (vignette 未補正) WhiteBoard_0001 → 動径 min/max 0.982 + +使い方: + python scripts/compare_smtias_tias_radial.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 ( # noqa: E402 + calc_distance_map, + calc_radial_min_max_ratio, + calc_radial_profile, +) +from src.analysis.uniformity import ( # noqa: E402 + REC709_COEFF_B, + REC709_COEFF_G, + REC709_COEFF_R, +) +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, load_image # noqa: E402 + +# compare_smtias_tias_relative.png と同一の入力 +SMTIAS_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_124500.dng" +TIAS_PATH = PROJECT_ROOT / "data" / "tias" / "whiteboard" / "WhiteBoard_0001.png" +ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json" +SMTIAS_ROI_KEY = "smtias.whiteboard" +TIAS_ROI_KEY = "tias.whiteboard" +OUTPUT_PATH = PROJECT_ROOT / "output" / "compare_smtias_tias_radial.png" + + +def _smtias_luminance(roi: dict) -> np.ndarray: + """SmTIAS DNG を linear + LSC 補正で輝度化し ROI を切り出す.""" + luma = dng_luminance(str(SMTIAS_PATH), apply_lsc=True) + return extract_roi(luma, roi["x"], roi["y"], roi["width"], roi["height"]) + + +def _tias_luminance(roi: dict) -> np.ndarray: + """TIAS PNG を Rec.709 で輝度化し ROI を切り出す(vignette 未補正).""" + img = load_image(str(TIAS_PATH)) + luma = ( + REC709_COEFF_R * img[:, :, 0] + + REC709_COEFF_G * img[:, :, 1] + + REC709_COEFF_B * img[:, :, 2] + ) + return extract_roi(luma, roi["x"], roi["y"], roi["width"], roi["height"]) + + +def _radial(luminance: np.ndarray) -> tuple[np.ndarray, dict]: + """ROI 輝度から動径プロファイルと min/max 統計を算出する.""" + distance_map = calc_distance_map(*luminance.shape) + profile = calc_radial_profile(luminance, distance_map) + radial = calc_radial_min_max_ratio(profile) + return profile, radial + + +def main() -> None: + """各自ピーク=1.0 で正規化した動径プロファイル重ね描きを生成して保存する.""" + smtias_roi = load_roi_config(str(ROI_CONFIG), SMTIAS_ROI_KEY) + tias_roi = load_roi_config(str(ROI_CONFIG), TIAS_ROI_KEY) + if smtias_roi is None or tias_roi is None: + raise SystemExit("ROI が config に見つかりません") + + smtias_prof, smtias_r = _radial(_smtias_luminance(smtias_roi)) + tias_prof, tias_r = _radial(_tias_luminance(tias_roi)) + + fig, ax = plt.subplots(figsize=(9, 6)) + + # 各自の最内ピーク(radial_max)で正規化して形だけを重ねる + sx, sy = smtias_prof[:, 0], smtias_prof[:, 1] / smtias_r["radial_max"] + tx, ty = tias_prof[:, 0], tias_prof[:, 1] / tias_r["radial_max"] + ax.plot( + sx, sy, marker="o", ms=5, color="#1f3b73", + label=f"SmTIAS min/max = {smtias_r['radial_min_max_ratio']:.3f}", + ) + ax.plot( + tx, ty, marker="s", ms=5, color="#e08214", + label=f"TIAS min/max = {tias_r['radial_min_max_ratio']:.3f}", + ) + + # 谷の底(= 動径 min/max 比)を水平線で強調 + ax.axhline(smtias_r["radial_min_max_ratio"], color="#1f3b73", ls=":", alpha=0.5) + ax.axhline(tias_r["radial_min_max_ratio"], color="#e08214", ls=":", alpha=0.5) + + ax.set_title("Radial profile: SmTIAS vs TIAS DFK23 (normalized, peak = 1.0)", fontsize=12) + ax.set_xlabel("Normalized distance from center") + ax.set_ylabel("Normalized mean luminance (own peak = 1.0)") + ax.grid(True, linestyle="--", alpha=0.5) + ax.legend(loc="lower left", fontsize=10) + fig.tight_layout() + + OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUTPUT_PATH), dpi=150) + plt.close(fig) + print(f"動径プロファイル重ね描きを保存しました: {OUTPUT_PATH}") + print(f" SmTIAS (124500) radial min/max = {smtias_r['radial_min_max_ratio']:.4f}") + print(f" TIAS (WhiteBoard_0001) radial min/max = {tias_r['radial_min_max_ratio']:.4f}") + + +if __name__ == "__main__": + main()