# 実機自動テスト計画 (Hardware-in-the-Loop Test Plan)

## 目的 (Purpose)

[PLAN_05](PLAN_05_テスト網羅性向上計画.md) の 4 層モデルで「層④: 実機」に残っている確認（SDK の実呼び出し・実画像）を，**Claude が実機を操作して自動で確認する**ようにする．人間の役割を「機材をつなぐ・チャートを置く・テストしてよいと合図する」という物理的な準備だけに絞る．

## 前提 (Preconditions)

2026-10-06 にユーザーと確認した．

| 項目 | 内容 |
| --- | --- |
| 接続 | カメラ（ImagingSource・Lumenera）と光源は，Claude が動作している開発 PC に接続できる |
| 機材 | 両機種がそろっている |
| 対象機種 | **ImagingSource だけを対象にし，Lumenera の実機テストは行わない**（2026-10-08 にユーザーが決定．Lumenera は解像度が相対的に低く今後あまり使わない見込みのため）．`--camera lucam` の実装は残すが，検査の対象にしない |
| 実行してよい条件 | **ユーザーが会話の中で明示的に合図したときだけ**．撮影の本番中に機材を操作しないため，合図なしに実機テストを実行しない |

## 仕組み (Design)

既存のセルフテスト（`TIASshot.exe --selftest`．偽カメラ・ヘッドレス）と同じ考え方で，実機版の検査モードを追加する．

- **起動**: `TIASshot.exe --hwtest --camera <lucam|iscam> [--with-chart] --hwtest-out <結果ファイル>`．ランナー `scripts/verify/run_hwtest.py` がビルドから実行・判定までを行う（`run_selftest.py` と同じ形）
- **ビルドフラグ**: `HWTEST`（Debug 系の構成のみ）．診断・評価機能を Release に含めない方針（CLAUDE.md・[ENV_06](../02_ENV/ENV_06_ビルド構成とビルドフラグ.md)）に従う
- **フォーム**: ImagingSource は SDK の画面部品（`ICImagingControl`）を使うため，ヘッドレスではなく**実際の `Form1` を表示して**動かす．人間がその場にいる前提なので表示してよい
- **カメラの選択**: 両機種を同時につないでも，`--camera` で指定した機種だけを使う（通常起動の `Form1_Load` は Lumenera を優先して探すため，そのままでは ImagingSource を検査できない）
- **結果**: 項目ごとの PASS / FAIL と測定値を結果ファイルに書き，終了コード（0=合格，1=不合格あり，2=実機テスト自体の異常，3=タイムアウト）で返す．セルフテストの結果ファイルと同じ書式にする

### 守ること (Safety)

- 保存先は一時フォルダ（`%TEMP%` 配下）にする．本番の保存先（`C:\TIAS_Data` 等）には書き込まない
- 同梱の `config.xml` は変更しない（一時フォルダに複製して保存先等を差し替える）
- カメラのデバイス状態ファイル（`{DeviceName}.xml`）は読むだけで，上書きしない．露光・ゲイン等を検査で変えた場合は，終了時に元の値へ戻す
  - ImagingSource の露光・ゲインは絶対値（`VCDAbsoluteValueProperty`．露光は秒，ゲインは dB）で読み書きし，元の絶対値そのものへ戻して読み戻しで確かめる（整数の段（`VCDRangeProperty`）で扱うと元の値に戻せないため）
- チャート不要の検査の間は自動校正（チャート検出・ホワイトバランスの調整・変換行列の算出）を止める（`CameraBase` の校正停止フラグ．`HWTEST` ビルド限定）．はめたチャートで校正が走ると露光・ゲイン・ホワイトバランスが検査中に変わるため
- 光源は検査の最後に必ずポートを閉じる（失敗した場合も）．消灯はしない（アプリ終了時に光源を消さない仕様に合わせる．`light` 項目の最後に再点灯した状態で終わる）
- 待機には必ずタイムアウトを付ける

## 検査項目 (Test Items)

### チャート不要（`--with-chart` なし）

※ 穴に何もはめないと中が暗く明るさの変化を評価しにくいため，**カラーチャート（または白板）をはめた状態で行う**．この検査の間は，チャートが写っても自動校正を走らせない（テストモードだけの処理．2026-10-08 にユーザーと合意）．

