diff --git a/CLAUDE.md b/CLAUDE.md index f582375..de7e27c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,6 +64,10 @@ - アーキテクチャ設計: docs/04_SPEC/SPEC_01_アーキテクチャ設計.md +### 05_TECH(技術設計) + +- DNG 対応要求仕様: docs/05_TECH/TECH_01_DNG対応要求仕様.md + ### 07_REPORT(報告書) - 照明均一性評価報告書: docs/07_REPORT/REPORT_01_照明均一性評価報告書.md 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" new file mode 100644 index 0000000..b77b907 --- /dev/null +++ "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" @@ -0,0 +1,438 @@ +# MiniTias-Evaluation DNG 対応要求仕様 (DNG Support Specification for MiniTias-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 プロジェクトから **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)**: 両セッションを橋渡しする + +## 概要 (Overview) + +MiniTias-Evaluation([MiniTias-Evaluation](https://github.com/rintoHasegawa/MiniTias-Evaluation) リポジトリ)に以下の機能追加を要求する: + +1. **DNG 読み込み I/F** の新規実装(10-bit BGGR Bayer 展開) +2. **メタデータ JSON 読み込み I/F** の新規実装(撮影条件・LSC マップ・hot pixel リスト等) +3. **既存 PNG 経路と並列対応**(後方互換) +4. **既存評価指標との互換**(CoV / 勾配 / C/P / max-min 等が DNG 入力でも同じロジックで算出可能) + +これらの実装は **SmTIAS-LightSim 側にコピーされる前提**で,再利用性の高い設計とする. + +## 背景 (Background) + +### 三項プロジェクトの関係 + +```text +┌─────────────────────────┐ ┌──────────────────────────┐ +│ MiniTias │ │ MiniTias-Evaluation │ +│ (Flutter / Android) │ ──→ │ (Python) │ +│ 撮影アプリ │ │ 実機データ評価ツール │ +│ - PNG 保存(通常モード) │ │ - 評価指標算出 │ +│ - DNG 保存(定量モード) │ │ - 可視化 │ +└─────────────────────────┘ └──────────────────────────┘ + │ + │ コピー(uniformity.py 等) + ▼ + ┌──────────────────────────┐ + │ SmTIAS-LightSim │ + │ (Python / Mitsuba 3) │ + │ シミュ vs 実機比較 │ + └──────────────────────────┘ +``` + +### 経緯のまとめ + +- 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 対応を依頼 + +### MiniTias-Evaluation の現状 + +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 にコピー)を踏襲する: + +| プロジェクト | 責務 | +| --- | --- | +| **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 のローカルリポジトリから読み込んでコピー + +## 既存ドキュメントとの整合 (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 パイプライン +- [Android Camera2 API DNG](https://developer.android.com/reference/android/hardware/camera2/DngCreator) +- [rawpy ドキュメント](https://letmaik.github.io/rawpy/api/)