diff --git a/.claude/template-sync-sha b/.claude/template-sync-sha index da97abe..411938e 100644 --- a/.claude/template-sync-sha +++ b/.claude/template-sync-sha @@ -1 +1 @@ -25655cd808cbc494676d576d0eab5e86126d39fb +324af3cdca8cc0c1390972c0d3aaeaa2c240e50f diff --git a/CLAUDE.md b/CLAUDE.md index 937a928..5fb55b3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,10 +1,10 @@ -# プロジェクト名 +# SmTIAS-Evaluation - +SmTIAS(定量撮影モード)の白板画像から照明均一性を評価するツール群.PNG / DNG(RAW_SENSOR) を入力に,CoV・max/min 比・中心周辺勾配・動径 min/max 比などの指標算出と可視化を行う. ## 開発進捗 -最新: TECH_05 Phase 1(DNG 読み込み I/F)と動径 min/max 比指標を実装.次は DNG の既存パイプライン統合(TECH_05 Phase 4〜) +最新: プロジェクト/デバイス名を SmTIAS へ改名し,`load_roi_config` と DNG 輝度化パイプラインを `src/`(`src/config.py`・`src/io/dng_pipeline.py`)へ集約.次はリリースタグ付け ※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは GUIDE_05「進捗記録の運用ルール(CLAUDE.md / PROGRESS.md)」を参照. ## 必須ルール(コード実装時) @@ -16,6 +16,7 @@ - ブランチ名: `feature/`/`fix/`/`docs/`/`chore/` + 英単語 2〜4 語(kebab-case) - コミットメッセージ: `[add]`/`[update]`/`[fix]`/`[remove]`/`[clean]` + 日本語 - コミット・push 後は `gh pr create` で PR を作成する +- **`/commit` はユーザーが明示的に指示した時のみ実行する.Claude が自発的に `/commit` や `git commit` を呼んではならない**(`/implement` 完了後も,案内するだけで自分ではコミットしない) ### エージェントチーム(GUIDE_05 準拠) @@ -63,14 +64,14 @@ ### 04_SPEC(仕様・設計) - アーキテクチャ設計: docs/04_SPEC/SPEC_01_アーキテクチャ設計.md +- 照明均一性評価アルゴリズム: docs/04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md ### 05_TECH(技術設計) - DNG 対応要求仕様: docs/05_TECH/TECH_01_DNG対応要求仕様.md -- SmTIAS-LightSim への引き継ぎ: docs/05_TECH/TECH_02_SmTIAS-LightSimへの引き継ぎ.md -### 07_REPORT(報告書) +### 06_TEST(テスト・評価報告) -- 照明均一性評価報告書: docs/07_REPORT/REPORT_01_照明均一性評価報告書.md -- PNG/DNG 評価比較報告書: docs/07_REPORT/REPORT_02_PNG_DNG評価比較報告書.md -- 外乱光影響評価報告書: docs/07_REPORT/REPORT_03_外乱光影響評価報告書.md +- 照明均一性評価報告書: docs/06_TEST/TEST_01_照明均一性評価報告書.md +- PNG/DNG 評価比較報告書: docs/06_TEST/TEST_02_PNG_DNG評価比較報告書.md +- 外乱光影響評価報告書: docs/06_TEST/TEST_03_外乱光影響評価報告書.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c47a66 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# SmTIAS-Evaluation + +SmTIAS(定量撮影モード)の白板画像から **照明均一性** を評価するツール群です. +PNG / DNG(RAW_SENSOR) を入力に,各種均一性指標の算出と可視化を行います. + +## 主な機能 + +- **均一性指標**: CoV・max/min 比・平均/標準偏差 +- **空間解析**: 中心/中間/周辺ゾーンの輝度勾配,中心周辺比,動径輝度プロファイル,動径 min/max 比 +- **再現性**: 位置合わせ付き SSIM による画像間再現性評価 +- **DNG 対応**: RAW_SENSOR DNG を linear で読み込み,`meta.json` の lscMap によるレンズシェーディング補正を順適用 +- **可視化**: 輝度マップ・ヒストグラム・動径プロファイル・ゾーンマップ,Streamlit ビューア + +## セットアップ + +Python 3.11 以上が必要です.詳細は [docs/02_ENV/ENV_02_環境構築手順.md](docs/02_ENV/ENV_02_環境構築手順.md) を参照してください. + +```bash +python -m venv .venv +.venv\Scripts\activate # Windows(macOS/Linux は source .venv/bin/activate) +pip install -r requirements.txt +pip install -r requirements-dev.txt +``` + +画像データは `data/`,解析結果は `output/` に置きます(どちらも `.gitignore` 対象). + +## 使い方 + +```bash +# PNG(白板画像 1 枚 / ディレクトリ一括) +python scripts/run_uniformity.py --image data/smtias/whiteboard/xxx.png +python scripts/run_uniformity.py --image data/smtias/whiteboard/ + +# DNG(定量モード,LSC 補正あり) +python scripts/run_uniformity_dng.py --image data/smtias/quantitative/xxx.dng + +# ROI 選択(対話的に矩形を選び config/roi_config.json へ保存) +python scripts/select_roi.py --image data/smtias/whiteboard/xxx.png + +# 結果ビューア(Streamlit) +streamlit run scripts/viewer.py +``` + +ROI は `config/roi_config.json` のキー(既定 `smtias.whiteboard`)で指定します. + +## プロジェクト構成 + +```text +src/ +├── config.py # ROI 設定の読み込み +├── io/ # 画像 I/O(PNG / DNG)と DNG 輝度化パイプライン +├── analysis/ # 均一性・空間解析・位置合わせ・再現性 +├── export/ # CSV 出力・バッチ集計 +└── visualization/ # matplotlib による各種プロット +scripts/ # 解析ランナー・ビューア・比較図生成 +tests/ # pytest +docs/ # 規約・設計・報告書(索引は CLAUDE.md) +``` + +## テスト + +```bash +python -m pytest +``` + +## ドキュメント + +設計・規約・評価報告は `docs/` 以下にカテゴリ別で整理しています(全体の索引は [CLAUDE.md](CLAUDE.md)). + +- `01_GUIDE/` — 規約・運用ルール(命名規則・Git 運用・コーディング規約・テスト方針 等) +- `02_ENV/` — 技術スタック・環境構築手順 +- `03_PLAN/` — 要件定義・開発ステップ +- `04_SPEC/` — アーキテクチャ設計・照明均一性評価アルゴリズム +- `05_TECH/` — 技術設計(DNG 対応要求仕様) +- `06_TEST/` — テスト・評価報告(照明均一性 / PNG・DNG 比較 / 外乱光影響) +- `PROGRESS.md` — 開発進捗のフルログ diff --git a/config/roi_config.json b/config/roi_config.json index c474086..1706232 100644 --- a/config/roi_config.json +++ b/config/roi_config.json @@ -1,5 +1,5 @@ { - "minitias": { + "smtias": { "whiteboard": { "x": 539, "y": 1015, diff --git "a/docs/01_GUIDE/GUIDE_03_\343\203\225\343\202\241\343\202\244\343\203\253\345\221\275\345\220\215\350\246\217\345\211\207.md" "b/docs/01_GUIDE/GUIDE_03_\343\203\225\343\202\241\343\202\244\343\203\253\345\221\275\345\220\215\350\246\217\345\211\207.md" index 2840ec6..f9d43d2 100644 --- "a/docs/01_GUIDE/GUIDE_03_\343\203\225\343\202\241\343\202\244\343\203\253\345\221\275\345\220\215\350\246\217\345\211\207.md" +++ "b/docs/01_GUIDE/GUIDE_03_\343\203\225\343\202\241\343\202\244\343\203\253\345\221\275\345\220\215\350\246\217\345\211\207.md" @@ -24,7 +24,6 @@ | `SPEC_` | `04_SPEC/` | ユーザーから見える仕様 | 企画書,画面遷移図,パラメータ設定 | | `TECH_` | `05_TECH/` | エンジニア向けの技術設計 | 通信プロトコル仕様,データベース設計,クラス設計 | | `TEST_` | `06_TEST/` | テスト計画,品質保証 | 単体テスト仕様書,シナリオテスト計画,品質基準 | -| `REPORT_` | `07_REPORT/` | 評価報告書,研究結果 | 照明均一性評価報告書,装置比較報告書 | ## 運用ルール (Operational Rules) diff --git "a/docs/01_GUIDE/GUIDE_07_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\351\200\240\350\246\217\345\211\207.md" "b/docs/01_GUIDE/GUIDE_07_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\351\200\240\350\246\217\345\211\207.md" index 11953bd..3e1c4ac 100644 --- "a/docs/01_GUIDE/GUIDE_07_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\351\200\240\350\246\217\345\211\207.md" +++ "b/docs/01_GUIDE/GUIDE_07_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\351\200\240\350\246\217\345\211\207.md" @@ -3,7 +3,7 @@ ## プロジェクトルート (Project Root) ```text -MiniTias-Evaluation/ +SmTIAS-Evaluation/ ├── src/ # ソースコード │ ├── io/ # 画像の読み込み │ ├── analysis/ # 解析ロジック @@ -14,7 +14,7 @@ ├── data/ # 入力画像(Git 管理外) ├── output/ # 解析結果(Git 管理外) ├── docs/ # ドキュメント -└── MiniTias/ # 参照用コピー(Git 管理外) +└── SmTIAS/ # 参照用コピー(Git 管理外) ``` ## 配置ルール (Placement Rules) @@ -24,7 +24,7 @@ | 解析ロジック | `src/` 配下の該当パッケージ | 1 モジュール = 1 責務 | | 実行スクリプト | `scripts/` | CLI のエントリーポイント | | テスト | `tests/` | `src/` の構造に対応させる | -| 入力画像 | `data/{装置名}/{撮影物}/` | 例: `data/minitias/whiteboard/` | +| 入力画像 | `data/{装置名}/{撮影物}/` | 例: `data/smtias/whiteboard/` | | 出力結果 | `output/figures/`,`output/results/` | 図は PNG,数値は CSV | | 設定ファイル | プロジェクトルート | `pyproject.toml`,`ruff.toml` 等 | diff --git "a/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" "b/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" index ebeb2b5..36c8797 100644 --- "a/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" +++ "b/docs/02_ENV/ENV_01_\346\212\200\350\241\223\343\202\271\343\202\277\343\203\203\343\202\257.md" @@ -2,7 +2,7 @@ ## 言語 (Language) -- **Python 3.12+** +- **Python 3.11+**(`pyproject.toml` の `requires-python = ">=3.11"`) ## 主要ライブラリ (Core Libraries) @@ -10,10 +10,13 @@ | --- | --- | --- | | NumPy | 数値計算・配列操作 | 画像データの行列演算の基盤 | | OpenCV (`opencv-python`) | 画像の読み込み・前処理 | PNG 読み込み,ROI 抽出,色空間変換に対応 | -| rawpy (`rawpy>=0.21.0`) | DNG (RAW_SENSOR) 読み込み | MiniTIAS 定量モードの 10bit Bayer DNG をデモザイク・linear 展開(libraw ベース) | -| colour-science | CIE L\*a\*b\* 変換,ΔE 計算 | CIE 規格準拠の色彩計算を正確に実装済み | +| rawpy (`rawpy>=0.21.0`) | DNG (RAW_SENSOR) 読み込み | SmTIAS 定量モードの 10bit Bayer DNG をデモザイク・linear 展開(libraw ベース) | +| SciPy | 位置合わせ(位相相関)・フィルタ | 再現性評価の位置合わせで FFT 相互相関・`scipy.ndimage` を使用 | +| scikit-image | SSIM 再現性評価 | `skimage.metrics.structural_similarity` で画像間 SSIM を算出 | | Matplotlib | グラフ・図の生成 | 論文掲載用の高品質な図を出力可能 | -| pandas | 数値結果の集計・CSV 出力 | 比較表の作成・データ管理に便利 | +| pandas | 数値結果の集計・表示 | ビューアでの集計表・データ管理に便利 | +| Pillow (`PIL`) | 図中の ROI オーバーレイ描画 | ビューアで矩形 ROI を画像へ重ね描き | +| Streamlit | 結果ビューア(GUI) | `scripts/viewer.py` の対話的な結果閲覧 UI | ## 開発ツール (Development Tools) @@ -29,7 +32,16 @@ - **実行方法**: CLI スクリプト(`python` コマンドで実行) - **Jupyter Notebook**: 必要に応じて探索的解析に使用(メインの解析パイプラインはスクリプトで実装) +## 将来導入予定 (Planned) + +以下は requirements に含めているが,**現状のコードでは未使用**(将来機能用の予約). + +| ライブラリ | 予定用途 | +| --- | --- | +| colour-science | CIE L\*a\*b\* 変換・ΔE2000 色差評価(CIE 規格準拠) | +| Jinja2 | 評価レポート(HTML 等)のテンプレート生成 | + ## 選定の補足 (Notes) -- **colour-science vs skimage**: `colour-science` は CIE 規格(ΔE2000 等)に厳密に準拠しており,学術論文での色差評価に適している.`skimage.color` でも基本的な Lab 変換は可能だが,ΔE 計算の選択肢が限られる +- **SSIM は scikit-image**: 再現性評価の SSIM は `skimage.metrics.structural_similarity` を使用.将来の色差(ΔE)評価には CIE 規格準拠の `colour-science` を予定(上記「将来導入予定」). - **Ruff**: Flake8 + Black + isort を 1 つのツールに統合しており,設定が簡潔 diff --git "a/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" "b/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" index 7458b7d..96a4b6c 100644 --- "a/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" +++ "b/docs/02_ENV/ENV_02_\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" @@ -12,7 +12,7 @@ ```bash git clone -cd MiniTias-Evaluation +cd SmTIAS-Evaluation ``` ### 仮想環境の作成・有効化 (Create Virtual Environment) @@ -43,7 +43,7 @@ ### 動作確認 (Verify Installation) ```bash -python -c "import cv2, colour, numpy, pandas, matplotlib; print('OK')" +python -c "import cv2, numpy, scipy, pandas, matplotlib, streamlit, skimage, PIL, rawpy; print('OK')" ``` `OK` と表示されれば成功. @@ -54,7 +54,7 @@ ```text data/ -├── minitias/ # MiniTIAS で撮影した画像 +├── smtias/ # SmTIAS で撮影した画像 │ └── whiteboard/ # 白板画像 └── tias/ # TIAS で撮影した画像 └── whiteboard/ # 白板画像 diff --git "a/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" "b/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" index a1234e6..2a32cbc 100644 --- "a/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" +++ "b/docs/02_ENV/ENV_03_\347\256\241\347\220\206\350\200\205\347\224\250\347\222\260\345\242\203\346\247\213\347\257\211\346\211\213\351\240\206.md" @@ -21,7 +21,7 @@ ## 初期ディレクトリの作成 (Create Directories) ```bash -mkdir -p data/minitias/whiteboard data/tias/whiteboard output +mkdir -p data/smtias/whiteboard data/tias/whiteboard output ``` ※ `data/` と `output/` は `.gitignore` で除外するため,空の `.gitkeep` ファイルは不要. diff --git "a/docs/03_PLAN/PLAN_01_\350\246\201\344\273\266\345\256\232\347\276\251\346\233\270.md" "b/docs/03_PLAN/PLAN_01_\350\246\201\344\273\266\345\256\232\347\276\251\346\233\270.md" index 7238c39..7bf71f4 100644 --- "a/docs/03_PLAN/PLAN_01_\350\246\201\344\273\266\345\256\232\347\276\251\346\233\270.md" +++ "b/docs/03_PLAN/PLAN_01_\350\246\201\344\273\266\345\256\232\347\276\251\346\233\270.md" @@ -2,9 +2,9 @@ ## プロジェクト概要 (Project Overview) -- **プロジェクト名**: MiniTIAS-Evaluation -- **目的**: 千葉大学で開発中のスマートフォン舌画像撮影システム MiniTIAS の画質を,既存の大型装置 TIAS と定量的に比較検証する -- **背景**: 積分球を用いた大型装置 TIAS とスマートフォン版 MiniTIAS で同一対象を撮影し,画像品質の差を定量的に評価する必要がある +- **プロジェクト名**: SmTIAS-Evaluation +- **目的**: 千葉大学で開発中のスマートフォン舌画像撮影システム SmTIAS の画質を,既存の大型装置 TIAS と定量的に比較検証する +- **背景**: 積分球を用いた大型装置 TIAS とスマートフォン版 SmTIAS で同一対象を撮影し,画像品質の差を定量的に評価する必要がある ## 対象ユーザー (Target Users) @@ -15,32 +15,32 @@ ### やること (In Scope) -- **照明均一性の評価**: 白板画像の輝度分布を解析し,TIAS と MiniTIAS の照明均一性を比較する -- **色再現性の評価**: 舌模型画像の色差(ΔE)を算出し,TIAS と MiniTIAS の色再現性を比較する +- **照明均一性の評価**: 白板画像の輝度分布を解析し,TIAS と SmTIAS の照明均一性を比較する +- **色再現性の評価**: 舌模型画像の色差(ΔE)を算出し,TIAS と SmTIAS の色再現性を比較する - **L\*a\*b\* 色空間変換**: 撮影画像を sRGB から CIE L\*a\*b\* 色空間に変換し,色彩値を定量比較する - **結果の可視化**: 解析結果をグラフ・表として出力する(論文掲載用) -- **バッチ処理**: 事前に撮影済みの PNG 画像を PC 上で事後処理する +- **バッチ処理**: 事前に撮影済みの画像(PNG / DNG)を PC 上で事後処理する ### やらないこと (Out of Scope) -- MiniTIAS アプリ(Flutter/Dart)の修正・機能追加(`MiniTIAS/` は参照用コピー) +- SmTIAS アプリ(Flutter/Dart)の修正・機能追加(`SmTIAS/` は参照用コピー) - リアルタイム解析・撮影と同時の処理 - Web アプリ・GUI の構築 - 他者への配布を前提としたパッケージ化 ## 検証の進め方 (Evaluation Strategy) -まずは **MiniTIAS 単体での照明均一性評価** から着手する.MiniTIAS で撮影した白板画像の輝度分布を解析し,照明の均一性を定量的に評価する.その後,TIAS との比較や色再現性の評価に拡張する. +まずは **SmTIAS 単体での照明均一性評価** から着手する.SmTIAS で撮影した白板画像の輝度分布を解析し,照明の均一性を定量的に評価する.その後,TIAS との比較や色再現性の評価に拡張する. ## 検証対象 (Evaluation Targets) | 優先度 | 対象 | 撮影物 | 評価項目 | 装置 | | --- | --- | --- | --- | --- | -| 1(最初) | 照明均一性 | 白板 | 輝度分布,均一性指標 | MiniTIAS のみ | -| 2(次) | 照明均一性比較 | 白板 | 輝度分布の装置間比較 | MiniTIAS vs TIAS | -| 3 | 色再現性 | 舌模型 | ΔE(色差),L\*a\*b\* 値の比較 | MiniTIAS vs TIAS | +| 1(最初) | 照明均一性 | 白板 | 輝度分布,均一性指標 | SmTIAS のみ | +| 2(次) | 照明均一性比較 | 白板 | 輝度分布の装置間比較 | SmTIAS vs TIAS | +| 3 | 色再現性 | 舌模型 | ΔE(色差),L\*a\*b\* 値の比較 | SmTIAS vs TIAS | ## データ (Data) -- **入力**: TIAS および MiniTIAS で撮影した PNG 画像(両方とも手元にあり) +- **入力**: 撮影画像.SmTIAS は白板 PNG と定量モードの DNG (RAW_SENSOR) が手元にある.TIAS 画像は今後取得予定(`data/tias/` は現状空). - **出力**: 解析結果(数値データ,グラフ,比較表) diff --git "a/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" index c93e2c5..c2c93b1 100644 --- "a/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" +++ "b/docs/03_PLAN/PLAN_02_\351\226\213\347\231\272\343\202\271\343\203\206\343\203\203\343\203\227.md" @@ -4,46 +4,66 @@ PLAN_01 の検証対象の優先度に従い,段階的に実装する.各ステップは独立してテスト・実行可能な単位とする. +実装状況の凡例: ✅ 完了 / 🟡 一部完了 / ⬜ 未着手.詳細な経緯は [docs/PROGRESS.md](../PROGRESS.md) を参照. + ## ステップ一覧 (Steps) -### ステップ 1: プロジェクト骨格の構築 +### ステップ 1: プロジェクト骨格の構築 ✅ 完了 - `src/`,`tests/` のディレクトリ・`__init__.py` を作成 - `loader.py` で PNG 読み込み・ROI 抽出を実装 - ユニットテストで動作確認 -### ステップ 2: MiniTIAS 照明均一性評価 +### ステップ 2: SmTIAS 照明均一性評価 ✅ 完了 - `uniformity.py` で輝度分布・均一性指標(CoV,標準偏差,最大/最小比)を実装 - `plotter.py` で輝度マップ・ヒストグラムを生成 - `exporter.py` で CSV・PNG 出力 -- MiniTIAS の白板画像で実行し結果を確認 +- SmTIAS の白板画像で実行し結果を確認 -### ステップ 3: TIAS との照明均一性比較 +**当初計画から追加で実装した項目(実装済み):** + +- `spatial.py`: 空間解析(中心/中間/周辺ゾーン,中心周辺勾配,動径輝度プロファイル,動径 min/max 比) +- `reproducibility.py` + `registration.py`: 位置合わせ(位相相関)付き SSIM による画像間再現性評価 +- `viewer.py`: Streamlit による結果ビューア / `select_roi.py`: OpenCV による対話的 ROI 選択 +- 報告書 TEST_01(照明均一性)/ TEST_02(PNG・DNG 比較)/ TEST_03(外乱光影響) + +### ステップ 2.5: DNG (RAW_SENSOR) 対応 ✅ 完了(TECH_05 Phase 1) + +- SmTIAS 定量モードの出力が PNG → DNG(RAW_SENSOR) に変わったため DNG 読み込み I/F を新設 +- `io/dng_loader.py`(linear 展開)+ `io/dng_pipeline.py`(LSC 補正・portrait 化・Rec.709 輝度化) +- `run_uniformity_dng.py` で PNG 版と同じ出力一式を DNG から生成 +- 残フェーズ(既存パイプライン統合・SPEC 更新 等)は [TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md) / PROGRESS.md を参照 + +### ステップ 3: TIAS との照明均一性比較 ⬜ 未着手 - ステップ 2 のパイプラインを TIAS 画像にも適用 - 装置間の比較グラフ(並列表示,差分)を追加 - 比較結果を CSV に出力 +- 前提: TIAS 画像の取得(`data/tias/` は現状空) -### ステップ 4: 色再現性評価 +### ステップ 4: 色再現性評価 ⬜ 未着手 - `color.py` で sRGB → L\*a\*b\* 変換・ΔE2000 計算を実装 - 舌模型画像の色差を装置間で比較 - 比較グラフ・表を出力 -### ステップ 5: 実行スクリプトの整備 +### ステップ 5: 実行スクリプトの整備 🟡 一部完了 -- `run_analysis.py` で全解析を一括実行できるようにする -- コマンドライン引数で解析対象を切り替え可能にする +- 解析の実行スクリプトを整備(実装: `run_uniformity.py`(PNG)/ `run_uniformity_dng.py`(DNG).当初想定の単一 `run_analysis.py` ではなく評価軸ごとに分割) +- コマンドライン引数(`--image` 等)で対象切り替え・ディレクトリ一括処理に対応済み +- 色再現性(ステップ 4)など未実装軸の統合は今後 ## 依存関係 (Dependencies) ```text -ステップ 1 → ステップ 2 → ステップ 3 +ステップ 1 → ステップ 2 → ステップ 2.5 (DNG) + → ステップ 3 → ステップ 4 → ステップ 5 ``` - ステップ 1 はすべてのステップの前提 +- ステップ 2.5(DNG 対応)はステップ 2 完了後に実施(実装済み) - ステップ 3 と 4 はステップ 2 の完了後,並行して進めることも可能 - ステップ 5 は 3・4 の完了後 diff --git "a/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" "b/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" index ab37fa7..e99276e 100644 --- "a/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" +++ "b/docs/04_SPEC/SPEC_01_\343\202\242\343\203\274\343\202\255\343\203\206\343\202\257\343\203\201\343\203\243\350\250\255\350\250\210.md" @@ -3,57 +3,97 @@ ## ディレクトリ構造 (Directory Structure) ```text -MiniTias-Evaluation/ +SmTIAS-Evaluation/ ├── src/ +│ ├── config.py # ROI 設定(roi_config.json)の読み込み │ ├── io/ -│ │ └── loader.py # 画像読み込み(OpenCV) +│ │ ├── loader.py # PNG 読み込み・ROI 抽出(OpenCV) +│ │ ├── dng_loader.py # DNG(RAW_SENSOR) 読み込み(rawpy, linear 展開) +│ │ └── dng_pipeline.py # DNG 輝度化(LSC 補正・portrait 化・Rec.709 輝度化) │ ├── analysis/ -│ │ ├── uniformity.py # 照明均一性評価 -│ │ └── color.py # 色再現性評価(ΔE,L*a*b*) +│ │ ├── uniformity.py # 照明均一性指標(CoV, 標準偏差, max/min, Rec.709 輝度) +│ │ ├── spatial.py # 空間解析(ゾーン・中心周辺勾配・動径プロファイル・動径 min/max 比) +│ │ ├── registration.py # 位相相関による並進位置合わせ +│ │ └── reproducibility.py # 位置合わせ付き SSIM 再現性評価 │ ├── visualization/ -│ │ └── plotter.py # グラフ・図の生成(Matplotlib) +│ │ └── plotter.py # 輝度マップ・ヒストグラム・動径プロファイル・ゾーンマップ(Matplotlib) │ └── export/ -│ └── exporter.py # CSV・画像の出力(pandas) +│ └── exporter.py # CSV 出力・バッチ集計(csv, NumPy) ├── scripts/ -│ └── run_analysis.py # 実行エントリーポイント +│ ├── run_uniformity.py # PNG 解析ランナー(単一画像/ディレクトリ一括) +│ ├── run_uniformity_dng.py # DNG 解析ランナー +│ ├── select_roi.py # 対話的 ROI 選択(OpenCV) +│ ├── viewer.py # 結果ビューア(Streamlit) +│ └── compare_*.py / plot_radial_clean.py # 報告書用の比較図・クリーン出力 ├── tests/ -├── data/ -│ ├── minitias/whiteboard/ -│ └── tias/whiteboard/ -└── output/ +├── config/ +│ └── roi_config.json # ROI 定義(キー: smtias.whiteboard 等) +├── data/ # 入力画像(.gitignore 対象) +│ ├── smtias/{whiteboard,quantitative}/ +│ └── tias/whiteboard/ # TIAS は今後取得(現状空) +└── output/ # 解析結果(.gitignore 対象) + ├── figures/ + └── results/ ``` ## 処理パイプライン (Processing Pipeline) ```text -PNG 画像 - ↓ loader.py 画像読み込み(OpenCV,BGR→RGB) - ↓ loader.py ROI 抽出(解析対象領域の切り出し) - ↓ uniformity.py 輝度分布の計算(均一性指標: CoV 等) - color.py 色空間変換(sRGB → L*a*b*)・ΔE 計算 - ↓ plotter.py グラフ生成(輝度マップ,色差分布等) - ↓ exporter.py CSV・PNG 出力 +入力画像 + ├─ PNG → loader.py 読み込み(OpenCV, BGR→RGB)+ ROI 抽出 + └─ DNG → dng_loader.py + dng_pipeline.py + linear 展開・LSC 補正・portrait 化・Rec.709 輝度化 + ROI 抽出 + ↓ uniformity.py 輝度分布・均一性指標(CoV, 標準偏差, max/min) + ↓ spatial.py 空間解析(ゾーン別統計・中心周辺比・勾配・動径プロファイル・動径 min/max 比) + ↓ reproducibility.py (バッチ時)位置合わせ付き SSIM による画像間再現性 + ↓ plotter.py 図生成(輝度マップ・ヒストグラム・動径プロファイル・ゾーンマップ) + ↓ exporter.py CSV 出力・バッチ集計 ``` +アルゴリズムの詳細は [SPEC_02 照明均一性評価アルゴリズム](SPEC_02_照明均一性評価アルゴリズム.md),DNG 経路の詳細は [TECH_01 DNG 対応要求仕様](../05_TECH/TECH_01_DNG対応要求仕様.md) を参照. + ## モジュール設計 (Module Design) | モジュール | 責務 | 主な依存 | | --- | --- | --- | -| `loader.py` | PNG 読み込み・ROI 抽出 | OpenCV,NumPy | -| `uniformity.py` | 輝度分布・均一性指標(CoV,標準偏差等)の算出 | NumPy | -| `color.py` | sRGB→L\*a\*b\* 変換・ΔE2000 計算 | colour-science,scipy | -| `plotter.py` | 輝度マップ・棒グラフ・散布図の生成 | Matplotlib | -| `exporter.py` | 数値結果を CSV,図を PNG で保存 | pandas,Matplotlib | -| `run_analysis.py` | 各モジュールを呼び出す実行スクリプト | 全モジュール | +| `config.py` | ROI 設定 JSON の読み込み | (標準ライブラリ) | +| `io/loader.py` | PNG 読み込み・ROI 抽出 | OpenCV,NumPy | +| `io/dng_loader.py` | DNG(RAW_SENSOR) を linear で読み込み | rawpy,NumPy | +| `io/dng_pipeline.py` | DNG の LSC 補正・portrait 化・Rec.709 輝度化 | NumPy(`uniformity` の係数を参照) | +| `analysis/uniformity.py` | 輝度変換・均一性指標(CoV,標準偏差,max/min)の算出 | NumPy | +| `analysis/spatial.py` | ゾーン分割・中心周辺比・勾配・動径プロファイル・動径 min/max 比 | NumPy | +| `analysis/registration.py` | 位相相関による並進位置合わせ(サブピクセル) | SciPy,NumPy | +| `analysis/reproducibility.py` | 位置合わせ付き SSIM の画像間再現性評価 | scikit-image,NumPy | +| `visualization/plotter.py` | 輝度マップ・ヒストグラム・動径プロファイル・ゾーンマップの生成 | Matplotlib | +| `export/exporter.py` | 数値結果を CSV 保存・バッチ集計 | csv,NumPy | +| `scripts/run_uniformity.py` | PNG 解析の実行(単一/一括) | 全モジュール | +| `scripts/run_uniformity_dng.py` | DNG 解析の実行 | 全モジュール | +| `scripts/select_roi.py` | 対話的 ROI 選択 | OpenCV | +| `scripts/viewer.py` | 結果ビューア | Streamlit,pandas,Pillow | ## 入出力仕様 (I/O Specification) ### 入力 (Input) -- 形式: PNG(8bit RGB) -- 配置: `data/minitias/whiteboard/`,`data/tias/whiteboard/` +- 形式: PNG(8bit RGB, sRGB)/ DNG(RAW_SENSOR, 10bit Bayer → rawpy で linear 展開) +- 配置: `data/smtias/{whiteboard,quantitative}/`,`data/tias/whiteboard/` + +#### DNG データ仕様 (DNG Data Format) + +定量モード DNG(SH-02M, RAW_SENSOR)の取り扱い上の固有値: + +- raw: 10-bit(整数 0〜1023) +- 黒レベル: 64(`SENSOR_BLACK_LEVEL_PATTERN`)→ 除去後 0〜959 +- 飽和警告のめやす: 値 > 900 +- デモザイク既定: バイリニア(`rawpy.DemosaicAlgorithm.LINEAR`).`demosaic` 引数で AHD/DHT に切替可 +- カラーマトリクスは DNG Exif(`ColorMatrix1/2`)に埋め込み済み(rawpy が参照).ただし linear 評価経路では `output_color=raw`(非適用)で読む +- linear ドメイン(TONEMAP 線形・sRGB OETF 未適用).輝度化の詳細は [SPEC_02](SPEC_02_照明均一性評価アルゴリズム.md) 「輝度変換」を参照 ### 出力 (Output) - `output/figures/` — グラフ画像(PNG,論文掲載用) - `output/results/` — 数値結果(CSV) + +## 将来導入予定 (Planned) + +- **色再現性評価**(`analysis/color.py`): sRGB → CIE L\*a\*b\* 変換・ΔE2000 計算による TIAS / SmTIAS の色差比較(現状未実装.要件は [PLAN_01](../03_PLAN/PLAN_01_要件定義書.md)・[PLAN_02](../03_PLAN/PLAN_02_開発ステップ.md) ステップ 4) diff --git "a/docs/04_SPEC/SPEC_02_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\343\202\242\343\203\253\343\202\264\343\203\252\343\202\272\343\203\240.md" "b/docs/04_SPEC/SPEC_02_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\343\202\242\343\203\253\343\202\264\343\203\252\343\202\272\343\203\240.md" index 484771c..d44b85d 100644 --- "a/docs/04_SPEC/SPEC_02_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\343\202\242\343\203\253\343\202\264\343\203\252\343\202\272\343\203\240.md" +++ "b/docs/04_SPEC/SPEC_02_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\343\202\242\343\203\253\343\202\264\343\203\252\343\202\272\343\203\240.md" @@ -2,7 +2,7 @@ ## 概要 (Overview) -MiniTIAS で撮影した白板画像の照明均一性を定量的に評価する.白板の白壁面領域の輝度分布を解析し,3 つの指標で均一性を定量化する. +SmTIAS で撮影した白板画像の照明均一性を定量的に評価する.白板の白壁面領域の輝度分布を解析し,3 つの指標で均一性を定量化する. ## 輝度変換 (Luminance Conversion) @@ -15,6 +15,8 @@ - $Y$: 輝度(0.0〜255.0, float64) - $R, G, B$: 各チャネルの画素値(0〜255, uint8) +> **DNG 経路**: RAW_SENSOR DNG では同じ Rec.709 係数を linear 値に適用し,PNG 図と軸を揃えるため ×255 スケールで表現する(CoV・max/min・中心周辺比はスケール不変).詳細は [TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md). + ### 選定理由 | 方法 | 定義 | 不採用の理由 | @@ -58,9 +60,9 @@ ### 対象領域 -MiniTIAS の開口部に白板を取り付けて撮影する.ROI は**白板の表面領域**(中央の白い四角形)とする.以下を除外する: +SmTIAS の開口部に白板を取り付けて撮影する.ROI は**白板の表面領域**(中央の白い四角形)とする.以下を除外する: -- 白板の外側(MiniTIAS 筐体の壁面) +- 白板の外側(SmTIAS 筐体の壁面) - 白板のフレーム・エッジ部分 - ラベル(「SmTIAS 001」等) @@ -73,7 +75,7 @@ ### 起動コマンド ```bash -python scripts/select_roi.py --image data/minitias/whiteboard/<画像ファイル名>.png --config config/roi_config.json --roi minitias.whiteboard +python scripts/select_roi.py --image data/smtias/whiteboard/<画像ファイル名>.png --config config/roi_config.json --roi smtias.whiteboard ``` OpenCV のウィンドウが開くので,マウスで矩形を選択して Enter で確定する.ESC でキャンセル. @@ -132,6 +134,16 @@ - 出力: 20 × 2 の配列(列 0 = ビン中心距離,列 1 = 平均輝度) - 最外ビンは上端を含める($d = 1.0$ の画素を欠損させない) +### 動径 min/max 比 (Radial Min/Max Ratio) + +放射状輝度プロファイル(20 ビンの輪帯平均)の最小値と最大値の比を,外れ画素にロバストな周辺減衰指標として算出する. + +$$\text{Radial Min/Max Ratio} = \frac{Y^{\text{ring}}_{\min}}{Y^{\text{ring}}_{\max}}$$ + +- 値域 $[0, 1]$.1.0 に近いほど均一(周辺減衰が小さい) +- 画素単位の max/min 比と異なり輪帯平均を用いるため,局所ノイズ・外れ画素に頑健 +- 併せて最小輝度ビンの正規化距離(`radial_min_distance`)を報告する + ## 位置合わせ (Image Registration) スマートフォンの取り付け位置の微小なずれを補正し,照明パターンの真の再現性を評価するために,位相相関法による並進位置合わせを実装する [10]. @@ -219,10 +231,10 @@ ```bash # ディレクトリ内の全画像を一括解析 -python scripts/run_uniformity.py --image data/minitias/whiteboard/ --config config/roi_config.json --roi minitias.whiteboard +python scripts/run_uniformity.py --image data/smtias/whiteboard/ --config config/roi_config.json --roi smtias.whiteboard # 単一画像の解析 -python scripts/run_uniformity.py --image data/minitias/whiteboard/<画像ファイル名>.png --config config/roi_config.json --roi minitias.whiteboard +python scripts/run_uniformity.py --image data/smtias/whiteboard/<画像ファイル名>.png --config config/roi_config.json --roi smtias.whiteboard ``` ### 出力 diff --git "a/docs/05_TECH/TECH_01_DNG\345\257\276\345\277\234\350\246\201\346\261\202\344\273\225\346\247\230.md" "b/docs/05_TECH/TECH_01_DNG\345\257\276\345\277\234\350\246\201\346\261\202\344\273\225\346\247\230.md" index b77b907..ba776bc 100644 --- "a/docs/05_TECH/TECH_01_DNG\345\257\276\345\277\234\350\246\201\346\261\202\344\273\225\346\247\230.md" +++ "b/docs/05_TECH/TECH_01_DNG\345\257\276\345\277\234\350\246\201\346\261\202\344\273\225\346\247\230.md" @@ -1,17 +1,14 @@ -# MiniTias-Evaluation DNG 対応要求仕様 (DNG Support Specification for MiniTias-Evaluation) +# SmTIAS-Evaluation DNG 対応要求仕様 (DNG Support Specification for SmTIAS-Evaluation) -> **本ドキュメントについて(取り込み注記)**: SmTIAS-LightSim プロジェクトから受領した DNG 対応要求仕様(先方での名称は TECH_05)を,本プロジェクトの命名規則(GUIDE_03)に従い `docs/05_TECH/TECH_01_DNG対応要求仕様.md` として取り込んだもの.文中の他ドキュメント参照(TECH_01/03/04・TEST_01・SPEC_04 等)は **SmTIAS-LightSim 側のドキュメント**を指し,本プロジェクト内には存在しない.本プロジェクトでの対応状況は `docs/PROGRESS.md` を参照. +> **本ドキュメントについて(取り込み注記)**: SmTIAS-LightSim プロジェクトから受領した DNG 対応要求仕様(先方での名称は TECH_05)を,本プロジェクトの命名規則(GUIDE_03)に従い取り込んだもの.文中の他ドキュメント参照(TECH_01/03/04・TEST_01・SPEC_04 等)は **SmTIAS-LightSim 側のドキュメント**を指し,本プロジェクト内には存在しない. +> +> 本書は**要求の記録**である.実装の詳細(API・データ仕様・アルゴリズム)は [SPEC_01](../04_SPEC/SPEC_01_アーキテクチャ設計.md) / [SPEC_02](../04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md),経緯の詳細は [PROGRESS.md](../PROGRESS.md) を参照.完了済みの実装計画(Phase 分割・検証手順 V1〜V4・Open Questions・関数シグネチャ案 等)や SmTIAS-LightSim への引き継ぎ手順は本整理で削除した(git 履歴に残る). -本ドキュメントは SmTIAS-LightSim プロジェクトから **MiniTias-Evaluation** プロジェクトに対する **DNG 対応要求仕様** である.MiniTias 側で実装された定量撮影モード([TECH_01](TECH_01_定量撮影モード要求仕様.md) / [TECH_03](TECH_03_定量モード実装報告とSHADING_MODE相談.md))の出力フォーマットが PNG から DNG (RAW_SENSOR) に変更されたことに伴い,MiniTias-Evaluation 側にも DNG 読み込み・解析機能の追加を依頼する. - -## 想定読者 (Intended Reader) - -- **MiniTias-Evaluation 側 Claude セッション**: 本ドキュメントを最初に読んで作業着手する -- **人間レビュー (rinto)**: 両セッションを橋渡しする +SmTIAS 側で実装された定量撮影モードの出力フォーマットが PNG から DNG (RAW_SENSOR) に変更されたことに伴い,SmTIAS-Evaluation 側にも DNG 読み込み・解析機能の追加を依頼したもの. ## 概要 (Overview) -MiniTias-Evaluation([MiniTias-Evaluation](https://github.com/rintoHasegawa/MiniTias-Evaluation) リポジトリ)に以下の機能追加を要求する: +SmTIAS-Evaluation([SmTIAS-Evaluation](https://github.com/rintoHasegawa/SmTIAS-Evaluation) リポジトリ)に対し,以下の機能追加を要求した: 1. **DNG 読み込み I/F** の新規実装(10-bit BGGR Bayer 展開) 2. **メタデータ JSON 読み込み I/F** の新規実装(撮影条件・LSC マップ・hot pixel リスト等) @@ -20,13 +17,28 @@ これらの実装は **SmTIAS-LightSim 側にコピーされる前提**で,再利用性の高い設計とする. +## 実装状況 (Implementation Status — SmTIAS-Evaluation) + +要求に対する SmTIAS-Evaluation での実装結果(要求と異なる点を含む). + +| 要求 | 状況 | 実体・備考 | +| --- | --- | --- | +| 1. DNG 読み込み I/F(`dng_loader.py` / `load_image_dng`) | ✅ 実装 | `src/io/dng_loader.py`.`rgb_float32`・`luma_float32`(`output_color=raw`)・`rgb_uint8`(sRGB)を実装 | +| 2. メタデータ JSON 読み込み(`metadata_loader.py`) | 🟡 一部 | 専用モジュールは未作成.LSC マップ読み込みは `src/io/dng_pipeline.py` の `lsc_gain_rgb` に内包.`load_metadata` / `load_hot_pixel_list` は未実装 | +| 3. Hot pixel マスク(`mask_hot_pixels`) | ⬜ 未実装 | SmTIAS 側のホット画素提供待ち | +| 4. LSC 参照(`apply_lsc_inverse`) | ⚠️ 方針転換 | 実機 DNG は LSC **未補正**と判明.逆適用ではなく **順適用**(`dng_pipeline.lsc_gain_rgb` でビネット除去).`apply_lsc_inverse` は未実装 | +| 5. PNG 経路との並列対応 | ✅ 実装 | `load_image` は無修正.DNG は `scripts/run_uniformity_dng.py` で別経路.統一 I/F `load_image_any` は未実装(任意項目) | +| 6. 評価指標との互換 | ✅ 実装 | `uniformity` / `spatial` 等は無修正で DNG 輝度(linear ×255 スケール)に適用 | + +> 仕様外で新規追加: `src/io/dng_pipeline.py`(DNG の LSC 順適用・portrait 化・Rec.709 輝度化をまとめた高レベル I/F)と,動径 min/max 比指標(`spatial.calc_radial_min_max_ratio`).Phase 4〜6(既存パイプライン統合・PNG/DNG 検証・SPEC 更新)は継続中.DNG のデータ仕様(10-bit・黒レベル・デモザイク等)は [SPEC_01](../04_SPEC/SPEC_01_アーキテクチャ設計.md) 「DNG データ仕様」に移設した. + ## 背景 (Background) ### 三項プロジェクトの関係 ```text ┌─────────────────────────┐ ┌──────────────────────────┐ -│ MiniTias │ │ MiniTias-Evaluation │ +│ SmTIAS │ │ SmTIAS-Evaluation │ │ (Flutter / Android) │ ──→ │ (Python) │ │ 撮影アプリ │ │ 実機データ評価ツール │ │ - PNG 保存(通常モード) │ │ - 評価指標算出 │ @@ -45,394 +57,26 @@ ### 経緯のまとめ - 2026-05-28: SmTIAS-LightSim 側 Step 8 キャリブで「実機 PNG 自体が定量データとして揺らぐ」を発見 ([TEST_01](../06_TEST/TEST_01_キャリブレーション検証履歴.md)) -- 2026-05-28: MiniTias 側に [TECH_01 定量撮影モード要求仕様](TECH_01_定量撮影モード要求仕様.md) を発行 -- 2026-06-01: MiniTias 側で実装完了.AQUOS sense3 (SH-02M) が LEVEL_3 で DNG 経路採用 ([TECH_03](TECH_03_定量モード実装報告とSHADING_MODE相談.md)) -- 2026-06-01: SHADING_MODE = HIGH_QUALITY を本流維持で合意 ([TECH_04](TECH_04_MiniTIASへの回答.md)) -- **本ドキュメント (2026-06-01)**: MiniTias-Evaluation 側にも DNG 対応を依頼 +- 2026-05-28: SmTIAS 側に [TECH_01 定量撮影モード要求仕様](TECH_01_定量撮影モード要求仕様.md) を発行 +- 2026-06-01: SmTIAS 側で実装完了.AQUOS sense3 (SH-02M) が LEVEL_3 で DNG 経路採用 ([TECH_03](TECH_03_定量モード実装報告とSHADING_MODE相談.md)) +- 2026-06-01: SHADING_MODE = HIGH_QUALITY を本流維持で合意 ([TECH_04](TECH_04_SmTIASへの回答.md)) +- 2026-06-01: 本要求(SmTIAS-Evaluation 側にも DNG 対応を依頼)を発行 -### MiniTias-Evaluation の現状 +### 役割分担 (Role Distribution) -MiniTias-Evaluation の I/O は現状 PNG 専用: - -```python -# 既存 I/F (src/io/loader.py) -def load_image(path: str) -> numpy.ndarray: - """PNG 画像を読み込み RGB 配列として返す(H x W x 3, uint8).""" -``` - -→ DNG 経路の追加が必要. - -## 役割分担 (Role Distribution) - -DNG 読み込みの責務は **MiniTias-Evaluation を一次窓口**とする.既存パターン(`uniformity.py` が MiniTias-Evaluation から SmTIAS-LightSim にコピー)を踏襲する: +DNG 読み込みの責務は **SmTIAS-Evaluation を一次窓口**とする.既存パターン(`uniformity.py` が SmTIAS-Evaluation から SmTIAS-LightSim にコピー)を踏襲する: | プロジェクト | 責務 | | --- | --- | -| **MiniTias** | DNG + meta.json の保存(実装済) | -| **MiniTias-Evaluation(一次窓口)** | DNG/meta.json の読み込み I/F を正規実装.既存評価指標との並列対応 | -| **SmTIAS-LightSim(コピー側)** | MiniTias-Evaluation の DNG 読み込み I/F をコピー(`uniformity.py` パターン継承) | - -→ MiniTias-Evaluation の実装完了後,SmTIAS-LightSim 側 Claude セッションでコピー作業を実施する. - -## 要求仕様 (Requirements) - -### 1. DNG 読み込み I/F - -新規モジュール `src/io/dng_loader.py` を作成: - -```python -def load_image_dng( - path: str, - demosaic: str = "bilinear", - black_level: int | None = None, - output_format: str = "rgb_float32", -) -> numpy.ndarray: - """MiniTIAS 定量モード DNG を読み込み配列として返す. - - Args: - path: DNG ファイルのパス. - demosaic: デモザイクアルゴリズム.既定 "bilinear". - "nearest"(最近傍)/ "bilinear"(双線形)/ "ahd"(適応的)から選択. - black_level: 黒レベル.None なら meta.json から自動取得 or DNG Exif から取得. - 通常は 64(SH-02M). - output_format: 出力形式.既定 "rgb_float32". - "rgb_float32": (H, W, 3) float32, linear, 値域 [0.0, 1.0](飽和は 1.0 でクリップしない) - "luma_float32": (H, W) float32, linear, 値域 [0.0, 1.0](Rec.709 重み) - "rgb_uint8": (H, W, 3) uint8, sRGB OETF 適用後(既存 PNG 経路と互換) - - Returns: - numpy.ndarray. 形状・型は output_format に依存. - - Raises: - FileNotFoundError: ファイルが存在しない場合. - ValueError: rawpy が DNG を読み込めなかった場合. - """ -``` - -#### 実装方針 - -- `rawpy.imread()` で 10-bit BGGR Bayer を読み込み -- 黒レベル除去(meta.json `sensorCharacteristics.black_level_pattern` から取得,未提供時は DNG Exif) -- デモザイク(`postprocess(demosaic_algorithm=...)` で実装) -- カラーマトリクス適用(DNG Exif `ColorMatrix1` を参照.無彩色シーンでは影響軽微だが選択肢として用意) -- 出力形式の変換 - -#### `output_format` の使い分け - -- `rgb_float32` / `luma_float32`: SmTIAS-LightSim のシミュ vs 実機比較に推奨(linear ドメインで突合) -- `rgb_uint8`: 既存 MiniTias-Evaluation パイプラインとの互換用(既存 `analysis/uniformity.py` 等が uint8 前提の場合に有用) - -### 2. メタデータ JSON 読み込み I/F - -新規モジュール `src/io/metadata_loader.py` を作成: - -```python -def load_metadata(path: str) -> dict: - """MiniTIAS 定量モード meta.json を読み込み辞書として返す. - - Args: - path: meta.json のパス. - - Returns: - 辞書.キー: settings / actual / sensorCharacteristics / lscMap / lscMapRowCount / - lscMapColumnCount / hardwareLevel 等. - 構造は TECH_03 のスキーマ準拠. - - Raises: - FileNotFoundError: ファイルが存在しない場合. - json.JSONDecodeError: JSON 形式が不正な場合. - """ - - -def load_lsc_map(path: str) -> numpy.ndarray: - """meta.json から LSC マップを (4, M, N) 配列として抽出する. - - Args: - path: meta.json のパス. - - Returns: - numpy.ndarray of shape (4, M, N), dtype=float32. - 4 = [R, G_even, G_odd, B], M = lscMapRowCount, N = lscMapColumnCount. - 値域: ≥ 1.0(無補正). - - Raises: - FileNotFoundError / KeyError / ValueError: 仕様外の場合. - """ - - -def load_hot_pixel_list(path: str) -> list[tuple[int, int]]: - """meta.json からホット画素リストを (col, row) のタプルリストとして抽出する. - - Args: - path: meta.json のパス. - - Returns: - ホット画素座標のリスト.MiniTias 側で取得実装後に有効化.未提供時は空リスト. - """ -``` - -### 3. Hot pixel マスク機能 - -メタデータから取得したホット画素を画像上でマスクする機能: - -```python -def mask_hot_pixels( - image: numpy.ndarray, - hot_pixels: list[tuple[int, int]], - method: str = "neighbor_mean", -) -> numpy.ndarray: - """ホット画素位置を周辺平均で置換する. - - Args: - image: 入力画像 (H, W) or (H, W, 3). - hot_pixels: ホット画素座標 (col, row) のリスト. - method: 置換方法."neighbor_mean"(周辺 3x3 平均)/ "zero"(0 で置換). - - Returns: - マスク済み画像.形状・型は入力と同じ. - """ -``` - -### 4. LSC マップ参照機能(オプション・将来検証用) - -本流の処理では使わないが,将来のフォールバック検証用に実装: - -```python -def apply_lsc_inverse( - image: numpy.ndarray, - lsc_map: numpy.ndarray, -) -> numpy.ndarray: - """LSC マップを画像に逆適用する(補正前 raw 相当に戻す). - - Args: - image: 補正済み画像 (H, W) or (H, W, 3).DNG (HIGH_QUALITY) を想定. - lsc_map: LSC ゲインマップ (4, M, N). - - Returns: - 逆適用後の画像.image_raw = image / interpolate(lsc_map, image.shape). - """ -``` - -- LSC マップ (13×17) を画像解像度 (例 3264×2448) にバイリニア補間 -- 画素ごとに割り算で逆適用 -- SHADING_MODE = OFF 撮影との比較検証に使用([TECH_04](TECH_04_MiniTIASへの回答.md) フォールバック条件 C1〜C3) - -### 5. 既存 PNG 経路との並列対応(後方互換) - -既存 `load_image` は **無修正**で温存.新規 DNG 関数を並列追加する形: - -```python -# 既存(無修正) -load_image(path) -> RGB uint8 - -# 新規追加 -load_image_dng(path, ...) -> 設定可能形式 -``` - -ユーザー側で拡張子を見て呼び分ける,もしくは統一インターフェースを別途用意する(後述). - -#### 統一インターフェースの選択肢 - -```python -def load_image_any(path: str, **kwargs) -> numpy.ndarray: - """拡張子から自動判定して読み込む. - - .png → load_image (uint8 RGB) - .dng → load_image_dng (kwargs で形式指定) - """ - ext = Path(path).suffix.lower() - if ext == ".png": - return load_image(path) - elif ext == ".dng": - return load_image_dng(path, **kwargs) - else: - raise ValueError(f"未対応のフォーマット: {ext}") -``` - -これは MiniTias-Evaluation 側の判断で実装するかは任意.推奨は実装. - -### 6. 評価指標との互換 - -既存 `src/analysis/uniformity.py` 等の評価関数は **無修正**で DNG 入力に対応できるよう,DNG 読み込み I/F の出力を既存関数の入力仕様に合わせる: - -| 既存関数の入力仕様 | DNG 経路での対応 | -| --- | --- | -| RGB uint8 (H, W, 3) | `output_format="rgb_uint8"` で uint8 にスケール(10-bit → 8-bit + sRGB OETF 適用) | -| Luma uint8 (H, W) | 上記後に Rec.709 重みでスカラー化 | -| Float32 linear (H, W, 3) | `output_format="rgb_float32"` で直接出力 | - -→ 既存解析パイプラインの修正コストを最小化する設計. - -## データフォーマット詳細 (Data Format Details) - -### DNG (RAW_SENSOR) の値域 - -- 10-bit raw: 整数値 0〜1023 -- 黒レベル: 64(`SENSOR_BLACK_LEVEL_PATTERN` から取得) -- 黒レベル除去後: 0〜959 -- 飽和判定: 値 > 900 を要警告(飽和の影響) -- DNG Exif にカラーマトリクス `ColorMatrix1/2` 等が埋め込み済み(rawpy が自動で参照) - -### sRGB OETF の扱い - -- 定量モード DNG は **linear ドメイン**(TONEMAP_MODE = CONTRAST_CURVE 線形のため sRGB OETF 未適用) -- 既存通常モード PNG は sRGB OETF 適用済(端末の TONEMAP デフォルト) -- 評価時の OETF の扱いは **解析対象次第**: - - 形状指標(CoV / 勾配等): linear ドメインで算出が物理的に整合 - - 既存実機との互換比較: sRGB ドメインで算出(既存 MiniTias-Evaluation の挙動と一致) - -→ `output_format` パラメータでユーザーが選択可能にする.既定は `rgb_float32`(linear)を推奨. - -### デモザイクアルゴリズム - -`rawpy.imread().postprocess(demosaic_algorithm=...)` のパラメータ: - -| 値 | 速度 | 品質 | 用途 | -| --- | --- | --- | --- | -| `rawpy.DemosaicAlgorithm.LINEAR` | 速 | 中 | バイリニア.**既定推奨** | -| `rawpy.DemosaicAlgorithm.AHD` | 中 | 高 | 適応的.カラー画像で有利だが照明均一性評価には過剰 | -| `rawpy.DemosaicAlgorithm.DHT` | 中 | 高 | 高品質.処理重め | - -→ 既定は `LINEAR` で運用.`demosaic` 引数でユーザーが切り替え可能. - -## 推奨実装方針 (Recommended Implementation) - -### ディレクトリ構造(提案) - -MiniTias-Evaluation 既存構造に従い: - -```text -src/io/ -├── __init__.py -├── loader.py # 既存 PNG 読み込み(無修正) -├── dng_loader.py # 新規: DNG 読み込み -├── metadata_loader.py # 新規: meta.json 読み込み -└── unified_loader.py # 新規(任意): 統一 I/F load_image_any -``` - -### 追加依存パッケージ - -`pyproject.toml` の dependencies に: - -```toml -[project] -dependencies = [ - "rawpy>=0.21.0", # DNG 読み込み - # 既存依存はそのまま -] -``` - -または `requirements.txt` に: - -```text -rawpy>=0.21.0 -``` - -### 段階的実装(Phase 分割) - -| Phase | 内容 | 推定工数 | -| --- | --- | --- | -| Phase 1 | DNG 読み込み I/F (`dng_loader.py`) と単体テスト | 1 日 | -| Phase 2 | メタデータ JSON 読み込み I/F (`metadata_loader.py`) と単体テスト | 半日 | -| Phase 3 | LSC マップ参照・hot pixel マスク機能の実装 | 半日 | -| Phase 4 | 既存解析パイプラインとの統合・動作確認 | 半日 | -| Phase 5 | 既存 PNG / 新規 DNG の並列対応検証(V1〜V3) | 半日 | -| Phase 6 | ドキュメント更新(SPEC_01/SPEC_02 等) | 半日 | - -合計: 約 3.5 日.`/implement` パイプラインで Phase ごとに進める. - -## SmTIAS-LightSim 側へのコピー手順 (Copy to SmTIAS-LightSim) - -MiniTias-Evaluation での実装完了後,SmTIAS-LightSim 側 Claude セッションで以下を実施: - -1. **コピー対象ファイル**: - - `MiniTias-Evaluation/src/io/dng_loader.py` → `SmTIAS-LightSim/src/sm_tias_lightsim/evaluation/dng_loader.py` - - `MiniTias-Evaluation/src/io/metadata_loader.py` → `SmTIAS-LightSim/src/sm_tias_lightsim/evaluation/metadata.py` -2. **既存パターンに従う** (`uniformity.py` と同じ流れ) -3. **ファイル先頭にコピー元を明記**: - ```python - """DNG 読み込みモジュール. - - MiniTias-Evaluation/src/io/dng_loader.py を SmTIAS-LightSim にコピーしたもの. - オリジナルは MiniTias-Evaluation 側で管理する.バグ修正・機能追加はそちらで行い, - 本プロジェクトに反映する形を取る. - """ - ``` -4. **既存 sm-evaluate との統合**: 既存 `cli/evaluate.py` に DNG 入力経路を追加 -5. **テストを SmTIAS-LightSim 側でも実装**(MiniTias-Evaluation 側のテストをコピー+プロジェクト固有部分を追加) - -## 検証方法 (Verification) - -実装完了後,以下を実施して受け入れ判定とする: - -### V1. PNG vs DNG 整合性 - -- 同一シーン(白板)を MiniTias で **通常モード PNG** + **定量モード DNG** で連続撮影 -- 両方を MiniTias-Evaluation で評価 -- 評価指標(CoV / 勾配 / C/P 等)の差を確認 - - **完全一致は期待しない**(定量モードは ISP 介入最小化のため差が出るのが正常) - - **DNG 経路でも妥当な値が出る**ことを確認(CoV > 0, 勾配 > 0 等の基本検証) - -### V2. 既存 PNG 経路の回帰 - -- 既存通常モード PNG での評価指標が **変化しない**こと -- 既存テストが全合格すること - -### V3. メタデータ JSON 読み込み - -- `load_metadata` で `settings.exposure_time_ns` 等が正しく取得できる -- `load_lsc_map` で (4, 13, 17) の配列が返る -- `load_hot_pixel_list` で空リスト or リスト(MiniTias 側実装次第)が返る - -### V4. SmTIAS-LightSim 側へのコピー後検証 - -- コピー後の SmTIAS-LightSim 側で同じ DNG を読み込んで **同じ評価指標**が出ること -- SmTIAS-LightSim の既存テストが全合格 - -## 開放質問 (Open Questions) - -実装着手時に確認が必要な事項: - -| ID | 質問 | 確認方法 | -| --- | --- | --- | -| Q1 | rawpy のバージョン互換性は問題ないか | 環境で `pip install rawpy` → 実 DNG で動作確認 | -| Q2 | デモザイク手法の既定値は `LINEAR` で OK か | 数枚の DNG で AHD と比較して照明均一性指標がほぼ同じか確認 | -| Q3 | カラーマトリクス適用は無彩色シーンで影響軽微か | 既定 OFF(rawpy デフォルト)か ON(DNG Exif 参照)かを実機で比較 | -| Q4 | 既存 `analysis/uniformity.py` は uint8 / float32 のどちらを受けるか | コードを確認.既存仕様に合わせて `output_format` の既定を決める | -| Q5 | 統一 I/F `load_image_any` を実装するか | MiniTias-Evaluation 側判断.SmTIAS-LightSim 側では便利だが任意 | -| Q6 | LSC 逆適用機能は Phase 3 で実装するか後回しか | フォールバック条件 C1〜C3 未発動なので Phase 3 では雛形のみで OK | -| Q7 | 既存ドキュメント (SPEC_01/SPEC_02) の更新範囲 | DNG 経路追加に伴う仕様変更を反映.Phase 6 で対応 | - -## 引き継ぎ手順 (Hand-off Procedure) - -MiniTias-Evaluation 側 Claude セッションでの作業着手手順.[TECH_02 MiniTIAS 引き継ぎ手順](TECH_02_MiniTIAS引き継ぎ手順.md) と同様の構造. - -### Step A: 環境準備 - -- rawpy パッケージを追加(`pip install rawpy` または `pyproject.toml` 更新 + `uv sync` 等) -- サンプル DNG を取得(MiniTias 側で撮影済の `reference_measurement/quantitative/` から 1 枚) - -### Step B: Phase 1(DNG 読み込み)から順次実装 - -- `/implement TECH_05 Phase 1: DNG 読み込み I/F` で起動 -- coder → tester → refactorer → doc 更新の標準パイプライン -- Phase 1 完了後に動作確認 → Phase 2 へ - -### Step C: 完了の伝達方法 - -- MiniTias-Evaluation 側で `/implement` 完了後,SmTIAS-LightSim 側で確認できる形にする: - - MiniTias-Evaluation `src/io/dng_loader.py` の最終形をリポジトリ master にマージ - - rinto が SmTIAS-LightSim 側 Claude セッションを開いて「コピーしてほしい」と指示 - - SmTIAS-LightSim 側 Claude が MiniTias-Evaluation のローカルリポジトリから読み込んでコピー +| **SmTIAS** | DNG + meta.json の保存(実装済) | +| **SmTIAS-Evaluation(一次窓口)** | DNG/meta.json の読み込み I/F を正規実装.既存評価指標との並列対応 | +| **SmTIAS-LightSim(コピー側)** | SmTIAS-Evaluation の DNG 読み込み I/F をコピー(`uniformity.py` パターン継承) | ## 既存ドキュメントとの整合 (Cross-References) -- [TECH_01 定量撮影モード要求仕様](TECH_01_定量撮影モード要求仕様.md) — 撮影モード側の要求書 -- [TECH_02 MiniTIAS 引き継ぎ手順](TECH_02_MiniTIAS引き継ぎ手順.md) — 引き継ぎパターンの参考 -- [TECH_03 定量モード実装報告と SHADING_MODE 相談](TECH_03_定量モード実装報告とSHADING_MODE相談.md) — MiniTias 側実装報告(meta.json スキーマ等) -- [TECH_04 MiniTIAS への回答](TECH_04_MiniTIASへの回答.md) — SHADING_MODE 判断とフェーズ A/B 分割 -- [TEST_01 キャリブレーション検証履歴](../06_TEST/TEST_01_キャリブレーション検証履歴.md) — 経緯 -- [TEST_03 輝度マップ形状追跡](../06_TEST/TEST_03_輝度マップ形状追跡.md) — 形のずれ議論 -- [SPEC_04 評価仕様](../04_SPEC/SPEC_04_評価仕様.md) — sm-evaluate パイプライン +- [TECH_01 定量撮影モード要求仕様](TECH_01_定量撮影モード要求仕様.md) — 撮影モード側の要求書(SmTIAS-LightSim 側) +- [TECH_03 定量モード実装報告と SHADING_MODE 相談](TECH_03_定量モード実装報告とSHADING_MODE相談.md) — SmTIAS 側実装報告(meta.json スキーマ等) +- [TECH_04 SmTIAS への回答](TECH_04_SmTIASへの回答.md) — SHADING_MODE 判断とフェーズ A/B 分割 +- [SPEC_01 アーキテクチャ設計](../04_SPEC/SPEC_01_アーキテクチャ設計.md) / [SPEC_02 照明均一性評価アルゴリズム](../04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md) — 実装の正規仕様 - [Android Camera2 API DNG](https://developer.android.com/reference/android/hardware/camera2/DngCreator) - [rawpy ドキュメント](https://letmaik.github.io/rawpy/api/) diff --git "a/docs/05_TECH/TECH_02_SmTIAS-LightSim\343\201\270\343\201\256\345\274\225\343\201\215\347\266\231\343\201\216.md" "b/docs/05_TECH/TECH_02_SmTIAS-LightSim\343\201\270\343\201\256\345\274\225\343\201\215\347\266\231\343\201\216.md" deleted file mode 100644 index 3db0f13..0000000 --- "a/docs/05_TECH/TECH_02_SmTIAS-LightSim\343\201\270\343\201\256\345\274\225\343\201\215\347\266\231\343\201\216.md" +++ /dev/null @@ -1,72 +0,0 @@ -# SmTIAS-LightSim への引き継ぎ (Hand-off to SmTIAS-LightSim) - -本ドキュメントは,MiniTias-Evaluation で実装した **DNG 読み込み I/F** を SmTIAS-LightSim 側へコピー・統合するための引き継ぎ書である.要求仕様は [TECH_01](TECH_01_DNG対応要求仕様.md)(SmTIAS-LightSim 側での名称は TECH_05). - -## 1. 実装スコープ (What is done / not done) - -TECH_01 の Phase 分割のうち,本引き継ぎ時点で完了しているのは **Phase 1(DNG 読み込み I/F)のみ**. - -| TECH_01 Phase | 内容 | 状態 | -| --- | --- | --- | -| Phase 1 | DNG 読み込み I/F (`dng_loader.py`) | ✅ **完了**(コピー対象) | -| Phase 2 | メタデータ JSON 読み込み I/F (`metadata_loader.py`) | ❌ 未実装 | -| Phase 3 | LSC マップ参照・hot pixel マスク | △ LSC 順適用はプロトタイプのみ(`run_uniformity_dng.py` 内) | -| Phase 4〜6 | 既存パイプライン統合 / PNG vs DNG / SPEC 更新 | ❌ 未着手 | - -## 2. コピー対象ファイル (Files to copy) - -| コピー元(MiniTias-Evaluation) | コピー先(SmTIAS-LightSim) | 区分 | -| --- | --- | --- | -| `src/io/dng_loader.py` | `src/sm_tias_lightsim/evaluation/dng_loader.py` | ★ 正規コピー対象 | -| `scripts/run_uniformity_dng.py` | (参考)`cli/` 等に統合 | LSC 順適用・向き合わせ・評価フローの参考 | -| `src/analysis/spatial.py` の `calc_radial_min_max_ratio` | 既存 spatial/uniformity 相当へ**マージ** | 動径 min/max 比指標(追加指標) | - -- **ファイル先頭にコピー元を明記**(TECH_01 指定): - ```python - """DNG 読み込みモジュール. - - MiniTias-Evaluation/src/io/dng_loader.py を SmTIAS-LightSim にコピーしたもの. - オリジナルは MiniTias-Evaluation 側で管理する.バグ修正・機能追加はそちらで行い, - 本プロジェクトに反映する形を取る. - """ - ``` -- `spatial.py` は SmTIAS-LightSim 側の既存均一性コードと競合しうるため,**丸ごとコピーせず `calc_radial_min_max_ratio` のみマージ**すること. - -## 3. ★重要な設計判断・注意点(コピーだけでは伝わらない) - -以下は実装の前提であり,**TECH_01(要求仕様)の記述と食い違う点を含む**.必ず反映すること. - -1. **DNG は LSC 未補正 — `apply_lsc_inverse` は誤り**: RAW_SENSOR DNG はレンズシェーディング**未補正**(補正は DNG OpcodeList2 の GainMap / meta.json lscMap として付随するが rawpy は適用しない).TECH_01 の `apply_lsc_inverse`(「DNG=補正済み」前提で逆適用)は実態と逆.照明ムラを測るには **lscMap を順方向に適用**してレンズビネットを除去する(`image × interpolate(lscMap)`). -2. **linear 系出力は `output_color=raw`**: カラーマトリクスを通すとチャンネル混合で per-channel 比例が崩れる.`rgb_float32`/`luma_float32` は `output_color=rawpy.ColorSpace.raw` で読む(`rgb_uint8` のみ sRGB).これにより入射光量とセンサ信号の比例(放射輝度リニアリティ)を担保. -3. **向き**: DNG はセンサ native が **landscape**.PNG/preview と同じ portrait にするには `rot90(-1)`(左右反転なし.フロントカメラ id=1 の preview はミラーされるが RAW はミラーされない). -4. **luma の Rec.709 は固定加重プロキシ**: `output_color=raw` では Rec.709 luma は厳密な CIE 輝度ではなく,既存 PNG 経路互換の固定加重. -5. **PNG は厳密に linear 化できない**: トーンカーブ・8bit・透過関数不明のため.DNG 採用の根拠([REPORT_02](../07_REPORT/REPORT_02_PNG_DNG評価比較報告書.md)). - -## 4. 引き渡す評価結果(暗闇=MiniTIAS のみ) - -代表結果は **外乱光に汚染されていない「MiniTIAS の照明のみ」の撮影**(`MiniTIAS_QM_20260601_124500`,室内灯 OFF)を採用する.外乱光の有無で評価はほぼ変わらない([REPORT_03](../07_REPORT/REPORT_03_外乱光影響評価報告書.md))が,純粋な装置照明の均一性を代表させるため暗闇条件を選定. - -評価条件: linear(output_color=raw)/ portrait / LSC 補正あり / 白板 ROI. - -| 指標 | 値 | -| --- | --- | -| CoV | 0.0695 | -| max/min 比 | 1.617 | -| 中心/周辺比 | 1.0911 | -| 勾配 (%) | 8.35 | -| 動径 min/max 比 | 0.8600 | - -成果物(図・CSV)はハンドオフ束 `handoff_smtias_lightsim/results_dark_124500/` に同梱. - -## 5. コピー後の検証 (Verification — TECH_01 V4) - -- コピー後の SmTIAS-LightSim 側で**同じ DNG**(`MiniTIAS_QM_20260601_124500.dng`)を読み込み,**上表と同じ評価指標**が出ること. -- SmTIAS-LightSim の既存テストが全合格すること. -- 依存追加: `rawpy>=0.21.0`. - -## 6. 参照 (References) - -- [TECH_01 DNG 対応要求仕様](TECH_01_DNG対応要求仕様.md) -- [REPORT_02 PNG/DNG 評価比較報告書](../07_REPORT/REPORT_02_PNG_DNG評価比較報告書.md) -- [REPORT_03 外乱光影響評価報告書](../07_REPORT/REPORT_03_外乱光影響評価報告書.md) -- 設計判断・経緯の詳細: `docs/PROGRESS.md` diff --git "a/docs/06_TEST/TEST_01_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" "b/docs/06_TEST/TEST_01_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" new file mode 100644 index 0000000..345aef7 --- /dev/null +++ "b/docs/06_TEST/TEST_01_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" @@ -0,0 +1,215 @@ +# 照明均一性評価報告書 (Illumination Uniformity Evaluation Report) + +## 評価概要 (Evaluation Overview) + +- 装置: SmTIAS(小型口腔内画像取得装置) +- 撮影日: 2026-04-08 +- 評価対象: 白板画像 12 枚 +- 手法: [SPEC_02_照明均一性評価アルゴリズム](../04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md) +- 集計統計出力: バッチ解析時に `output/results/summary_statistics.csv` として自動出力される +- 輝度変換: ITU-R BT.709(Y = 0.2126R + 0.7152G + 0.0722B)[1] +- 均一性指標: CoV,標準偏差,最大/最小比 +- 空間分析: 3 ゾーン(center d<0.33 / middle / periphery d≥0.66) +- ROI: x=539, y=1015, w=1400, h=1409 + +## 結果の要約 (Results Summary) + +### 均一性指標 (Uniformity Metrics) + +| 画像名 | 平均輝度 | 標準偏差 | CoV | 最大/最小比 | 最大値 | 最小値 | +| --- | --- | --- | --- | --- | --- | --- | +| SmTIAS_20260408_140317 | 145.97 | 6.18 | 0.0423 | 1.387 | 161.65 | 116.52 | +| SmTIAS_20260408_140342 | 145.31 | 6.18 | 0.0425 | 1.375 | 162.65 | 118.30 | +| SmTIAS_20260408_140408 | 144.58 | 6.11 | 0.0423 | 1.493 | 161.65 | 108.30 | +| SmTIAS_20260408_141805 | 145.06 | 6.17 | 0.0426 | 1.417 | 160.65 | 113.37 | +| SmTIAS_20260408_141900 | 144.98 | 6.21 | 0.0428 | 1.422 | 165.65 | 116.52 | +| SmTIAS_20260408_141945 | 144.85 | 6.21 | 0.0429 | 1.440 | 167.58 | 116.37 | +| SmTIAS_20260408_144434 | 146.71 | 6.26 | 0.0426 | 1.392 | 164.65 | 118.30 | +| SmTIAS_20260408_145011 | 146.59 | 6.22 | 0.0424 | 1.371 | 163.08 | 118.94 | +| SmTIAS_20260408_145320 | 146.66 | 6.27 | 0.0427 | 1.374 | 162.65 | 118.37 | +| SmTIAS_20260408_150524 | 146.74 | 6.24 | 0.0425 | 1.412 | 164.23 | 116.30 | +| SmTIAS_20260408_155530 | 145.00 | 6.19 | 0.0427 | 1.358 | 160.65 | 118.30 | +| SmTIAS_20260408_155541 | 144.95 | 6.20 | 0.0428 | 1.381 | 160.65 | 116.30 | + +| 統計量 | 平均輝度 | 標準偏差 | CoV | 最大/最小比 | 最大値 | 最小値 | +| --- | --- | --- | --- | --- | --- | --- | +| 平均 (Mean) | 145.62 | 6.20 | 0.0426 | 1.402 | 162.98 | 116.32 | +| 画像間 SD | 0.81 | 0.04 | 0.0002 | 0.036 | — | — | +| 画像間 CV | 0.0056 | 0.0067 | 0.0044 | 0.0257 | — | — | +| 最小 (Min) | 144.58 | 6.11 | 0.0423 | 1.358 | 160.65 | 108.30 | +| 最大 (Max) | 146.74 | 6.27 | 0.0429 | 1.493 | 167.58 | 118.94 | +| 範囲 (Range) | 2.16 | 0.16 | 0.0006 | 0.135 | 6.93 | 10.64 | + +### 空間分析指標 (Spatial Analysis Metrics) + +| 画像名 | 中心平均 | 中間平均 | 周辺平均 | 中心/周辺比 | 勾配量 (%) | +| --- | --- | --- | --- | --- | --- | +| SmTIAS_20260408_140317 | 151.02 | 146.97 | 141.61 | 1.0664 | 6.23 | +| SmTIAS_20260408_140342 | 150.33 | 146.31 | 140.97 | 1.0664 | 6.23 | +| SmTIAS_20260408_140408 | 149.50 | 145.57 | 140.29 | 1.0656 | 6.16 | +| SmTIAS_20260408_141805 | 149.92 | 146.07 | 140.81 | 1.0647 | 6.08 | +| SmTIAS_20260408_141900 | 149.89 | 146.00 | 140.67 | 1.0655 | 6.15 | +| SmTIAS_20260408_141945 | 149.79 | 145.85 | 140.54 | 1.0658 | 6.17 | +| SmTIAS_20260408_144434 | 151.68 | 147.70 | 142.43 | 1.0650 | 6.10 | +| SmTIAS_20260408_145011 | 151.46 | 147.58 | 142.34 | 1.0641 | 6.02 | +| SmTIAS_20260408_145320 | 151.63 | 147.65 | 142.36 | 1.0651 | 6.11 | +| SmTIAS_20260408_150524 | 151.68 | 147.73 | 142.46 | 1.0647 | 6.08 | +| SmTIAS_20260408_155530 | 149.62 | 146.02 | 140.84 | 1.0623 | 5.86 | +| SmTIAS_20260408_155541 | 149.60 | 145.96 | 140.78 | 1.0627 | 5.90 | + +| 統計量 | 中心平均 | 中間平均 | 周辺平均 | 中心/周辺比 | 勾配量 (%) | +| --- | --- | --- | --- | --- | --- | +| 平均 (Mean) | 150.51 | 146.62 | 141.34 | 1.0649 | 6.09 | +| 画像間 SD | 0.87 | 0.81 | 0.80 | 0.0012 | 0.11 | +| 画像間 CV | 0.0058 | 0.0055 | 0.0057 | 0.0012 | 0.0183 | +| 最小 (Min) | 149.50 | 145.57 | 140.29 | 1.0623 | 5.86 | +| 最大 (Max) | 151.68 | 147.73 | 142.46 | 1.0664 | 6.23 | +| 範囲 (Range) | 2.18 | 2.16 | 2.17 | 0.0041 | 0.37 | + +### 代表画像の可視化 (Representative Visualization) + +代表画像として SmTIAS_20260408_144434(中央値に近い値)を使用する. + +[図: 輝度マップ(SmTIAS_20260408_144434)] + +[図: 輝度ヒストグラム(SmTIAS_20260408_144434)] + +[図: 放射状輝度プロファイル(SmTIAS_20260408_144434)] + +[図: ゾーンオーバーレイマップ(SmTIAS_20260408_144434)] + +### 再現性評価: SSIM (Reproducibility: SSIM) + +12 枚の輝度マップに対するペアワイズ SSIM(全 66 ペア)の集計結果を以下に示す. + +#### 位置合わせなし SSIM (Raw SSIM) + +| 統計量 | 値 | +| --- | --- | +| 平均 SSIM | 0.9112 | +| 最小 SSIM | 0.9092 | +| 最大 SSIM | 0.9134 | +| 標準偏差 | 0.0012 | +| ペア数 | 66 | + +#### 位置合わせ後 SSIM (Registered SSIM) + +| 統計量 | 値 | +| --- | --- | +| 平均 SSIM(位置合わせ後) | 0.9114 | +| 最小 SSIM(位置合わせ後) | 0.9095 | +| 最大 SSIM(位置合わせ後) | 0.9136 | +| 標準偏差(位置合わせ後) | 0.0012 | +| ペア数 | 66 | + +#### シフト量統計 (Shift Magnitude Statistics) + +| 統計量 | 値 | +| --- | --- | +| 平均シフト量(ピクセル) | 0.041 | +| 最大シフト量(ピクセル) | 0.082 | + + +## 考察 (Discussion) + +### 均一性の水準評価 (Uniformity Level Assessment) + +- CoV が全画像で 0.0423–0.0429 の範囲にあり,0.05 を下回る +- 照明工学の実務では CoV < 0.10 が許容範囲,CoV < 0.05 が良好とされる経験的基準が広く用いられている [3] +- 本評価結果は「良好」の水準に該当する +- 標準偏差も 6.11–6.27 と安定しており,平均輝度に対して約 4% のばらつきに収まる + +### 最大/最小比の解釈 (Max/Min Ratio Interpretation) + +- 全画像の最大/最小比は 1.358–1.493 の範囲 +- SmTIAS_20260408_140408 のみ 1.493 と他の画像より突出(最小値 108.30 が特異的に低い) +- この画像を除くと範囲は 1.358–1.440 に収まり,局所的な異常の可能性がある +- 最小値の低下は ROI 端部の影やセンサーノイズが原因と考えられる + +### 中心-周辺勾配の特徴 (Center-Periphery Gradient Characteristics) + +- 全画像で中心 > 中間 > 周辺の単調減少パターンが確認された +- 中心/周辺比は 1.0623–1.0664 で,中心が周辺より約 6.5% 明るい +- 勾配量は 5.86–6.23% で,中心から周辺にかけて一貫した輝度減衰がある +- これは典型的なビネッティング(周辺光量低下)パターンであり,以下が原因と推定される: + - LED 光源の配置が中心方向に集中している(主因) + - 筐体内壁の反射特性の不均一 + - レンズ系のコサイン 4 乗則による周辺光量低下の可能性 [5](ただし Android カメラの ISP によるレンズシェーディング補正が適用されるため,寄与は限定的と考えられる) + +### 再現性の評価 (Reproducibility Assessment) + +画像間変動係数(CV = 標準偏差 / 平均,母集団標準偏差 ddof=0)に基づいた定量的な再現性評価を以下に示す [7][8]. + +- **平均輝度の CV が 0.0056(約 0.6%)** → 撮影ごとの輝度レベルが極めて安定しており,高い再現性を示す +- **CoV の CV が 0.0044(約 0.4%)** → 均一性評価指標自体の再現性も高く,評価結果が撮影条件に依存しにくいことを示す +- **中心/周辺比の CV が 0.0012(約 0.1%)** → 空間的な照明パターンが撮影間で非常に安定しており,ビネッティング特性が固定されていることを確認 +- **勾配量の CV が 0.0183(約 1.8%)** → 他の指標と比べてやや大きいが,範囲は 0.37%(5.86–6.23%)に収まり,実用上は十分に安定している +- 以上より,12 枚の撮影間で照明条件が極めて安定しており,SmTIAS の照明系は高い再現性を持つと結論する + +#### SSIM による構造的再現性 (Structural Reproducibility via SSIM) + +- 位置合わせなしのペアワイズ SSIM の平均が 0.9112(標準偏差 0.0012)であり,全 66 ペアで SSIM > 0.90 を達成している +- SPEC_02 の解釈基準(≥ 0.95 で「高い再現性」)には達していない +- 位相相関法による位置合わせ後の SSIM は平均 0.9114 であり,位置合わせなし(0.9112)からの改善は +0.0002 とごくわずかである +- 平均シフト量が 0.041 ピクセル(最大 0.082 ピクセル)と極めて小さく,スマートフォンの取り付け位置ずれは SSIM 低下の主因ではないことが判明した +- SSIM が 0.91 にとどまる主因は,撮影ごとのセンサーノイズ・LED の微小な出力変動等,空間的に非系統的な要因と考えられる +- 一方で,取り付け位置のずれがサブピクセル精度で安定していることは,SmTIAS の固定機構の精度が高いことを示す有意義な知見である +- CV が要約統計量のばらつきのみを評価するのに対し,SSIM は画像全体の輝度・コントラスト・構造パターンの一致度を評価する +- 両指標を併用することで,マクロレベル(CV)とピクセルレベル(SSIM)の再現性を相補的に確認できる +- SSIM の標準偏差が 0.0012 と極めて小さく,全 66 ペアで SSIM が 0.909–0.914 の狭い範囲に収まっている点は,画像間の差異が系統的ではなくランダムノイズに支配されていることを裏付ける + +#### SSIM 解釈基準の妥当性 (Validity of SSIM Interpretation Thresholds) + +- SPEC_02 の解釈基準(≥ 0.95 で「高い再現性」)は画像処理分野の一般的な目安であるが,実カメラで繰り返し撮影した画像に対しては過度に厳しい可能性がある +- SSIM はピクセル単位の局所構造を比較するため,撮影ごとに不可避に変化するセンサーノイズの影響を強く受ける [9] +- CV が 0.4–0.6% と極めて低い値を示しているにもかかわらず SSIM が 0.91 にとどまるのは,SSIM がノイズ成分を「構造の差異」として捉えることに起因する +- 本評価のように実カメラ撮影のセンサーノイズが支配的な条件下では,SSIM ≥ 0.90 かつ CV < 1% を「高い再現性」と解釈することが妥当と考えられる + +## 改善提案 (Improvement Proposals) + +### 拡散構造の最適化 (Diffuser Optimization) + +- 現状の約 6% の中心-周辺勾配を低減するため,拡散板の追加または変更を提案 +- 拡散板の厚みや材質を調整し,周辺方向への光の散乱を増加させる +- 目標: 勾配量を 3% 以下に低減 + +### 光源配置の調整 (Light Source Arrangement) + +- LED の配置角度を外向きに調整し,中心への光の集中を緩和する +- 追加の周辺 LED を配置し,周辺光量を直接的に補う + +### 反射板の追加 (Reflector Addition) + +- 筐体内壁の反射率を向上させる反射板またはコーティングを追加 +- 内壁での反射光により周辺光量を補完する + +### ソフトウェア補正 (Software Correction) + +- 暫定対策として,フラットフィールド補正を提案 [6] +- 白板画像の平均輝度マップを正規化し,撮影画像を除算することで照明ムラを補正 +- ハードウェア改善と併用可能 + +## 今後の課題 (Future Work) + +- TIAS(従来型口腔内画像取得装置)との照明均一性の比較評価(開発ステップ 3) +- 長期間(日単位・週単位)にわたる経時安定性の評価 +- 動作温度の変化に対する照明特性の依存性評価 +- 異なるホワイトバランス・露出設定が均一性指標に与える影響の評価 +- 撮影距離(白板と開口部の距離)の変化に対する均一性の評価 + +## 参考文献 (References) + +- [1] ITU, "BT.709-6: Parameter values for the HDTV standards for production and international programme exchange," ITU-R, 2015. +- [2] C. Poynton, *Digital Video and HD: Algorithms and Interfaces*, 2nd ed. Morgan Kaufmann, 2012. +- [3] EN 12464-1:2021, "Light and lighting — Lighting of work places — Part 1: Indoor work places," CEN, 2021. +- [4] ANSI/IES LM-79-24, "Approved Method: Optical and Electrical Measurements of Solid-State Lighting Products," IES, 2024. +- [5] W. J. Smith, *Modern Optical Engineering*, 4th ed. McGraw-Hill, 2007. +- [6] R. C. Gonzalez and R. E. Woods, *Digital Image Processing*, 4th ed. Pearson, 2018. +- [7] ISO 5725-2:2025, "Accuracy (trueness and precision) of measurement methods and results — Part 2," ISO, 2025. +- [8] G. F. Reed, F. Lynn, and B. D. Meade, "Use of Coefficient of Variation in Assessing Variability of Quantitative Assays," *Clin. Diagn. Lab. Immunol.*, vol. 9, no. 6, pp. 1235–1239, 2002. +- [9] Z. Wang, A. C. Bovik, H. R. Sheikh, and E. P. Simoncelli, "Image quality assessment: from error visibility to structural similarity," *IEEE Trans. Image Process.*, vol. 13, no. 4, pp. 600–612, 2004. DOI: 10.1109/TIP.2003.819861 +- [10] H. Foroosh, J. B. Zerubia, and M. Berthod, "Extension of phase correlation to subpixel registration," *IEEE Trans. Image Process.*, vol. 11, no. 3, pp. 188–200, 2002. DOI: 10.1109/83.988953 + +### 関連ドキュメント (Related Documents) + +- [SPEC_02_照明均一性評価アルゴリズム](../04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md) diff --git "a/docs/06_TEST/TEST_02_PNG_DNG\350\251\225\344\276\241\346\257\224\350\274\203\345\240\261\345\221\212\346\233\270.md" "b/docs/06_TEST/TEST_02_PNG_DNG\350\251\225\344\276\241\346\257\224\350\274\203\345\240\261\345\221\212\346\233\270.md" new file mode 100644 index 0000000..843adb5 --- /dev/null +++ "b/docs/06_TEST/TEST_02_PNG_DNG\350\251\225\344\276\241\346\257\224\350\274\203\345\240\261\345\221\212\346\233\270.md" @@ -0,0 +1,101 @@ +# PNG / DNG 照明均一性評価 比較報告書 (PNG vs DNG Uniformity Evaluation Comparison) + +## 概要 (Overview) + +SmTIAS 撮影アプリの定量撮影モードの出力フォーマットが **PNG から DNG (RAW_SENSOR) に変更**された([TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md))ことに伴い,照明均一性評価の結果が PNG 経路と DNG 経路でどのように変わるかを比較した. + +結論を先に述べると,**PNG→DNG は単なるファイル形式の変更ではなく,「何を測っているか」が変わる**.PNG は sRGB エンコード済み・端末 ISP でレンズシェーディング補正済みの「処理後」データであり,DNG は linear・補正前の「物理生信号」である.このため評価指標の数値は大きく変化する. + +## 前提・対象データ (Scope and Samples) + +| 項目 | 内容 | +| --- | --- | +| PNG サンプル | `data/smtias/whiteboard/SmTIAS_20260408_140317.png`(2026-04-08 撮影) | +| DNG サンプル | `data/smtias/quantitative/SmTIAS_QM_20260601_102753.dng`(2026-06-01 撮影,SH-02M 定量モード) | +| 評価領域 (ROI) | `config/roi_config.json` の `smtias.whiteboard`(白板中央,全条件で同一) | +| 向き | portrait に統一(DNG は landscape native のため `rot90(-1)` で PNG と整合) | +| 評価指標 | CoV / max-min比 / 中心-周辺比 / 勾配 / 動径 min/max 比(20点輪帯平均の最小/最大) | + +> **重要な前提(本報告書の限界)**: PNG(4月)と DNG(6月)は**別セッションの撮影でフレーミングが異なる**.したがって本報告書は,TECH_01 の検証 V1 が要求する「同一シーンを PNG/DNG で連続撮影した厳密な対照比較」**ではない**.**評価手法・ドメインの違いが指標に与える影響**を既存サンプルで定量的に示すものである.厳密な同一シーン比較は今後の課題(後述). + +## 評価条件(ドメイン)の違い (Domain Differences) + +PNG 経路と DNG 経路は,評価に入る前の信号の性質が根本的に異なる. + +| 観点 | PNG(従来) | DNG(新規・定量モード) | +| --- | --- | --- | +| 信号ドメイン | **sRGB**(OETF/ガンマ適用済み) | **linear**(TONEMAP=CONTRAST_CURVE_LINEAR,OETF 未適用) | +| レンズシェーディング | **端末 ISP で補正済み**(SHADING_MODE=HIGH_QUALITY が YUV/JPEG に適用) | **未補正**(RAW_SENSOR は ISP 前.補正は DNG OpcodeList2 の GainMap / meta.json の lscMap として付随するのみ) | +| ビット深度 | 8bit | 10bit | +| 入射光量との関係 | ガンマで非線形 | **比例(放射輝度リニア)** | + +→ つまり PNG をそのまま評価した「従来値」と,DNG を物理的に正しく評価した値は,**同じものを測っていない**.公平に比較するにはドメインを揃える必要がある. + +## 結果比較 (Results) + +同一 ROI で算出した均一性指標(数値が大きいほどムラが大きい.動径 min/max のみ 1.0 に近いほど均一): + +| 条件 | CoV | max/min | 中心/周辺比 | 勾配(%) | 動径 min/max | +| --- | --- | --- | --- | --- | --- | +| **PNG sRGB(従来評価)** | 0.0423 | 1.387 | 1.066 | 6.23 | 0.8964 | +| PNG 擬似linear化(inverse-sRGB 仮定)※ | 0.0901 | 2.031 | 1.148 | 12.88 | 0.7902 | +| DNG linear(LSC 補正なし) | 0.1477 | 2.287 | 1.456 | 31.33 | 0.5304 | +| **DNG linear(LSC 補正あり)** | 0.0689 | 1.641 | 1.095 | 8.69 | 0.8533 | + +> ※ **「PNG 擬似linear化」は近似であり,真のシーン linear(入射光量比例)ではない**.詳細は考察「4. PNG は厳密には linear 化できない」を参照. + +## 考察:なぜ結果が変わるのか (Analysis) + +数値変化は主に3つの要因に分解できる. + +### 1. ドメイン(sRGB ガンマ)の影響 — 最大の要因 + +同じ PNG データでも,**sRGB のまま(CoV 0.0423)か擬似linear化(CoV 0.0901)かで CoV が約2倍**変わる(撮影は同一,ドメインのみ変更).sRGB の OETF は暗部を持ち上げ明部を圧縮するため,**ムラを実際より穏やかに見せる**.従来の PNG 評価値(CoV 0.042)は,このガンマ圧縮により**楽観的に小さく出ていた**. + +なお CoV・max/min・動径比は**スケール不変**なので,0〜255 と 0〜1 のスケール差では変わらない.変化はあくまでドメイン(ガンマ)によるもの. + +### 2. レンズシェーディング補正の有無 + +DNG raw は未補正のため,**レンズビネット(端ほど暗い)が指標に混入**する.LSC 補正なしの DNG は中心/周辺比 1.456・勾配 31.3%・動径 min/max 0.530 と大きなムラを示すが,これは**照明ムラではなくレンズ特性**を多く含む.lscMap を順方向適用してレンズビネットを除去すると,中心/周辺比 1.456→1.095,勾配 31.3%→8.7%,動径 min/max 0.530→0.853 と**劇的に平坦化**する(残りが照明そのもののムラ). + +### 3. 撮影セッションの違い + +PNG(4月)と DNG(6月)は別撮影で,照明配置・フレーミング・距離が異なる.この差も数値に含まれるため,厳密な大小比較はできない(前提参照). + +### 4. PNG は厳密には linear 化できない(重要な限界) + +上表の「PNG 擬似linear化」は inverse-sRGB OETF を掛けたものだが,これは**真のシーン linear(入射光量比例)を復元しない**.理由: + +- **透過関数が不明**: 本サンプル PNG は ICC プロファイル・gAMA・EXIF を一切持たず,**sRGB である保証すらない**(sRGB と仮定して逆変換しているだけ). +- **トーンカーブが残る(最大の壁)**: 端末 PNG は `linear生信号 →[WB・カラー行列・レンズ補正・トーンカーブ・NR・シャープ化]→ 表示RGB → sRGB符号化 → 8bit` を経る.inverse-sRGB が戻せるのは最後の符号化だけで,得られるのは「**表示参照 linear**」.間のトーンカーブ(端末固有・非公開のコントラスト曲線)は焼き込まれたままで,入射光量には比例しない. +- **不可逆な損失**: 8bit 量子化(暗部の段差が逆ガンマで増幅)・クリップ(白飛び/黒潰れ)は復元不能. + +→ つまり **PNG は確実には linear 化できない**.これこそが定量 DNG モード採用の理由([TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md) 背景「実機 PNG 自体が定量データとして揺らぐ」)であり,DNG (RAW_SENSOR, TONEMAP=CONTRAST_CURVE_LINEAR) は最初から真のシーン linear を出すことでこの問題を回避する. + +### 公平な比較(ドメインを揃える,ただし近似) + +両者を linear ドメインに寄せると比較の目安は得られる(PNG は上記の通り**擬似linear=近似**である点に注意): + +- **PNG 擬似linear化: CoV 0.0901**(近似) vs **DNG linear+LSC: CoV 0.0689**(真の linear) +- → 目安としては **DNG(LSC補正)の方が均一**.従来の「PNG 0.042 < DNG 0.069 で DNG が悪い」という見え方は,sRGB ガンマが PNG のムラを圧縮していた錯覚. +- ただし PNG 側が近似である以上この比較は厳密ではなく,**確実な比較には同一シーンを DNG で撮影することが必要**(後述 残課題). + +## 結論と推奨 (Conclusion) + +1. **PNG→DNG の移行で評価値が変わるのは正常**.主因は形式ではなく,sRGB→linear のドメイン変化とレンズシェーディング補正の有無. +2. **物理的に正しい照明均一性評価は DNG linear + LSC 補正経路**で行うべき.入射光量に比例した信号で,レンズ特性を分離して照明ムラのみを測れる. +3. 従来の **PNG sRGB 評価値は,ガンマ圧縮によりムラを過小評価**していた点に注意.過去の PNG 値と DNG 値を近づけるには PNG を擬似linear化(inverse-sRGB)できるが,**PNG は真のシーン linear を復元できない**(トーンカーブ・8bit・透過関数不明のため).したがって**厳密な比較には DNG での再撮影が必要**. +4. ノイズに弱い画素単位 max/min 比に代わり,**動径 min/max 比(20点輪帯平均)**をロバストな減衰指標として併用する. + +## 残課題 (Open Issues) + +- **厳密な V1 比較**: 同一シーン(白板)を同一フレーミングで PNG(通常モード)+ DNG(定量モード)連続撮影し,同条件で比較する([TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md) 検証 V1). +- **ROI のフレーミング差対応**: PNG/DNG で白板の写る位置・スケールが異なるため,キャプチャごとの ROI 調整または同一フレーミング撮影が必要(TECH_01 Phase 5). +- **LSC 順適用機能の正式実装**: 現状は検証スクリプトでの順適用.本流の評価フローへ正式統合する(TECH_01 Phase 3〜4). + +## 参照 (References) + +- [TECH_01 DNG 対応要求仕様](../05_TECH/TECH_01_DNG対応要求仕様.md) +- [TEST_01 照明均一性評価報告書](TEST_01_照明均一性評価報告書.md) +- 評価実装: `src/io/dng_loader.py`, `src/analysis/spatial.py`, `scripts/run_uniformity_dng.py` +- 設計判断・経緯の詳細: `docs/PROGRESS.md` diff --git "a/docs/06_TEST/TEST_03_\345\244\226\344\271\261\345\205\211\345\275\261\351\237\277\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" "b/docs/06_TEST/TEST_03_\345\244\226\344\271\261\345\205\211\345\275\261\351\237\277\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" new file mode 100644 index 0000000..1eeafbc --- /dev/null +++ "b/docs/06_TEST/TEST_03_\345\244\226\344\271\261\345\205\211\345\275\261\351\237\277\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" @@ -0,0 +1,77 @@ +# 外乱光影響評価報告書 (Ambient Light Influence Evaluation) + +## 概要 (Overview) + +SmTIAS の照明均一性測定が,**外部の室内照明(外乱光)にどれだけ影響を受けるか**を評価した.SmTIAS 装置自前の照明のみで撮影した DNG と,それに室内灯(外乱光)を加えて撮影した DNG を,同一指標で比較する. + +結論を先に述べると,**外乱光の有無で評価結果はほぼ変わらない(指標差 1% 未満)**.SmTIAS 自前照明が支配的で,測定は室内照明環境に対してロバストである. + +## 前提・対象データ (Scope and Samples) + +| 項目 | 内容 | +| --- | --- | +| 条件 A(SmTIAS のみ) | `SmTIAS_QM_20260601_124500`(室内灯 OFF,2026-06-01 12:45 撮影) | +| 条件 B(SmTIAS + 外光) | `SmTIAS_QM_20260601_102753`(室内灯 ON,2026-06-01 10:27 撮影) | +| 機種 / モード | SH-02M / 定量モード(RAW_SENSOR,露出・ISO 等は両者同一固定) | +| 評価領域 (ROI) | `config/roi_config.json` の `smtias.whiteboard`(白板中央,両者同一) | +| 評価条件 | linear(output_color=raw)/ portrait / LSC 補正あり・なし両方 | + +> **重要な前提(本評価の限界)**: 条件 A・B は**別セッションの撮影**(時刻が異なり,三脚固定でないため微小な位置ずれの可能性).したがって「外乱光の純粋な寄与」を厳密に分離したものではなく,**外乱光の影響がセッション間ばらつきと比べて支配的かどうか**を判定する位置づけである.厳密な切り分けは今後の課題(後述). + +## 結果 (Results) + +### raw センサ統計(黒レベル=64,全画素) + +| 条件 | raw mean | min | max | std | mean R/G/B | +| --- | --- | --- | --- | --- | --- | +| A: SmTIAS のみ | 441.2 | 89 | 983 | 161.0 | 355.9 / 537.4 / 333.6 | +| B: SmTIAS + 外光 | 431.4 | 90 | 949 | 155.7 | 349.4 / 525.4 / 325.1 | + +### 均一性指標(白板 ROI,数値が大きいほどムラ大.動径 min/max のみ 1.0 で均一) + +| 条件 | ROI mean | CoV | max/min | 中心/周辺比 | 勾配(%) | 動径 min/max | +| --- | --- | --- | --- | --- | --- | --- | +| **A: SmTIAS のみ**(LSC 補正) | 207.82 | 0.0695 | 1.617 | 1.0911 | 8.35 | 0.8600 | +| **B: SmTIAS + 外光**(LSC 補正) | 208.77 | 0.0689 | 1.641 | 1.0952 | 8.69 | 0.8533 | +| A: SmTIAS のみ(LSC なし) | 170.58 | 0.1464 | — | 1.4511 | 31.09 | 0.5343 | +| B: SmTIAS + 外光(LSC なし) | 171.39 | 0.1477 | — | 1.4563 | 31.33 | 0.5304 | +| **差(A−B,LSC 補正)** | −0.95 | +0.0006 | −0.024 | −0.004 | −0.34 | +0.007 | + +輝度マップ・ヒストグラムも両条件でほぼ重なり,空間パターン・分布形状に有意な違いは見られない. + +## 考察 (Analysis) + +### 1. 均一性指標はほぼ同一 — 外乱光の影響は評価に表れない + +LSC 補正後の CoV は 0.0695(A)vs 0.0689(B),勾配は 8.35%(A)vs 8.69%(B),動径 min/max は 0.860(A)vs 0.853(B)と,**すべて 1% 未満の差**.外乱光を加えても照明均一性の評価結果は実質的に変わらない.SmTIAS 自前照明が白板を支配的に照らしており,室内照明の寄与は相対的に微小と考えられる. + +### 2. 「外光を足したのに暗い」という矛盾 — 外乱光の寄与はばらつき以下 + +外乱光は信号を**加える**しかできないため,物理的には B(SmTIAS+外光)≧ A(SmTIAS のみ)の明るさになるはずである.しかし実測では **A の方が明るい**(raw mean 441.2 vs 431.4,R/G/B 各チャンネルとも A が上). + +これは,外乱光の寄与が **セッション間のばらつき(≈2〜3%:撮影時刻 10:27 vs 12:45 の差,微小な位置ずれ,照明のドリフト等)に埋もれている**ことを意味する.すなわち **外乱光の影響 < セッション間ばらつき**であり,外乱光の純寄与はこのデータからは分離できないほど小さい. + +### 3. 評価上の含意 + +照明均一性評価(CoV・勾配等)に対して,**通常の室内照明環境は実害を与えない**.これは測定器としての SmTIAS にとって望ましい性質(外乱光に対するロバスト性)である. + +## 結論 (Conclusion) + +1. **外乱光(室内照明)の有無で,照明均一性の評価指標はほぼ変わらない(差 1% 未満)**.SmTIAS 自前照明が支配的. +2. 明るさの差(≈2〜3%)はむしろ「SmTIAS のみ」の方が明るく,物理的に逆向き.→ **外乱光の寄与はセッション間ばらつき以下**で,測定には埋もれて見えない. +3. したがって **SmTIAS の照明均一性測定は,通常の室内照明環境に対してロバスト**と判断できる. + +## 限界と厳密な切り分け方法 (Limitations & Controlled Test) + +本評価は別セッション撮影のため,外乱光の純寄与を 2〜3% のばらつきから分離できていない.外乱光の影響「だけ」を正確に測るには,以下の対照実験が必要: + +- **位置・露出を完全固定**(三脚等)し,**室内灯のみを ON/OFF** した連続2枚を撮影する(A: SmTIAS のみ / B: SmTIAS + 外光). +- 差分画像 **B − A** が外乱光の純寄与.これにより「一様なオフセットか/窓・照明方向の勾配か」「何 DN 乗るか」まで定量できる. +- この方式ならセッション間ばらつきを排除でき,外乱光の空間分布・絶対量を分離評価できる. + +## 参照 (References) + +- [TEST_02 PNG/DNG 評価比較報告書](TEST_02_PNG_DNG評価比較報告書.md) +- [TECH_01 DNG 対応要求仕様](../05_TECH/TECH_01_DNG対応要求仕様.md) +- 評価実装: `scripts/run_uniformity_dng.py`(linear + LSC 補正経路) +- 対象データ: `data/smtias/quantitative/`(gitignore 対象) diff --git "a/docs/07_REPORT/REPORT_01_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" "b/docs/07_REPORT/REPORT_01_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" deleted file mode 100644 index a2d34f9..0000000 --- "a/docs/07_REPORT/REPORT_01_\347\205\247\346\230\216\345\235\207\344\270\200\346\200\247\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" +++ /dev/null @@ -1,215 +0,0 @@ -# 照明均一性評価報告書 (Illumination Uniformity Evaluation Report) - -## 評価概要 (Evaluation Overview) - -- 装置: MiniTIAS(小型口腔内画像取得装置) -- 撮影日: 2026-04-08 -- 評価対象: 白板画像 12 枚 -- 手法: [SPEC_02_照明均一性評価アルゴリズム](../04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md) -- 集計統計出力: バッチ解析時に `output/results/summary_statistics.csv` として自動出力される -- 輝度変換: ITU-R BT.709(Y = 0.2126R + 0.7152G + 0.0722B)[1] -- 均一性指標: CoV,標準偏差,最大/最小比 -- 空間分析: 3 ゾーン(center d<0.33 / middle / periphery d≥0.66) -- ROI: x=539, y=1015, w=1400, h=1409 - -## 結果の要約 (Results Summary) - -### 均一性指標 (Uniformity Metrics) - -| 画像名 | 平均輝度 | 標準偏差 | CoV | 最大/最小比 | 最大値 | 最小値 | -| --- | --- | --- | --- | --- | --- | --- | -| MiniTIAS_20260408_140317 | 145.97 | 6.18 | 0.0423 | 1.387 | 161.65 | 116.52 | -| MiniTIAS_20260408_140342 | 145.31 | 6.18 | 0.0425 | 1.375 | 162.65 | 118.30 | -| MiniTIAS_20260408_140408 | 144.58 | 6.11 | 0.0423 | 1.493 | 161.65 | 108.30 | -| MiniTIAS_20260408_141805 | 145.06 | 6.17 | 0.0426 | 1.417 | 160.65 | 113.37 | -| MiniTIAS_20260408_141900 | 144.98 | 6.21 | 0.0428 | 1.422 | 165.65 | 116.52 | -| MiniTIAS_20260408_141945 | 144.85 | 6.21 | 0.0429 | 1.440 | 167.58 | 116.37 | -| MiniTIAS_20260408_144434 | 146.71 | 6.26 | 0.0426 | 1.392 | 164.65 | 118.30 | -| MiniTIAS_20260408_145011 | 146.59 | 6.22 | 0.0424 | 1.371 | 163.08 | 118.94 | -| MiniTIAS_20260408_145320 | 146.66 | 6.27 | 0.0427 | 1.374 | 162.65 | 118.37 | -| MiniTIAS_20260408_150524 | 146.74 | 6.24 | 0.0425 | 1.412 | 164.23 | 116.30 | -| MiniTIAS_20260408_155530 | 145.00 | 6.19 | 0.0427 | 1.358 | 160.65 | 118.30 | -| MiniTIAS_20260408_155541 | 144.95 | 6.20 | 0.0428 | 1.381 | 160.65 | 116.30 | - -| 統計量 | 平均輝度 | 標準偏差 | CoV | 最大/最小比 | 最大値 | 最小値 | -| --- | --- | --- | --- | --- | --- | --- | -| 平均 (Mean) | 145.62 | 6.20 | 0.0426 | 1.402 | 162.98 | 116.32 | -| 画像間 SD | 0.81 | 0.04 | 0.0002 | 0.036 | — | — | -| 画像間 CV | 0.0056 | 0.0067 | 0.0044 | 0.0257 | — | — | -| 最小 (Min) | 144.58 | 6.11 | 0.0423 | 1.358 | 160.65 | 108.30 | -| 最大 (Max) | 146.74 | 6.27 | 0.0429 | 1.493 | 167.58 | 118.94 | -| 範囲 (Range) | 2.16 | 0.16 | 0.0006 | 0.135 | 6.93 | 10.64 | - -### 空間分析指標 (Spatial Analysis Metrics) - -| 画像名 | 中心平均 | 中間平均 | 周辺平均 | 中心/周辺比 | 勾配量 (%) | -| --- | --- | --- | --- | --- | --- | -| MiniTIAS_20260408_140317 | 151.02 | 146.97 | 141.61 | 1.0664 | 6.23 | -| MiniTIAS_20260408_140342 | 150.33 | 146.31 | 140.97 | 1.0664 | 6.23 | -| MiniTIAS_20260408_140408 | 149.50 | 145.57 | 140.29 | 1.0656 | 6.16 | -| MiniTIAS_20260408_141805 | 149.92 | 146.07 | 140.81 | 1.0647 | 6.08 | -| MiniTIAS_20260408_141900 | 149.89 | 146.00 | 140.67 | 1.0655 | 6.15 | -| MiniTIAS_20260408_141945 | 149.79 | 145.85 | 140.54 | 1.0658 | 6.17 | -| MiniTIAS_20260408_144434 | 151.68 | 147.70 | 142.43 | 1.0650 | 6.10 | -| MiniTIAS_20260408_145011 | 151.46 | 147.58 | 142.34 | 1.0641 | 6.02 | -| MiniTIAS_20260408_145320 | 151.63 | 147.65 | 142.36 | 1.0651 | 6.11 | -| MiniTIAS_20260408_150524 | 151.68 | 147.73 | 142.46 | 1.0647 | 6.08 | -| MiniTIAS_20260408_155530 | 149.62 | 146.02 | 140.84 | 1.0623 | 5.86 | -| MiniTIAS_20260408_155541 | 149.60 | 145.96 | 140.78 | 1.0627 | 5.90 | - -| 統計量 | 中心平均 | 中間平均 | 周辺平均 | 中心/周辺比 | 勾配量 (%) | -| --- | --- | --- | --- | --- | --- | -| 平均 (Mean) | 150.51 | 146.62 | 141.34 | 1.0649 | 6.09 | -| 画像間 SD | 0.87 | 0.81 | 0.80 | 0.0012 | 0.11 | -| 画像間 CV | 0.0058 | 0.0055 | 0.0057 | 0.0012 | 0.0183 | -| 最小 (Min) | 149.50 | 145.57 | 140.29 | 1.0623 | 5.86 | -| 最大 (Max) | 151.68 | 147.73 | 142.46 | 1.0664 | 6.23 | -| 範囲 (Range) | 2.18 | 2.16 | 2.17 | 0.0041 | 0.37 | - -### 代表画像の可視化 (Representative Visualization) - -代表画像として MiniTIAS_20260408_144434(中央値に近い値)を使用する. - -[図: 輝度マップ(MiniTIAS_20260408_144434)] - -[図: 輝度ヒストグラム(MiniTIAS_20260408_144434)] - -[図: 放射状輝度プロファイル(MiniTIAS_20260408_144434)] - -[図: ゾーンオーバーレイマップ(MiniTIAS_20260408_144434)] - -### 再現性評価: SSIM (Reproducibility: SSIM) - -12 枚の輝度マップに対するペアワイズ SSIM(全 66 ペア)の集計結果を以下に示す. - -#### 位置合わせなし SSIM (Raw SSIM) - -| 統計量 | 値 | -| --- | --- | -| 平均 SSIM | 0.9112 | -| 最小 SSIM | 0.9092 | -| 最大 SSIM | 0.9134 | -| 標準偏差 | 0.0012 | -| ペア数 | 66 | - -#### 位置合わせ後 SSIM (Registered SSIM) - -| 統計量 | 値 | -| --- | --- | -| 平均 SSIM(位置合わせ後) | 0.9114 | -| 最小 SSIM(位置合わせ後) | 0.9095 | -| 最大 SSIM(位置合わせ後) | 0.9136 | -| 標準偏差(位置合わせ後) | 0.0012 | -| ペア数 | 66 | - -#### シフト量統計 (Shift Magnitude Statistics) - -| 統計量 | 値 | -| --- | --- | -| 平均シフト量(ピクセル) | 0.041 | -| 最大シフト量(ピクセル) | 0.082 | - - -## 考察 (Discussion) - -### 均一性の水準評価 (Uniformity Level Assessment) - -- CoV が全画像で 0.0423–0.0429 の範囲にあり,0.05 を下回る -- 照明工学の実務では CoV < 0.10 が許容範囲,CoV < 0.05 が良好とされる経験的基準が広く用いられている [3] -- 本評価結果は「良好」の水準に該当する -- 標準偏差も 6.11–6.27 と安定しており,平均輝度に対して約 4% のばらつきに収まる - -### 最大/最小比の解釈 (Max/Min Ratio Interpretation) - -- 全画像の最大/最小比は 1.358–1.493 の範囲 -- MiniTIAS_20260408_140408 のみ 1.493 と他の画像より突出(最小値 108.30 が特異的に低い) -- この画像を除くと範囲は 1.358–1.440 に収まり,局所的な異常の可能性がある -- 最小値の低下は ROI 端部の影やセンサーノイズが原因と考えられる - -### 中心-周辺勾配の特徴 (Center-Periphery Gradient Characteristics) - -- 全画像で中心 > 中間 > 周辺の単調減少パターンが確認された -- 中心/周辺比は 1.0623–1.0664 で,中心が周辺より約 6.5% 明るい -- 勾配量は 5.86–6.23% で,中心から周辺にかけて一貫した輝度減衰がある -- これは典型的なビネッティング(周辺光量低下)パターンであり,以下が原因と推定される: - - LED 光源の配置が中心方向に集中している(主因) - - 筐体内壁の反射特性の不均一 - - レンズ系のコサイン 4 乗則による周辺光量低下の可能性 [5](ただし Android カメラの ISP によるレンズシェーディング補正が適用されるため,寄与は限定的と考えられる) - -### 再現性の評価 (Reproducibility Assessment) - -画像間変動係数(CV = 標準偏差 / 平均,母集団標準偏差 ddof=0)に基づいた定量的な再現性評価を以下に示す [7][8]. - -- **平均輝度の CV が 0.0056(約 0.6%)** → 撮影ごとの輝度レベルが極めて安定しており,高い再現性を示す -- **CoV の CV が 0.0044(約 0.4%)** → 均一性評価指標自体の再現性も高く,評価結果が撮影条件に依存しにくいことを示す -- **中心/周辺比の CV が 0.0012(約 0.1%)** → 空間的な照明パターンが撮影間で非常に安定しており,ビネッティング特性が固定されていることを確認 -- **勾配量の CV が 0.0183(約 1.8%)** → 他の指標と比べてやや大きいが,範囲は 0.37%(5.86–6.23%)に収まり,実用上は十分に安定している -- 以上より,12 枚の撮影間で照明条件が極めて安定しており,MiniTIAS の照明系は高い再現性を持つと結論する - -#### SSIM による構造的再現性 (Structural Reproducibility via SSIM) - -- 位置合わせなしのペアワイズ SSIM の平均が 0.9112(標準偏差 0.0012)であり,全 66 ペアで SSIM > 0.90 を達成している -- SPEC_02 の解釈基準(≥ 0.95 で「高い再現性」)には達していない -- 位相相関法による位置合わせ後の SSIM は平均 0.9114 であり,位置合わせなし(0.9112)からの改善は +0.0002 とごくわずかである -- 平均シフト量が 0.041 ピクセル(最大 0.082 ピクセル)と極めて小さく,スマートフォンの取り付け位置ずれは SSIM 低下の主因ではないことが判明した -- SSIM が 0.91 にとどまる主因は,撮影ごとのセンサーノイズ・LED の微小な出力変動等,空間的に非系統的な要因と考えられる -- 一方で,取り付け位置のずれがサブピクセル精度で安定していることは,MiniTIAS の固定機構の精度が高いことを示す有意義な知見である -- CV が要約統計量のばらつきのみを評価するのに対し,SSIM は画像全体の輝度・コントラスト・構造パターンの一致度を評価する -- 両指標を併用することで,マクロレベル(CV)とピクセルレベル(SSIM)の再現性を相補的に確認できる -- SSIM の標準偏差が 0.0012 と極めて小さく,全 66 ペアで SSIM が 0.909–0.914 の狭い範囲に収まっている点は,画像間の差異が系統的ではなくランダムノイズに支配されていることを裏付ける - -#### SSIM 解釈基準の妥当性 (Validity of SSIM Interpretation Thresholds) - -- SPEC_02 の解釈基準(≥ 0.95 で「高い再現性」)は画像処理分野の一般的な目安であるが,実カメラで繰り返し撮影した画像に対しては過度に厳しい可能性がある -- SSIM はピクセル単位の局所構造を比較するため,撮影ごとに不可避に変化するセンサーノイズの影響を強く受ける [9] -- CV が 0.4–0.6% と極めて低い値を示しているにもかかわらず SSIM が 0.91 にとどまるのは,SSIM がノイズ成分を「構造の差異」として捉えることに起因する -- 本評価のように実カメラ撮影のセンサーノイズが支配的な条件下では,SSIM ≥ 0.90 かつ CV < 1% を「高い再現性」と解釈することが妥当と考えられる - -## 改善提案 (Improvement Proposals) - -### 拡散構造の最適化 (Diffuser Optimization) - -- 現状の約 6% の中心-周辺勾配を低減するため,拡散板の追加または変更を提案 -- 拡散板の厚みや材質を調整し,周辺方向への光の散乱を増加させる -- 目標: 勾配量を 3% 以下に低減 - -### 光源配置の調整 (Light Source Arrangement) - -- LED の配置角度を外向きに調整し,中心への光の集中を緩和する -- 追加の周辺 LED を配置し,周辺光量を直接的に補う - -### 反射板の追加 (Reflector Addition) - -- 筐体内壁の反射率を向上させる反射板またはコーティングを追加 -- 内壁での反射光により周辺光量を補完する - -### ソフトウェア補正 (Software Correction) - -- 暫定対策として,フラットフィールド補正を提案 [6] -- 白板画像の平均輝度マップを正規化し,撮影画像を除算することで照明ムラを補正 -- ハードウェア改善と併用可能 - -## 今後の課題 (Future Work) - -- TIAS(従来型口腔内画像取得装置)との照明均一性の比較評価(開発ステップ 3) -- 長期間(日単位・週単位)にわたる経時安定性の評価 -- 動作温度の変化に対する照明特性の依存性評価 -- 異なるホワイトバランス・露出設定が均一性指標に与える影響の評価 -- 撮影距離(白板と開口部の距離)の変化に対する均一性の評価 - -## 参考文献 (References) - -- [1] ITU, "BT.709-6: Parameter values for the HDTV standards for production and international programme exchange," ITU-R, 2015. -- [2] C. Poynton, *Digital Video and HD: Algorithms and Interfaces*, 2nd ed. Morgan Kaufmann, 2012. -- [3] EN 12464-1:2021, "Light and lighting — Lighting of work places — Part 1: Indoor work places," CEN, 2021. -- [4] ANSI/IES LM-79-24, "Approved Method: Optical and Electrical Measurements of Solid-State Lighting Products," IES, 2024. -- [5] W. J. Smith, *Modern Optical Engineering*, 4th ed. McGraw-Hill, 2007. -- [6] R. C. Gonzalez and R. E. Woods, *Digital Image Processing*, 4th ed. Pearson, 2018. -- [7] ISO 5725-2:2025, "Accuracy (trueness and precision) of measurement methods and results — Part 2," ISO, 2025. -- [8] G. F. Reed, F. Lynn, and B. D. Meade, "Use of Coefficient of Variation in Assessing Variability of Quantitative Assays," *Clin. Diagn. Lab. Immunol.*, vol. 9, no. 6, pp. 1235–1239, 2002. -- [9] Z. Wang, A. C. Bovik, H. R. Sheikh, and E. P. Simoncelli, "Image quality assessment: from error visibility to structural similarity," *IEEE Trans. Image Process.*, vol. 13, no. 4, pp. 600–612, 2004. DOI: 10.1109/TIP.2003.819861 -- [10] H. Foroosh, J. B. Zerubia, and M. Berthod, "Extension of phase correlation to subpixel registration," *IEEE Trans. Image Process.*, vol. 11, no. 3, pp. 188–200, 2002. DOI: 10.1109/83.988953 - -### 関連ドキュメント (Related Documents) - -- [SPEC_02_照明均一性評価アルゴリズム](../04_SPEC/SPEC_02_照明均一性評価アルゴリズム.md) diff --git "a/docs/07_REPORT/REPORT_02_PNG_DNG\350\251\225\344\276\241\346\257\224\350\274\203\345\240\261\345\221\212\346\233\270.md" "b/docs/07_REPORT/REPORT_02_PNG_DNG\350\251\225\344\276\241\346\257\224\350\274\203\345\240\261\345\221\212\346\233\270.md" deleted file mode 100644 index db04287..0000000 --- "a/docs/07_REPORT/REPORT_02_PNG_DNG\350\251\225\344\276\241\346\257\224\350\274\203\345\240\261\345\221\212\346\233\270.md" +++ /dev/null @@ -1,101 +0,0 @@ -# PNG / DNG 照明均一性評価 比較報告書 (PNG vs DNG Uniformity Evaluation Comparison) - -## 概要 (Overview) - -MiniTIAS 撮影アプリの定量撮影モードの出力フォーマットが **PNG から DNG (RAW_SENSOR) に変更**された([TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md))ことに伴い,照明均一性評価の結果が PNG 経路と DNG 経路でどのように変わるかを比較した. - -結論を先に述べると,**PNG→DNG は単なるファイル形式の変更ではなく,「何を測っているか」が変わる**.PNG は sRGB エンコード済み・端末 ISP でレンズシェーディング補正済みの「処理後」データであり,DNG は linear・補正前の「物理生信号」である.このため評価指標の数値は大きく変化する. - -## 前提・対象データ (Scope and Samples) - -| 項目 | 内容 | -| --- | --- | -| PNG サンプル | `data/minitias/whiteboard/MiniTIAS_20260408_140317.png`(2026-04-08 撮影) | -| DNG サンプル | `data/minitias/quantitative/MiniTIAS_QM_20260601_102753.dng`(2026-06-01 撮影,SH-02M 定量モード) | -| 評価領域 (ROI) | `config/roi_config.json` の `minitias.whiteboard`(白板中央,全条件で同一) | -| 向き | portrait に統一(DNG は landscape native のため `rot90(-1)` で PNG と整合) | -| 評価指標 | CoV / max-min比 / 中心-周辺比 / 勾配 / 動径 min/max 比(20点輪帯平均の最小/最大) | - -> **重要な前提(本報告書の限界)**: PNG(4月)と DNG(6月)は**別セッションの撮影でフレーミングが異なる**.したがって本報告書は,TECH_01 の検証 V1 が要求する「同一シーンを PNG/DNG で連続撮影した厳密な対照比較」**ではない**.**評価手法・ドメインの違いが指標に与える影響**を既存サンプルで定量的に示すものである.厳密な同一シーン比較は今後の課題(後述). - -## 評価条件(ドメイン)の違い (Domain Differences) - -PNG 経路と DNG 経路は,評価に入る前の信号の性質が根本的に異なる. - -| 観点 | PNG(従来) | DNG(新規・定量モード) | -| --- | --- | --- | -| 信号ドメイン | **sRGB**(OETF/ガンマ適用済み) | **linear**(TONEMAP=CONTRAST_CURVE_LINEAR,OETF 未適用) | -| レンズシェーディング | **端末 ISP で補正済み**(SHADING_MODE=HIGH_QUALITY が YUV/JPEG に適用) | **未補正**(RAW_SENSOR は ISP 前.補正は DNG OpcodeList2 の GainMap / meta.json の lscMap として付随するのみ) | -| ビット深度 | 8bit | 10bit | -| 入射光量との関係 | ガンマで非線形 | **比例(放射輝度リニア)** | - -→ つまり PNG をそのまま評価した「従来値」と,DNG を物理的に正しく評価した値は,**同じものを測っていない**.公平に比較するにはドメインを揃える必要がある. - -## 結果比較 (Results) - -同一 ROI で算出した均一性指標(数値が大きいほどムラが大きい.動径 min/max のみ 1.0 に近いほど均一): - -| 条件 | CoV | max/min | 中心/周辺比 | 勾配(%) | 動径 min/max | -| --- | --- | --- | --- | --- | --- | -| **PNG sRGB(従来評価)** | 0.0423 | 1.387 | 1.066 | 6.23 | 0.8964 | -| PNG 擬似linear化(inverse-sRGB 仮定)※ | 0.0901 | 2.031 | 1.148 | 12.88 | 0.7902 | -| DNG linear(LSC 補正なし) | 0.1477 | 2.287 | 1.456 | 31.33 | 0.5304 | -| **DNG linear(LSC 補正あり)** | 0.0689 | 1.641 | 1.095 | 8.69 | 0.8533 | - -> ※ **「PNG 擬似linear化」は近似であり,真のシーン linear(入射光量比例)ではない**.詳細は考察「4. PNG は厳密には linear 化できない」を参照. - -## 考察:なぜ結果が変わるのか (Analysis) - -数値変化は主に3つの要因に分解できる. - -### 1. ドメイン(sRGB ガンマ)の影響 — 最大の要因 - -同じ PNG データでも,**sRGB のまま(CoV 0.0423)か擬似linear化(CoV 0.0901)かで CoV が約2倍**変わる(撮影は同一,ドメインのみ変更).sRGB の OETF は暗部を持ち上げ明部を圧縮するため,**ムラを実際より穏やかに見せる**.従来の PNG 評価値(CoV 0.042)は,このガンマ圧縮により**楽観的に小さく出ていた**. - -なお CoV・max/min・動径比は**スケール不変**なので,0〜255 と 0〜1 のスケール差では変わらない.変化はあくまでドメイン(ガンマ)によるもの. - -### 2. レンズシェーディング補正の有無 - -DNG raw は未補正のため,**レンズビネット(端ほど暗い)が指標に混入**する.LSC 補正なしの DNG は中心/周辺比 1.456・勾配 31.3%・動径 min/max 0.530 と大きなムラを示すが,これは**照明ムラではなくレンズ特性**を多く含む.lscMap を順方向適用してレンズビネットを除去すると,中心/周辺比 1.456→1.095,勾配 31.3%→8.7%,動径 min/max 0.530→0.853 と**劇的に平坦化**する(残りが照明そのもののムラ). - -### 3. 撮影セッションの違い - -PNG(4月)と DNG(6月)は別撮影で,照明配置・フレーミング・距離が異なる.この差も数値に含まれるため,厳密な大小比較はできない(前提参照). - -### 4. PNG は厳密には linear 化できない(重要な限界) - -上表の「PNG 擬似linear化」は inverse-sRGB OETF を掛けたものだが,これは**真のシーン linear(入射光量比例)を復元しない**.理由: - -- **透過関数が不明**: 本サンプル PNG は ICC プロファイル・gAMA・EXIF を一切持たず,**sRGB である保証すらない**(sRGB と仮定して逆変換しているだけ). -- **トーンカーブが残る(最大の壁)**: 端末 PNG は `linear生信号 →[WB・カラー行列・レンズ補正・トーンカーブ・NR・シャープ化]→ 表示RGB → sRGB符号化 → 8bit` を経る.inverse-sRGB が戻せるのは最後の符号化だけで,得られるのは「**表示参照 linear**」.間のトーンカーブ(端末固有・非公開のコントラスト曲線)は焼き込まれたままで,入射光量には比例しない. -- **不可逆な損失**: 8bit 量子化(暗部の段差が逆ガンマで増幅)・クリップ(白飛び/黒潰れ)は復元不能. - -→ つまり **PNG は確実には linear 化できない**.これこそが定量 DNG モード採用の理由([TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md) 背景「実機 PNG 自体が定量データとして揺らぐ」)であり,DNG (RAW_SENSOR, TONEMAP=CONTRAST_CURVE_LINEAR) は最初から真のシーン linear を出すことでこの問題を回避する. - -### 公平な比較(ドメインを揃える,ただし近似) - -両者を linear ドメインに寄せると比較の目安は得られる(PNG は上記の通り**擬似linear=近似**である点に注意): - -- **PNG 擬似linear化: CoV 0.0901**(近似) vs **DNG linear+LSC: CoV 0.0689**(真の linear) -- → 目安としては **DNG(LSC補正)の方が均一**.従来の「PNG 0.042 < DNG 0.069 で DNG が悪い」という見え方は,sRGB ガンマが PNG のムラを圧縮していた錯覚. -- ただし PNG 側が近似である以上この比較は厳密ではなく,**確実な比較には同一シーンを DNG で撮影することが必要**(後述 残課題). - -## 結論と推奨 (Conclusion) - -1. **PNG→DNG の移行で評価値が変わるのは正常**.主因は形式ではなく,sRGB→linear のドメイン変化とレンズシェーディング補正の有無. -2. **物理的に正しい照明均一性評価は DNG linear + LSC 補正経路**で行うべき.入射光量に比例した信号で,レンズ特性を分離して照明ムラのみを測れる. -3. 従来の **PNG sRGB 評価値は,ガンマ圧縮によりムラを過小評価**していた点に注意.過去の PNG 値と DNG 値を近づけるには PNG を擬似linear化(inverse-sRGB)できるが,**PNG は真のシーン linear を復元できない**(トーンカーブ・8bit・透過関数不明のため).したがって**厳密な比較には DNG での再撮影が必要**. -4. ノイズに弱い画素単位 max/min 比に代わり,**動径 min/max 比(20点輪帯平均)**をロバストな減衰指標として併用する. - -## 残課題 (Open Issues) - -- **厳密な V1 比較**: 同一シーン(白板)を同一フレーミングで PNG(通常モード)+ DNG(定量モード)連続撮影し,同条件で比較する([TECH_01](../05_TECH/TECH_01_DNG対応要求仕様.md) 検証 V1). -- **ROI のフレーミング差対応**: PNG/DNG で白板の写る位置・スケールが異なるため,キャプチャごとの ROI 調整または同一フレーミング撮影が必要(TECH_01 Phase 5). -- **LSC 順適用機能の正式実装**: 現状は検証スクリプトでの順適用.本流の評価フローへ正式統合する(TECH_01 Phase 3〜4). - -## 参照 (References) - -- [TECH_01 DNG 対応要求仕様](../05_TECH/TECH_01_DNG対応要求仕様.md) -- [REPORT_01 照明均一性評価報告書](REPORT_01_照明均一性評価報告書.md) -- 評価実装: `src/io/dng_loader.py`, `src/analysis/spatial.py`, `scripts/run_uniformity_dng.py` -- 設計判断・経緯の詳細: `docs/PROGRESS.md` diff --git "a/docs/07_REPORT/REPORT_03_\345\244\226\344\271\261\345\205\211\345\275\261\351\237\277\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" "b/docs/07_REPORT/REPORT_03_\345\244\226\344\271\261\345\205\211\345\275\261\351\237\277\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" deleted file mode 100644 index 694ebca..0000000 --- "a/docs/07_REPORT/REPORT_03_\345\244\226\344\271\261\345\205\211\345\275\261\351\237\277\350\251\225\344\276\241\345\240\261\345\221\212\346\233\270.md" +++ /dev/null @@ -1,77 +0,0 @@ -# 外乱光影響評価報告書 (Ambient Light Influence Evaluation) - -## 概要 (Overview) - -MiniTIAS の照明均一性測定が,**外部の室内照明(外乱光)にどれだけ影響を受けるか**を評価した.SmTIAS 装置自前の照明のみで撮影した DNG と,それに室内灯(外乱光)を加えて撮影した DNG を,同一指標で比較する. - -結論を先に述べると,**外乱光の有無で評価結果はほぼ変わらない(指標差 1% 未満)**.SmTIAS 自前照明が支配的で,測定は室内照明環境に対してロバストである. - -## 前提・対象データ (Scope and Samples) - -| 項目 | 内容 | -| --- | --- | -| 条件 A(MiniTIAS のみ) | `MiniTIAS_QM_20260601_124500`(室内灯 OFF,2026-06-01 12:45 撮影) | -| 条件 B(MiniTIAS + 外光) | `MiniTIAS_QM_20260601_102753`(室内灯 ON,2026-06-01 10:27 撮影) | -| 機種 / モード | SH-02M / 定量モード(RAW_SENSOR,露出・ISO 等は両者同一固定) | -| 評価領域 (ROI) | `config/roi_config.json` の `minitias.whiteboard`(白板中央,両者同一) | -| 評価条件 | linear(output_color=raw)/ portrait / LSC 補正あり・なし両方 | - -> **重要な前提(本評価の限界)**: 条件 A・B は**別セッションの撮影**(時刻が異なり,三脚固定でないため微小な位置ずれの可能性).したがって「外乱光の純粋な寄与」を厳密に分離したものではなく,**外乱光の影響がセッション間ばらつきと比べて支配的かどうか**を判定する位置づけである.厳密な切り分けは今後の課題(後述). - -## 結果 (Results) - -### raw センサ統計(黒レベル=64,全画素) - -| 条件 | raw mean | min | max | std | mean R/G/B | -| --- | --- | --- | --- | --- | --- | -| A: MiniTIAS のみ | 441.2 | 89 | 983 | 161.0 | 355.9 / 537.4 / 333.6 | -| B: MiniTIAS + 外光 | 431.4 | 90 | 949 | 155.7 | 349.4 / 525.4 / 325.1 | - -### 均一性指標(白板 ROI,数値が大きいほどムラ大.動径 min/max のみ 1.0 で均一) - -| 条件 | ROI mean | CoV | max/min | 中心/周辺比 | 勾配(%) | 動径 min/max | -| --- | --- | --- | --- | --- | --- | --- | -| **A: MiniTIAS のみ**(LSC 補正) | 207.82 | 0.0695 | 1.617 | 1.0911 | 8.35 | 0.8600 | -| **B: MiniTIAS + 外光**(LSC 補正) | 208.77 | 0.0689 | 1.641 | 1.0952 | 8.69 | 0.8533 | -| A: MiniTIAS のみ(LSC なし) | 170.58 | 0.1464 | — | 1.4511 | 31.09 | 0.5343 | -| B: MiniTIAS + 外光(LSC なし) | 171.39 | 0.1477 | — | 1.4563 | 31.33 | 0.5304 | -| **差(A−B,LSC 補正)** | −0.95 | +0.0006 | −0.024 | −0.004 | −0.34 | +0.007 | - -輝度マップ・ヒストグラムも両条件でほぼ重なり,空間パターン・分布形状に有意な違いは見られない. - -## 考察 (Analysis) - -### 1. 均一性指標はほぼ同一 — 外乱光の影響は評価に表れない - -LSC 補正後の CoV は 0.0695(A)vs 0.0689(B),勾配は 8.35%(A)vs 8.69%(B),動径 min/max は 0.860(A)vs 0.853(B)と,**すべて 1% 未満の差**.外乱光を加えても照明均一性の評価結果は実質的に変わらない.SmTIAS 自前照明が白板を支配的に照らしており,室内照明の寄与は相対的に微小と考えられる. - -### 2. 「外光を足したのに暗い」という矛盾 — 外乱光の寄与はばらつき以下 - -外乱光は信号を**加える**しかできないため,物理的には B(MiniTIAS+外光)≧ A(MiniTIAS のみ)の明るさになるはずである.しかし実測では **A の方が明るい**(raw mean 441.2 vs 431.4,R/G/B 各チャンネルとも A が上). - -これは,外乱光の寄与が **セッション間のばらつき(≈2〜3%:撮影時刻 10:27 vs 12:45 の差,微小な位置ずれ,照明のドリフト等)に埋もれている**ことを意味する.すなわち **外乱光の影響 < セッション間ばらつき**であり,外乱光の純寄与はこのデータからは分離できないほど小さい. - -### 3. 評価上の含意 - -照明均一性評価(CoV・勾配等)に対して,**通常の室内照明環境は実害を与えない**.これは測定器としての SmTIAS にとって望ましい性質(外乱光に対するロバスト性)である. - -## 結論 (Conclusion) - -1. **外乱光(室内照明)の有無で,照明均一性の評価指標はほぼ変わらない(差 1% 未満)**.SmTIAS 自前照明が支配的. -2. 明るさの差(≈2〜3%)はむしろ「MiniTIAS のみ」の方が明るく,物理的に逆向き.→ **外乱光の寄与はセッション間ばらつき以下**で,測定には埋もれて見えない. -3. したがって **MiniTIAS の照明均一性測定は,通常の室内照明環境に対してロバスト**と判断できる. - -## 限界と厳密な切り分け方法 (Limitations & Controlled Test) - -本評価は別セッション撮影のため,外乱光の純寄与を 2〜3% のばらつきから分離できていない.外乱光の影響「だけ」を正確に測るには,以下の対照実験が必要: - -- **位置・露出を完全固定**(三脚等)し,**室内灯のみを ON/OFF** した連続2枚を撮影する(A: MiniTIAS のみ / B: MiniTIAS + 外光). -- 差分画像 **B − A** が外乱光の純寄与.これにより「一様なオフセットか/窓・照明方向の勾配か」「何 DN 乗るか」まで定量できる. -- この方式ならセッション間ばらつきを排除でき,外乱光の空間分布・絶対量を分離評価できる. - -## 参照 (References) - -- [REPORT_02 PNG/DNG 評価比較報告書](REPORT_02_PNG_DNG評価比較報告書.md) -- [TECH_01 DNG 対応要求仕様](../05_TECH/TECH_01_DNG対応要求仕様.md) -- 評価実装: `scripts/run_uniformity_dng.py`(linear + LSC 補正経路) -- 対象データ: `data/minitias/quantitative/`(gitignore 対象) diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 8c81cb2..9953d3a 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -10,13 +10,19 @@ 「進捗記録の運用ルール(CLAUDE.md / PROGRESS.md)」を参照 --> -- [x] **ステップ 2: MiniTIAS 照明均一性評価 完了**: 照明均一性の評価指標算出・可視化を実装し,REPORT_01(照明均一性評価報告書)を作成済み.次はステップ 3. +- [x] **ステップ 2: SmTIAS 照明均一性評価 完了**: 照明均一性の評価指標算出・可視化を実装し,REPORT_01(照明均一性評価報告書)を作成済み.次はステップ 3. - 注: 本エントリはテンプレート同期で PROGRESS.md を新設した際の移記ベースライン(詳細経緯は `git log` を参照).以降の進捗は本ファイル末尾に追記する. -- [x] **TECH_05 Phase 1: DNG 読み込み I/F + 動径 min/max 比指標**: MiniTias 定量モードの出力が PNG→DNG(RAW_SENSOR) に変わったため DNG 読み込み I/F(`src/io/dng_loader.py`)を新設.あわせて照明均一性に動径 min/max 比指標を追加.SmTIAS-LightSim へのコピー前提で再利用性重視. +- [x] **TECH_05 Phase 1: DNG 読み込み I/F + 動径 min/max 比指標**: SmTIAS 定量モードの出力が PNG→DNG(RAW_SENSOR) に変わったため DNG 読み込み I/F(`src/io/dng_loader.py`)を新設.あわせて照明均一性に動径 min/max 比指標を追加.SmTIAS-LightSim へのコピー前提で再利用性重視. - **設計判断(線形性)**: linear 系出力は `output_color=raw`(カラーマトリクス非適用)とし,入射光量とセンサ信号の比例(放射輝度リニアリティ)を担保.カラーマトリクス経路はチャンネル混合で per-channel 比例が崩れるため不採用.luma は既存 PNG 経路互換のため Rec.709 維持(raw 経路では厳密な CIE 輝度でなく固定加重プロキシ). - **重要な発見(仕様との食い違い)**: RAW_SENSOR DNG はレンズシェーディング**未補正**(補正は DNG の OpcodeList2 GainMap・meta.json lscMap として付随するが rawpy は適用しない/lscMap ゲインは 1〜5).TECH_05 の `apply_lsc_inverse` は「DNG=補正済み」前提だが実際は未補正.照明ムラを測るには lscMap を**順方向適用**してレンズビネットを除去する向きで Phase 3 を設計すること.LSC 順適用で白板 ROI の CoV は約半減を実測. - **向き**: DNG はセンサ native が landscape,PNG/preview は portrait.PNG と揃えるには `rot90(-1)`(左右反転なし).フロントカメラ(id=1) の preview はミラーされるが RAW はミラーされないため反転不要(ラベル位置で確認). - **PNG vs DNG 比較の注意**: 違いはスケール(0-1/0-255)でなくドメイン(PNG=sRGB / DNG=linear).CoV 等はスケール不変なので,公平比較には PNG を inverse-sRGB で linear 化する必要がある. - **動径 min/max 比**: 画素単位 max/min は外れ画素に弱いため,20点輪帯平均の min/max をロバスト指標として追加. - **後段(未着手)**: TECH_05 Phase 4(既存パイプライン統合)/ Phase 5(PNG vs DNG,撮影フレーミング差への ROI 対応)/ Phase 6(SPEC_01/02 更新)/ ビューア(viewer.py) の DNG 対応.SPEC のフル更新は TECH_05 Phase 6 に委ねる.`scripts/run_uniformity_dng.py` は PNG 版の DNG 対応ランナーとして暫定追加(共通化は将来). + +- [x] **リリース準備: SmTIAS へ全面改名 + 共通化リファクタ + ドキュメント整理**: 新バージョンのタグ付けに向けて以下を実施. + - **改名**: プロジェクト/デバイス名を MiniTIAS → SmTIAS に全面統一(`pyproject.toml`,ROI キー `smtias.whiteboard`,docs/scripts/tests/src,GitHub リポジトリ名 `SmTIAS-Evaluation`,`data/`・`output/` の実データ/成果物ファイル名).参照コピー `MiniTias/`(外部リポジトリ)は対象外. + - **リファクタ(共通化)**: 重複していた `load_roi_config` を `src/config.py` に集約.`run_uniformity_dng.py` 内の DNG 輝度化パイプライン(`dng_luminance`/`lsc_gain_rgb`/`_upsample`)を `src/io/dng_pipeline.py` に移設し,compare 系・plot 系スクリプトが「スクリプト間 import」をやめて `src/` から取得するように変更(上記「共通化は将来」を解消).`dng_loader.py` の Rec.709 係数重複は SmTIAS-LightSim への単体コピー前提のため意図的に据え置き. + - **ドキュメント**: ルート `README.md` を新設.CLAUDE.md にプロジェクト概要と SPEC_02 索引を追加.TECH_01 のローカル TECH_02 リンク切れを修正. + - **検証**: pytest 381 passed,主要スクリプト(run/plot/compare 各種)の smoke test も成功(指標値は改名前と一致). diff --git a/pyproject.toml b/pyproject.toml index 7a1347a..25fdc80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,21 @@ [project] -name = "minitias-evaluation" -version = "0.1.0" -description = "MiniTIAS 画質検証プロジェクト" +name = "smtias-evaluation" +version = "1.0.0" +description = "SmTIAS 画質検証プロジェクト" requires-python = ">=3.11" dependencies = [ - "jinja2", + "numpy", + "opencv-python", + "scipy", + "matplotlib", + "pandas", + "streamlit", + "scikit-image", + "Pillow", "rawpy>=0.21.0", + # 将来機能用(現状コードでは未使用) + "colour-science", + "jinja2", ] [tool.pytest.ini_options] diff --git a/requirements.txt b/requirements.txt index 8dd4fe5..d408932 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,13 @@ numpy opencv-python -colour-science scipy matplotlib pandas streamlit -jinja2 scikit-image +Pillow rawpy>=0.21.0 + +# 将来機能用(現状コードでは未使用) +colour-science +jinja2 diff --git a/scripts/compare_ambient_maps.py b/scripts/compare_ambient_maps.py new file mode 100644 index 0000000..6713a1c --- /dev/null +++ b/scripts/compare_ambient_maps.py @@ -0,0 +1,92 @@ +"""暗闇 vs 室内(外乱光)の輝度マップを共通スケールで並べるスライドを生成する. + +REPORT_03(外乱光影響評価)に対応.compare_ambient_radial.py(動径オーバーレイ)の +輝度マップ版で,同一 DNG 定量モード(linear + LSC 補正,露出/ISO 固定)の 2 枚を +**共通の絶対カラースケール** で並置し,空間パターンがほぼ同じこと(外乱光ロバスト性)を示す. + +共通スケールにする理由(各自正規化しない): +- 両者は同じ linear 単位・同じ露出/ISO・同じ LSC 補正なので絶対値が正当に比較可能. +- 共通カラースケールでないと「2 枚がほぼ同じ」が言えない(各自スケールだと差が誇張/隠蔽される). + +対象(REPORT_03 の条件 A/B): +- 暗闇 (lights OFF) : SmTIAS_QM_20260601_124500 +- 室内 (lights ON) : SmTIAS_QM_20260601_102753 + +使い方: + python scripts/compare_ambient_maps.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.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 + +# REPORT_03 の条件 A(暗闇=室内灯OFF)/ 条件 B(室内=室内灯ON) +DARK_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_124500.dng" +INDOOR_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_102753.dng" +ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json" +ROI_KEY = "smtias.whiteboard" +OUTPUT_PATH = PROJECT_ROOT / "output" / "figures" / "compare_ambient_maps.png" + + +def _roi_luminance(path: Path, roi: dict) -> np.ndarray: + """DNG(linear + LSC)の ROI 輝度画像を返す.""" + luma = dng_luminance(str(path), apply_lsc=True) + return extract_roi(luma, roi["x"], roi["y"], roi["width"], roi["height"]) + + +def main() -> None: + """共通スケールの 2 枚並べスライドを生成して保存する.""" + roi = load_roi_config(str(ROI_CONFIG), ROI_KEY) + if roi is None: + raise SystemExit(f"ROI '{ROI_KEY}' が config に見つかりません") + + dark = _roi_luminance(DARK_PATH, roi) + indoor = _roi_luminance(INDOOR_PATH, roi) + + # 共通カラースケール: 2 枚を結合した 1〜99 パーセンタイル(ノイズ外れ値にロバスト) + both = np.concatenate([dark.ravel(), indoor.ravel()]) + vmin = float(np.percentile(both, 1)) + vmax = float(np.percentile(both, 99)) + + fig, axes = plt.subplots(1, 2, figsize=(13, 6)) + panels = [ + (axes[0], dark, "Dark (lights OFF) 124500"), + (axes[1], indoor, "Indoor (lights ON) 102753"), + ] + im = None + for ax, luminance, title in panels: + im = ax.imshow( + luminance, cmap="hot", interpolation="nearest", vmin=vmin, vmax=vmax + ) + ax.set_title(title, fontsize=11) + ax.set_xlabel("X (px)") + ax.set_ylabel("Y (px)") + + cbar = fig.colorbar(im, ax=axes, fraction=0.025, pad=0.02) + cbar.set_label("Luminance (absolute, linear 0-255 scale)") + + fig.suptitle( + "Ambient light effect on luminance map (DNG, linear + LSC, shared scale)", + fontsize=13, y=0.98, + ) + + OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUTPUT_PATH), dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"共通スケール比較スライドを保存しました: {OUTPUT_PATH}") + print(f" 共通カラースケール vmin={vmin:.1f} vmax={vmax:.1f}") + print(f" Dark (124500) ROI mean = {dark.mean():.2f}") + print(f" Indoor (102753) ROI mean = {indoor.mean():.2f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/compare_ambient_radial.py b/scripts/compare_ambient_radial.py new file mode 100644 index 0000000..8b78475 --- /dev/null +++ b/scripts/compare_ambient_radial.py @@ -0,0 +1,94 @@ +"""暗闇 vs 室内(外乱光)の動径プロファイル重ね描きスライドを生成する. + +REPORT_03(外乱光影響評価)に対応.同一 DNG 定量モード(linear + LSC 補正,露出/ISO 固定)で +撮った 2 枚を,動径プロファイルを **絶対輝度のまま** 1 枚に重ねて比較する. + +軸を絶対輝度にする理由(正規化しない): +- 両者は同じ linear 単位・同じ露出/ISO・同じ LSC 補正なので絶対値が正当に比較可能. +- 「外光を足したのに暗い」(暗闇 raw mean 441.2 > 室内 431.4)という逆転は絶対値でしか見えない. + 各自の最大で正規化するとこの"レベルも含めてほぼ同一"という情報が消える. +- 2 本がほぼ重なること自体が「外乱光にロバスト」というメッセージになる. + +対象(REPORT_03 の条件 A/B): +- 暗闇 (lights OFF) : SmTIAS_QM_20260601_124500 → min/max 0.860 +- 室内 (lights ON) : SmTIAS_QM_20260601_102753 → min/max 0.853 + +使い方: + python scripts/compare_ambient_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.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 + +# REPORT_03 の条件 A(暗闇=室内灯OFF)/ 条件 B(室内=室内灯ON) +DARK_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_124500.dng" +INDOOR_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_102753.dng" +ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json" +ROI_KEY = "smtias.whiteboard" +OUTPUT_PATH = PROJECT_ROOT / "output" / "figures" / "compare_ambient_radial.png" + + +def _radial(path: Path, roi: dict) -> tuple[np.ndarray, dict]: + """DNG(linear + LSC)の ROI 動径プロファイルと min/max 統計を返す.""" + luma = dng_luminance(str(path), apply_lsc=True) + luma = extract_roi(luma, roi["x"], roi["y"], roi["width"], roi["height"]) + distance_map = calc_distance_map(*luma.shape) + profile = calc_radial_profile(luma, distance_map) + radial = calc_radial_min_max_ratio(profile) + return profile, radial + + +def main() -> None: + """重ね描きスライドを生成して保存する.""" + roi = load_roi_config(str(ROI_CONFIG), ROI_KEY) + if roi is None: + raise SystemExit(f"ROI '{ROI_KEY}' が config に見つかりません") + + dark_prof, dark_r = _radial(DARK_PATH, roi) + indoor_prof, indoor_r = _radial(INDOOR_PATH, roi) + + fig, ax = plt.subplots(figsize=(9, 6)) + + ax.plot( + dark_prof[:, 0], dark_prof[:, 1], + marker="o", ms=5, color="#1f3b73", + label=f"Dark (lights OFF) min/max = {dark_r['radial_min_max_ratio']:.3f}", + ) + ax.plot( + indoor_prof[:, 0], indoor_prof[:, 1], + marker="s", ms=5, color="#e08214", + label=f"Indoor (lights ON) min/max = {indoor_r['radial_min_max_ratio']:.3f}", + ) + + ax.set_title("Ambient light effect on radial profile (DNG, linear + LSC)", fontsize=12) + ax.set_xlabel("Normalized distance from center") + ax.set_ylabel("Mean luminance (absolute, linear 0-255 scale)") + ax.grid(True, linestyle="--", alpha=0.5) + ax.legend(loc="upper right", 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" Dark (124500) min/max = {dark_r['radial_min_max_ratio']:.4f}") + print(f" Indoor (102753) min/max = {indoor_r['radial_min_max_ratio']:.4f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/compare_png_dng_slide.py b/scripts/compare_png_dng_slide.py new file mode 100644 index 0000000..b28f64d --- /dev/null +++ b/scripts/compare_png_dng_slide.py @@ -0,0 +1,185 @@ +"""PNG / DNG 比較スライド 1 枚を一括生成するスクリプト. + +PNG(sRGB)と DNG(linear + LSC 補正)の照明均一性を「公平に」比較するため, +スケール差(8bit/10bit, sRGB/linear の絶対値差)を消して **減衰カーブの形** だけを +重ねて見せる.具体的には: + +- 輝度マップ 2 枚: 各自の動径ピーク(最内ピーク輪帯平均)で正規化し,共通 colorbar で並べる +- 動径プロファイル重ね描き: 20 分割輪帯平均を **最大=1.0** で正規化して 1 枚に重ねる + (谷の底がそのまま動径 min/max 比に一致する) +- 数値表: 動径 min/max 比・CoV・max/min 比・中心/周辺比・勾配 + +注意: PNG(4月)と DNG(6月)は別セッション撮影でフレーミングが異なるため, +厳密な同一シーン比較ではない(REPORT_02 の前提を参照).本図は評価ドメインの違いが +減衰指標に与える影響を示すもの. + +使い方: + python scripts/compare_png_dng_slide.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, + calc_uniformity, +) +from src.analysis.spatial import calc_spatial_uniformity # 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, load_image # noqa: E402 + +# 比較対象(PNG sRGB と DNG linear+LSC) +PNG_PATH = PROJECT_ROOT / "data" / "smtias" / "whiteboard" / "SmTIAS_20260408_144434.png" +DNG_PATH = PROJECT_ROOT / "data" / "smtias" / "quantitative" / "SmTIAS_QM_20260601_124500.dng" +ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json" +ROI_KEY = "smtias.whiteboard" +OUTPUT_PATH = PROJECT_ROOT / "output" / "figures" / "compare_png_dng_slide.png" + +# 正規化輝度マップの共通カラースケール(動径ピーク=1.0 を基準とした相対輝度) +MAP_VMIN = 0.80 +MAP_VMAX = 1.02 + + +def _png_luminance(roi: dict) -> np.ndarray: + """PNG を Rec.709 で輝度化し ROI を切り出す(sRGB ドメインのまま).""" + img = load_image(str(PNG_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 _dng_luminance(roi: dict) -> np.ndarray: + """DNG を linear + LSC 補正で輝度化し ROI を切り出す.""" + luma = dng_luminance(str(DNG_PATH), apply_lsc=True) + return extract_roi(luma, roi["x"], roi["y"], roi["width"], roi["height"]) + + +def _profile(luminance: np.ndarray) -> tuple[np.ndarray, dict, dict]: + """動径プロファイル・min/max 統計・均一性/空間統計をまとめて算出する.""" + distance_map = calc_distance_map(*luminance.shape) + profile = calc_radial_profile(luminance, distance_map) + radial = calc_radial_min_max_ratio(profile) + metrics = {**calc_uniformity(luminance), **calc_spatial_uniformity(luminance)["zone_stats"]} + metrics["radial_min_max_ratio"] = radial["radial_min_max_ratio"] + return profile, radial, metrics + + +def _draw_map(ax, luminance: np.ndarray, peak: float, title: str) -> None: + """動径ピークで正規化した相対輝度マップを共通カラースケールで描く.""" + relative = luminance / peak + im = ax.imshow( + relative, cmap="hot", interpolation="nearest", vmin=MAP_VMIN, vmax=MAP_VMAX + ) + ax.set_title(title, fontsize=11) + ax.set_xlabel("X (px)") + ax.set_ylabel("Y (px)") + return im + + +def main() -> None: + """比較スライドを生成して保存する.""" + roi = load_roi_config(str(ROI_CONFIG), ROI_KEY) + if roi is None: + raise SystemExit(f"ROI '{ROI_KEY}' が config に見つかりません") + + png_lum = _png_luminance(roi) + dng_lum = _dng_luminance(roi) + + png_profile, png_radial, png_metrics = _profile(png_lum) + dng_profile, dng_radial, dng_metrics = _profile(dng_lum) + + png_peak = png_radial["radial_max"] + dng_peak = dng_radial["radial_max"] + + fig = plt.figure(figsize=(13, 9)) + gs = fig.add_gridspec(2, 2, height_ratios=[1.05, 1.0], hspace=0.32, wspace=0.28) + + # 上段: 正規化輝度マップ 2 枚(共通カラースケール) + ax_png = fig.add_subplot(gs[0, 0]) + ax_dng = fig.add_subplot(gs[0, 1]) + _draw_map(ax_png, png_lum, png_peak, "PNG (sRGB) relative luminance") + im = _draw_map(ax_dng, dng_lum, dng_peak, "DNG (linear + LSC) relative luminance") + cbar = fig.colorbar(im, ax=[ax_png, ax_dng], fraction=0.025, pad=0.02) + cbar.set_label("Relative luminance (radial peak = 1.0)") + + # 下段左: 動径プロファイル重ね描き(max=1.0 正規化) + ax_prof = fig.add_subplot(gs[1, 0]) + px = png_profile[:, 0] + py = png_profile[:, 1] / png_peak + dx = dng_profile[:, 0] + dy = dng_profile[:, 1] / dng_peak + ax_prof.plot(px, py, marker="o", ms=4, color="steelblue", + label=f"PNG (sRGB) min/max={png_radial['radial_min_max_ratio']:.3f}") + ax_prof.plot(dx, dy, marker="s", ms=4, color="darkorange", + label=f"DNG (linear+LSC) min/max={dng_radial['radial_min_max_ratio']:.3f}") + # 谷の底(= min/max 比)を水平線で強調 + ax_prof.axhline(png_radial["radial_min_max_ratio"], color="steelblue", ls=":", alpha=0.5) + ax_prof.axhline(dng_radial["radial_min_max_ratio"], color="darkorange", ls=":", alpha=0.5) + ax_prof.set_title("Radial profile (normalized, peak = 1.0)", fontsize=11) + ax_prof.set_xlabel("Normalized distance from center") + ax_prof.set_ylabel("Normalized mean luminance") + ax_prof.grid(True, linestyle="--", alpha=0.5) + ax_prof.legend(loc="lower left", fontsize=9) + + # 下段右: 数値表 + ax_tbl = fig.add_subplot(gs[1, 1]) + ax_tbl.axis("off") + rows = [ + ("Radial min/max ratio", f"{png_metrics['radial_min_max_ratio']:.3f}", + f"{dng_metrics['radial_min_max_ratio']:.3f}"), + ("CoV", f"{png_metrics['cov']:.3f}", f"{dng_metrics['cov']:.3f}"), + ("Max/min ratio", f"{png_metrics['max_min_ratio']:.3f}", + f"{dng_metrics['max_min_ratio']:.3f}"), + ("Center/periphery", f"{png_metrics['center_periphery_ratio']:.3f}", + f"{dng_metrics['center_periphery_ratio']:.3f}"), + ("Gradient (%)", f"{png_metrics['gradient_magnitude']:.2f}", + f"{dng_metrics['gradient_magnitude']:.2f}"), + ] + table = ax_tbl.table( + cellText=rows, + colLabels=["Metric", "PNG (sRGB)", "DNG (linear+LSC)"], + cellLoc="center", + loc="center", + ) + table.auto_set_font_size(False) + table.set_fontsize(11) + table.scale(1.0, 1.8) + for j in range(3): + table[0, j].set_facecolor("#dfe6ee") + table[0, j].set_text_props(weight="bold") + ax_tbl.set_title( + "Lower min/max & higher CoV = DNG reveals stronger edge falloff\n" + "(sRGB gamma flattens PNG; not a strict same-scene comparison)", + fontsize=9, loc="center", + ) + + fig.suptitle("PNG -> DNG: how peripheral falloff appears under each domain", + fontsize=14, y=0.98) + + OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(str(OUTPUT_PATH), dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"比較スライドを保存しました: {OUTPUT_PATH}") + print(f" PNG radial min/max = {png_radial['radial_min_max_ratio']:.4f}, CoV = {png_metrics['cov']:.4f}") + print(f" DNG radial min/max = {dng_radial['radial_min_max_ratio']:.4f}, CoV = {dng_metrics['cov']:.4f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_radial_clean.py b/scripts/plot_radial_clean.py new file mode 100644 index 0000000..1dc89e6 --- /dev/null +++ b/scripts/plot_radial_clean.py @@ -0,0 +1,123 @@ +"""動径輝度プロファイルを「min/max 比注記・凡例なし」で出力する一回限りのスクリプト. + +既存の動径プロファイル図(plot_radial_profile)から,右上の min/max ratio 注記と +左下の凡例を除いたクリーン版 PNG を出力する.データは run_uniformity_dng.py と同じ +DNG パイプライン(LSC 補正あり・portrait・linear)で再計算する. + +使い方: + python scripts/plot_radial_clean.py \\ + --image data/smtias/quantitative/SmTIAS_QM_20260601_124500.dng +""" + +import argparse +import sys +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt # noqa: E402 +import numpy as np # noqa: E402 + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(PROJECT_ROOT)) + +from src.analysis.spatial import calc_spatial_uniformity # 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 + + +def plot_radial_profile_clean(radial_profile: np.ndarray, output_path: str) -> None: + """動径プロファイルを min/max 比注記・凡例なしで保存する. + + plot_radial_profile と同一の見た目だが,右上の min/max ratio テキストと + 左下の凡例(Mean Luminance / Max / Min)を描画しない. + 最大点(緑)・最小点(赤)のマーカーは残す. + + Args: + radial_profile: 放射状プロファイルの NumPy 配列(N x 2, float64). + 列 0 = 正規化距離, 列 1 = 平均輝度. + output_path: 出力先ファイルパス(PNG). + """ + Path(output_path).parent.mkdir(parents=True, exist_ok=True) + + distances = radial_profile[:, 0] + luminances = radial_profile[:, 1] + + min_idx = int(np.argmin(luminances)) + max_idx = int(np.argmax(luminances)) + lmin = float(luminances[min_idx]) + lmax = float(luminances[max_idx]) + + fig, ax = plt.subplots(figsize=(8, 5)) + ax.plot(distances, luminances, marker="o", color="steelblue") + ax.scatter(distances[max_idx], lmax, color="green", s=80, zorder=5) + ax.scatter(distances[min_idx], lmin, color="red", s=80, zorder=5) + + ax.set_title("Radial Luminance Profile") + ax.set_xlabel("Normalized Distance from Center") + ax.set_ylabel("Mean Luminance") + ax.grid(True, linestyle="--", alpha=0.5) + # min/max ratio 注記・凡例ともに描画しない + fig.tight_layout() + fig.savefig(output_path, dpi=150) + plt.close(fig) + + print(f"クリーン版 動径プロファイルを保存しました: {output_path}") + + +def main() -> None: + """エントリーポイント.""" + parser = argparse.ArgumentParser( + description="動径輝度プロファイルを min/max 比注記・凡例なしで出力する." + ) + parser.add_argument("--image", required=True, help="解析対象の DNG ファイルパス") + parser.add_argument( + "--config", + default=str(PROJECT_ROOT / "config" / "roi_config.json"), + help="ROI 設定ファイルのパス(デフォルト: config/roi_config.json)", + ) + parser.add_argument( + "--roi", + default="smtias.whiteboard", + help="ROI のキー(ドット区切り,デフォルト: smtias.whiteboard)", + ) + parser.add_argument( + "--output", + default=str(PROJECT_ROOT / "output"), + help="出力ディレクトリのパス(デフォルト: output/)", + ) + parser.add_argument( + "--no-lsc", + action="store_true", + help="LSC(レンズシェーディング)補正を無効化する(既定は補正あり)", + ) + args = parser.parse_args() + + roi = None + if args.config: + try: + roi = load_roi_config(args.config, args.roi) + except FileNotFoundError as e: + print(f"警告: {e} → 画像全体を解析します") + + luma_full = dng_luminance(args.image, apply_lsc=not args.no_lsc) + if roi is not None: + luminance = extract_roi( + luma_full, roi["x"], roi["y"], roi["width"], roi["height"] + ) + else: + luminance = luma_full + + spatial = calc_spatial_uniformity(luminance) + + stem = Path(args.image).stem + output_path = ( + Path(args.output) / "figures" / f"{stem}_radial_profile_clean.png" + ) + plot_radial_profile_clean(spatial["radial_profile"], str(output_path)) + + +if __name__ == "__main__": + main() diff --git a/scripts/run_uniformity.py b/scripts/run_uniformity.py index b79741a..2094d0a 100644 --- a/scripts/run_uniformity.py +++ b/scripts/run_uniformity.py @@ -1,16 +1,15 @@ -"""MiniTIAS 白板画像の照明均一性評価スクリプト. +"""SmTIAS 白板画像の照明均一性評価スクリプト. 使い方(単一画像): - python scripts/run_uniformity.py --image data/minitias/whiteboard/xxx.png - python scripts/run_uniformity.py --image data/minitias/whiteboard/xxx.png \\ - --config config/roi_config.json --roi minitias.whiteboard + python scripts/run_uniformity.py --image data/smtias/whiteboard/xxx.png + python scripts/run_uniformity.py --image data/smtias/whiteboard/xxx.png \\ + --config config/roi_config.json --roi smtias.whiteboard 使い方(ディレクトリ一括): - python scripts/run_uniformity.py --image data/minitias/whiteboard/ + python scripts/run_uniformity.py --image data/smtias/whiteboard/ """ import argparse -import json import sys from pathlib import Path @@ -21,6 +20,7 @@ sys.path.insert(0, str(PROJECT_ROOT)) from src.analysis.reproducibility import calc_pairwise_ssim_registered # noqa: E402 +from src.config import load_roi_config # noqa: E402 from src.analysis.spatial import calc_spatial_uniformity # noqa: E402 from src.analysis.uniformity import calc_uniformity, to_grayscale # noqa: E402 from src.export.exporter import ( # noqa: E402 @@ -41,36 +41,6 @@ ) -def load_roi_config(config_path: str, roi_key: str) -> dict | None: - """ROI 設定ファイルを読み込んで指定キーの ROI を返す. - - Args: - config_path: JSON 設定ファイルのパス. - roi_key: ドット区切りのキー(例: "minitias.whiteboard"). - - Returns: - ROI の辞書(x, y, width, height).キーが存在しない場合は None. - - Raises: - FileNotFoundError: 設定ファイルが存在しない場合. - """ - config_file = Path(config_path) - if not config_file.exists(): - raise FileNotFoundError(f"設定ファイルが見つかりません: {config_path}") - - with config_file.open(encoding="utf-8") as f: - config = json.load(f) - - # ドット区切りでネストされたキーを辿る - node = config - for key in roi_key.split("."): - if not isinstance(node, dict) or key not in node: - return None - node = node[key] - - return node if isinstance(node, dict) else None - - def analyze_single_image( image_path: str, roi: dict | None, @@ -160,7 +130,7 @@ def main() -> None: """エントリーポイント.""" parser = argparse.ArgumentParser( - description="MiniTIAS 白板画像の照明均一性を評価する." + description="SmTIAS 白板画像の照明均一性を評価する." ) parser.add_argument( "--image", @@ -174,8 +144,8 @@ ) parser.add_argument( "--roi", - default="minitias.whiteboard", - help="ROI のキー(ドット区切り,デフォルト: minitias.whiteboard)", + default="smtias.whiteboard", + help="ROI のキー(ドット区切り,デフォルト: smtias.whiteboard)", ) parser.add_argument( "--output", diff --git a/scripts/run_uniformity_dng.py b/scripts/run_uniformity_dng.py index 88d74ed..cd04bb6 100644 --- a/scripts/run_uniformity_dng.py +++ b/scripts/run_uniformity_dng.py @@ -1,4 +1,4 @@ -"""MiniTIAS 定量モード DNG の照明均一性評価スクリプト(PNG 版 run_uniformity.py の DNG 対応版). +"""SmTIAS 定量モード DNG の照明均一性評価スクリプト(PNG 版 run_uniformity.py の DNG 対応版). PNG 版と同じ出力一式(輝度マップ / ヒストグラム / 動径プロファイル / ゾーンマップ / 均一性 CSV / コンソール指標)を,DNG に対して同じ ROI・同じ向き(portrait)で出力する. @@ -9,28 +9,21 @@ - DNG はセンサ native が landscape のため,PNG と同じ portrait へ rot90(-1) で向き合わせ 使い方: - python scripts/run_uniformity_dng.py --image data/minitias/quantitative/xxx.dng + python scripts/run_uniformity_dng.py --image data/smtias/quantitative/xxx.dng """ import argparse -import json import sys from pathlib import Path -import numpy as np - PROJECT_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(PROJECT_ROOT)) from src.analysis.spatial import calc_spatial_uniformity # noqa: E402 -from src.analysis.uniformity import ( # noqa: E402 - REC709_COEFF_B, - REC709_COEFF_G, - REC709_COEFF_R, - calc_uniformity, -) +from src.analysis.uniformity import calc_uniformity # noqa: E402 +from src.config import load_roi_config # noqa: E402 from src.export.exporter import ensure_output_dirs, export_results # noqa: E402 -from src.io.dng_loader import load_image_dng # noqa: E402 +from src.io.dng_pipeline import dng_luminance # noqa: E402 from src.io.loader import extract_roi # noqa: E402 from src.visualization.plotter import ( # noqa: E402 plot_histogram, @@ -40,120 +33,6 @@ ) -def load_roi_config(config_path: str, roi_key: str) -> dict | None: - """ROI 設定ファイルを読み込んで指定キーの ROI を返す. - - Args: - config_path: JSON 設定ファイルのパス. - roi_key: ドット区切りのキー(例: "minitias.whiteboard"). - - Returns: - ROI の辞書(x, y, width, height).キーが存在しない場合は None. - - Raises: - FileNotFoundError: 設定ファイルが存在しない場合. - """ - config_file = Path(config_path) - if not config_file.exists(): - raise FileNotFoundError(f"設定ファイルが見つかりません: {config_path}") - - with config_file.open(encoding="utf-8") as f: - config = json.load(f) - - # ドット区切りでネストされたキーを辿る - node = config - for key in roi_key.split("."): - if not isinstance(node, dict) or key not in node: - return None - node = node[key] - - return node if isinstance(node, dict) else None - - -def _upsample(grid: np.ndarray, shape: tuple[int, int]) -> np.ndarray: - """(rows, cols) グリッドを画像サイズ shape へバイリニア補間する. - - Args: - grid: 補間元グリッドの NumPy 配列(rows x cols, float64). - shape: 補間先の画像サイズ (height, width). - - Returns: - 補間後の NumPy 配列(height x width, float64). - """ - rows, cols = grid.shape - h, w = shape - ys = np.linspace(0, rows - 1, h) - xs = np.linspace(0, cols - 1, w) - y0 = np.floor(ys).astype(int) - y1 = np.minimum(y0 + 1, rows - 1) - x0 = np.floor(xs).astype(int) - x1 = np.minimum(x0 + 1, cols - 1) - wy = (ys - y0)[:, None] - wx = (xs - x0)[None, :] - top = grid[y0][:, x0] * (1 - wx) + grid[y0][:, x1] * wx - bot = grid[y1][:, x0] * (1 - wx) + grid[y1][:, x1] * wx - return top * (1 - wy) + bot * wy - - -def lsc_gain_rgb(meta_path: Path, shape: tuple[int, int]) -> np.ndarray: - """meta.json の lscMap (4, M, N) を (H, W, 3) の RGB ゲインへ補間する. - - 4ch = [R, G_even, G_odd, B].G は G_even/G_odd の平均を用いる. - - Args: - meta_path: meta.json ファイルのパス. - shape: 補間先の画像サイズ (height, width). - - Returns: - RGB ゲインの NumPy 配列(H x W x 3, float64). - """ - meta = json.loads(meta_path.read_text(encoding="utf-8")) - rows, cols = meta["lscMapRowCount"], meta["lscMapColumnCount"] - m = np.array(meta["lscMap"], dtype=np.float64).reshape(rows, cols, 4) - gr = _upsample(m[:, :, 0], shape) - gg = _upsample((m[:, :, 1] + m[:, :, 2]) / 2.0, shape) - gb = _upsample(m[:, :, 3], shape) - return np.stack([gr, gg, gb], axis=2) - - -def dng_luminance(image_path: str, apply_lsc: bool) -> np.ndarray: - """DNG から portrait・linear・(任意で LSC 補正済)の輝度画像を返す. - - - output_format="rgb_float32"(output_color=raw, linear)で読み込み - - apply_lsc=True なら lscMap を順適用してレンズビネットを除去 - - Rec.709 で輝度化(uniformity.py と同じ係数) - - PNG と同じ portrait へ rot90(-1) で向き合わせ - - Args: - image_path: DNG ファイルのパス. - apply_lsc: True の場合,同名 .meta.json から LSC ゲインを読み込んで補正する. - - Returns: - portrait 向きの輝度画像(H x W, float64).値域は 0〜255 の linear スケール. - """ - rgb = load_image_dng(image_path, output_format="rgb_float32") # (H,W,3) linear landscape - - if apply_lsc: - meta_path = Path(image_path).with_suffix("").with_suffix(".meta.json") - if not meta_path.exists(): - # ".meta.json" は二重拡張子なので with_suffix では拾えない場合がある - meta_path = Path(str(Path(image_path).with_suffix("")) + ".meta.json") - if not meta_path.exists(): - raise FileNotFoundError(f"meta.json が見つかりません(LSC 補正に必要): {meta_path}") - rgb = rgb * lsc_gain_rgb(meta_path, rgb.shape[:2]) - - luma = ( - REC709_COEFF_R * rgb[:, :, 0] - + REC709_COEFF_G * rgb[:, :, 1] - + REC709_COEFF_B * rgb[:, :, 2] - ) - # linear のまま 0〜255 スケールで表現する(PNG 図と軸を揃えて比較するため). - # CoV・max/min・中心周辺比はスケール不変なので不変,mean/std のみ ×255 となる. - # 既存 plot_histogram が range=(0,255) 固定であることにも整合する. - luma = luma * 255.0 - return np.rot90(luma, -1) # portrait(PNG と同じ向き) - - def analyze_single_dng( image_path: str, roi: dict | None, @@ -202,7 +81,7 @@ print(f" 勾配量 (%) : {zone_stats['gradient_magnitude']:.2f}") print(f" 動径min/max比 : {spatial['radial_min_max_ratio']:.4f}") - stem = Path(image_path).stem # 例: MiniTIAS_QM_20260601_102753 + stem = Path(image_path).stem # 例: SmTIAS_QM_20260601_102753 figures = output_base / "figures" plot_luminance_map(luminance, str(figures / f"{stem}_luminance_map.png")) plot_histogram(luminance, str(figures / f"{stem}_histogram.png")) @@ -216,7 +95,7 @@ def main() -> None: """エントリーポイント.""" parser = argparse.ArgumentParser( - description="MiniTIAS 定量モード DNG の照明均一性を評価する(PNG 版と同じ出力一式)." + description="SmTIAS 定量モード DNG の照明均一性を評価する(PNG 版と同じ出力一式)." ) parser.add_argument("--image", required=True, help="解析対象の DNG ファイルパス") parser.add_argument( @@ -226,8 +105,8 @@ ) parser.add_argument( "--roi", - default="minitias.whiteboard", - help="ROI のキー(ドット区切り,デフォルト: minitias.whiteboard)", + default="smtias.whiteboard", + help="ROI のキー(ドット区切り,デフォルト: smtias.whiteboard)", ) parser.add_argument( "--output", diff --git a/scripts/select_roi.py b/scripts/select_roi.py index 94786d7..5aca67d 100644 --- a/scripts/select_roi.py +++ b/scripts/select_roi.py @@ -5,8 +5,8 @@ 使い方: python scripts/select_roi.py \\ - --image data/minitias/whiteboard/MiniTIAS_20260408_140317.png \\ - --key minitias.whiteboard + --image data/smtias/whiteboard/SmTIAS_20260408_140317.png \\ + --key smtias.whiteboard 操作: マウスでドラッグして矩形を描き,Space または Enter で確定, @@ -70,7 +70,7 @@ Args: config: 対象の辞書(インプレース更新). - key: ドット区切りキー(例: "minitias.whiteboard"). + key: ドット区切りキー(例: "smtias.whiteboard"). value: セットする値. """ keys = key.split(".") @@ -156,7 +156,7 @@ parser.add_argument( "--key", required=True, - help="ドット区切りの保存キー(例: minitias.whiteboard)", + help="ドット区切りの保存キー(例: smtias.whiteboard)", ) parser.add_argument( "--config", diff --git a/scripts/viewer.py b/scripts/viewer.py index 094288f..fefb434 100644 --- a/scripts/viewer.py +++ b/scripts/viewer.py @@ -1,4 +1,4 @@ -"""MiniTIAS 照明均一性評価 結果ビューア. +"""SmTIAS 照明均一性評価 結果ビューア. 使い方: streamlit run scripts/viewer.py @@ -36,7 +36,7 @@ RESULTS_DIR = PROJECT_ROOT / "output" / "results" FIGURES_DIR = PROJECT_ROOT / "output" / "figures" -DATA_DIR = PROJECT_ROOT / "data" / "minitias" / "whiteboard" +DATA_DIR = PROJECT_ROOT / "data" / "smtias" / "whiteboard" ROI_CONFIG = PROJECT_ROOT / "config" / "roi_config.json" SUMMARY_CSV = RESULTS_DIR / "summary_uniformity.csv" SPATIAL_CSV = RESULTS_DIR / "summary_spatial.csv" @@ -48,7 +48,7 @@ return None with open(ROI_CONFIG, encoding="utf-8") as f: config = json.load(f) - return config.get("minitias", {}).get("whiteboard") + return config.get("smtias", {}).get("whiteboard") def overlay_roi(image_path: Path, roi: dict) -> Image.Image: @@ -433,11 +433,11 @@ def main() -> None: """Streamlit アプリのエントリーポイント.""" st.set_page_config( - page_title="MiniTIAS 照明均一性評価ビューア", + page_title="SmTIAS 照明均一性評価ビューア", page_icon=None, layout="wide", ) - st.title("MiniTIAS 照明均一性評価ビューア") + st.title("SmTIAS 照明均一性評価ビューア") # サマリー CSV の読み込み try: diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..463bd09 --- /dev/null +++ b/src/config.py @@ -0,0 +1,34 @@ +"""設定ファイル(ROI 等)の読み込みユーティリティ.""" + +import json +from pathlib import Path + + +def load_roi_config(config_path: str, roi_key: str) -> dict | None: + """ROI 設定ファイルを読み込んで指定キーの ROI を返す. + + Args: + config_path: JSON 設定ファイルのパス. + roi_key: ドット区切りのキー(例: "smtias.whiteboard"). + + Returns: + ROI の辞書(x, y, width, height).キーが存在しない場合は None. + + Raises: + FileNotFoundError: 設定ファイルが存在しない場合. + """ + config_file = Path(config_path) + if not config_file.exists(): + raise FileNotFoundError(f"設定ファイルが見つかりません: {config_path}") + + with config_file.open(encoding="utf-8") as f: + config = json.load(f) + + # ドット区切りでネストされたキーを辿る + node = config + for key in roi_key.split("."): + if not isinstance(node, dict) or key not in node: + return None + node = node[key] + + return node if isinstance(node, dict) else None diff --git a/src/io/dng_loader.py b/src/io/dng_loader.py index 70c77eb..1a20f69 100644 --- a/src/io/dng_loader.py +++ b/src/io/dng_loader.py @@ -1,4 +1,4 @@ -"""MiniTIAS 定量モード DNG 読み込みモジュール.""" +"""SmTIAS 定量モード DNG 読み込みモジュール.""" from pathlib import Path @@ -33,7 +33,7 @@ black_level: int | None = None, output_format: str = "rgb_float32", ) -> numpy.ndarray: - """MiniTIAS 定量モード DNG を読み込み配列として返す. + """SmTIAS 定量モード DNG を読み込み配列として返す. rawpy の postprocess() を linear パラメータで呼び出すことで, ISP(ガンマ・自動輝度・カメラ WB)の介入を最小化した linear raw 値を取り出す. diff --git a/src/io/dng_pipeline.py b/src/io/dng_pipeline.py new file mode 100644 index 0000000..ed042da --- /dev/null +++ b/src/io/dng_pipeline.py @@ -0,0 +1,105 @@ +"""DNG 定量モードの輝度化パイプライン. + +DNG(RAW_SENSOR)を読み込み,LSC(レンズシェーディング)補正を順適用して +PNG 経路と同じ portrait・linear・Rec.709 輝度画像へ変換する一連の処理をまとめる. + +- 読み込みは load_image_dng(output_format="rgb_float32", linear, output_color=raw) +- DNG は RAW_SENSOR で未補正のため,meta.json の lscMap でレンズビネットを順適用(任意) +- DNG はセンサ native が landscape のため,PNG と同じ portrait へ rot90(-1) で向き合わせ +""" + +import json +from pathlib import Path + +import numpy as np + +from src.analysis.uniformity import ( + REC709_COEFF_B, + REC709_COEFF_G, + REC709_COEFF_R, +) +from src.io.dng_loader import load_image_dng + + +def _upsample(grid: np.ndarray, shape: tuple[int, int]) -> np.ndarray: + """(rows, cols) グリッドを画像サイズ shape へバイリニア補間する. + + Args: + grid: 補間元グリッドの NumPy 配列(rows x cols, float64). + shape: 補間先の画像サイズ (height, width). + + Returns: + 補間後の NumPy 配列(height x width, float64). + """ + rows, cols = grid.shape + h, w = shape + ys = np.linspace(0, rows - 1, h) + xs = np.linspace(0, cols - 1, w) + y0 = np.floor(ys).astype(int) + y1 = np.minimum(y0 + 1, rows - 1) + x0 = np.floor(xs).astype(int) + x1 = np.minimum(x0 + 1, cols - 1) + wy = (ys - y0)[:, None] + wx = (xs - x0)[None, :] + top = grid[y0][:, x0] * (1 - wx) + grid[y0][:, x1] * wx + bot = grid[y1][:, x0] * (1 - wx) + grid[y1][:, x1] * wx + return top * (1 - wy) + bot * wy + + +def lsc_gain_rgb(meta_path: Path, shape: tuple[int, int]) -> np.ndarray: + """meta.json の lscMap (4, M, N) を (H, W, 3) の RGB ゲインへ補間する. + + 4ch = [R, G_even, G_odd, B].G は G_even/G_odd の平均を用いる. + + Args: + meta_path: meta.json ファイルのパス. + shape: 補間先の画像サイズ (height, width). + + Returns: + RGB ゲインの NumPy 配列(H x W x 3, float64). + """ + meta = json.loads(meta_path.read_text(encoding="utf-8")) + rows, cols = meta["lscMapRowCount"], meta["lscMapColumnCount"] + m = np.array(meta["lscMap"], dtype=np.float64).reshape(rows, cols, 4) + gr = _upsample(m[:, :, 0], shape) + gg = _upsample((m[:, :, 1] + m[:, :, 2]) / 2.0, shape) + gb = _upsample(m[:, :, 3], shape) + return np.stack([gr, gg, gb], axis=2) + + +def dng_luminance(image_path: str, apply_lsc: bool) -> np.ndarray: + """DNG から portrait・linear・(任意で LSC 補正済)の輝度画像を返す. + + - output_format="rgb_float32"(output_color=raw, linear)で読み込み + - apply_lsc=True なら lscMap を順適用してレンズビネットを除去 + - Rec.709 で輝度化(uniformity.py と同じ係数) + - PNG と同じ portrait へ rot90(-1) で向き合わせ + + Args: + image_path: DNG ファイルのパス. + apply_lsc: True の場合,同名 .meta.json から LSC ゲインを読み込んで補正する. + + Returns: + portrait 向きの輝度画像(H x W, float64).値域は 0〜255 の linear スケール. + """ + rgb = load_image_dng(image_path, output_format="rgb_float32") # (H,W,3) linear landscape + + if apply_lsc: + meta_path = Path(image_path).with_suffix("").with_suffix(".meta.json") + if not meta_path.exists(): + # ".meta.json" は二重拡張子なので with_suffix では拾えない場合がある + meta_path = Path(str(Path(image_path).with_suffix("")) + ".meta.json") + if not meta_path.exists(): + raise FileNotFoundError(f"meta.json が見つかりません(LSC 補正に必要): {meta_path}") + rgb = rgb * lsc_gain_rgb(meta_path, rgb.shape[:2]) + + luma = ( + REC709_COEFF_R * rgb[:, :, 0] + + REC709_COEFF_G * rgb[:, :, 1] + + REC709_COEFF_B * rgb[:, :, 2] + ) + # linear のまま 0〜255 スケールで表現する(PNG 図と軸を揃えて比較するため). + # CoV・max/min・中心周辺比はスケール不変なので不変,mean/std のみ ×255 となる. + # 既存 plot_histogram が range=(0,255) 固定であることにも整合する. + luma = luma * 255.0 + return np.rot90(luma, -1) # portrait(PNG と同じ向き) diff --git a/tests/analysis/test_radial_min_max.py b/tests/analysis/test_radial_min_max.py index dd27514..303c705 100644 --- a/tests/analysis/test_radial_min_max.py +++ b/tests/analysis/test_radial_min_max.py @@ -1,6 +1,6 @@ """calc_radial_min_max_ratio の単体テスト. -仕様 (実装サマリー / TECH_05_MiniTiasEvaluation_DNG対応要求仕様.md): +仕様 (実装サマリー / TECH_05_SmTIASEvaluation_DNG対応要求仕様.md): - calc_radial_min_max_ratio(radial_profile) -> dict - 引数: 20 点動径プロファイル配列 (N x 2, float64) - 返り値キー: diff --git a/tests/io/test_dng_loader.py b/tests/io/test_dng_loader.py index 7ce1edf..3d91275 100644 --- a/tests/io/test_dng_loader.py +++ b/tests/io/test_dng_loader.py @@ -1,6 +1,6 @@ """dng_loader.py の単体テスト. -仕様 (TECH_05_MiniTiasEvaluation_DNG対応要求仕様.md): +仕様 (TECH_05_SmTIASEvaluation_DNG対応要求仕様.md): - load_image_dng: DNG ファイルを読み込み numpy.ndarray として返す - output_format="rgb_float32": (H, W, 3) float32 linear [0, 1] - output_format="luma_float32": (H, W) float32 linear [0, 1], Rec.709 係数 @@ -25,9 +25,9 @@ _SAMPLE_DNG_PATH = ( Path(__file__).parent.parent.parent / "data" - / "minitias" + / "smtias" / "quantitative" - / "MiniTIAS_QM_20260601_102753.dng" + / "SmTIAS_QM_20260601_102753.dng" ) _DNG_AVAILABLE = _SAMPLE_DNG_PATH.exists() diff --git a/tests/scripts/test_run_uniformity.py b/tests/scripts/test_run_uniformity.py index e346d5d..8996248 100644 --- a/tests/scripts/test_run_uniformity.py +++ b/tests/scripts/test_run_uniformity.py @@ -19,7 +19,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) -from scripts.run_uniformity import load_roi_config +from src.config import load_roi_config # --------------------------------------------------------------------------- @@ -34,7 +34,7 @@ def roi_config_file(self, tmp_path: Path) -> Path: """テスト用 ROI 設定 JSON ファイルを生成して返す fixture.""" config = { - "minitias": { + "smtias": { "whiteboard": {"x": 100, "y": 200, "width": 400, "height": 300} }, "tias": { @@ -47,7 +47,7 @@ def test_returns_roi_dict_for_valid_key(self, roi_config_file: Path) -> None: """正常: 存在するキーで ROI 辞書が返ること.""" - result = load_roi_config(str(roi_config_file), "minitias.whiteboard") + result = load_roi_config(str(roi_config_file), "smtias.whiteboard") assert result == {"x": 100, "y": 200, "width": 400, "height": 300} def test_returns_roi_dict_for_another_valid_key(self, roi_config_file: Path) -> None: @@ -62,36 +62,36 @@ def test_returns_none_for_partial_key(self, roi_config_file: Path) -> None: """正常: ネストの途中で存在しないキーでも None が返ること.""" - result = load_roi_config(str(roi_config_file), "minitias.nonexistent") + result = load_roi_config(str(roi_config_file), "smtias.nonexistent") assert result is None def test_raises_file_not_found_for_missing_config(self, tmp_path: Path) -> None: """異常: 設定ファイルが存在しない場合に FileNotFoundError が送出されること.""" missing_path = str(tmp_path / "no_such_config.json") with pytest.raises(FileNotFoundError): - load_roi_config(missing_path, "minitias.whiteboard") + load_roi_config(missing_path, "smtias.whiteboard") def test_file_not_found_error_contains_path(self, tmp_path: Path) -> None: """異常: FileNotFoundError のメッセージにファイルパスが含まれること.""" missing_path = str(tmp_path / "missing_roi.json") with pytest.raises(FileNotFoundError, match="missing_roi.json"): - load_roi_config(missing_path, "minitias.whiteboard") + load_roi_config(missing_path, "smtias.whiteboard") def test_returns_dict_type_for_valid_key(self, roi_config_file: Path) -> None: """正常: 戻り値が dict 型であること.""" - result = load_roi_config(str(roi_config_file), "minitias.whiteboard") + result = load_roi_config(str(roi_config_file), "smtias.whiteboard") assert isinstance(result, dict) def test_returned_roi_has_required_keys(self, roi_config_file: Path) -> None: """正常: 返された ROI が x, y, width, height キーを持つこと.""" - result = load_roi_config(str(roi_config_file), "minitias.whiteboard") + result = load_roi_config(str(roi_config_file), "smtias.whiteboard") assert result is not None for key in ("x", "y", "width", "height"): assert key in result, f"ROI に '{key}' キーが存在しない" def test_returns_none_for_empty_dot_split_key(self, tmp_path: Path) -> None: """エッジケース: 設定に存在しないトップレベルキーで None が返ること.""" - config = {"minitias": {"whiteboard": {"x": 0, "y": 0, "width": 10, "height": 10}}} + config = {"smtias": {"whiteboard": {"x": 0, "y": 0, "width": 10, "height": 10}}} config_path = tmp_path / "config.json" config_path.write_text(json.dumps(config), encoding="utf-8") @@ -101,9 +101,9 @@ def test_returns_none_when_leaf_value_is_not_dict(self, tmp_path: Path) -> None: """エッジケース: 指定キーの値が dict でない場合に None が返ること.""" # キーの値が辞書ではなくスカラー値の場合 - config = {"minitias": {"whiteboard": "not_a_dict"}} + config = {"smtias": {"whiteboard": "not_a_dict"}} config_path = tmp_path / "config.json" config_path.write_text(json.dumps(config), encoding="utf-8") - result = load_roi_config(str(config_path), "minitias.whiteboard") + result = load_roi_config(str(config_path), "smtias.whiteboard") assert result is None diff --git a/tests/scripts/test_select_roi.py b/tests/scripts/test_select_roi.py index d0cff6b..570b373 100644 --- a/tests/scripts/test_select_roi.py +++ b/tests/scripts/test_select_roi.py @@ -135,8 +135,8 @@ """正常: 値として辞書をセットできること.""" config: dict = {} roi = {"x": 10, "y": 20, "width": 100, "height": 80} - set_nested(config, "minitias.whiteboard", roi) - assert config == {"minitias": {"whiteboard": roi}} + set_nested(config, "smtias.whiteboard", roi) + assert config == {"smtias": {"whiteboard": roi}} # --------------------------------------------------------------------------- @@ -155,7 +155,7 @@ def test_loads_existing_json_file(self, tmp_path: Path) -> None: """正常: 既存の JSON ファイルが正しく読み込まれること.""" - config_data = {"minitias": {"whiteboard": {"x": 10, "y": 20, "width": 100, "height": 80}}} + config_data = {"smtias": {"whiteboard": {"x": 10, "y": 20, "width": 100, "height": 80}}} config_path = tmp_path / "roi_config.json" config_path.write_text(json.dumps(config_data), encoding="utf-8") @@ -204,10 +204,10 @@ def test_saved_json_is_valid(self, tmp_path: Path) -> None: """正常: 保存された JSON が有効な形式であること.""" - config = {"minitias": {"whiteboard": {"x": 10, "y": 20}}} + config = {"smtias": {"whiteboard": {"x": 10, "y": 20}}} config_path = tmp_path / "config.json" save_config(config, config_path) # JSON として正常にパースできること loaded = json.loads(config_path.read_text(encoding="utf-8")) - assert loaded["minitias"]["whiteboard"]["x"] == 10 + assert loaded["smtias"]["whiteboard"]["x"] == 10 diff --git a/tests/scripts/test_viewer.py b/tests/scripts/test_viewer.py index f83b168..3e39fba 100644 --- a/tests/scripts/test_viewer.py +++ b/tests/scripts/test_viewer.py @@ -6,7 +6,7 @@ - CLAUDE.md が存在しない場合に FileNotFoundError を送出する - load_roi: config/roi_config.json から ROI 設定を読み込む - ファイルが存在しない場合 None を返す - - ファイルが存在し minitias.whiteboard キーがある場合 dict を返す + - ファイルが存在し smtias.whiteboard キーがある場合 dict を返す - ファイルが存在するが対応キーがない場合 None を返す - overlay_roi: 元画像に ROI 矩形をオーバーレイした PIL Image を返す - 戻り値が PIL Image であること @@ -122,14 +122,14 @@ result = viewer_module.load_roi() assert result is None - def test_returns_dict_when_config_has_minitias_whiteboard( + def test_returns_dict_when_config_has_smtias_whiteboard( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - """正常: minitias.whiteboard キーが存在する設定ファイルから ROI dict を返すこと.""" + """正常: smtias.whiteboard キーが存在する設定ファイルから ROI dict を返すこと.""" import scripts.viewer as viewer_module config = { - "minitias": { + "smtias": { "whiteboard": {"x": 100, "y": 200, "width": 400, "height": 300} } } @@ -147,7 +147,7 @@ import scripts.viewer as viewer_module config = { - "minitias": { + "smtias": { "whiteboard": {"x": 10, "y": 20, "width": 50, "height": 60} } } @@ -160,10 +160,10 @@ for key in ("x", "y", "width", "height"): assert key in result, f"ROI に '{key}' キーが存在しない" - def test_returns_none_when_minitias_key_absent( + def test_returns_none_when_smtias_key_absent( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - """正常: minitias キーが存在しない設定ファイルの場合 None を返すこと.""" + """正常: smtias キーが存在しない設定ファイルの場合 None を返すこと.""" import scripts.viewer as viewer_module config = {"other_device": {"whiteboard": {"x": 0, "y": 0, "width": 10, "height": 10}}} @@ -177,10 +177,10 @@ def test_returns_none_when_whiteboard_key_absent( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: - """正常: minitias キーはあるが whiteboard キーがない場合 None を返すこと.""" + """正常: smtias キーはあるが whiteboard キーがない場合 None を返すこと.""" import scripts.viewer as viewer_module - config = {"minitias": {"other_region": {"x": 0, "y": 0, "width": 10, "height": 10}}} + config = {"smtias": {"other_region": {"x": 0, "y": 0, "width": 10, "height": 10}}} config_path = tmp_path / "roi_config.json" config_path.write_text(json.dumps(config), encoding="utf-8") monkeypatch.setattr(viewer_module, "ROI_CONFIG", config_path) @@ -194,7 +194,7 @@ """正常: ROI が存在する場合の戻り値が dict 型であること.""" import scripts.viewer as viewer_module - config = {"minitias": {"whiteboard": {"x": 5, "y": 5, "width": 100, "height": 100}}} + config = {"smtias": {"whiteboard": {"x": 5, "y": 5, "width": 100, "height": 100}}} config_path = tmp_path / "roi_config.json" config_path.write_text(json.dumps(config), encoding="utf-8") monkeypatch.setattr(viewer_module, "ROI_CONFIG", config_path)