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..f8906c5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -4,7 +4,7 @@
## 開発進捗
-最新: 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 フラグを撤去(挙動不変・テスト 14/14 パス)
※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは `.claude/rules/progress-log.md` を参照(該当ファイル編集時に自動ロードされる).
## 必須ルール(コード実装時)
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/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..7548e21 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"
@@ -91,7 +91,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..abe6969 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`(メモリ修正の効果検証ツール)
+ - `MEMMONITOR`(メモリ修正の効果検証ツール)
- メモリ計測の結果は [docs/06_TEST/TEST_01_メモリ修正効果計測.md](../06_TEST/TEST_01_メモリ修正効果計測.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..bc54b53 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_01](../06_TEST/TEST_01_メモリ修正効果計測.md))は**撮影フロー対象**であり,本件(プレビュー放置)はカバーしていない.プレビュー放置シナリオの観測は別途行う.
- GDI ハンドルのリークが疑われるため,観測ウィンドウに **GDI オブジェクト数**(`GetGuiResources` 等)の表示を追加して,メモリだけでなくハンドル数も時間で増えないことを確認することを推奨する(任意).
## 修正順序まとめ (Summary)
@@ -81,5 +81,4 @@
## 関連 (Related)
-- [PLAN_02 バグ修正計画](PLAN_02_バグ修正計画.md) — 連続撮影クラッシュ(撮影経路).本計画とは別系統.
- [TEST_01 メモリ修正効果計測](../06_TEST/TEST_01_メモリ修正効果計測.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..a724142 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,7 +12,7 @@
| --- | --- | --- | --- |
| **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 → 現行で**効いた,という切り分けを示す.
@@ -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 版すべてに移植・確定)
@@ -138,7 +137,7 @@
## 関連 (Related)
-- ビルドフラグ(`MEMMONITOR` / `LEAKY_REPRO`): [ENV_06_ビルド構成とビルドフラグ](../02_ENV/ENV_06_ビルド構成とビルドフラグ.md).
+- ビルドフラグ(`MEMMONITOR`): [ENV_06_ビルド構成とビルドフラグ](../02_ENV/ENV_06_ビルド構成とビルドフラグ.md).
- 自動ベンチによるメモリ計測(参考・別手段): [TEST_01_メモリ修正効果計測](../06_TEST/TEST_01_メモリ修正効果計測.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"
index dba8fe8..4489ba2 100644
--- "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"
@@ -64,7 +64,7 @@
## 限界・補足 (Limitations)
-- 条件A/B は `ConvertImage` 単体の隔離計測。条件C は連続撮影フローを**構造再現**したもので、生産者-消費者スレッディング・多チャンネル・フレームバッファを含むが、**カメラ取得部のみ合成フレームに差し替えた再現**であり本番メソッド(`Shot`/`RunShotLoop`/`SaveThread`)そのものの呼び出しではない。実フレームレート・実カメラ・ファイル I/O(既定 OFF)までは含まない。実使用そのものの挙動は、`MEMMONITOR` ビルドのメモリ観測ウィンドウ+実機撮影の録画で別途確認する(PLAN_02「効果検証(メモリ修正)の方法」参照)。
+- 条件A/B は `ConvertImage` 単体の隔離計測。条件C は連続撮影フローを**構造再現**したもので、生産者-消費者スレッディング・多チャンネル・フレームバッファを含むが、**カメラ取得部のみ合成フレームに差し替えた再現**であり本番メソッド(`Shot`/`RunShotLoop`/`SaveThread`)そのものの呼び出しではない。実フレームレート・実カメラ・ファイル I/O(既定 OFF)までは含まない。実使用そのものの挙動は、`MEMMONITOR` ビルドのメモリ観測ウィンドウ+実機撮影の録画で別途確認する。
- 優先度5(Clear 時の未 Dispose)は未修正のため本対比の対象外(修正前/後どちらの変種にも同じだけ含まれ、差分には現れない)。
## 再現方法 (Reproduction)
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 5ab92b6..06b4c24 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -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_01)ではなく「普通にアプリを使ったときの実挙動」を対象にした。
+ - 計測基盤: 観測ウィンドウのグラフを左軸メモリ+右軸 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 に変換される点に注意)。