# 定量モード実装報告と SHADING_MODE 相談 (Implementation Report & SHADING_MODE Consultation)

本ドキュメントは [TECH_01 定量撮影モード要求仕様](TECH_01_定量撮影モード要求仕様.md) を MiniTIAS リポジトリで実装した結果の **MiniTIAS 側からの報告書** であり，併せて **SmTIAS-LightSim 側との方針確認・相談事項** をまとめたものである．

## 想定読者 (Intended Reader)

- **SmTIAS-LightSim 側 Claude セッション**: 本ドキュメントを読んで MiniTIAS 側の実装状況を把握し，未確定事項に対する判断・指示を返す
- **人間レビュー (rinto)**: 両セッションを橋渡しし，研究方針に照らして判断する

## 概要 (Overview)

MiniTIAS 側で TECH_01 の要求仕様に基づいた **定量撮影モード（DNG 経路）** の実装が完了した（[MiniTias リポジトリ master ブランチ](https://github.com/rintoHasegawa/MiniTIAS) に取り込み済み）．本ドキュメントの目的は以下の 2 点：

1. **実装結果の報告**：TECH_01 の各 Phase に対して何を実装したか，AQUOS sense3 (SH-02M) で何が確認できたかを共有する
2. **SHADING_MODE の扱いの相談**：定量モードのキー設定である `SHADING_MODE` を MiniTIAS 側でどう選んだか，研究上どのような含意があるか，SmTIAS-LightSim 側の解析パイプラインとの整合を確認したい

## TECH_01 要求に対する実装状況 (Implementation Status)

| Phase | 要求 | 実装状況 |
| --- | --- | --- |
| Phase 1 | Camera2 詳細制御の MethodChannel 実装（露光・WB 固定，LSC マップ取得） | ✅ 完了 |
| Phase 2 | LSC マップ・メタデータ保存ロジック | ✅ 完了 |
| Phase 3 | 撮影画面 UI に定量モードトグル追加 | ⚠️ 仕様変更（後述）．**トグル UI は追加せず通常モードを置換** |
| Phase 4 | ハードウェアサポート判定とユーザー案内 | ✅ 診断機能を `DiagnosticsProvider` として実装（UI 非表示．裏で再利用可能） |
| Phase 5 | RAW_SENSOR (DNG) 対応（任意） | ✅ 完了．第 1 優先経路として採用 |

### Phase 3 の変更点

TECH_01 では「撮影モード切替トグル」で通常モードと定量モードを並存させる想定だったが，MiniTIAS 側の運用判断として **通常モード（PNG 経路）を temporarily に無効化し，シャッターは常に定量モードを呼ぶ**構成にした．既存の `captureFullResolutionPng` コードは温存している．

理由：

- MiniTIAS の運用上の存在意義は SmTIAS-LightSim との定量比較のみ
- トグル UI を残すと「誤って通常モードで撮影してしまい後で気付く」事故が起きる
- 研究フェーズ完了後にトグル追加 or 通常モード復活は容易

## 実装した撮影設定 (Camera2 CaptureRequest)

| キー | 設定値 | 備考 |
| --- | --- | --- |
| `CONTROL_MODE` | `OFF` | マニュアル全制御 |
| `CONTROL_AE_MODE` | `OFF` | 自動露光停止 |
| `SENSOR_EXPOSURE_TIME` | `16,666,666 ns`（= 1/60 s） | **ハードコード**．後段で半自動キャリブ機能の追加を検討中 |
| `SENSOR_SENSITIVITY` | `40`（センサ最低 ISO） | 診断で確認したセンサのレンジ最低値 |
| `SENSOR_FRAME_DURATION` | `33,333,333 ns`（30fps 相当） | 露光時間 ≤ frame duration を満たす任意値 |
| `BLACK_LEVEL_LOCK` | `true` | フレーム間で黒レベルのドリフトを抑える |
| `CONTROL_AWB_MODE` | `OFF` | 自動 WB 停止 |
| `COLOR_CORRECTION_MODE` | `TRANSFORM_MATRIX` | マニュアル制御モード |
| `COLOR_CORRECTION_GAINS` | `RggbChannelVector(1.0, 1.0, 1.0, 1.0)` | RAW 出力では Bayer 値に効かないが request 値として明示 |
| `COLOR_CORRECTION_TRANSFORM` | 単位行列（`ColorSpaceTransform`） | 同上 |
| `NOISE_REDUCTION_MODE` | `OFF` | NR による画素値の改変を防ぐ |
| `EDGE_MODE` | `OFF` | エッジ強調を防ぐ |
| `TONEMAP_MODE` | `CONTRAST_CURVE` | カーブは R/G/B すべて線形 `[(0,0),(1,1)]` |
| **`SHADING_MODE`** | **`HIGH_QUALITY`** | ★後述の相談対象．OFF も技術的に可能 |
| `STATISTICS_LENS_SHADING_MAP_MODE` | `ON` | LSC マップを CaptureResult に含める |
| `CONTROL_AF_MODE` | `OFF` | **AQUOS sense3 のフロントカメラは固定焦点で OFF のみサポート** |
| `FLASH_MODE` | `OFF` | LED はアタッチメント側で制御 |
| `CONTROL_VIDEO_STABILIZATION_MODE` | `OFF` | 手ブレ補正による画像ずれを防ぐ |

## 撮影フロー (Capture Flow)

```text
シャッター押下
   │
   ▼
プレビュー停止（camera パッケージの CameraController.dispose）
   │
   ▼
baseName 生成（MiniTIAS_QM_YYYYMMDD_HHmmss．同秒重複時 _1, _2 ...）
   │
   ▼
Camera2 API でフロントカメラを開く
ImageReader（RAW_SENSOR, 3264×2448, maxImages=3）
   │
   ▼
上記マニュアル制御パラメータを CaptureRequest に適用
   │
   ▼
連続 3 枚キャプチャ
（設定反映確実化のため最初の 2 枚は破棄．3 枚目を本番とする）
   │
   ▼
DngCreator で DNG ファイルを直接書き込み
（FileOutputStream → ${dir}/${baseName}.dng）
   │
   ▼
LSC マップ取得（CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP）
メタデータ Map 構築（settings / actual / sensorCharacteristics / lscMap）
   │
   ▼
Dart 側でメタデータ JSON 保存
（${dir}/${baseName}.meta.json）
   │
   ▼
シャッター瞬間に取得済みのライブプレビュースナップショット JPEG を保存
（${dir}/${baseName}.preview.jpg．アプリ内目視確認用．解析対象外）
   │
   ▼
MediaStore 通知 → Camera2 解放 → プレビュー再開
```

**従来の「1 秒 AE/AF 安定待ち」は省略**した．AE/AWB/AF 全部 OFF のため安定待ちの意味がないことと，「3 枚連続キャプチャの 2 枚破棄」で設定反映の不確実性を吸収する設計にした．

## AQUOS sense3 (SH-02M) 実機検証結果 (Device Verification)

TECH_01 の開放質問 Q1〜Q4 に対する実機での確認結果．診断機能（`getCameraDiagnostics`）の出力は MiniTIAS リポジトリの [`docs/06_TEST/camera_diagnostics_20260531_161734.json`](../06_TEST/camera_diagnostics_20260531_161734.json) に保存している．

### 主要項目

| 項目 | 値 |
| --- | --- |
| `INFO_SUPPORTED_HARDWARE_LEVEL` | **`LEVEL_3`**（最上位） |
| `REQUEST_AVAILABLE_CAPABILITIES` | `BACKWARD_COMPATIBLE`, **`MANUAL_SENSOR`**, **`MANUAL_POST_PROCESSING`**, **`RAW`**, `READ_SENSOR_SETTINGS`, `BURST_CAPTURE`, `PRIVATE_REPROCESSING`, `YUV_REPROCESSING` |
| `SENSOR_INFO_EXPOSURE_TIME_RANGE` | `79,806 ns 〜 871,614,530 ns`（≈ 1/12530 s 〜 0.87 s） |
| `SENSOR_INFO_SENSITIVITY_RANGE` | `40 〜 2506` |
| `RAW_SENSOR` 出力サイズ | `3264 × 2448`（1 種類のみ） |
| `SENSOR_INFO_COLOR_FILTER_ARRANGEMENT` | `3 (BGGR)` |
| `SENSOR_BLACK_LEVEL_PATTERN` | `[[64, 64], [64, 64]]` |
| `SENSOR_INFO_WHITE_LEVEL` | `1023`（10-bit） |
| `SENSOR_INFO_PHYSICAL_SIZE` | `3.558 mm × 2.668 mm` |
| `SENSOR_INFO_PIXEL_ARRAY_SIZE` | `3264 × 2448` |
| `LENS_INFO_MINIMUM_FOCUS_DISTANCE` | `0.0`（無限遠固定） |
| `LENS_INFO_HYPERFOCAL_DISTANCE` | `0.8189`（≈ 1.22 m） |
| `STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES` | `[OFF (0), ON (1)]` |
| `CONTROL_AF_AVAILABLE_MODES` | **`[OFF (0)] のみ`**（AF 機能なし） |
| `SHADING_AVAILABLE_MODES` | `[OFF, FAST, HIGH_QUALITY]` |

### 注目点

- **TECH_01 の Phase 1〜5 すべてが実装可能**な能力構成だった
- **AF は OFF のみ**．レンズは固定焦点（無限遠固定）でアタッチメント光学系で近接被写体にピントを合わせている前提
- LSC マップサイズは実キャプチャ結果から **13 × 17** と判明（特性からは hidden API のため取得不可だった）

## 保存ファイル仕様 (Output File Specification)

### ファイル命名規則

撮影 1 回で 3 ファイルを生成．basename は共通．

| 種別 | ファイル名 | 概要 |
| --- | --- | --- |
| RAW 画像 | `MiniTIAS_QM_YYYYMMDD_HHmmss.dng` | 解析の本体（約 16 MB） |
| メタデータ | `MiniTIAS_QM_YYYYMMDD_HHmmss.meta.json` | 撮影設定 + 実適用値 + LSC マップ + センサ特性（約 24 KB） |
| プレビュー | `MiniTIAS_QM_YYYYMMDD_HHmmss.preview.jpg` | アプリ内表示・確認用．解析対象外 |

`QM` = Quantitative Mode．同秒重複時は `_1`, `_2` ... と連番を付与．

### DNG (RAW_SENSOR)

- フォーマット: TIFF/EP ベースの Adobe DNG（`DngCreator` 経由）
- データ: 10-bit BGGR Bayer，3264×2448
- Exif メタ: DngCreator が `CameraCharacteristics` から自動で埋め込み（`ColorMatrix1/2`, `ForwardMatrix1/2`, `AsShotNeutral`, `NoiseProfile`, `BlackLevelPattern` 等）

### meta.json の構造（実例抜粋）

[`MiniTIAS_QM_20260531_172612.meta.json`](https://github.com/rintoHasegawa/MiniTIAS) から抜粋（白板を SHADING_MODE = HIGH_QUALITY で撮影したもの）：

```json
{
  "captureTimestampUtc": "2026-05-31T08:26:13.235Z",
  "deviceModel": "SH-02M",
  "androidVersion": "9",
  "cameraId": "1",
  "hardwareLevel": 3,
  "captureMode": "quantitative",
  "imageFormat": "RAW_SENSOR",
  "imageSize": [3264, 2448],

  "settings": {
    "control_mode": "OFF",
    "ae_mode": "OFF",
    "exposure_time_ns": 16666666,
    "sensor_sensitivity_iso": 40,
    "frame_duration_ns": 33333333,
    "awb_mode": "OFF",
    "color_correction_gains": [1.0, 1.0, 1.0, 1.0],
    "color_correction_transform": "identity",
    "noise_reduction_mode": "OFF",
    "edge_mode": "OFF",
    "tonemap_mode": "CONTRAST_CURVE_LINEAR",
    "shading_mode": "HIGH_QUALITY",
    "statistics_lens_shading_map_mode": "ON",
    "af_mode": "OFF",
    "black_level_lock": true
  },

  "actual": {
    "exposure_time_ns": 16653365,
    "sensor_sensitivity": 40,
    "frame_duration_ns": 33333333,
    "sensor_timestamp": 4894940190334,
    "dynamic_black_level": null,
    "dynamic_white_level": 1023,
    "neutral_color_point": ["1/1", "1/1", "1/1"],
    "color_gains": [1.0, 1.0, 1.0, 1.0],
    "color_transform": [
      ["1/1", "0/1", "0/1"],
      ["0/1", "1/1", "0/1"],
      ["0/1", "0/1", "1/1"]
    ]
  },

  "sensorCharacteristics": {
    "color_filter_arrangement": 3,
    "black_level_pattern": "BlackLevelPattern([64, 64], [64, 64])",
    "white_level": 1023,
    "physical_size": [3.557760, 2.668320],
    "pixel_array_size": [3264, 2448],
    "active_array_size": "Rect(0, 0 - 3264, 2448)"
  },

  "lscMap": [
    [5.105, 4.232, 4.111, 3.847, 4.982, 3.966, 3.916, 3.744, 4.459, 3.503, 3.502, 3.369, 3.860, 3.025, 2.995, 2.905, 3.367, 2.647, 2.595, 2.522, 2.995, 2.393, 2.349, 2.285, 2.765, 2.235, 2.199, 2.133, 2.613, 2.130, 2.093, 2.032, 2.577, 2.087, 2.051, 1.991, 2.610, 2.125, 2.093, 2.022, 2.728, 2.214, 2.187, 2.110, 2.950, 2.365, 2.359, 2.251, 3.294, 2.614, 2.637, 2.500, 3.846, 3.006, 3.042, 2.868, 4.426, 3.456, ...],
    ...（行 1〜行 12 続く）
  ],
  "lscMapRowCount": 13,
  "lscMapColumnCount": 17
}
```

#### `actual` フィールドについての注意

- `actual` は CaptureResult から読み戻した**実適用値**．Request 値（`settings`）と微妙に異なる場合がある（例：露光時間 16,666,666 → 16,653,365 でセンサが量子化）
- `dynamic_black_level` は `null` で返ることがある（端末依存）．代わりに `sensorCharacteristics.black_level_pattern` の静的値を参照可
- `neutral_color_point` と `color_transform` が単位行列なのは AWB OFF + identity gains 設定の結果

#### LSC マップ

- 形状: 4 ch × N rows × M cols（4 = R / G_even / G_odd / B）の各チャネルゲイン
- 本実装の保存形式: **1 行を 4 × M 個の float が並んだフラット配列**として `lscMap[row]` に格納．行数・列数は `lscMapRowCount` / `lscMapColumnCount` で別途記録
- 値の意味: `corrected = raw × gain`．中央 ≈ 1.0，周辺で 5× 程度まで上がる典型的な cos⁴ 補正マップになっている
- **ただし `SHADING_MODE = OFF` で撮影した場合は全要素 `1.0` の単位マップが返ってくることを確認済み**（次節で詳述）

### preview.jpg

シャッター瞬間のライブプレビュー JPEG をそのまま保存（Camera2 ISP がフル稼働した状態の画像）．**解析対象ではない**．撮影者がアプリ内で「撮れた」を判断するためだけのもの．解像度はプレビューストリーム依存（おそらく 1280×720 程度）．

## SHADING_MODE の扱い（★相談事項） (Consultation on SHADING_MODE)

### 経緯

実装初期は **TECH_01 の表に明記されていた `HIGH_QUALITY` を採用**した．しかし実装途中で MiniTIAS 側は以下を観察した：

1. `SHADING_MODE = OFF` で撮影すると **LSC マップが全要素 `1.0`** になる（Android 仕様：「補正は適用しない + 単位マップを返す」）
2. `SHADING_MODE = HIGH_QUALITY` で撮影すると LSC が **画像に適用された状態**で DNG に保存される．LSC マップは ISP が適用したゲインの実値

結果：**HIGH_QUALITY を採用した**現状の DNG は，ISP によって cos⁴ 由来のシェーディングが既に補正された状態のデータになっている．

### 研究上の含意

SmTIAS-LightSim 側の Mitsuba 3 シミュレーションは「物理的な cos⁴ 落ち + LED 配光 + 鏡面反射」を再現するものと理解している．この物理シミュレーション結果と直接比較したいデータは：

- (A) **LSC 適用済み画像（現状の DNG）**: ISP が「メーカー校正の LSC マップ」を掛けて平坦化したもの．物理的な cos⁴ がほぼ取り除かれている
- (B) **LSC 適用前の生 Bayer**: 物理的 cos⁴ がそのまま残っている．Mitsuba 出力と同じ前提

(A) と Mitsuba を直接比較すると **比較対象に LSC マップ分のずれが系統的に乗る**ことになる．これが [TEST_01](../06_TEST/TEST_01_キャリブレーション検証履歴.md) で残差 2.1× が解消しなかった一因の可能性もある（推測）．

### 取りうる選択肢

研究の整合のために考えられる方向性を以下に列挙する．**MiniTIAS 側からの判断はせず**，SmTIAS-LightSim 側で解析パイプラインを構築する立場からの選択肢として提示する：

| 選択肢 | MiniTIAS 側の対応 | SmTIAS-LightSim 側の対応 | データの性質 |
| --- | --- | --- | --- |
| (1) 現状維持（HQ） | 変更なし | `DNG ÷ LSC マップ` を計算して LSC 適用前相当に戻す処理を実装 | DNG は LSC 適用済み．逆適用で物理生 Bayer を再構成 |
| (2) OFF に切替 | `SHADING_MODE = OFF` に変更 | そのまま Mitsuba 出力と比較 | DNG は物理生 Bayer．LSC マップは情報なし（全 1.0） |
| (3) 両方撮影 | 1 シーンに対し HQ と OFF の 2 枚を連続撮影 | 比較・検証で使い分け | 情報量最大．ただし撮影時間と保存容量が 2 倍 |

#### MiniTIAS 側で対応する場合のコスト感

- (1): コスト 0（現状）．SmTIAS-LightSim 側で逆適用ロジックが必要
- (2): Kotlin 側 CaptureRequest を 1 行変更．連動して meta.json の `settings.shading_mode` も `"OFF"` に．約 30 分
- (3): キャプチャ処理を 2 連続化．ファイル名規則の拡張（例：`*_hq.dng` / `*_off.dng`）．約 1 時間

### MiniTIAS 側から確認したい事項

- SmTIAS-LightSim 側の解析パイプラインで「LSC マップを使った逆適用」は実装済み or 実装可能か？
- 物理シミュレーション（Mitsuba）の出力は「LSC 適用前の生 Bayer 相当」で間違いないか？
- 既存 baseline `MiniTIAS_20260408_144434`（通常モード PNG）との互換性をどう扱うか？（既に方針として「定量モード以降は新規 baseline」となっていれば問題なし）

選択肢 (1)〜(3) のうちどれを採用するか，または別案があるか，**SmTIAS-LightSim 側の判断を求めたい**．本相談への返答を受けて MiniTIAS 側で必要な実装変更を行う．

## その他の未確認事項 (Other Open Issues)

### 露光時間 / ISO の運用

現状 `1/60 s + ISO 40` ハードコードで撮影している．照明条件（アタッチメント装着時の LED 強度），被写体（白板 / 舌模型 / 実舌）によって最適値は異なるはず．

- 白板：飽和しない範囲で最大露光（SN 比最大）
- 舌模型：白板用設定だと暗すぎる可能性

MiniTIAS 側で検討中の対応：

- (a) 半自動キャリブ機能（AE を 1 回走らせて値を読み，以降固定）
- (b) 白板用・舌模型用の 2 プリセット
- (c) ハードコード変更（撮影前に手動で設定）

SmTIAS-LightSim 側で「白板撮影時の理想的なセンサ平均値」（例：10-bit のうち 500 前後）の目安があれば共有してほしい．

### V1 連射再現性検証の担当

TECH_01 検証 V1（10 連射 CoV < 1%）を実施するには：

- MiniTIAS 側：連射 UI（10 ショットを連続保存）の実装が必要
- 解析：DNG を読み込んで mean / std / CoV を計算する Python スクリプトが必要

連射機能は MiniTIAS 側で実装予定．解析スクリプトは MiniTIAS 内に `scripts/verify_repeatability.py` 等で置くか，SmTIAS-LightSim 側の sm-evaluate に統合するか方針を相談したい．

### MTP 経由ファイル転送の注意点

DNG が 16 MB 級のため MTP コピー直後にファイルサイズが正しく見えないケースを実装中に確認した．**`adb pull` の使用を推奨**．SmTIAS-LightSim 側のデータ受け取り手順としてもこれを前提にすると安全．

## MiniTIAS 側で検討中の改善案 (Planned Improvements)

SmTIAS-LightSim 側との打ち合わせ事項と並行して，以下を MiniTIAS 側で進める予定．優先度は内部判断による：

| 優先度 | 項目 | 概要 |
| --- | --- | --- |
| 高 | 画素統計を meta.json に追加 | 飽和・露光不足の自動判定用．`mean_per_channel`, `max_per_channel`, `p99`, `saturated_pixel_ratio` 等 |
| 高 | Hot pixel map 取得 | `STATISTICS_HOT_PIXEL_MAP_MODE = ON` で常時光る画素一覧を meta に記録．PC 側でマスク可能に |
| 高 | 連射機能（V1 検証用） | 10 連射 UI．`MiniTIAS_QM_*_burst{N}.dng` 等で連番保存 |
| 中 | 露光時間・ISO の半自動キャリブ機能 | 白板で AE 1 ショット → 値を読んで固定 |
| 中 | レンズ・センサ校正値を meta に追加 | `LENS_INTRINSIC_CALIBRATION`, `LENS_DISTORTION`, `SENSOR_REFERENCE_ILLUMINANT1/2`, `SENSOR_COLOR_TRANSFORM1/2` 等 |
| 低 | Dark frame / Flat field キャリブ機能 | `(対象 − Dark) / (Flat − Dark)` 補正のための基準フレーム撮影 |

これらは SmTIAS-LightSim 側からの要望順位とすり合わせて優先度を確定したい．

## 関連ドキュメント (Related Documents)

- [TECH_01 定量撮影モード要求仕様](TECH_01_定量撮影モード要求仕様.md) — 本実装の原始要求
- [TECH_02 MiniTIAS 引き継ぎ手順](TECH_02_MiniTIAS引き継ぎ手順.md) — 引き継ぎ時の作業ガイド
- [`docs/06_TEST/camera_diagnostics_20260531_161734.json`](../06_TEST/camera_diagnostics_20260531_161734.json) — AQUOS sense3 (SH-02M) の能力診断結果（フル）
- [SPEC_01 画面機能仕様書](../04_SPEC/SPEC_01_画面機能仕様書.md) — MiniTIAS 側の撮影仕様（定量モード反映済み）
- [PLAN_01 要件定義書](../03_PLAN/PLAN_01_要件定義書.md) — ファイル管理仕様
- [PROGRESS.md](../PROGRESS.md) — 実装の進捗ログと設計判断・失敗パターン
- [Android Camera2 API SHADING_MODE](https://developer.android.com/reference/android/hardware/camera2/CaptureRequest#SHADING_MODE)
- [Android Camera2 API STATISTICS_LENS_SHADING_CORRECTION_MAP](https://developer.android.com/reference/android/hardware/camera2/CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP)
