diff --git "a/docs/05_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.txt" "b/docs/05_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.txt" new file mode 100644 index 0000000..a60c933 --- /dev/null +++ "b/docs/05_PLAN/PLAN_01_\343\203\252\343\203\225\343\202\241\343\202\257\343\202\277\343\203\252\343\203\263\343\202\260\350\250\210\347\224\273.txt" @@ -0,0 +1,102 @@ +======================================================================== +リファクタリング計画 (Refactoring Plan) +======================================================================== + +0. 方針 (Policy) +------------------------------------------------------------------------ +・処理の変更は一切行わない.構造の整理のみを目的とする. +・各フェーズ完了後に必ずテストを実施し,動作が変わっていないことを確認してから次へ進む. +・テストの具体的な手順は `TEST_01_テスト仕様.txt` を参照する. + + +1. 現状の問題点 (Current Issues) +------------------------------------------------------------------------ +`CameraBase.cs` に以下の責務が集中している. + + ・カメラ制御の抽象定義(Connect/Disconnect/Shot) + ・チャート検出(ArUco マーカー検出,ホモグラフィ計算,マスク生成) + ・色補正処理(TCC行列計算,多項式拡張,画像変換) + ・ファイル I/O(CSV読み書き,保存フォルダ生成,撮影情報出力) + ・サウンド再生 + + +2. 目標とするディレクトリ構成 (Target Structure) +------------------------------------------------------------------------ + + TIASshot/ + ├── Cameras/ ← カメラ制御(CameraBase, IScam, Lucam) + ├── ChartDetection/ ← チャート検出(CameraBaseから分離) + ├── ColorCorrection/ ← 色補正・TCC計算(CameraBaseから分離) + ├── UI/ ← フォーム(Form1, PreviewMonitor) + ├── Config.cs ← 設定ファイル読み込み(変更なし) + ├── LightSource.cs ← 光源制御(変更なし) + └── Program.cs ← エントリポイント(変更なし) + + +3. フェーズ構成 (Phases) +------------------------------------------------------------------------ + +3-1. フェーズ1: フォルダ整理とファイル移動 + + ■ 作業内容 + ・以下のフォルダを作成し,既存ファイルを移動する. + - `Cameras/` ← CameraBase.cs, IScam.cs, Lucam.cs + - `UI/` ← Form1.cs, Form1.Designer.cs, Form1.resx, + PreviewMonitor.cs, PreviewMonitor.Designer.cs, PreviewMonitor.resx + ・名前空間(namespace)はそのまま `TIASshot` で統一する. + ・ファイルの中身は変更しない. + + ■ テスト + ・ビルドが成功することを確認する. + ・アプリが起動し,カメラが接続されることを確認する(手動). + +3-2. フェーズ2: 色補正処理の分離 + + ■ 作業内容 + ・`ColorCorrection/` フォルダを作成する. + ・CameraBase から以下を新クラス `ColorCorrector` に切り出す. + - フィールド: `TCC_SRGB`, `TCC_XYZ`, `ChannelList`, `ExtendChannels`, `UpdateRate` + - メソッド: `ExtendMat`, `CalcConvertMatrix`, `ConvertColor`, `ConvertImage` + - メソッド: `CalcTcc`(チャート RGB 値の受け取りと行列計算・保存を担当) + ・CameraBase は `ColorCorrector` を保持し,処理を委譲する. + + ■ テスト + ・ビルドが成功することを確認する. + ・回帰テスト: 同一入力画像から算出した変換行列が,リファクタリング前と一致することを確認する. + - 具体的な手順は `TEST_01_テスト仕様.txt` の「2. 回帰テスト」を参照する. + +3-3. フェーズ3: チャート検出処理の分離 + + ■ 作業内容 + ・`ChartDetection/` フォルダを作成する. + ・CameraBase から以下を新クラス `ChartDetector` に切り出す. + - フィールド: `ARDict`, `PointsDst40`, `PointsDst41`, `_detectionCount`, `_lastPosition` + - メソッド: `DetectChart` + - 出力: チャートマスクリスト(`List`)を戻り値または引数で渡す + ・CameraBase は `ChartDetector` を保持し,処理を委譲する. + + ■ テスト + ・ビルドが成功することを確認する. + ・回帰テスト: 保存済みの実画像を入力として,チャートマスクの位置が一致することを確認する. + - 具体的な手順は `TEST_01_テスト仕様.txt` の「3. チャート検出テスト」を参照する. + +3-4. フェーズ4: ファイル I/O・サウンドの分離 + + ■ 作業内容 + ・CameraBase から以下をユーティリティクラスとして切り出す(配置先は `Cameras/` または新フォルダ). + - `LoadMatFromCsv`, `SaveMatToCsv` + - `SetSaveFolder`, `WriteInfo` + - `EventSound` + + ■ テスト + ・ビルドが成功することを確認する. + ・撮影からファイル保存までの一連の動作を手動で確認する. + ・保存された CSV・画像の内容がリファクタリング前と一致することを確認する. + + +4. 共通ルール (Common Rules) +------------------------------------------------------------------------ +・各フェーズは独立したブランチではなく,`refactor/code-cleanup` ブランチ上で順に進める. +・フェーズ完了ごとにコミットする.コミットメッセージは `[clean]` タグを使用する. + - 例: `[clean] 色補正処理を ColorCorrector クラスに分離` +・テストが通らない場合は,次のフェーズに進まない. diff --git "a/docs/05_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.txt" "b/docs/05_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.txt" new file mode 100644 index 0000000..073b746 --- /dev/null +++ "b/docs/05_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.txt" @@ -0,0 +1,126 @@ +======================================================================== +バグ修正計画 (Bug Fix Plan) +======================================================================== + +0. 前提 (Prerequisites) +------------------------------------------------------------------------ +・PLAN_01_リファクタリング計画.txt の全フェーズが完了し, + 動作がリファクタリング前と同一であることを確認してから着手する. +・修正は1件ずつ独立して行い,都度テストを実施する. +・コミットメッセージは `[fix]` タグを使用する. + - 例: `[fix] _shots へのスレッドセーフなアクセスに修正` + + +1. 症状 (Symptom) +------------------------------------------------------------------------ +・連続撮影で4枚以上取得するとアプリがたまにクラッシュする. +・再現性は不安定(毎回ではなく「たまに」落ちる). + + +2. 原因と修正方針 (Causes and Fix Policy) +------------------------------------------------------------------------ + +2-1. 優先度1: _shots へのスレッドセーフでないアクセス(根本原因) +------------------------------------------------------------------------ +■ 原因 + `_shots`(`List`)は Shot スレッドと SaveThread から同時にアクセスされる. + `List` はスレッドセーフではないため,以下の2点で競合が発生する. + + A. リサイズ競合 + ・`List` の内部配列は容量が足りなくなると自動で拡張される + (デフォルト容量 4 → 5枚目の Add 時に 8 に拡張). + ・拡張中に SaveThread が `_shots[saveCount]` を読むとメモリ破壊が起きる. + ・「4枚以上でたまに落ちる」という症状に一致する. + + B. TOCTOU(確認と使用の間の競合) + ・SaveThread の `_shots.Count` チェックと `_shots[saveCount]` アクセスの + 間に Shot スレッドがリサイズを起こすと `IndexOutOfRangeException` が発生する. + +■ 修正方針 + ・`_shots` を `List` から `System.Collections.Concurrent.ConcurrentQueue` + またはロック付きアクセスに変更する. + ・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 メモリリーク(蓄積) diff --git "a/docs/06_TEST/TEST_01_\343\203\206\343\202\271\343\203\210\344\273\225\346\247\230.txt" "b/docs/06_TEST/TEST_01_\343\203\206\343\202\271\343\203\210\344\273\225\346\247\230.txt" new file mode 100644 index 0000000..516ae3f --- /dev/null +++ "b/docs/06_TEST/TEST_01_\343\203\206\343\202\271\343\203\210\344\273\225\346\247\230.txt" @@ -0,0 +1,87 @@ +======================================================================== +テスト仕様 (Test Specification) +======================================================================== + +0. 方針 (Policy) +------------------------------------------------------------------------ +リファクタリングによって処理が変わっていないことを確認するためのテスト仕様. +各フェーズの適用タイミングは `PLAN_01_リファクタリング計画.txt` を参照する. + +テストの種類: + ・ビルドテスト: コンパイルエラーがないことを確認する + ・回帰テスト: リファクタリング前後の出力が一致することを確認する + ・手動テスト: 実機を使って動作を目視確認する + + +1. ビルドテスト (Build Test) +------------------------------------------------------------------------ +全フェーズで実施する. + +■ 手順 + 1. Visual Studio で「ビルド」→「ソリューションのビルド」を実行する + 2. エラーが0件であることを確認する + 3. 警告が増加していないことを確認する(リファクタリング前の警告数を記録しておく) + +■ 合格条件 + ・ビルドエラーが0件 + + +2. 回帰テスト: 色補正処理(フェーズ2) +------------------------------------------------------------------------ +リファクタリング前に基準データを作成し,リファクタリング後の出力と比較する. + +2-1. 基準データの作成(リファクタリング前に実施) + 1. 実機でキャリブレーションを実行する + 2. 保存された以下のファイルを「基準データ」として別フォルダに保管する + ・`TCC_RGB.csv` ← チャート24色票のRGB値 + ・`Conv_RGB-sRGB{CN}.csv` ← RGB→sRGB 変換行列 + ・`Conv_RGB-XYZ{CN}.csv` ← RGB→XYZ 変換行列 + ・`Conv_sRGB-XYZ{CN}.csv` ← sRGB→XYZ 変換行列 + ・`TCC_sRGB{CN}.csv` ← チャートのsRGB変換値 + ・`sRGB{CN}_0001.jpg` ← 撮影した sRGB 変換画像1枚 + +2-2. 比較(リファクタリング後に実施) + 1. 同一のキャリブレーション画像・撮影条件で再度実行する + ※ 実機でのキャリブレーション再現が困難な場合は, + TCC_RGB.csv を直接入力として行列計算のみ比較する + 2. 基準データと出力ファイルを比較する + +■ 合格条件 + ・CSV の数値が全行・全列で一致する(または浮動小数点誤差 ±1e-6 以内) + ・画像のピクセル値が全ピクセルで一致する + + +3. チャート検出テスト(フェーズ3) +------------------------------------------------------------------------ +保存済みの実画像を入力として,チャートマスクの位置を比較する. + +3-1. 基準データの作成(リファクタリング前に実施) + 1. キャリブレーション時に保存される `TCC_ROIs.jpg` を基準データとして保管する + ※ TCC_ROIs.jpg はチャートの ROI マスク位置を緑色で重ねた確認画像 + +3-2. 比較(リファクタリング後に実施) + 1. 同一条件でキャリブレーションを実行する + 2. 出力された `TCC_ROIs.jpg` を基準データと目視比較する + ※ 緑色のマスク位置が完全に一致していることを確認する + +■ 合格条件 + ・マスクの位置・形状が基準データと目視で一致する + + +4. 手動テスト(全フェーズ) +------------------------------------------------------------------------ +実機を使った動作確認.フェーズごとに実施する. + +■ 確認項目 + ・アプリが正常に起動する + ・カメラが自動で接続される(Lucam または ImagingSource) + ・プレビューが表示される + ・舌診チャートを映すとキャリブレーションが自動開始される + ・キャリブレーション完了後に撮影ボタンが有効になる + ・1枚撮影が正常に完了し,ファイルが保存される + ・連続撮影が正常に完了し,ファイルが保存される + ・光源の ON/OFF が切り替わる + ・アプリを閉じてもエラーが出ない + +■ 合格条件 + ・上記の全項目でエラー・クラッシュが発生しない