| ID | 確認内容 | 判定の仕方 |
| --- | --- | --- |
| `connect` | 指定した機種に接続でき，機種名・シリアル番号が取れる | `Connect()` の成否 |
| `preview` | プレビューが流れる | 一定時間に届いたフレーム数（フレームレートの下限） |
| `exposure-gain` | 露光・ゲインの設定がフレームに効く | 基準の明るさを探してから，露光を 1 段（2^n 秒の段で半分・2 倍），ゲインを範囲の幅の ±10% 上げ下げしたときの平均輝度の変化の向きと量．飽和した向きは確認しない．終了時に元の値へ戻ったことを読み戻しで確かめる |
| `light` | 光源が点灯・消灯し，閉じた後に開き直せる | 点灯時と消灯時のフレームの平均輝度の差，ポートの再オープンの成否 |
| `close-preview` | プレビュー中に閉じる処理を呼んでもすぐ戻る | 後始末の所要時間（上限つき）と例外の有無，閉じた後に届いたフレーム数 |
| `reconnect` | 切断した後に再接続できる | 2 回目の `Connect()` の成否とフレームの到着 |
| `cleanup` | 後始末と書き込み禁止 | 露光・ゲインを戻す → カメラ切断 → 光源のポートを閉じる（消灯しない）の成否，設定・デバイス状態ファイル・本番の保存先が変わっていないこと |

露光・ゲインの基準探し: 元の露光に最も近い 2^n 秒の段に合わせ，平均輝度が中間（30〜220）かつ飽和画素（254 以上）の割合が 1% 以下になるまで露光を 1 段ずつ動かす（最大 12 段）．実測で露光 1 段は 0.497 倍（黒レベル 15.2 を引いた値）≈ 0.5 倍，ゲイン 1 dB は 1.122 倍 ≈ 10^(1/20) と理論どおりに効いた．

### チャートが必要（`--with-chart`．人間がチャートをカメラの前に置いてから実行する）

※ チャート不要の検査と同じくカラーチャートをはめたまま続けて実行できる（付け外しは不要）．こちらは自動校正を走らせて結果を確かめる．

| ID | 確認内容 | 判定の仕方 |
| --- | --- | --- |
| `calibrate` | 実物の舌診チャートで自動校正が完了する | 校正完了までの時間（上限つき）と，変換後の色票と参照値の色差 |
| `shot` | 単発・連続撮影で保存される | 保存されたファイル・Info.csv の中身，撮影時刻の間隔（設定値との差） |
| `close-saving` | 保存中に閉じようとすると取り消され，保存後は閉じられる | 取り消しの成否と表示文，保存完了後の後始末の順序 |
| `close-calibrating` | 校正中に閉じてもすぐ戻る | 後始末の所要時間と例外の有無 |

※ 判定のしきい値（色差の上限，撮影間隔の許容誤差等）は，最初の実行で実測値を見てから決める．チャート不要の検査のしきい値はフェーズ 1 で決めた（下記「確定したしきい値と実測値」）．

### 確定したしきい値と実測値 (Thresholds and Measurements)

フェーズ 1 の実測（ImagingSource DFK 33UX178．シリアル 35620307・10620446．カラーチャートをはめた状態）から決めた．しきい値の本体は `HwTest/HwTestJudge.cs`．

| 項目 | しきい値 | 実測値 |
| --- | --- | --- |
| `preview` のフレームレート | 10 fps 以上 | 13〜18 fps |
| `exposure-gain` の露光 | 1 段下げで比 0.8 以下，1 段上げで比 1.25 以上（向きごと．飽和した向きは確認しない） | 下げ 0.545（2026-10-08 13:38 の実行．上げは比 1.322 だが飽和画素 70% のため確認しない向き） |
| `exposure-gain` のゲイン | 平均輝度の差 3 以上（片側の向きだけ期待どおりでも合格） | 下げ −15.6（同上．上げは +47.1 だが飽和画素 68% のため確認しない向き） |
| 基準の明るさ | 平均輝度 30〜220，飽和画素（254 以上）の割合 1% 以下 | 元の露光に近い段（0.015625 秒）で 158.9 |
| 飽和の判定 | 254 以上の画素の割合が 1% を超える | — |
| `light` の点灯と消灯の差 | 平均輝度の差 100 以上 | 153〜196 |
| `close-preview` の閉じる処理 | 1000 ms 以内，閉じた後に届くフレーム 2 以下 | 75〜221 ms，0 フレーム |
| `reconnect` | 再接続後に 5 フレーム以上 | 964 ms で 5 フレーム（2026-10-08 13:38 の実行） |
| 露光・ゲインの復元 | 元の値との相対誤差 1% 以内 | 一致 |

## 進め方 (Workflow)

