diff --git a/.claude/rules/error-handling.md b/.claude/rules/error-handling.md index 7c69a4c..2ecc185 100644 --- a/.claude/rules/error-handling.md +++ b/.claude/rules/error-handling.md @@ -49,5 +49,4 @@ ## 関連 (Related) -- 撮影・保存処理の例外時の中断・終端は [PLAN_02_バグ修正計画](../../docs/03_PLAN/PLAN_02_バグ修正計画.md)(特に優先度3)と整合させる. - テストとの関係は [testing](testing.md) を参照する(エラー処理のうち純粋ロジックで検証可能な部分は自動テストの対象とする). diff --git a/CLAUDE.md b/CLAUDE.md index 4b6742b..54ecc30 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,15 +1,24 @@ -# プロジェクト名 +# TIAS Shot - +舌診用の撮影ソフトウェア(WinForms / .NET Framework 4.8 / x64).産業用カメラ(ImagingSource・Lumenera)で舌を撮影し,舌診チャートによる自動校正と色変換行列の算出を経て sRGB 変換画像を保存する.上流(nakaguchi)v1.7 からのフォークで,クラッシュ・メモリリーク修正と性能改善を積んでいる([docs/変更まとめ_v1.6-v1.7から.md](docs/変更まとめ_v1.6-v1.7から.md)). ## 開発進捗 -最新: PLAN_03 プレビューメモリリーク(原因1〜3)+派生の Lucam クロススレッド GDI クラッシュ・副モニタ描画飢餓を修正し両プレビューをタイマー駆動化,フレームコピーを LockBits で高速化(~25ms→~2.5ms).FastClone に仕様ベーステスト追加(UI/並行系は実機 MEMMONITOR 計測で検証) +最新: 3 バージョン(v1.6/v1.7/現行)を実機 MEMMONITOR で性能評価計測し,2 段階改善(保存経路 v1.6→v1.7・プレビュー経路 v1.7→現行)を定量化(PLAN_04/TEST_02・PR #18).あわせて完了済み PLAN_01/02 と対比用 LEAKY_REPRO フラグを撤去(挙動不変) ※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは `.claude/rules/progress-log.md` を参照(該当ファイル編集時に自動ロードされる). ## 必須ルール(コード実装時) - +### プロジェクト固有の規約(本体は `.claude/rules/` の path-scoped rule・該当ファイル編集時に自動ロード) + +- **テスト方針**(`.claude/rules/testing.md`): ピュアロジック層は NUnit で必ず自動テスト,ハードウェア結合層(カメラ/UI/スレッド)は対象外としビルド+実機で担保.テストも **x64 必須**(`AnyCPU` はネイティブ DLL ロードで実行時に失敗) +- **エラーハンドリング方針**(`.claude/rules/error-handling.md`): `ErrorMsg`+`bool` 返却・`ShowMessage`/`MessageBox`・`Debug.WriteLine` の既存パターンに従う.空 `catch` 禁止.**`Mat`・`Bitmap` 等のネイティブ資源は `using`/`try-finally` で確実に解放**(GC 任せはリーク→OOM/GDI 枯渇クラッシュ) +- ※ コーディング規約(coding-style)・プロジェクト構成規則(project-structure)は**未整備**(GUIDE_01 の規約整備フェーズ参照) + +### rule 未収載の固有不変条件(コード変更時に壊さないこと) + +- **プレビュー表示 Bitmap の所有権モデル**: カメラコールバックが渡す入力 bmp は「読むだけ」,表示は UI 所有コピー(`FastClone`),入力の Dispose 責任は呼び出し側.コールバックスレッドから毎フレーム `Invalidate()` しない(描画はタイマー駆動).経緯は [PLAN_03](docs/03_PLAN/PLAN_03_プレビューメモリリーク修正計画.md) +- **診断・評価機能は Release に含めない**: ビルドフラグ(`MEMMONITOR`・`WHITEBOARD`)でコードごと出し分ける([ENV_06](docs/02_ENV/ENV_06_ビルド構成とビルドフラグ.md)) ### Git 運用 @@ -63,16 +72,30 @@ ### 03_PLAN(計画) - +- プレビューメモリリーク修正計画: docs/03_PLAN/PLAN_03_プレビューメモリリーク修正計画.md +- 性能評価計測計画: docs/03_PLAN/PLAN_04_性能評価計測計画.md +- ※ PLAN_01(リファクタリング)・PLAN_02(バグ修正)は完了につき削除済み(要点は docs/PROGRESS.md,経緯は git 履歴) ### 04_SPEC(仕様・設計) - +- 機能仕様: docs/04_SPEC/SPEC_01_機能仕様.md +- 設定ファイル仕様: docs/04_SPEC/SPEC_02_設定ファイル仕様.md +- 白板撮影プロトコル(照明均一性評価): docs/04_SPEC/SPEC_03_白板撮影_照明均一性評価.md +- 白板撮影データ仕様(SmTIAS 引き渡し用): docs/04_SPEC/SPEC_04_白板撮影データ仕様.md ### 05_TECH(技術設計) - +- クラス設計: docs/05_TECH/TECH_01_クラス設計.md +- カメラ制御仕様: docs/05_TECH/TECH_02_カメラ制御仕様.md +- 色補正処理仕様: docs/05_TECH/TECH_03_色補正処理仕様.md ### 06_TEST(テスト) - +- テスト仕様(回帰・手動テスト手順): docs/06_TEST/TEST_01_テスト仕様.md +- 性能評価計測結果(v1.6/v1.7/現行の 3 版比較): docs/06_TEST/TEST_02_性能評価計測結果.md +- メモリ修正効果計測(自動ベンチ): docs/06_TEST/TEST_03_メモリ修正効果計測.md + +### その他(docs 直下) + +- 進捗フルログ: docs/PROGRESS.md +- 上流向け変更サマリ(v1.6/v1.7 から何をどう直したか・差分確認付き): docs/変更まとめ_v1.6-v1.7から.md diff --git a/README.md b/README.md index 3d7aecf..4dcd620 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,34 @@ # TIAS Shot -TIAS撮影ソフトウェア +TIAS 撮影ソフトウェア(舌診用).舌診チャートによる自動校正と色変換行列の算出を経て,sRGB 変換画像を保存する. -## 説明 -- 新カメラ ImagingSource DBK33UX178(ソースコード上は DFK33UX178,実機の型番は要確認), DFK23UX249 対応 +## 説明 (Features) + +- 新カメラ ImagingSource DBK33UX178(ソースコード上は DFK33UX178,実機の型番は要確認),DFK23UX249 対応 - 旧カメラ Lumenera Lw110 対応 -- 舌診チャート対応 +- 舌診チャート対応(チャート検出による自動校正) - 変換行列の次数を変更可能(設定ファイルで指定) -- 光源のON/OFF制御 -- sRGB変換写真の保存 +- 光源の ON/OFF 制御 +- sRGB 変換写真の保存 +- 白板撮影モード(照明均一性評価用・`WHITEBOARD` ビルド限定) +- メモリ観測ツール(性能計測用・`MEMMONITOR` ビルド限定) -## 開発環境 -- Visual C# 2022 -- OpenCV Sharp 4.10 +## v1.6/v1.7 からの変更 (Changes since v1.6/v1.7) + +本リポジトリは上流(nakaguchi)v1.7 からのフォークで,クラッシュ修正・メモリリーク修正・性能改善を積んでいる.**何をどう直したか(各変更の状況と差分の確認方法)**は次を参照する. + +- [docs/変更まとめ_v1.6-v1.7から.md](docs/変更まとめ_v1.6-v1.7から.md) — 変更ごとの「状況/変更/効果/差分確認コマンド」 +- [docs/06_TEST/TEST_02_性能評価計測結果.md](docs/06_TEST/TEST_02_性能評価計測結果.md) — 3 バージョン実機計測の定量比較 + +## 開発環境 (Development Environment) + +- Visual Studio 2022(Visual C#) +- .NET Framework 4.8 / **x64 のみ**(OpenCvSharp がネイティブ DLL に依存) +- OpenCvSharp 4.10 +- カメラ SDK: IC Imaging Control 3.5(ImagingSource)/lumenera.api(Lumenera) + +環境構築は [docs/02_ENV/ENV_02_環境構築手順.md](docs/02_ENV/ENV_02_環境構築手順.md),ビルド・テスト・実行コマンドは [docs/02_ENV/ENV_04_開発コマンド.md](docs/02_ENV/ENV_04_開発コマンド.md),ビルドフラグ(`WHITEBOARD`・`MEMMONITOR`)は [docs/02_ENV/ENV_06_ビルド構成とビルドフラグ.md](docs/02_ENV/ENV_06_ビルド構成とビルドフラグ.md) を参照する. + +## ドキュメント (Documents) + +設計・仕様・テストのドキュメントは `docs/` にカテゴリ別(`01_GUIDE`〜`06_TEST`)で置く.全体像は [docs/02_ENV/ENV_05_ディレクトリ構成.md](docs/02_ENV/ENV_05_ディレクトリ構成.md),開発の経緯は [docs/PROGRESS.md](docs/PROGRESS.md) を参照する. diff --git a/TIASshot.Tests/BitmapUtilTests.cs b/TIASshot.Tests/BitmapUtilTests.cs index 7e1f32b..05a7041 100644 --- a/TIASshot.Tests/BitmapUtilTests.cs +++ b/TIASshot.Tests/BitmapUtilTests.cs @@ -16,7 +16,7 @@ /// Lucam コールバックスレッドと UI スレッドの GDI 競合・WM_PAINT/WM_TIMER の /// スケジューリングは,実メッセージループ・実カメラ・副ディスプレイに依存する /// ハードウェア結合層(GUIDE_08)であり,NUnit の自動ユニットテストの対象外とする。 - /// これらは実機の MEMMONITOR 計測(TEST_01 参照)で反復検証済み。 + /// これらは実機の MEMMONITOR 計測(TEST_03 参照)で反復検証済み。 /// フェイクの UI ループを組んでまで検証する脆いテストは作らない。 /// [TestFixture] diff --git a/TIASshot/Cameras/IScam.cs b/TIASshot/Cameras/IScam.cs index f6e3eef..1652a83 100644 --- a/TIASshot/Cameras/IScam.cs +++ b/TIASshot/Cameras/IScam.cs @@ -55,9 +55,6 @@ ErrorMsg = $"設定ファイル{configFile}の読み込みに失敗しました"; return false; } - //if (!_ic.LoadShowSaveDeviceState(configFile)) { - // return false; - //} _roi = new Rect( Config.GetInt($"{DeviceName}/ROI/X"), Config.GetInt($"{DeviceName}/ROI/Y"), diff --git a/TIASshot/Cameras/Lucam.cs b/TIASshot/Cameras/Lucam.cs index 892364f..ea5b953 100644 --- a/TIASshot/Cameras/Lucam.cs +++ b/TIASshot/Cameras/Lucam.cs @@ -268,15 +268,6 @@ } /// - /// 校正 露光時間とホワイトバランスの自動調整 - /// - public void Calibration() { - Debug.WriteLine("校正前"); - SetSnapParam(); - _calibrating = 30; - } - - /// /// 撮影パラメータの設定 /// private void SetSnapParam() { diff --git a/TIASshot/ColorCorrection/ColorCorrector.cs b/TIASshot/ColorCorrection/ColorCorrector.cs index a578385..16388b0 100644 --- a/TIASshot/ColorCorrection/ColorCorrector.cs +++ b/TIASshot/ColorCorrection/ColorCorrector.cs @@ -181,34 +181,12 @@ } /// - /// 画像の色変換. - /// - /// LEAKY_REPRO 定義時は優先度2修正前(中間 Mat 未解放)の挙動に戻すデモ用分岐。 - /// LEAKY_REPRO を定義してビルドすると flatten / extended / converted / convertedImage を - /// Dispose せずに返すリーク実装(git 0003cc2^ 相当)になり,撮影を繰り返すと - /// メモリ観測ウィンドウ上でメモリが増加し続ける様子を録画で確認できる。 - /// 通常ビルド(LEAKY_REPRO 未定義)では入れ子 using による修正後実装が使われる。 - /// + /// 画像の色変換.中間 Mat は入れ子 using で確実に解放する. /// /// /// /// 8bit 画像 internal Mat ConvertImage(Mat src, Mat conv) { -#if LEAKY_REPRO - // ---- リーク版(LEAKY_REPRO): 修正前の挙動を忠実に再現(中間 Mat を Dispose しない)---- - if (src.Type() != MatType.CV_64FC3) { - src.ConvertTo(src, MatType.CV_64FC3); - } - var flatten = src.Reshape(3, src.Height * src.Width); - var extended = ExtendMat(flatten, conv.Rows); - var converted = (extended * conv).ToMat(); - var convertedImage = converted.Reshape(3, src.Height); - - var convImg8 = new Mat(); - convertedImage.ConvertTo(convImg8, MatType.CV_8UC3); - return convImg8; // flatten / extended / converted / convertedImage は Dispose しない(=リーク) -#else - // ---- 修正後(通常ビルド): 中間 Mat を入れ子 using で解放 ---- if (src.Type() != MatType.CV_64FC3) { src.ConvertTo(src, MatType.CV_64FC3); } @@ -225,7 +203,6 @@ convertedImage.ConvertTo(convImg8, MatType.CV_8UC3); return convImg8; } -#endif } /// diff --git a/TIASshot/UI/Form1.Designer.cs b/TIASshot/UI/Form1.Designer.cs index c17a308..cbe6996 100644 --- a/TIASshot/UI/Form1.Designer.cs +++ b/TIASshot/UI/Form1.Designer.cs @@ -32,7 +32,6 @@ this.picPreview = new System.Windows.Forms.PictureBox(); this.picDisplay = new System.Windows.Forms.PictureBox(); this.btnShotMulti = new System.Windows.Forms.Button(); - this.btnCalib = new System.Windows.Forms.Button(); this.txtMessage = new System.Windows.Forms.TextBox(); this.txtSaveFolder = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); @@ -136,18 +135,6 @@ this.btnShotMulti.UseVisualStyleBackColor = true; this.btnShotMulti.Click += new System.EventHandler(this.btnShotMulti_Click); // - // btnCalib - // - this.btnCalib.AllowDrop = true; - this.btnCalib.Location = new System.Drawing.Point(100, 579); - this.btnCalib.Name = "btnCalib"; - this.btnCalib.Size = new System.Drawing.Size(88, 37); - this.btnCalib.TabIndex = 7; - this.btnCalib.Text = "校正"; - this.btnCalib.UseVisualStyleBackColor = true; - this.btnCalib.Visible = false; - this.btnCalib.Click += new System.EventHandler(this.btnCalib_Click); - // // txtMessage // this.txtMessage.BackColor = System.Drawing.Color.LemonChiffon; @@ -332,7 +319,6 @@ this.Controls.Add(this.label3); this.Controls.Add(this.txtSaveFolder); this.Controls.Add(this.txtMessage); - this.Controls.Add(this.btnCalib); this.Controls.Add(this.btnShotMulti); this.Controls.Add(this.picDisplay); this.Controls.Add(this.picPreview); @@ -364,7 +350,6 @@ private System.Windows.Forms.PictureBox picPreview; private System.Windows.Forms.PictureBox picDisplay; private System.Windows.Forms.Button btnShotMulti; - private System.Windows.Forms.Button btnCalib; private System.Windows.Forms.TextBox txtMessage; private System.Windows.Forms.TextBox txtSaveFolder; private System.Windows.Forms.Label label3; diff --git a/TIASshot/UI/Form1.cs b/TIASshot/UI/Form1.cs index accbd62..433df66 100644 --- a/TIASshot/UI/Form1.cs +++ b/TIASshot/UI/Form1.cs @@ -147,15 +147,6 @@ } /// - /// 校正 - /// - /// - /// - private void btnCalib_Click(object sender, EventArgs e) { - //_lucam.Calibration(); - } - - /// /// 画像表示 /// ワーカースレッドから呼び出される /// diff --git a/TIASshot/UI/MemoryMonitorForm.Designer.cs b/TIASshot/UI/MemoryMonitorForm.Designer.cs index 7169937..699aa5f 100644 --- a/TIASshot/UI/MemoryMonitorForm.Designer.cs +++ b/TIASshot/UI/MemoryMonitorForm.Designer.cs @@ -20,7 +20,6 @@ this.chart = new System.Windows.Forms.DataVisualization.Charting.Chart(); this.lblCurrent = new System.Windows.Forms.Label(); this.lblPreview = new System.Windows.Forms.Label(); - this.lblMode = new System.Windows.Forms.Label(); this.btnStartStop = new System.Windows.Forms.Button(); this.btnReset = new System.Windows.Forms.Button(); this.btnSaveCsv = new System.Windows.Forms.Button(); @@ -87,16 +86,6 @@ this.chart.Name = "chart"; this.chart.TabIndex = 0; - // lblMode(Text / ForeColor は MemoryMonitorForm.cs コンストラクタで設定) - this.lblMode.AutoSize = false; - this.lblMode.Dock = System.Windows.Forms.DockStyle.Top; - this.lblMode.Font = new System.Drawing.Font("Consolas", 10F, System.Drawing.FontStyle.Bold); - this.lblMode.Height = 26; - this.lblMode.Name = "lblMode"; - this.lblMode.TabIndex = 3; - this.lblMode.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.lblMode.Padding = new System.Windows.Forms.Padding(4, 0, 0, 0); - // lblCurrent this.lblCurrent.AutoSize = false; this.lblCurrent.Dock = System.Windows.Forms.DockStyle.Top; @@ -164,7 +153,6 @@ this.Controls.Add(this.chart); this.Controls.Add(this.lblPreview); this.Controls.Add(this.lblCurrent); - this.Controls.Add(this.lblMode); this.Controls.Add(this.panelButtons); this.Name = "MemoryMonitorForm"; // Text は MemoryMonitorForm.cs コンストラクタで設定 @@ -177,7 +165,6 @@ private System.Windows.Forms.DataVisualization.Charting.Chart chart; private System.Windows.Forms.Label lblCurrent; private System.Windows.Forms.Label lblPreview; - private System.Windows.Forms.Label lblMode; private System.Windows.Forms.Button btnStartStop; private System.Windows.Forms.Button btnReset; private System.Windows.Forms.Button btnSaveCsv; diff --git a/TIASshot/UI/MemoryMonitorForm.cs b/TIASshot/UI/MemoryMonitorForm.cs index 5d560fa..39b4cee 100644 --- a/TIASshot/UI/MemoryMonitorForm.cs +++ b/TIASshot/UI/MemoryMonitorForm.cs @@ -66,16 +66,7 @@ this.KeyPreview = true; this.KeyDown += MemoryMonitorForm_KeyDown; - // モードラベルとウィンドウタイトルをビルド種別に応じて設定 -#if LEAKY_REPRO - lblMode.Text = "モード: リーク再現(修正前)"; - lblMode.ForeColor = System.Drawing.Color.Red; - this.Text = "メモリ観測ウィンドウ [MEMMONITOR] — リーク再現"; -#else - lblMode.Text = "モード: 修正後"; - lblMode.ForeColor = System.Drawing.Color.Green; - this.Text = "メモリ観測ウィンドウ [MEMMONITOR] — 修正後"; -#endif + this.Text = "メモリ観測ウィンドウ [MEMMONITOR]"; _outputDir = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, diff --git "a/docs/02_ENV/ENV_05_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" "b/docs/02_ENV/ENV_05_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" index 180435d..8bdcde3 100644 --- "a/docs/02_ENV/ENV_05_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" +++ "b/docs/02_ENV/ENV_05_\343\203\207\343\202\243\343\203\254\343\202\257\343\203\210\343\203\252\346\247\213\346\210\220.md" @@ -34,10 +34,17 @@ ├── UI/ ← フォーム(WinForms) │ ├── Form1.cs ← メインフォーム(UI・操作イベント) │ ├── Form1.Designer.cs ← メインフォームのデザイナー自動生成コード +│ ├── Form1.WhiteBoard.cs ← 白板撮影ボタン(WHITEBOARD ビルド限定の partial) +│ ├── Form1.MemMonitor.cs ← メモリ観測ウィンドウ起動・F9 トグル(MEMMONITOR 限定の partial) │ ├── PreviewMonitor.cs ← プレビューモニタフォーム -│ └── PreviewMonitor.Designer.cs ← プレビューモニタのデザイナー自動生成コード +│ ├── PreviewMonitor.Designer.cs ← プレビューモニタのデザイナー自動生成コード +│ ├── MemoryMonitorForm.cs ← メモリ観測ウィンドウ(MEMMONITOR 限定) +│ ├── MemoryMonitorForm.Designer.cs ← 同デザイナーコード(MEMMONITOR 限定) +│ ├── PreviewProfiler.cs ← プレビュー計測カウンタ(MEMMONITOR 限定) +│ └── TimedPictureBox.cs ← 副モニタ実描画時間の計測(MEMMONITOR 限定) └── Utils/ ← ユーティリティ ├── IoUtil.cs ← ファイル I/O(CSV・保存フォルダ・撮影情報)・サウンド + ├── BitmapUtil.cs ← Bitmap 生画素コピー(FastClone・純粋ロジック) └── ImageStats.cs ← 飽和統計算出・JSON 生成(白板撮影用・純粋ロジック) ``` @@ -82,7 +89,14 @@ ├── packages.config ← NuGet パッケージ一覧(NUnit 等) ├── IoUtilTests.cs ← CSV 読み書き往復のテスト ├── ConfigTests.cs ← config.xml パースのテスト -└── ColorCorrectorTests.cs ← 次元拡張・変換行列・色変換のテスト +├── ColorCorrectorTests.cs ← 次元拡張・変換行列・色変換のテスト +├── GainCalcTests.cs ← ゲイン更新比率計算のテスト +├── ImageStatsTests.cs ← 飽和統計・JSON 生成のテスト(白板撮影用) +├── BitmapUtilTests.cs ← FastClone の仕様ベーステスト +├── ConvertImageMemoryBenchmark.cs ← ConvertImage 単体のメモリ計測ベンチ([Explicit]) +├── ContinuousShootMemoryBenchmark.cs ← 連続撮影フロー再現のメモリ計測ベンチ([Explicit]) +├── MemBenchSupport.cs ← メモリベンチ共通ユーティリティ +└── MemBenchSupportTests.cs ← 同ユーティリティのテスト ``` ## ドキュメント docs/ (Documents) @@ -91,7 +105,7 @@ docs/ ├── 01_GUIDE/ ← チームルール・規約(命名,Git,コーディング,コメント) ├── 02_ENV/ ← 開発環境・構成に関するドキュメント(本ディレクトリ) -├── 03_PLAN/ ← 進行計画・要件定義(リファクタリング計画,バグ修正計画) +├── 03_PLAN/ ← 進行計画・要件定義(プレビューメモリリーク修正計画,性能評価計測計画) ├── 04_SPEC/ ← ユーザーから見える仕様(機能仕様,設定ファイル仕様) ├── 05_TECH/ ← クラス設計・アルゴリズム等の技術設計(カメラ制御,色補正処理) └── 06_TEST/ ← テスト計画・品質保証(テスト仕様) diff --git "a/docs/02_ENV/ENV_06_\343\203\223\343\203\253\343\203\211\346\247\213\346\210\220\343\201\250\343\203\223\343\203\253\343\203\211\343\203\225\343\203\251\343\202\260.md" "b/docs/02_ENV/ENV_06_\343\203\223\343\203\253\343\203\211\346\247\213\346\210\220\343\201\250\343\203\223\343\203\253\343\203\211\343\203\225\343\203\251\343\202\260.md" index b909b99..f22905f 100644 --- "a/docs/02_ENV/ENV_06_\343\203\223\343\203\253\343\203\211\346\247\213\346\210\220\343\201\250\343\203\223\343\203\253\343\203\211\343\203\225\343\203\251\343\202\260.md" +++ "b/docs/02_ENV/ENV_06_\343\203\223\343\203\253\343\203\211\346\247\213\346\210\220\343\201\250\343\203\223\343\203\253\343\203\211\343\203\225\343\203\251\343\202\260.md" @@ -1,12 +1,11 @@ # ビルド構成とビルドフラグ (Build Configurations and Flags) -本プロジェクトは,診断・評価用の機能(メモリ観測・白板撮影・リーク再現)を**条件コンパイルシンボル(ビルドフラグ)**で出し分けている.配布版(Release)にはこれらをコードごと含めない方針である.本ドキュメントは,どのフラグが何を有効化し,どの構成で定義されるかをまとめる. +本プロジェクトは,診断・評価用の機能(メモリ観測・白板撮影)を**条件コンパイルシンボル(ビルドフラグ)**で出し分けている.配布版(Release)にはこれらをコードごと含めない方針である.本ドキュメントは,どのフラグが何を有効化し,どの構成で定義されるかをまとめる. ## 設計方針 (Policy) - **Release(配布版)=素の製品**.診断・評価・デモ用の機能はコードごと除外する. - **Debug=開発・検証版**.診断/評価機能(`MEMMONITOR`・`WHITEBOARD`)を自動で含める. -- 一部のデモ用フラグ(`LEAKY_REPRO`)は既定では未定義とし,必要なときだけ**手動で**付与して対比計測に使う. ## ソリューション構成 (Solution Configurations) @@ -26,7 +25,6 @@ | --- | --- | --- | --- | --- | | `MEMMONITOR` | メモリ観測ウィンドウ+プレビュー計測 | `Debug\|x64`(自動) | ✗ | `UI/MemoryMonitorForm`,`UI/PreviewProfiler`,`UI/TimedPictureBox`,`UI/Form1.MemMonitor`(F9 トグル),`Form1.ShowImage` 等のプレビュー計測 | | `WHITEBOARD` | 白板撮影モード(照明均一性評価用) | `Debug\|x64`(自動) | ✗ | 白板撮影ボタン(`Form1` の専用 partial),`Cameras/IScam.ShotWhiteBoard` 等 | -| `LEAKY_REPRO` | メモリ修正(優先度2)前の挙動を再現するデモ用 | **どの構成でも未定義(手動オプトイン)** | ✗ | `ColorCorrection/ColorCorrector.ConvertImage` のリーク版分岐,`UI/MemoryMonitorForm` のモード表示 | | `DEBUG` | .NET 標準デバッグシンボル | `Debug\|x64` | ✗ | 標準(一部の開発時分岐) | | `TRACE` | .NET 標準トレースシンボル | 全構成 | ○ | `System.Diagnostics.Trace` | @@ -43,26 +41,13 @@ - ImagingSource(IScam)でのみ実装.未対応カメラでは通知して何もしない. - 配布版の舌診撮影フローには不要なため Release では未定義. -### LEAKY_REPRO — リーク再現(対比計測デモ用) - -- メモリ修正(PLAN_02 優先度2=`ConvertImage` の中間 Mat 未解放)の**修正前の挙動**を忠実に再現するためのデモ用フラグ. -- 既定(未定義)では入れ子 `using` による**修正後**実装が使われる.`LEAKY_REPRO` を定義してビルドすると,`ConvertImage` が中間 Mat を Dispose しないリーク版に切り替わり,観測ウィンドウのモード表示も「リーク再現(修正前)」になる. -- 「修正前→修正後」をメモリ観測で対比録画するときにのみ使う.どの標準構成にも含めないため,使うときは手動で付与する(下記). - ## ビルド方法 (How to Build) 標準構成(Debug / Release)のビルド・テスト・実行コマンドは [ENV_04_開発コマンド](ENV_04_開発コマンド.md) を参照する.`Debug|x64` では `WHITEBOARD` と `MEMMONITOR` が自動で有効になり(メモリ観測ウィンドウが開き,白板撮影ボタンが表示される),`Release|x64` ではいずれの診断フラグも含まれない. -### LEAKY_REPRO を有効にする(対比計測デモ時のみ) - -`LEAKY_REPRO` はソリューション構成に含まれないため,一時的に手動で付与する.いずれかの方法で行い,**計測後は元に戻す**こと. - -- Visual Studio: プロジェクトのプロパティ →[ビルド]→ 条件付きコンパイル シンボル に `LEAKY_REPRO` を追加してビルド. -- または `TIASshot.csproj` の `Debug|x64` の `` に一時的に `;LEAKY_REPRO` を追記してビルド. - ## 関連 (Related) - 各フラグ導入の経緯・設計判断は [docs/PROGRESS.md](../PROGRESS.md) の該当エントリを参照: - `WHITEBOARD`(白板撮影の専用ビルド記号化) - - `MEMMONITOR` / `LEAKY_REPRO`(メモリ修正の効果検証ツール) -- メモリ計測の結果は [docs/06_TEST/TEST_01_メモリ修正効果計測.md](../06_TEST/TEST_01_メモリ修正効果計測.md). + - `MEMMONITOR`(メモリ修正の効果検証ツール) +- メモリ計測の結果は [docs/06_TEST/TEST_03_メモリ修正効果計測.md](../06_TEST/TEST_03_メモリ修正効果計測.md). diff --git "a/docs/03_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.md" "b/docs/03_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.md" deleted file mode 100644 index 205f661..0000000 --- "a/docs/03_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.md" +++ /dev/null @@ -1,124 +0,0 @@ -# リファクタリング計画 (Refactoring Plan) - -## 方針 (Policy) - -- 処理の変更は一切行わない.構造の整理のみを目的とする. -- 各フェーズ完了後に必ずテストを実施し,動作が変わっていないことを確認してから次へ進む. -- テストの具体的な手順は `TEST_01_テスト仕様.md` を参照する. - -## 現状の問題点 (Current Issues) - -`CameraBase.cs` に以下の責務が集中している. - -- カメラ制御の抽象定義(Connect/Disconnect/Shot) -- チャート検出(ArUco マーカー検出,ホモグラフィ計算,マスク生成) -- 色補正処理(TCC行列計算,多項式拡張,画像変換) -- ファイル I/O(CSV読み書き,保存フォルダ生成,撮影情報出力) -- サウンド再生 - -## 目標とするディレクトリ構成 (Target Structure) - -```text -TIASshot/ -├── Cameras/ ← カメラ制御(CameraBase, IScam, Lucam) -├── ChartDetection/ ← チャート検出(CameraBaseから分離) -├── ColorCorrection/ ← 色補正・TCC計算(CameraBaseから分離) -├── UI/ ← フォーム(Form1, PreviewMonitor) -├── Config.cs ← 設定ファイル読み込み(変更なし) -├── LightSource.cs ← 光源制御(変更なし) -└── Program.cs ← エントリポイント(変更なし) -``` - -## フェーズ構成 (Phases) - -### フェーズ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` で統一する. -- ファイルの中身は変更しない. - -**テスト** - -- ビルドが成功することを確認する. -- アプリが起動し,カメラが接続されることを確認する(手動). - -### フェーズ2: 色補正処理の分離 - -**作業内容** - -- `ColorCorrection/` フォルダを作成する. -- CameraBase から以下を新クラス `ColorCorrector` に切り出す. - - フィールド: `TCC_SRGB`, `TCC_XYZ`, `ChannelList`, `ExtendChannels` - - メソッド: `ExtendMat`, `CalcConvertMatrix`, `ConvertColor`, `ConvertImage` - - メソッド: `CalcTcc`(チャート RGB 値の受け取りと行列計算・保存を担当) -- CameraBase は `ColorCorrector` を保持し,処理を委譲する. -- ※ `UpdateRate` はゲイン/ホワイトバランス補正(`GetRatio`)専用で色補正とは別関心事のため,`CameraBase` に残す(当初案から変更). - -**テスト** - -- ビルドが成功することを確認する. -- 回帰テスト: 同一入力画像から算出した変換行列が,リファクタリング前と一致することを確認する. - - 具体的な手順は `TEST_01_テスト仕様.md` の「回帰テスト: 色補正処理」を参照する. - -### フェーズ3: チャート検出処理の分離 - -**作業内容** - -- `ChartDetection/` フォルダを作成する. -- CameraBase から以下を新クラス `ChartDetector` に切り出す. - - フィールド: `ARDict`, `PointsDst40`, `PointsDst41`, `_detectionCount`, `_lastPosition` - - メソッド: `DetectChart` - - 出力: チャートマスクリスト(`List`)を戻り値または引数で渡す -- CameraBase は `ChartDetector` を保持し,処理を委譲する. - -**テスト** - -- ビルドが成功することを確認する. -- 回帰テスト: 保存済みの実画像を入力として,チャートマスクの位置が一致することを確認する. - - 具体的な手順は `TEST_01_テスト仕様.md` の「チャート検出テスト」を参照する. - -### フェーズ4: ファイル I/O・サウンドの分離 - -**作業内容** - -- CameraBase から以下をユーティリティクラスとして切り出す(配置先は `Utils/IoUtil`). - - `LoadMatFromCsv`, `SaveMatToCsv` - - `SetSaveFolder`, `WriteInfo` - - `EventSound` -- ※ `SetSaveFolder`/`WriteInfo`/`SetInfo` は `_saveFolder`/`_ShotInfo`(派生クラスと共有する状態)に結合するため,純粋ロジックのみ `IoUtil` へ移し,`CameraBase` 側は薄いラッパーとして残した(当初案から変更). - -**テスト** - -- ビルドが成功することを確認する. -- 撮影からファイル保存までの一連の動作を手動で確認する. -- 保存された CSV・画像の内容がリファクタリング前と一致することを確認する. - -### フェーズ5: 派生カメラクラスの重複処理の集約(当初案に追加) - -**動機** - -- フェーズ1〜4 完了後も,`IScam` と `Lucam` にプレビュー/校正フロー(白パッチ計測・ゲイン更新・`CalcTcc`・チャート検出)と撮影ループ(CSV 書き込み・タイミング制御)のほぼ同一コードが重複して残っていた. - -**作業内容** - -- 重複していた共通骨格を `CameraBase` にテンプレートメソッドとして集約する. - - プレビュー/校正フロー → `CameraBase` の共通メソッド+カメラ固有部を抽象メソッド化 - - 撮影ループ → `CameraBase` の共通メソッド+撮影前後処理・フレーム取得を抽象メソッド化 -- カメラ固有部(ゲイン/ホワイトバランス反映,撮影情報の記録,フレーム取得,撮影前後処理)は抽象メソッドで派生クラスに残す. -- ※ `IScam` の撮影前後処理(Sink 切替・最初のフレーム捨て)は `Lucam` と実行順序が異なるため,共通骨格に取り込まず派生クラス側に残した(順序維持を優先). - -**テスト** - -- ビルドが 0 エラー 0 警告で成功することを確認する. -- 校正・1 枚撮影・連続撮影の一連の動作を実機で確認する(ハードウェア依存パスのため自動テスト不可). - -## 共通ルール (Common Rules) - -- 各フェーズは独立したブランチではなく,`refactor/code-cleanup` ブランチ上で順に進める. -- フェーズ完了ごとにコミットする.コミットメッセージは `[clean]` タグを使用する. - - 例: `[clean] 色補正処理を ColorCorrector クラスに分離` -- テストが通らない場合は,次のフェーズに進まない. diff --git "a/docs/03_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" "b/docs/03_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" deleted file mode 100644 index 9d3e2f3..0000000 --- "a/docs/03_PLAN/PLAN_02_\343\203\220\343\202\260\344\277\256\346\255\243\350\250\210\347\224\273.md" +++ /dev/null @@ -1,188 +0,0 @@ -# バグ修正計画 (Bug Fix Plan) - -## 前提 (Prerequisites) - -- `PLAN_01_リファクタリング計画.md` の全フェーズ(Phase 1〜5)が完了し,動作がリファクタリング前と同一であることを確認してから着手する. -- 修正は1件ずつ独立して行い,都度テストを実施する. -- コミットメッセージは `[fix]` タグを使用する. - - 例: `[fix] _shots へのスレッドセーフなアクセスに修正` -- 本計画はリファクタリング(PLAN_01)前に作成されたため,各原因の**該当箇所はリファクタ後の現構造に更新済み**.撮影制御は `Cameras/CameraBase.cs`(共通骨格 `RunShotLoop`・`SaveThread` 等)と `Cameras/Lucam.cs`・`Cameras/IScam.cs`(派生クラスの `Shot`)に,色変換は `ColorCorrection/ColorCorrector.cs` に分離されている. - -## 症状 (Symptom) - -- 連続撮影で4枚以上取得するとアプリがたまにクラッシュする. -- 再現性は不安定(毎回ではなく「たまに」落ちる). - -## 原因と修正方針 (Causes and Fix Policy) - -### 優先度1: `_shots` へのスレッドセーフでないアクセス(根本原因) - -> **状態: 完了(2026-06-07)** — `_shots` を `BlockingCollection` 化して生産者-消費者パイプラインに変更(`RunShotLoop` で `Add`/`CompleteAdding`、`SaveThread` で `GetConsumingEnumerable`).撮影ループは `try/finally` で `CompleteAdding()` を保証.詳細は `docs/PROGRESS.md` および `git log` を参照. - -**原因** - -`_shots`(`CameraBase` の `List` フィールド)は撮影スレッドと保存スレッド(`SaveThread`)から同時にアクセスされる. -`List` はスレッドセーフではないため,以下の2点で競合が発生する. - -- リサイズ競合 - - `List` の内部配列は容量が足りなくなると自動で拡張される(デフォルト容量 4 → 5枚目の Add 時に 8 に拡張). - - 拡張中に `SaveThread` が `_shots[saveCount]` を読むとメモリ破壊が起きる. - - 「4枚以上でたまに落ちる」という症状に一致する. -- TOCTOU(確認と使用の間の競合) - - `SaveThread` の `_shots.Count` チェックと `_shots[saveCount]` アクセスの間に撮影スレッドがリサイズを起こすと `IndexOutOfRangeException` が発生する. - -**該当箇所(リファクタ後)** - -- 書き込み: `Cameras/CameraBase.cs` の `RunShotLoop`(`_shots.Add(CaptureFrame())`),および各派生クラスの `Shot`(`Cameras/Lucam.cs`・`Cameras/IScam.cs` の `_shots.Clear()`). -- 読み取り: `Cameras/CameraBase.cs` の `SaveThread`(`_shots.Count` / `_shots[saveCount]`). -- スレッド起動: 各派生クラスの `Shot` 内 `new Thread(() => SaveThread(numImages))`. -- ※ `SaveThread` はインデックス(`_shots[saveCount]`)でアクセスするため,置き換え先はインデックス参照可能な構造(ロック付き `List` 等)が前提.`ConcurrentQueue` を使う場合は保存側を Dequeue ベースに作り替える必要がある点に注意. - -**修正方針** - -- `_shots` へのアクセスをスレッドセーフ化する(ロック付きアクセス,または保存側を `ConcurrentQueue` + Dequeue に作り替える). -- `SaveThread` の `Thread.Sleep` ポーリング待機をスレッドセーフな同期(`SemaphoreSlim`・`BlockingCollection` 等)に変更する. - -**テスト** - -- 連続撮影を10枚以上で複数回繰り返し,クラッシュしないことを確認する. - -### 優先度2: `ConvertImage` 内の Mat 未 Dispose によるメモリ蓄積 - -> **状態: 完了(2026-06-07)** — `ConvertImage(Mat src, Mat conv)` の中間 Mat(`flatten`/`extended`/`converted`/`convertedImage`)を入れ子 `using` で確実に Dispose.返り値 `convImg8` は `using` の外で返却(use-after-dispose なし).`ColorCorrectorTests` に回帰テストを追加.詳細は `docs/PROGRESS.md` および `git log` を参照. - -**原因** - -`ColorCorrector.ConvertImage(Mat src, Mat conv)` 内で生成される中間 Mat(`flatten`,`extended`,`converted`,`convertedImage`)が Dispose されないまま残る. -1回の呼び出しで数百 MB〜1 GB 超が蓄積し,画像枚数×チャンネル数分だけ積み重なる. -(例: IScam 1840×2440,17次元,4枚連続撮影 → 数十 GB 規模) -※ 返り値 `convImg8` は呼び出し側(`CameraBase.SaveImages`)で `using` により Dispose されているが,`ConvertImage` 内部の中間 Mat は解放されていない. - -**該当箇所(リファクタ後)** - -- `ColorCorrection/ColorCorrector.cs` の `ConvertImage(Mat src, Mat conv)`(private オーバーロード). - -**修正方針** - -- `flatten`,`extended`,`converted`,`convertedImage` を `using` ブロックで管理して確実に Dispose する. - -**テスト** - -- タスクマネージャでメモリ使用量を監視しながら連続撮影を繰り返し,撮影ごとにメモリが増加し続けないことを確認する. - -### 優先度3: `Parallel.For` 内での未捕捉例外 - -**原因** - -`ColorCorrector.ExtendMat` の `Parallel.For` 内で OOM 等の例外が発生した場合,`AggregateException` としてラップされて `SaveThread` に伝播する. -`SaveThread` で捕捉されなければアプリがクラッシュする. -※ 優先度2の修正でメモリ蓄積が解消されれば,発生頻度は大幅に下がる. - -**該当箇所(リファクタ後)** - -- 例外発生源: `ColorCorrection/ColorCorrector.cs` の `ExtendMat`(`Parallel.For`). -- 捕捉すべき箇所: `Cameras/CameraBase.cs` の `SaveThread`(`SaveImages` → `ColorCorrector.ConvertImage` → `ExtendMat` の呼び出し経路). - -**修正方針** - -- `SaveThread` に例外ハンドラを追加し,クラッシュではなくエラーメッセージを表示して処理を中断するようにする. -- `ExtendMat` の `Parallel.For` に try-catch を追加する. - -**テスト** - -- 例外発生時にアプリが落ちずエラーが表示されることを確認する. - -### 優先度4: `ConvertImage` による `_shots[i]` の in-place 書き換え - -**原因** - -`ColorCorrector.ConvertImage(Mat src, Mat conv)` 内の `src.ConvertTo(src, MatType.CV_64FC3)` が,引数で渡された `_shots[i]` 自体を CV_8UC3 から CV_64FC3 に書き換える副作用がある(`src` は `CameraBase.SaveImages` から `_shots[saveCount]` がそのまま渡される). -特に IScam の `new Mat(imgt_full, _roi)`(サブマトリクス)に対して in-place の型変換を行うと不正な動作を引き起こす可能性がある. -また複数チャンネルを処理する際,2回目以降は変換済みのデータに対して処理をかけることになり,変換結果が誤る. - -**該当箇所(リファクタ後)** - -- `ColorCorrection/ColorCorrector.cs` の `ConvertImage(Mat src, Mat conv)` 冒頭の `src.ConvertTo(src, MatType.CV_64FC3)`. -- 呼び出し元: `Cameras/CameraBase.cs` の `SaveImages`(`_colorCorrector.ConvertImage(img, channel)`,`img` = `_shots[saveCount]`). - -**修正方針** - -- `ConvertImage` の冒頭で `src` をコピーし,コピー側を変換するようにする(`src` を直接書き換えない). - -**テスト** - -- 複数チャンネル(例: `4,10,17`)で撮影し,各チャンネルの sRGB 変換画像が正しく出力されることを確認する. - -### 優先度5: Mat 未 Dispose によるメモリリーク - -**原因 A: `_shots.Clear()` で Mat が解放されない** - -- 撮影ごとに `_shots.Clear()` が呼ばれるが,格納された Mat は Dispose されない. -- 該当箇所: 各派生クラスの `Shot`(`Cameras/Lucam.cs`・`Cameras/IScam.cs`)の `_shots.Clear()`. - -**原因 B: `_chartMasks.Clear()` で Mat が解放されない** - -- チャート再検出時に `_chartMasks.Clear()` が呼ばれるが,同様に Dispose されない. -- 該当箇所: `Cameras/CameraBase.cs` の `DetectChart` の `_chartMasks.Clear()`. - -**修正方針** - -- Clear 前に各 Mat を Dispose するヘルパーを追加する(`CameraBase` 側に共通ヘルパーを置くのが妥当). - -**テスト** - -- タスクマネージャでメモリ使用量を監視し,撮影・キャリブを繰り返してもメモリが増加し続けないことを確認する. - -### 優先度6: `Form1` の `InvokeRequired` 分岐で `Invoke` の戻り値を返していない(潜在バグ) - -※ 上記の連続撮影クラッシュ(優先度1〜5)とは**独立した別系統のバグ**.現状はワーカースレッドから呼ばれていないため顕在化していないが,将来撮影呼び出しをスレッド化した際にデータ名取得失敗等の不具合になりうる. - -**原因** - -`Form1` の `GetDataName` / `GetNumMultiShots` / `GetMultiShotsInterval` は,`InvokeRequired` が true のとき `Invoke((MethodInvoker)delegate { GetDataName(); })` のように**戻り値を捨てている**.`MethodInvoker` は `void` デリゲートのため `Invoke` の戻り値は常に `null`/既定値となり,UI スレッド外から呼ぶと正しい値(テキストボックスの内容)が返らない. - -- 現状: これらは `CameraBase.ShotOne` / `ShotMulti` から呼ばれ,その起点はボタンクリックハンドラ(UI スレッド)のため `InvokeRequired` が false 側を通り顕在化していない. -- リスク: 撮影処理をワーカースレッド化(連続撮影のマルチスレッド化)すると,このパスを通って `GetDataName` が `null` を返し,保存フォルダ名や撮影枚数が不正になる. - -**該当箇所** - -- `UI/Form1.cs` の `GetDataName` / `GetNumMultiShots` / `GetMultiShotsInterval` の `InvokeRequired` 分岐. - -**修正方針** - -- 戻り値を返すデリゲート(`Func` / `Func`)で `Invoke` し,その結果を返すように修正する. - - 例: `return (string)Invoke(new Func(() => txtDataName.Text));` - -**テスト** - -- 各メソッドが UI スレッド・ワーカースレッドのいずれから呼ばれても正しい値を返すことを確認する. - -## 修正順序まとめ (Summary) - -| 順位 | 原因 | 該当ファイル | クラッシュとの関連 | -| --- | --- | --- | --- | -| 1 | `_shots` のスレッドセーフでないアクセス | `CameraBase.cs` / 各 `Shot` | 直接原因(「たまに落ちる」に一致) | -| 2 | `ConvertImage` 内の Mat 未 Dispose | `ColorCorrector.cs` | 直接原因(OOM によるクラッシュ) | -| 3 | `Parallel.For` 内の未捕捉例外 | `ColorCorrector.cs` / `CameraBase.cs` | 間接原因(2の修正で頻度減) | -| 4 | `ConvertImage` の in-place 書き換え | `ColorCorrector.cs` | 誤動作(変換結果の誤り) | -| 5 | `_shots` / `_chartMasks` の Mat 未 Dispose | 各 `Shot` / `CameraBase.cs` | メモリリーク(蓄積) | -| 6 | `Form1` の `InvokeRequired` 戻り値欠落 | `Form1.cs` | 別系統(現状潜在・クラッシュとは無関係) | - -## 効果検証(メモリ修正)の方法 (Verifying the Memory Fixes) - -優先度2(`ConvertImage` の中間 Mat 解放)の効果を、定量・デモの両面で検証できるようにしてある.いずれも専用のコンパイル記号で出し分け,本番(Release)には含めない. - -### 定量比較(自動・数値) - -- 2 種類の NUnit ベンチ(いずれも `[Explicit]`・通常テストでは走らず明示指定で実行)を用意してある.修正前(リーク版を忠実に再現)と修正後(実 `ConvertImage`)を同条件で計測し,`Process.PrivateMemorySize64` / `WorkingSet64` の baseline・peak・増分を比較する(カメラ実機不要・ヘッドレス): - - `TIASshot.Tests/ConvertImageMemoryBenchmark.cs`: `ConvertImage` 単体を反復する隔離計測. - - `TIASshot.Tests/ContinuousShootMemoryBenchmark.cs`: 連続撮影フロー(`BlockingCollection` の生産者-消費者・1 フレーム複数チャンネル変換・フレームバッファ)を**合成フレームで構造再現**したフロー計測(カメラ取得部のみ差し替え.本番メソッドそのものの呼び出しではない).本番に近い「フレーム単位の増え方」を測れる. - - リーク再現ロジックと計測ユーティリティは `TIASshot.Tests/MemBenchSupport.cs` に共有化. -- Mat はネイティブメモリのため `GC.GetTotalMemory` では捕捉できない.計測ループ中は GC を呼ばない(本番のリーク機序を再現するため). -- 代表的な計測結果は `docs/06_TEST/TEST_01_メモリ修正効果計測.md` に記録. - -### デモ(実アプリのメモリをライブ可視化) - -- コンパイル記号 **`MEMMONITOR`**: 定義してビルドすると,TIASshot 起動時に「メモリ観測ウィンドウ」(`UI/MemoryMonitorForm`)が自動で開く.`System.Windows.Forms.Timer` で実プロセスのメモリを定期サンプリングし,ライブ折れ線グラフ+現在値で表示,CSV 保存も可能.実際に撮影しながら実使用時のメモリ挙動を録画できる.Release には含めない(既定で Debug 構成のみ定義). -- コンパイル記号 **`LEAKY_REPRO`**: 定義してビルドすると,`ColorCorrector.ConvertImage` が優先度2修正前(中間 Mat 未解放)の挙動に戻る.観測ウィンドウのモード表示も「リーク再現(修正前)」に切り替わる.既定ではどの構成にも未定義(=修正後)で,対比録画したいときだけ一時的に定義する. -- 依存: 観測ウィンドウは `System.Windows.Forms.DataVisualization`(Chart)を使用する. diff --git "a/docs/03_PLAN/PLAN_03_\343\203\227\343\203\254\343\203\223\343\203\245\343\203\274\343\203\241\343\203\242\343\203\252\343\203\252\343\203\274\343\202\257\344\277\256\346\255\243\350\250\210\347\224\273.md" "b/docs/03_PLAN/PLAN_03_\343\203\227\343\203\254\343\203\223\343\203\245\343\203\274\343\203\241\343\203\242\343\203\252\343\203\252\343\203\274\343\202\257\344\277\256\346\255\243\350\250\210\347\224\273.md" index 155b4a3..e393b03 100644 --- "a/docs/03_PLAN/PLAN_03_\343\203\227\343\203\254\343\203\223\343\203\245\343\203\274\343\203\241\343\203\242\343\203\252\343\203\252\343\203\274\343\202\257\344\277\256\346\255\243\350\250\210\347\224\273.md" +++ "b/docs/03_PLAN/PLAN_03_\343\203\227\343\203\254\343\203\223\343\203\245\343\203\274\343\203\241\343\203\242\343\203\252\343\203\252\343\203\274\343\202\257\344\277\256\346\255\243\350\250\210\347\224\273.md" @@ -2,7 +2,7 @@ ## 前提 (Prerequisites) -- 本計画は,`PLAN_02_バグ修正計画.md`(連続撮影クラッシュ=撮影経路の修正)とは**独立した別系統のバグ**を扱う.PLAN_02 は「撮影時」の不具合,本計画は「プレビュー表示中(撮影していなくても)長時間起動で落ちる」不具合を対象とする. +- 本計画は,連続撮影クラッシュ(撮影経路の修正)とは**独立した別系統のバグ**を扱う.撮影経路の不具合が「撮影時」に起きるのに対し,本計画は「プレビュー表示中(撮影していなくても)長時間起動で落ちる」不具合を対象とする. - 修正は1件ずつ独立して行い,都度ビルド・動作確認する. - コミットメッセージは `[fix]` タグを使用する. - 該当箇所はリファクタ後の現構造(`Cameras/`・`UI/`)で記載する.プレビュー供給は `Cameras/IScam.cs`・`Cameras/Lucam.cs`,表示は `UI/Form1.cs`・`UI/PreviewMonitor.cs` に分かれている. @@ -68,7 +68,7 @@ ## 検証 (Verification) - `MEMMONITOR` ビルド(`UI/MemoryMonitorForm`)で,**撮影せずプレビュー表示のまま放置**し,メモリ(PrivateMemory/WorkingSet)が時間とともに増え続けないことを確認する.修正前後で対比する. -- 既存のメモリ計測ベンチ(`PLAN_02` の「効果検証」・[TEST_01](../06_TEST/TEST_01_メモリ修正効果計測.md))は**撮影フロー対象**であり,本件(プレビュー放置)はカバーしていない.プレビュー放置シナリオの観測は別途行う. +- 既存のメモリ計測ベンチ([TEST_03](../06_TEST/TEST_03_メモリ修正効果計測.md))は**撮影フロー対象**であり,本件(プレビュー放置)はカバーしていない.プレビュー放置シナリオの観測は別途行う. - GDI ハンドルのリークが疑われるため,観測ウィンドウに **GDI オブジェクト数**(`GetGuiResources` 等)の表示を追加して,メモリだけでなくハンドル数も時間で増えないことを確認することを推奨する(任意). ## 修正順序まとめ (Summary) @@ -81,5 +81,4 @@ ## 関連 (Related) -- [PLAN_02 バグ修正計画](PLAN_02_バグ修正計画.md) — 連続撮影クラッシュ(撮影経路).本計画とは別系統. -- [TEST_01 メモリ修正効果計測](../06_TEST/TEST_01_メモリ修正効果計測.md) — 撮影経路のメモリ計測結果. +- [TEST_03 メモリ修正効果計測](../06_TEST/TEST_03_メモリ修正効果計測.md) — 撮影経路のメモリ計測結果. diff --git "a/docs/03_PLAN/PLAN_04_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\350\250\210\347\224\273.md" "b/docs/03_PLAN/PLAN_04_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\350\250\210\347\224\273.md" index 9bfdad6..10150c0 100644 --- "a/docs/03_PLAN/PLAN_04_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\350\250\210\347\224\273.md" +++ "b/docs/03_PLAN/PLAN_04_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\350\250\210\347\224\273.md" @@ -12,13 +12,13 @@ | --- | --- | --- | --- | | **v1.6** | `4b107e4`(nakaguchi タグ `v1.6`) | ベースライン | 保存経路・プレビュー経路とも未修正(リークあり) | | **v1.7** | `5315d2d`(nakaguchi タグ `v1.7`「メモリ使用効率化」) | `SaveThread` が `_shots` から pop して都度 Dispose+変換行列の中間 Mat 解放 | **保存経路**を改善(プレビューは未修正) | -| **現行** | `HEAD`(GitHub `rintoHasegawa/TIASshot` main) | PLAN_02(`ConvertImage`)+PLAN_03(プレビュー)+FastClone | **プレビュー経路**を修正+コピー高速化 | +| **現行** | `HEAD`(GitHub `rintoHasegawa/TIASshot` main) | `ConvertImage` の Mat 解放(メモリ)+PLAN_03(プレビュー)+FastClone | **プレビュー経路**を修正+コピー高速化 | → 計測のストーリー: **保存経路は v1.6 → v1.7 で,プレビュー経路は v1.7 → 現行で**効いた,という切り分けを示す. ## 計測手段と役割分担 (Method & Roles) -- **手段**: 実機 MEMMONITOR(メモリ観測ウィンドウ+プレビュー計測)を使ったライブ計測.自動 NUnit ベンチ(TEST_01)ではなく,**実カメラで普通にアプリを使ったときの挙動**を対象とする. +- **手段**: 実機 MEMMONITOR(メモリ観測ウィンドウ+プレビュー計測)を使ったライブ計測.自動 NUnit ベンチ(TEST_03)ではなく,**実カメラで普通にアプリを使ったときの挙動**を対象とする. - **カメラ**: IScam(ImagingSource DFK 23UX249.白板撮影で使用したもの). - **役割分担**: - **Claude**: 3 本の計装済みビルド(後述の使い捨てブランチ)と実行チェックリストを用意し,出力 CSV を集計して結果ドキュメント(TEST_02)にまとめる. @@ -41,8 +41,7 @@ - v1.7 は**フラット構成**(`TIASshot/*.cs` 直置き),現行は `UI/`・`Cameras/` 等に**再編済み**のため,cherry-pick は不可.**各タグへ手作業で移植**する. - 作業は各タグから切った**使い捨てブランチ**(`bench/v1.6`・`bench/v1.7`)上で行い,**`main` には一切マージしない**. - 移植は結合度で 2 層に分ける(Tier A / Tier B). -- 移植の原則: 旧版へは**観測フック(カウンタ/Stopwatch)のみ**を足し,**計測対象のロジック(撮影・保存・プレビューのコピー方式・Invalidate タイミング)は一切変えない**.変えると測りたい timing 自体が動く. -- 旧版に `LEAKY_REPRO` は不要(旧版自体が「修正前」であるため). +- 移植の原則: 旧版へは**観測フック(カウンタ/Stopwatch)のみ**を足し,**計測対象のロジック(撮影・保存・プレビューのコピー方式・Invalidate タイミング)は一切変えない**.変えると測りたい timing 自体が動く.旧版自体が「修正前」であるため,リーク再現用の仕掛けは不要(旧版をそのまま計測すればよい). ### Tier A: メモリ観測(低結合・3 版すべてに移植・確定) @@ -122,7 +121,7 @@ ## 注意点・リスク (Cautions & Risks) -- **実機 OOM 上限**: 自動ベンチ(TEST_01)には打ち切り上限ガードがあったが,実機観測窓(`MemoryMonitorForm`)にはガードが無い(記録するのみ).S1 でリーク版(v1.6)を長く回すと OOM に達し得るため,**サイクル数 M・放置時間 T に上限を決めておく**(値は計測実施時に確定). +- **実機 OOM 上限**: 自動ベンチ(TEST_03)には打ち切り上限ガードがあったが,実機観測窓(`MemoryMonitorForm`)にはガードが無い(記録するのみ).S1 でリーク版(v1.6)を長く回すと OOM に達し得るため,**サイクル数 M・放置時間 T に上限を決めておく**(値は計測実施時に確定). - **移植の非マージ**: `bench/v1.6`・`bench/v1.7` は計測専用の使い捨てブランチであり,`main` にマージ・push・PR しない. - 旧版が現行と同じツールチェーン(x64 / .NET Framework 4.8 / OpenCvSharp パッケージ)でビルドできるかは移植着手時に確認する. @@ -134,12 +133,10 @@ ## 成果物 (Deliverables) - 本計画: `docs/03_PLAN/PLAN_04_性能評価計測計画.md`. -- 計測結果: `docs/06_TEST/TEST_02_性能評価計測結果.md`(TEST_01 と同形式の 3 版比較表). +- 計測結果: `docs/06_TEST/TEST_02_性能評価計測結果.md`(TEST_03 と同形式の 3 版比較表). ## 関連 (Related) -- ビルドフラグ(`MEMMONITOR` / `LEAKY_REPRO`): [ENV_06_ビルド構成とビルドフラグ](../02_ENV/ENV_06_ビルド構成とビルドフラグ.md). -- 自動ベンチによるメモリ計測(参考・別手段): [TEST_01_メモリ修正効果計測](../06_TEST/TEST_01_メモリ修正効果計測.md). +- ビルドフラグ(`MEMMONITOR`): [ENV_06_ビルド構成とビルドフラグ](../02_ENV/ENV_06_ビルド構成とビルドフラグ.md). +- 自動ベンチによるメモリ計測(参考・別手段): [TEST_03_メモリ修正効果計測](../06_TEST/TEST_03_メモリ修正効果計測.md). - プレビューメモリリーク修正の詳細: [PLAN_03_プレビューメモリリーク修正計画](PLAN_03_プレビューメモリリーク修正計画.md). - - diff --git "a/docs/06_TEST/TEST_01_\343\203\206\343\202\271\343\203\210\344\273\225\346\247\230.md" "b/docs/06_TEST/TEST_01_\343\203\206\343\202\271\343\203\210\344\273\225\346\247\230.md" index fc177bf..b7c4650 100644 --- "a/docs/06_TEST/TEST_01_\343\203\206\343\202\271\343\203\210\344\273\225\346\247\230.md" +++ "b/docs/06_TEST/TEST_01_\343\203\206\343\202\271\343\203\210\344\273\225\346\247\230.md" @@ -2,8 +2,7 @@ ## 方針 (Policy) -リファクタリングによって処理が変わっていないことを確認するためのテスト仕様. -各フェーズの適用タイミングは `PLAN_01_リファクタリング計画.md` を参照する. +リファクタリング等,挙動を変えない変更で処理が変わっていないことを確認するためのテスト仕様. テストの種類: diff --git "a/docs/06_TEST/TEST_01_\343\203\241\343\203\242\343\203\252\344\277\256\346\255\243\345\212\271\346\236\234\350\250\210\346\270\254.md" "b/docs/06_TEST/TEST_01_\343\203\241\343\203\242\343\203\252\344\277\256\346\255\243\345\212\271\346\236\234\350\250\210\346\270\254.md" deleted file mode 100644 index dba8fe8..0000000 --- "a/docs/06_TEST/TEST_01_\343\203\241\343\203\242\343\203\252\344\277\256\346\255\243\345\212\271\346\236\234\350\250\210\346\270\254.md" +++ /dev/null @@ -1,80 +0,0 @@ -# メモリ修正効果の計測結果(PLAN_02 優先度2) (Memory Fix Effect Measurement) - -: 2026-06-23 -対象: PLAN_02 優先度2「`ColorCorrector.ConvertImage` の中間 Mat 未 Dispose(OOM リーク)」修正 -計測者: 自動ベンチ(NUnit、ヘッドレス実行・カメラ実機不要) - -## 目的 (Purpose) - -修正前(中間 Mat を解放しない=旧実装)と修正後(入れ子 `using` で解放)で、連続処理時のメモリ使用量の「増え方」がどれだけ変わるかを同一条件で定量比較する。 - -## 方法 (Method) - -- ベンチ: `TIASshot.Tests/ConvertImageMemoryBenchmark.cs`(`[Explicit]`)。 - - 修正後 = 実コード `ColorCorrector.ConvertImage`(using 版)。 - - 修正前 = 旧実装を忠実に再現(`flatten`/`extended`/`converted`/`convertedImage` を解放しない)。 - - 同一画像を N 回反復し、各反復後に `Process.PrivateMemorySize64` / `WorkingSet64` をサンプリング。 -- 指標: Mat はネイティブメモリのため `GC.GetTotalMemory` では捕捉できない → `PrivateMemorySize64`(コミット量)を主指標とする。 -- 計測ループ中は GC を強制しない(本番のリーク機序を再現するため)。物理メモリ枯渇を防ぐ打ち切り上限ガード付き。 -- 実行環境: x64 / .NET Framework 4.8 / 物理メモリ 16 GB。 - -## 結果 (Results) - -### 条件A: IScam フルスケール(2440×1840・17ch・40 回) - -| 指標 | 修正前(リーク) | 修正後(using) | -| --- | --- | --- | -| Private baseline | 0.05 GB | 0.05 GB | -| Private peak | **2.83 GB** | **0.62 GB** | -| 増分 (peak−base) | 2.78 GB | 0.57 GB | -| 1 回あたり平均増分 | **71 MB/回** | **15 MB/回** | -| 推移 | 0.82→1.49→2.16→2.83 GB と階段状に上昇 | 全 40 回 0.62 GB で一定 | - -→ 修正後の増分は修正前の **約 20%**。 - -### 条件B: 小サイズ・多反復(900×700・17ch・60 回) - -| 指標 | 修正前(リーク) | 修正後(using) | -| --- | --- | --- | -| Private peak | **0.53 GB** | **0.13 GB** | -| 増分 (peak−base) | 0.48 GB | 0.08 GB | -| 1 回あたり平均増分 | **8 MB/回** | **1 MB/回** | -| 推移 | 反復に伴い増加(0.16→0.44→0.53 GB) | 全 60 回 0.13 GB で一定 | - -→ 修正後の増分は修正前の **約 17%**。 - -### 条件C: 連続撮影フロー再現(フルスケール・2440×1840・channels 4/10/17・8 フレーム) - -`ContinuousShootMemoryBenchmark`(`BlockingCollection` の生産者-消費者・1 フレーム複数チャンネル変換・フレームバッファを合成フレームで構造再現)での計測。単体反復でなく「実機の連続撮影に近いフロー」での結果。 - -| 指標 | 修正前(リーク) | 修正後(using) | -| --- | --- | --- | -| Private peak | **2.60 GB** | **0.92 GB** | -| 増分 (peak−base) | 2.55 GB | 0.87 GB | -| **1 フレームあたり増分** | **326 MB/frame** | **112 MB/frame** | -| WorkingSet peak | 2.47 GB | 0.92 GB | - -→ 修正後の増分は修正前の **約 34%**。修正前は 1 フレームごとに約 326 MB 積み上がる(フレーム数を増やせば打ち切り上限=OOM へ)。修正後は約 0.9 GB で頭打ち(処理中の一時ピークで蓄積なし)。修正後の per-frame 112 MB は主に「保持中フレーム+処理中の一時メモリ」で,反復しても天井は一定. - -## 解釈 (Interpretation) - -- **修正後は反復しても増えない**(peak の天井が一定=蓄積ゼロ)。各呼び出しで中間 Mat が確定的に解放される。 -- **修正前は反復に伴いメモリが増える**(1 回あたり数十 MB〜)。本番は IScam 17ch × 複数フレームを `SaveThread` で高速に回すため、ファイナライザの回収が追いつかず確保が先行 → OOM クラッシュに至る(PLAN_02 の症状)。 -- 修正前のグラフが滑らかでなく**階段状/のこぎり波**になるのは、OpenCvSharp が GC にメモリ圧を通知し、未解放でも GC+ファイナライザが周期的にネイティブメモリを一掃するため。見るべきは振れ幅でなく**ピーク包絡線のトレンド**(修正後=水平、修正前=上昇)。 - -## 限界・補足 (Limitations) - -- 条件A/B は `ConvertImage` 単体の隔離計測。条件C は連続撮影フローを**構造再現**したもので、生産者-消費者スレッディング・多チャンネル・フレームバッファを含むが、**カメラ取得部のみ合成フレームに差し替えた再現**であり本番メソッド(`Shot`/`RunShotLoop`/`SaveThread`)そのものの呼び出しではない。実フレームレート・実カメラ・ファイル I/O(既定 OFF)までは含まない。実使用そのものの挙動は、`MEMMONITOR` ビルドのメモリ観測ウィンドウ+実機撮影の録画で別途確認する(PLAN_02「効果検証(メモリ修正)の方法」参照)。 -- 優先度5(Clear 時の未 Dispose)は未修正のため本対比の対象外(修正前/後どちらの変種にも同じだけ含まれ、差分には現れない)。 - -## 再現方法 (Reproduction) - -```powershell -# テストプロジェクトを Debug|x64 でビルド後 -$vstest = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" -$dll = "TIASshot.Tests\bin\x64\Debug\TIASshot.Tests.dll" -$env:BENCH_WIDTH="2440"; $env:BENCH_HEIGHT="1840"; $env:BENCH_CHANNEL="17"; $env:BENCH_FRAMES="40"; $env:BENCH_CEILING_GB="10" -& $vstest $dll /Platform:x64 /Framework:".NETFramework,Version=v4.8" /Tests:Compare_LeakyVsFixed_MemoryUsage /logger:"console;verbosity=normal" -``` - -※ スケールは環境変数 `BENCH_WIDTH/HEIGHT/CHANNEL/FRAMES/CEILING_GB` で調整可。 diff --git "a/docs/06_TEST/TEST_02_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\347\265\220\346\236\234.md" "b/docs/06_TEST/TEST_02_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\347\265\220\346\236\234.md" index 24d8386..65af726 100644 --- "a/docs/06_TEST/TEST_02_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\347\265\220\346\236\234.md" +++ "b/docs/06_TEST/TEST_02_\346\200\247\350\203\275\350\251\225\344\276\241\350\250\210\346\270\254\347\265\220\346\236\234.md" @@ -77,7 +77,7 @@ - **プレビュー性能は v1.6 と数値まで一致**(コピー ~45 ms・`callback_fps` ~17・副モニタ飢餓 ~3fps).v1.7 がプレビュー経路に手を付けていないことをデータが裏付ける. - **メモリのピーク包絡線は v1.6 の ~890 MB → v1.7 は ~700 MB に低下**(`SaveThread` の Mat 都度 Dispose+校正時の中間 Mat 解放).ただしプレビュー経路のリークは残るため,放置区間でも上昇は続き,現行のような平坦な天井にはならない. -- ※ 本プロトコルは単発撮影のため保存経路の差(v1.6→v1.7)は控えめに出る.保存経路単体の効果は連続撮影で顕著になり,自動ベンチの [TEST_01_メモリ修正効果計測](TEST_01_メモリ修正効果計測.md) に隔離計測値がある. +- ※ 本プロトコルは単発撮影のため保存経路の差(v1.6→v1.7)は控えめに出る.保存経路単体の効果は連続撮影で顕著になり,自動ベンチの [TEST_03_メモリ修正効果計測](TEST_03_メモリ修正効果計測.md) に隔離計測値がある. ## v1.6 の結果 (v1.6) @@ -127,12 +127,11 @@ 改善は 2 経路・2 段階で起きたことが定量的に確認できた. -- **保存経路(メモリ)は v1.6 → v1.7 で改善**: ピーク包絡線が ~890 MB → ~700 MB に低下(`SaveThread`/校正の Mat 解放).単発撮影プロトコルのため差は控えめだが方向は明確(連続撮影での隔離値は TEST_01). +- **保存経路(メモリ)は v1.6 → v1.7 で改善**: ピーク包絡線が ~890 MB → ~700 MB に低下(`SaveThread`/校正の Mat 解放).単発撮影プロトコルのため差は控えめだが方向は明確(連続撮影での隔離値は TEST_03). - **プレビュー経路は v1.7 → 現行で改善**: コピー ~45 ms → ~2.6 ms(FastClone,約 18 倍),`callback_fps` ~17 → ~30,副モニタ ~3fps(飢餓)→ ~19fps,メモリ包絡線 ~700 MB(上昇)→ ~200 MB(平坦・リーク解消).v1.6/v1.7 のプレビュー指標が数値まで一致することが,この経路が v1.7 まで未修正だった裏付けになる. - GDI/ハンドルはどの版も暴走せず,メモリ問題の原因がネイティブ画素バッファ(ハンドル枯渇ではない)であることを 3 版で一貫して確認. ## 関連 (Related) - 計測計画・指標定義・移植方針: [PLAN_04_性能評価計測計画](../03_PLAN/PLAN_04_性能評価計測計画.md). -- 自動ベンチによるメモリ計測(別手段・参考): [TEST_01_メモリ修正効果計測](TEST_01_メモリ修正効果計測.md). - +- 自動ベンチによるメモリ計測(別手段・参考): [TEST_03_メモリ修正効果計測](TEST_03_メモリ修正効果計測.md). diff --git "a/docs/06_TEST/TEST_03_\343\203\241\343\203\242\343\203\252\344\277\256\346\255\243\345\212\271\346\236\234\350\250\210\346\270\254.md" "b/docs/06_TEST/TEST_03_\343\203\241\343\203\242\343\203\252\344\277\256\346\255\243\345\212\271\346\236\234\350\250\210\346\270\254.md" new file mode 100644 index 0000000..4489ba2 --- /dev/null +++ "b/docs/06_TEST/TEST_03_\343\203\241\343\203\242\343\203\252\344\277\256\346\255\243\345\212\271\346\236\234\350\250\210\346\270\254.md" @@ -0,0 +1,80 @@ +# メモリ修正効果の計測結果(PLAN_02 優先度2) (Memory Fix Effect Measurement) + +: 2026-06-23 +対象: PLAN_02 優先度2「`ColorCorrector.ConvertImage` の中間 Mat 未 Dispose(OOM リーク)」修正 +計測者: 自動ベンチ(NUnit、ヘッドレス実行・カメラ実機不要) + +## 目的 (Purpose) + +修正前(中間 Mat を解放しない=旧実装)と修正後(入れ子 `using` で解放)で、連続処理時のメモリ使用量の「増え方」がどれだけ変わるかを同一条件で定量比較する。 + +## 方法 (Method) + +- ベンチ: `TIASshot.Tests/ConvertImageMemoryBenchmark.cs`(`[Explicit]`)。 + - 修正後 = 実コード `ColorCorrector.ConvertImage`(using 版)。 + - 修正前 = 旧実装を忠実に再現(`flatten`/`extended`/`converted`/`convertedImage` を解放しない)。 + - 同一画像を N 回反復し、各反復後に `Process.PrivateMemorySize64` / `WorkingSet64` をサンプリング。 +- 指標: Mat はネイティブメモリのため `GC.GetTotalMemory` では捕捉できない → `PrivateMemorySize64`(コミット量)を主指標とする。 +- 計測ループ中は GC を強制しない(本番のリーク機序を再現するため)。物理メモリ枯渇を防ぐ打ち切り上限ガード付き。 +- 実行環境: x64 / .NET Framework 4.8 / 物理メモリ 16 GB。 + +## 結果 (Results) + +### 条件A: IScam フルスケール(2440×1840・17ch・40 回) + +| 指標 | 修正前(リーク) | 修正後(using) | +| --- | --- | --- | +| Private baseline | 0.05 GB | 0.05 GB | +| Private peak | **2.83 GB** | **0.62 GB** | +| 増分 (peak−base) | 2.78 GB | 0.57 GB | +| 1 回あたり平均増分 | **71 MB/回** | **15 MB/回** | +| 推移 | 0.82→1.49→2.16→2.83 GB と階段状に上昇 | 全 40 回 0.62 GB で一定 | + +→ 修正後の増分は修正前の **約 20%**。 + +### 条件B: 小サイズ・多反復(900×700・17ch・60 回) + +| 指標 | 修正前(リーク) | 修正後(using) | +| --- | --- | --- | +| Private peak | **0.53 GB** | **0.13 GB** | +| 増分 (peak−base) | 0.48 GB | 0.08 GB | +| 1 回あたり平均増分 | **8 MB/回** | **1 MB/回** | +| 推移 | 反復に伴い増加(0.16→0.44→0.53 GB) | 全 60 回 0.13 GB で一定 | + +→ 修正後の増分は修正前の **約 17%**。 + +### 条件C: 連続撮影フロー再現(フルスケール・2440×1840・channels 4/10/17・8 フレーム) + +`ContinuousShootMemoryBenchmark`(`BlockingCollection` の生産者-消費者・1 フレーム複数チャンネル変換・フレームバッファを合成フレームで構造再現)での計測。単体反復でなく「実機の連続撮影に近いフロー」での結果。 + +| 指標 | 修正前(リーク) | 修正後(using) | +| --- | --- | --- | +| Private peak | **2.60 GB** | **0.92 GB** | +| 増分 (peak−base) | 2.55 GB | 0.87 GB | +| **1 フレームあたり増分** | **326 MB/frame** | **112 MB/frame** | +| WorkingSet peak | 2.47 GB | 0.92 GB | + +→ 修正後の増分は修正前の **約 34%**。修正前は 1 フレームごとに約 326 MB 積み上がる(フレーム数を増やせば打ち切り上限=OOM へ)。修正後は約 0.9 GB で頭打ち(処理中の一時ピークで蓄積なし)。修正後の per-frame 112 MB は主に「保持中フレーム+処理中の一時メモリ」で,反復しても天井は一定. + +## 解釈 (Interpretation) + +- **修正後は反復しても増えない**(peak の天井が一定=蓄積ゼロ)。各呼び出しで中間 Mat が確定的に解放される。 +- **修正前は反復に伴いメモリが増える**(1 回あたり数十 MB〜)。本番は IScam 17ch × 複数フレームを `SaveThread` で高速に回すため、ファイナライザの回収が追いつかず確保が先行 → OOM クラッシュに至る(PLAN_02 の症状)。 +- 修正前のグラフが滑らかでなく**階段状/のこぎり波**になるのは、OpenCvSharp が GC にメモリ圧を通知し、未解放でも GC+ファイナライザが周期的にネイティブメモリを一掃するため。見るべきは振れ幅でなく**ピーク包絡線のトレンド**(修正後=水平、修正前=上昇)。 + +## 限界・補足 (Limitations) + +- 条件A/B は `ConvertImage` 単体の隔離計測。条件C は連続撮影フローを**構造再現**したもので、生産者-消費者スレッディング・多チャンネル・フレームバッファを含むが、**カメラ取得部のみ合成フレームに差し替えた再現**であり本番メソッド(`Shot`/`RunShotLoop`/`SaveThread`)そのものの呼び出しではない。実フレームレート・実カメラ・ファイル I/O(既定 OFF)までは含まない。実使用そのものの挙動は、`MEMMONITOR` ビルドのメモリ観測ウィンドウ+実機撮影の録画で別途確認する。 +- 優先度5(Clear 時の未 Dispose)は未修正のため本対比の対象外(修正前/後どちらの変種にも同じだけ含まれ、差分には現れない)。 + +## 再現方法 (Reproduction) + +```powershell +# テストプロジェクトを Debug|x64 でビルド後 +$vstest = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" +$dll = "TIASshot.Tests\bin\x64\Debug\TIASshot.Tests.dll" +$env:BENCH_WIDTH="2440"; $env:BENCH_HEIGHT="1840"; $env:BENCH_CHANNEL="17"; $env:BENCH_FRAMES="40"; $env:BENCH_CEILING_GB="10" +& $vstest $dll /Platform:x64 /Framework:".NETFramework,Version=v4.8" /Tests:Compare_LeakyVsFixed_MemoryUsage /logger:"console;verbosity=normal" +``` + +※ スケールは環境変数 `BENCH_WIDTH/HEIGHT/CHANNEL/FRAMES/CEILING_GB` で調整可。 diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 5ab92b6..02ce442 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -72,7 +72,7 @@ - 計測の要点: Mat はネイティブメモリで `GC.GetTotalMemory` では捕捉不可 → `Process.PrivateMemorySize64`/`WorkingSet64` を使用。OpenCvSharp が GC にメモリ圧を通知するためリーク版でも GC+ファイナライザで周期回収が起き、グラフはのこぎり波になる(見るべきは振れ幅でなくピーク包絡線のトレンド:修正後=天井一定、リーク=上昇)。 - 後段: 優先度5(`_shots`/`_chartMasks` の Clear 時未解放)は未修正のため LEAKY_REPRO の対象外(対比は優先度2のみ)。グラフのトレンド可視化(包絡線・移動平均等)は要望次第で追加余地。 -- [x] **連続撮影フロー再現のメモリ計測ベンチ+計測結果レポート** (2026-06-23): 既存の単体ループベンチ(`ConvertImage` 隔離)では本番の連続撮影フローを再現できていなかったため、カメラ取得部だけを合成フレームに差し替えて本番フローを構造再現するベンチ(`ContinuousShootMemoryBenchmark`)を追加。生産者-消費者スレッディング・1 フレーム複数チャンネル変換・フレームバッファを含む。修正前/後の計測結果を `docs/06_TEST/TEST_01_メモリ修正効果計測.md` にまとめた(カメラ実機不要・ヘッドレスで Claude 側が実行可能)。 +- [x] **連続撮影フロー再現のメモリ計測ベンチ+計測結果レポート** (2026-06-23): 既存の単体ループベンチ(`ConvertImage` 隔離)では本番の連続撮影フローを再現できていなかったため、カメラ取得部だけを合成フレームに差し替えて本番フローを構造再現するベンチ(`ContinuousShootMemoryBenchmark`)を追加。生産者-消費者スレッディング・1 フレーム複数チャンネル変換・フレームバッファを含む。修正前/後の計測結果を `docs/06_TEST/TEST_03_メモリ修正効果計測.md` にまとめた(カメラ実機不要・ヘッドレスで Claude 側が実行可能)。 - 設計判断: 本番 `CameraBase.RunShotLoop`/`SaveThread`/`SaveImages` を直接ヘッドレス実行するには Form1・Config・TCC・校正で埋まる `_convRGB2SRGB` 依存が重すぎる → 本番コード無改造のまま「フローの構造再現」を選択(本番メソッドそのものの呼び出しではない旨を明記)。リーク再現と計測ユーティリティは `MemBenchSupport` に共有化し既存ベンチと統一。 - 結果の要点: フロー再現フルスケールで修正後増分は修正前の約 34%(per-frame 112 vs 326 MB)。単体隔離(約 17〜20%)と数値が違うのは、フロー版がフレーム保持+多チャンネル+並行処理の一時メモリを含むため。 - 役割分担の整理: メモリの「数値」はベンチ(Claude 側・再現性高)で、実使用の「動画デモ」は MEMMONITOR +実機録画(人間)で、と用途を分けた。 @@ -86,6 +86,17 @@ - 計測スカフォールドは MEMMONITOR 限定(Release 未含有)でそのまま残置(今後の検証・GDI 観測用。PLAN_03 も GDI 数観測を推奨)。 - 後段: 両プレビューの上限 ~10fps は毎フレームのフルフレーム Bitmap コピー(各 ~25ms)が律速だったため、別コミットで高速化(下記)。Phase 2/3(自動テスト・リファクタ)は本変更が WinForms UI スレッド・GDI ライフサイクル・カメラコールバック並行性主体で既存ユニットテスト基盤の対象外のため、実機メモリ計測での検証に代替(ユーザー指示によりこの検証済み状態でコミット)。 +- [x] **3 バージョン(v1.6/v1.7/現行)の性能評価計測** (2026-07-14): 上流 v1.6/v1.7 と現行フォークを実機 MEMMONITOR で同一プロトコル計測し、メモリリーク・フレームコピー時間・プレビュー FPS を横並び比較。改善が「保存経路 v1.6→v1.7」「プレビュー経路 v1.7→現行」の 2 段階で起きたことを定量化(計画 PLAN_04・結果 TEST_02、PR #18 で main へ)。 + - 動機: 研究室共有用に、素の上流版と現行の性能差を録画+スライドで示したい。数値ベンチ(TEST_03)ではなく「普通にアプリを使ったときの実挙動」を対象にした。 + - 計測基盤: 観測ウィンドウのグラフを左軸メモリ+右軸 FPS(本/副モニタ)の 2 軸化し、起動時に本体+観測窓を録画向けに自動配置。基準記録(校正→単発撮影 30 秒間隔×3→放置 180 秒)の秒数を全版で再現。 + - 旧版移植(設計判断): bench/v1.6・v1.7(throwaway・非マージ)へ観測フックのみ注入しプレビューのロジックは不変(リーク・コピー方式・Invalidate タイミングを保存)。v1.7 は v1.6 の移植を cherry-pick で流用(プレビュー経路が同一のため)。 + - 失敗パターンの圧縮: 旧版は毎フレーム Invalidate で本体 UI スレッドが飽和し、同一スレッドの観測窓がグラフ描画・サンプリングともに WM_PAINT 洪水で飢餓(更新が遅い・サンプル間隔が乱れる)→ 観測窓を専用 UI スレッドへ分離(測定中立)+WS_EX_COMPOSITED でラベルのちらつき抑制。 + - 結果: メモリ包絡線 v1.6 ~890MB → v1.7 ~700MB → 現行 ~200MB で平坦。コピー ~46ms(new Bitmap)→ ~2.6ms(FastClone)。副モニタ FPS ~3(飢餓)→ ~19。v1.6/v1.7 のプレビュー指標が数値まで一致=プレビュー経路が v1.7 まで未修正だった裏付け。 + +- [x] **完了済み計画(PLAN_01/02)の削除と LEAKY_REPRO フラグの撤去** (2026-07-14): 完了済みで本ログに要点が残る PLAN_01(リファクタリング)・PLAN_02(バグ修正)を削除し、削除ファイルへの実リンク・ファイル名参照を整理(歴史的言及は残置)。対比デモ用ビルドフラグ LEAKY_REPRO とその分岐コードを撤去。 + - 動機(LEAKY_REPRO): 3 版評価の完了で「旧版そのもの」が修正前を担うようになり、現行コード内でリークを再現する対比用フラグが不要になった。 + - 撤去内容: ColorCorrector.ConvertImage のリーク分岐を除去し修正版(入れ子 using)のみに、観測窓のモード表示(lblMode)を Designer 含め削除、ENV_06/PLAN_04 の記述も削除。標準構成では未定義のデッドコード除去のため挙動不変(ビルド成功・色補正テスト全パスで確認)。 + - [x] **プレビューのフレームコピーを LockBits で高速化** (2026-07-11): プレビュー表示経路で毎フレーム生成する Bitmap コピー(`new Bitmap(bmp)`)が 1 回 ~25ms かかり、コールバックスレッドの律速で実効 ~10fps に頭打ちだった問題を、生画素の直接コピーに置換して解消。実機計測で コピー ~25ms→~2.5ms(約10倍)、`callback_fps` ~16→~30、両プレビュー描画 ~10→~20fps を確認。 - 原因: `new Bitmap(Image)` は内部で 32bppARGB への変換を伴う GDI+ DrawImage 経路を通るため遅い。プレビューは 24bppRgb(`Mat.ToBitmap()` 由来)で、同一フォーマットの生画素をそのままコピーすれば足りる。 - 方針: `LockBits` で src/dst の生画素ポインタを取得し `RtlMoveMemory`(kernel32)で行単位ネイティブコピーする `FastClone`(`/unsafe` 不要)。生成物は入力と独立した画素バッファ=所有権・スレッド安全性(Lucam クロススレッド Dispose と非衝突)は従来の `new Bitmap(bmp)` と同一で不変。src は ReadOnly ロックのみで非破壊。インデックスカラー(8bpp 以下・パレット保持要)は安全網として従来の GDI+ フルコピーにフォールバック(この経路は色は等価だが PixelFormat が 32bppArgb に変換される点に注意)。 diff --git "a/docs/\345\244\211\346\233\264\343\201\276\343\201\250\343\202\201_v1.6-v1.7\343\201\213\343\202\211.md" "b/docs/\345\244\211\346\233\264\343\201\276\343\201\250\343\202\201_v1.6-v1.7\343\201\213\343\202\211.md" new file mode 100644 index 0000000..3af1988 --- /dev/null +++ "b/docs/\345\244\211\346\233\264\343\201\276\343\201\250\343\202\201_v1.6-v1.7\343\201\213\343\202\211.md" @@ -0,0 +1,102 @@ +# v1.6/v1.7 からの変更まとめ (Changes since v1.6/v1.7) + +本書は,上流(v1.6 / v1.7)を知っている読者に向けて,**各変更について「状況 → 具体的な差分」を 1 まとまり**で確認できるようにしたサマリである.クラッシュ修正・メモリリーク修正・性能改善を主軸とし,構造リファクタ・追加機能は末尾に概要のみ示す. + +## 使い方 (How to Read) + +各変更に,**該当ファイルへのリンク**(クリックでエディタに開く)と,その変更を **VS Code の左右差分で開く `git difftool` コマンド**を付けた.リポジトリさえあれば**どのマシン・どの OS でも動く**(GitHub 不要・埋め込みリンクなし). + +**初回だけ VS Code を差分ツールに設定する**(全リポジトリ共通): + +```bash +git config --global diff.tool vscode +git config --global difftool.vscode.cmd 'code --wait --diff "$LOCAL" "$REMOTE"' +``` + +- **`git difftool -y ^ -- `**: そのコミットで当該ファイルが**変更前 ↔ 変更後**でどう変わったかを **VS Code の左右差分**で開く. +- 設定せずに見たいときは **Timeline**: 該当ファイルを開き,エクスプローラーの「タイムライン」でコミットをクリックすると同じ左右差分が開く. +- ターミナル内のインライン差分で十分なら `git show -- ` でも可. + +## クラッシュ修正 (Crash Fixes) + +### 連続撮影クラッシュ(`_shots` スレッドセーフ化) + +- **状況**: 連続撮影で 4 枚以上取得するとたまにクラッシュしていた.撮影スレッドと保存スレッドが `_shots`(`List`)を無保護共有し,容量拡張時のリサイズ競合・TOCTOU が原因. +- **変更**: 生産者-消費者を `BlockingCollection` に置換し,撮影/保存間を安全化. +- **効果**: 連続撮影の確率的クラッシュを解消. +- **差分で確認(VS Code 左右差分)**: + - [CameraBase.cs](../TIASshot/Cameras/CameraBase.cs) — `git difftool -y 5df961c^ 5df961c -- TIASshot/Cameras/CameraBase.cs` + +### OOM クラッシュ(`ConvertImage` 中間 Mat 解放) + +- **状況**: 連続撮影で `ColorCorrector.ConvertImage` の中間 Mat(`flatten`/`extended`/`converted`/`convertedImage`)が未解放で数十 GB 積み上がり OOM.IScam 17ch × 複数フレームを高速に回すとファイナライザ回収が追いつかない. +- **変更**: 中間 Mat を入れ子 `using` で確定解放. +- **効果**: 連続撮影の OOM を解消. +- **差分で確認(VS Code 左右差分)**: + - [ColorCorrector.cs](../TIASshot/ColorCorrection/ColorCorrector.cs) — `git difftool -y 0003cc2^ 0003cc2 -- TIASshot/ColorCorrection/ColorCorrector.cs` + +### Lucam クロススレッド GDI クラッシュ + +- **状況**: Lucam は二重バッファ `_bmps[]` をコールバックスレッドで Dispose する.入力 bmp を直接 `picDisplay.Image` に載せると UI の `OnPaint` と別スレッド同時アクセスでクラッシュしうる(v1.6 からの潜在バグ). +- **変更**: 入力 bmp は「読むだけ」に統一し,表示は呼び出しスレッド上で UI 所有コピーを生成する設計に変更.修正は**表示側(`Form1.ShowImage`)と入力供給側(`IScam` が自分の `ToBitmap` を破棄)**に入る(`Lucam.cs` 自体は不変). +- **効果**: GDI 同時アクセスクラッシュを解消. +- **差分で確認(VS Code 左右差分)**: + - [Form1.cs](../TIASshot/UI/Form1.cs) — `git difftool -y 252cde3^ 252cde3 -- TIASshot/UI/Form1.cs`(`ShowImage` の所有権変更) + - [IScam.cs](../TIASshot/Cameras/IScam.cs) — `git difftool -y 252cde3^ 252cde3 -- TIASshot/Cameras/IScam.cs`(入力 `ToBitmap` の破棄) + +## メモリリーク修正 (Memory Leak Fixes) + +### プレビュー経路リーク(原因1〜3) + +- **状況**: プレビュー表示のまま長時間放置で落ちる.`ShowImage` のフルコピー Bitmap が毎フレームリーク(原因1),表示バッファの取りこぼしフレーム未 Dispose(原因2),`PreviewMonitor.UpdateImage` の上書き前未 Dispose(原因3).撮影しなくても蓄積する. +- **変更**: 表示バッファの所有権を UI 側に統一し,取りこぼしフレームを確実に Dispose. +- **効果**: 放置クラッシュを解消(メモリ天井が一定に). +- **差分で確認(VS Code 左右差分)**: + - [Form1.cs](../TIASshot/UI/Form1.cs) — `git difftool -y 252cde3^ 252cde3 -- TIASshot/UI/Form1.cs` + - [PreviewMonitor.cs](../TIASshot/UI/PreviewMonitor.cs) — `git difftool -y 252cde3^ 252cde3 -- TIASshot/UI/PreviewMonitor.cs` + +## 性能改善 (Performance) + +### 副モニタ描画飢餓 → タイマー駆動化 + +- **状況**: 校正完了後にコールバックの毎フレーム `Invalidate()` 洪水で Form1 が常時 WM_PAINT 保留になり,同一 UI スレッドの副モニタが描画飢餓(`sub_paint_fps`≈2〜3). +- **変更**: コールバックからの毎フレーム `Invalidate()` を全廃し,両プレビューを ≒30fps の UI タイマー駆動に統一. +- **効果**: 副モニタが steady に復帰(~19fps). +- **差分で確認(VS Code 左右差分)**: + - [Form1.cs](../TIASshot/UI/Form1.cs) — `git difftool -y 252cde3^ 252cde3 -- TIASshot/UI/Form1.cs` + +### フレームコピー高速化(FastClone) + +- **状況**: プレビューで毎フレーム生成する `new Bitmap(bmp)`(32bppARGB 変換を伴う GDI+ 経路で ~46ms)が律速で実効 FPS が頭打ち. +- **変更**: 同一フォーマットの生画素を `LockBits`+`RtlMoveMemory` で行単位コピーする `FastClone` に置換(所有権・スレッド安全性は不変).FastClone 本体は新規 `Utils/BitmapUtil.cs`. +- **効果**: コピー ~46ms→~2.6ms,実効 FPS が倍増. +- **差分で確認(VS Code 左右差分)**: + - [Form1.cs](../TIASshot/UI/Form1.cs) — `git difftool -y 609baf6^ 609baf6 -- TIASshot/UI/Form1.cs` + - [BitmapUtil.cs](../TIASshot/Utils/BitmapUtil.cs) — `git difftool -y 609baf6^ 609baf6 -- TIASshot/Utils/BitmapUtil.cs` + +## 定量結果 (Quantitative Results) + +3 バージョンを実機で同一プロトコル計測した比較(詳細は [TEST_02](06_TEST/TEST_02_性能評価計測結果.md)).保存経路のメモリは v1.6→v1.7 で上流が改善済み,プレビュー経路は v1.7→現行で本フォークが改善した. + +| 指標 | v1.6 | v1.7 | 現行 | +| --- | --- | --- | --- | +| メモリ `private` 包絡線 | ~200→834 MB 上昇 | ~200→700 MB 上昇 | **~180〜200 MB で平坦** | +| フレームコピー時間 | ~46 ms | ~45 ms | **~2.6 ms** | +| コールバック実効 FPS | ~17 | ~17 | **~30** | +| 副モニタ描画 FPS | ~3(飢餓) | ~3(飢餓) | **~19** | + +→ v1.6/v1.7 のプレビュー指標が一致するのは,プレビュー経路が v1.7 まで未修正だったため. + +## その他の変更(概要) (Other Changes) + +主軸ではないが,あわせて入った変更.差分は `git difftool -y ^ `(パス省略でそのコミットの全変更ファイルを順に開く)や `git show ` で辿れる. + +- **構造リファクタ**: フラット構成を `Cameras/`・`ColorCorrection/`・`ChartDetection/`・`UI/`・`Utils/` に再編し,`IScam`/`Lucam` の重複フローをテンプレートメソッドに集約(挙動不変).`e278f27`(Phase1)ほか,`8a00494`(テンプレートメソッド化). +- **追加機能**: 白板撮影モード(`WHITEBOARD` ビルド限定・`2854dba`)とメモリ観測ツール(`MEMMONITOR` ビルド限定・`243ddeb`).いずれも配布ビルドには含めない([ENV_06](02_ENV/ENV_06_ビルド構成とビルドフラグ.md)). + +## 詳細索引 (References) + +- 全変更の経緯・設計判断・失敗パターン: [PROGRESS.md](PROGRESS.md) +- プレビューメモリリーク修正の詳細: [PLAN_03](03_PLAN/PLAN_03_プレビューメモリリーク修正計画.md) +- 性能評価の計画と結果: [PLAN_04](03_PLAN/PLAN_04_性能評価計測計画.md) / [TEST_02](06_TEST/TEST_02_性能評価計測結果.md) +- コミット一覧: `git log --oneline`/ファイル単位の履歴: `git log -p -- `