# バグ修正計画 (Bug Fix Plan)

## 0. 前提 (Prerequisites)

- `PLAN_01_リファクタリング計画.md` の全フェーズが完了し，動作がリファクタリング前と同一であることを確認してから着手する．
- 修正は1件ずつ独立して行い，都度テストを実施する．
- コミットメッセージは `[fix]` タグを使用する．
  - 例: `[fix] _shots へのスレッドセーフなアクセスに修正`

## 1. 症状 (Symptom)

- 連続撮影で4枚以上取得するとアプリがたまにクラッシュする．
- 再現性は不安定（毎回ではなく「たまに」落ちる）．

## 2. 原因と修正方針 (Causes and Fix Policy)

### 2-1. 優先度1: `_shots` へのスレッドセーフでないアクセス（根本原因）

**原因**

`_shots`（`List<Mat>`）は Shot スレッドと SaveThread から同時にアクセスされる．
`List<T>` はスレッドセーフではないため，以下の2点で競合が発生する．

- リサイズ競合
  - `List<T>` の内部配列は容量が足りなくなると自動で拡張される（デフォルト容量 4 → 5枚目の Add 時に 8 に拡張）．
  - 拡張中に SaveThread が `_shots[saveCount]` を読むとメモリ破壊が起きる．
  - 「4枚以上でたまに落ちる」という症状に一致する．
- TOCTOU（確認と使用の間の競合）
  - SaveThread の `_shots.Count` チェックと `_shots[saveCount]` アクセスの間に Shot スレッドがリサイズを起こすと `IndexOutOfRangeException` が発生する．

**修正方針**

- `_shots` を `List<Mat>` から `System.Collections.Concurrent.ConcurrentQueue<Mat>` またはロック付きアクセスに変更する．
- SaveThread の待機ロジックをスレッドセーフな方法（`SemaphoreSlim` 等）に変更する．

**テスト**

- 連続撮影を10枚以上で複数回繰り返し，クラッシュしないことを確認する．

### 2-2. 優先度2: `ConvertImage` 内の Mat 未 Dispose によるメモリ蓄積

**原因**

`ConvertImage` 内で生成される中間 Mat（`extended`，`converted`，`convertedImage`）が Dispose されないまま残る．
1回の呼び出しで数百 MB〜1 GB 超が蓄積し，画像枚数×チャンネル数分だけ積み重なる．
（例: IScam 1840×2440，17次元，4枚連続撮影 → 数十 GB 規模）

**修正方針**

- `extended`，`converted`，`convertedImage` を `using` ブロックで管理して確実に Dispose する．

**テスト**

- タスクマネージャでメモリ使用量を監視しながら連続撮影を繰り返し，撮影ごとにメモリが増加し続けないことを確認する．

### 2-3. 優先度3: `Parallel.For` 内での未捕捉例外

**原因**

`ExtendMat` の `Parallel.For` 内で OOM 等の例外が発生した場合，`AggregateException` としてラップされて SaveThread に伝播する．
SaveThread で捕捉されなければアプリがクラッシュする．
※ 優先度2の修正でメモリ蓄積が解消されれば，発生頻度は大幅に下がる．

**修正方針**

- SaveThread に例外ハンドラを追加し，クラッシュではなくエラーメッセージを表示して処理を中断するようにする．
- `ExtendMat` の `Parallel.For` に try-catch を追加する．

**テスト**

- 例外発生時にアプリが落ちずエラーが表示されることを確認する．

### 2-4. 優先度4: `ConvertImage` による `_shots[i]` の in-place 書き換え

**原因**

`ConvertImage` 内の `src.ConvertTo(src, CV_64FC3)` が `_shots[i]` 自体を CV_8UC3 から CV_64FC3 に書き換える副作用がある．
特に IScam の `new Mat(imgt_full, _roi)`（サブマトリクス）に対して in-place の型変換を行うと不正な動作を引き起こす可能性がある．
また複数チャンネルを処理する際，2回目以降は変換済みのデータに対して処理をかけることになり，変換結果が誤る．

**修正方針**

- `ConvertImage` の冒頭で `src` をコピーし，コピー側を変換するようにする（`src` を直接書き換えない）．

**テスト**

- 複数チャンネル（例: `4,10,17`）で撮影し，各チャンネルの sRGB 変換画像が正しく出力されることを確認する．

### 2-5. 優先度5: Mat 未 Dispose によるメモリリーク

**原因 A: `_shots.Clear()` で Mat が解放されない**

- 撮影ごとに `_shots.Clear()` が呼ばれるが，格納された Mat は Dispose されない．

**原因 B: `_chartMasks.Clear()` で Mat が解放されない**

- チャート再検出時に `_chartMasks.Clear()` が呼ばれるが，同様に Dispose されない．

**修正方針**

- Clear 前に各 Mat を Dispose するヘルパーを追加する．

**テスト**

- タスクマネージャでメモリ使用量を監視し，撮影・キャリブを繰り返してもメモリが増加し続けないことを確認する．

## 3. 修正順序まとめ (Summary)

| 順位 | 原因 | クラッシュとの関連 |
| ---- | ---- | ------------------ |
| 1 | `_shots` のスレッドセーフでないアクセス | 直接原因（「たまに落ちる」に一致） |
| 2 | `ConvertImage` 内の Mat 未 Dispose | 直接原因（OOM によるクラッシュ） |
| 3 | `Parallel.For` 内の未捕捉例外 | 間接原因（2の修正で頻度減） |
| 4 | `ConvertImage` の in-place 書き換え | 誤動作（変換結果の誤り） |
| 5 | `_shots` / `_chartMasks` の Mat 未 Dispose | メモリリーク（蓄積） |