1. 人間が機材をつなぎ，カラーチャート（または白板）を穴にはめて，「実機テストして」等と合図する
2. Claude（verifier）が `python scripts/verify/run_hwtest.py --camera <機種> [--with-chart]` を実行し，結果ファイルと終了コードで判定する
3. 対象は ImagingSource（`--camera iscam`）だけ．Lumenera は行わない（上記「対象機種」）
4. 人間に残る確認は，画面の見た目の最終確認など，数値で置き換えられないものだけにする

## 変更が必要なもの (Changes)

| 対象 | 変更内容 |
| --- | --- |
| `TIASshot`（本体） | `HWTEST` フラグの追加，`--hwtest` の実装（検査項目・結果ファイル），カメラを機種指定で接続する口 |
| `scripts/verify/run_hwtest.py` | ビルド・実行・判定のランナー |
| `.claude/verify-profile.md` | 実機カメラ・光源・シリアルポートへの接続を，「ユーザーが会話の中で合図したとき」に限って許可する．合図がなければ実機テストは実行せず「検証不能（合図なし）」と報告する |
| `.claude/rules/testing.md` | 層④のうち実機テストで確認できる項目を明記する |
| [TEST_01](../06_TEST/TEST_01_テスト仕様.md) | 手動テストの項目を，実機テストで置き換えた分だけ減らす |
| [ENV_04](../02_ENV/ENV_04_開発コマンド.md)・[ENV_06](../02_ENV/ENV_06_ビルド構成とビルドフラグ.md) | 実機テストのコマンドと `HWTEST` フラグの説明 |

## フェーズ (Phases)

### フェーズ 1: 基盤とチャート不要の検査

- `HWTEST` フラグ，`--hwtest`，機種指定の接続，結果ファイル，ランナーを作る
- チャート不要の検査（`connect`・`preview`・`exposure-gain`・`light`・`close-preview`・`reconnect`）を実装する
- 検証プロファイルを更新する
- 完了条件: ImagingSource で人間の合図のもと実行でき，しきい値を実測から決めて全項目が合格する（当初は両機種としていたが，2026-10-08 に Lumenera を対象外とした）
- **状態: ImagingSource で完了（2026-10-08．DFK 33UX178 の 2 個体で 7/7 合格）．Lumenera の実機テストは行わない（対象外）**
- 実機テストで見つかった不具合: 同梱の `DFK33UX178.xml` は別個体（シリアル 41520093）のものを指しており，旧 `IScam.Connect` はファイルのデバイスを開いていたため，別個体では接続できず例外が外へ漏れていた．機種名でデバイスを開いてから設定だけを適用し，失敗は `ErrorMsg`＋`false` で返すように直した（`Cameras/IScamDeviceSelector`．[TECH_02](../05_TECH/TECH_02_カメラ制御仕様.md)）
- 仕様変更: アプリ終了時に光源を消灯しない（ポートを閉じるだけ．上流 v1.7 と同じ．2026-10-08 にユーザーが確定）．実機テストの後始末も同じく消灯しない

### フェーズ 2: チャートが必要な検査

- `calibrate`・`shot`・`close-saving`・`close-calibrating` を実装する
- 完了条件: ImagingSource で実物のチャートを使って全項目が合格する

### フェーズ 3: 手動テストの縮小

- TEST_01 の手動テストと PLAN_05 の「手動確認の目標形」を，実機テストで置き換えた分だけ縮める

## PLAN_05 との関係 (Relation to PLAN_05)

- PLAN_05 フェーズ 3b（SDK アダプタからの判断ロジックの切り出し）とは独立に進められる．3b で切り出したロジックは層①で，SDK の呼び出しそのものは本計画の実機テストで確認する，という分担になる
- PLAN_05 フェーズ 4（実機録画データによる回帰）の撮影は，本計画の実機テストの仕組みを使って自動化できる可能性がある（録画フレームの保存を検査モードに加える）

## リスクと対策 (Risks)

| リスク | 対策 |
| --- | --- |
| 撮影の本番中に Claude が機材を操作してしまう | 実行はユーザーの合図があったときだけ．検証プロファイルで明記し，合図がなければ検証不能として報告する |
| 検査でカメラの設定が変わったまま残る | デバイス状態ファイルは上書きしない．変えた露光・ゲインは終了時に元に戻す |
| 光源のポートが開いたまま残る | 検査の最後（失敗時も）にポートを閉じる．光源は点いたままでよい（アプリ終了時と同じ仕様） |
| 実機の個体差・照明で判定が不安定になる | しきい値は実測から余裕を持って決める．輝度は絶対値ではなく変化の向きで判定する |
| SDK がハングして検査が終わらない | 検査全体と各項目にタイムアウトを付け，ランナー側でもプロセスを打ち切る |
