diff --git a/CLAUDE.md b/CLAUDE.md index 7918e2d..01fa40a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ ## 開発進捗 -最新: テストプロジェクト(TIASshot.Tests, NUnit/x64)を立ち上げ,ピュアロジック層(ColorCorrector/Config/IoUtil)に初期テストを追加 +最新: ゲイン補正式(GetRatio)を純粋メソッド GainCalc に抽出してテスト対象化(挙動不変・実機で校正収束確認済み) ※ 本欄は**最新ステップ 1 行のみ上書き更新**.詳細な進捗履歴(動機・設計判断・失敗パターン)は [docs/PROGRESS.md](docs/PROGRESS.md) に追記する.運用ルールは GUIDE_05「進捗記録の運用ルール(CLAUDE.md / PROGRESS.md)」を参照. ## 必須ルール(コード実装時) diff --git a/TIASshot.Tests/GainCalcTests.cs b/TIASshot.Tests/GainCalcTests.cs new file mode 100644 index 0000000..121ecaf --- /dev/null +++ b/TIASshot.Tests/GainCalcTests.cs @@ -0,0 +1,147 @@ +using NUnit.Framework; + +namespace TIASshot.Tests { + /// + /// GainCalc.CalcRatio のテスト + /// + [TestFixture] + public class GainCalcTests { + + // --------------------------------------------------------------- + // 通常ケース + // --------------------------------------------------------------- + + /// + /// CalcRatio_通常ケースupdateRate0_5_正しい比率を返す: + /// value=100, target=200, updateRate=0.5 → (200/100 - 1) * 0.5 + 1 = 1.5 + /// + [Test] + public void CalcRatio_通常ケースupdateRate0_5_正しい比率を返す() { + float result = GainCalc.CalcRatio(100.0f, 200.0f, 0.5f); + Assert.AreEqual(1.5f, result, 1e-6f); + } + + // --------------------------------------------------------------- + // updateRate が変わると比率が変わること + // --------------------------------------------------------------- + + /// + /// CalcRatio_updateRate1_0_係数1倍の比率を返す: + /// value=100, target=200, updateRate=1.0 → (200/100 - 1) * 1.0 + 1 = 2.0 + /// + [Test] + public void CalcRatio_updateRate1_0_係数1倍の比率を返す() { + float result = GainCalc.CalcRatio(100.0f, 200.0f, 1.0f); + Assert.AreEqual(2.0f, result, 1e-6f); + } + + /// + /// CalcRatio_updateRate0_1_updateRateが小さいほど変化量が小さい: + /// value=100, target=200, updateRate=0.1 → (200/100 - 1) * 0.1 + 1 = 1.1 + /// + [Test] + public void CalcRatio_updateRate0_1_updateRateが小さいほど変化量が小さい() { + float result = GainCalc.CalcRatio(100.0f, 200.0f, 0.1f); + Assert.AreEqual(1.1f, result, 1e-6f); + } + + // --------------------------------------------------------------- + // value == target のとき比率は 1.0 + // --------------------------------------------------------------- + + /// + /// CalcRatio_valueがtargetと等しい_updateRateによらず比率は1: + /// value == target のとき ratio = target/value = 1、(1 - 1) * anything + 1 = 1 + /// + [Test] + public void CalcRatio_valueがtargetと等しい_updateRate0_5_比率は1() { + float result = GainCalc.CalcRatio(150.0f, 150.0f, 0.5f); + Assert.AreEqual(1.0f, result, 1e-6f); + } + + /// + /// CalcRatio_valueがtargetと等しい_updateRateを変えても結果が同じ: + /// value == target のとき updateRate=0.5 と updateRate=1.0 の結果が一致する + /// (updateRate に依存しないことを比較で検証する) + /// + [Test] + public void CalcRatio_valueがtargetと等しい_updateRateを変えても結果が同じ() { + float result05 = GainCalc.CalcRatio(150.0f, 150.0f, 0.5f); + float result10 = GainCalc.CalcRatio(150.0f, 150.0f, 1.0f); + Assert.AreEqual(result05, result10, 1e-6f, "value==target のとき updateRate によらず結果が同じ"); + } + + // --------------------------------------------------------------- + // value == 255.0f の特別扱い + // --------------------------------------------------------------- + + /// + /// CalcRatio_value255_updateRate0_5_係数は常に1_0になる: + /// value=255 のとき updateRate を無視して係数 1.0 を使う + /// → (200/255 - 1) * 1.0 + 1 = 200/255 + /// + [Test] + public void CalcRatio_value255_updateRate0_5_係数は常に1_0になる() { + float expected = 200.0f / 255.0f; + float result = GainCalc.CalcRatio(255.0f, 200.0f, 0.5f); + Assert.AreEqual(expected, result, 1e-6f); + } + + /// + /// CalcRatio_value255_updateRateを変えても結果が同じ: + /// value=255 では updateRate=0.5 と updateRate=1.0 の結果が一致する + /// + [Test] + public void CalcRatio_value255_updateRateを変えても結果が同じ() { + float result05 = GainCalc.CalcRatio(255.0f, 200.0f, 0.5f); + float result10 = GainCalc.CalcRatio(255.0f, 200.0f, 1.0f); + Assert.AreEqual(result05, result10, 1e-6f, "value==255 のとき updateRate によらず結果が同じ"); + } + + // --------------------------------------------------------------- + // ゲイン減少方向(target < value) + // --------------------------------------------------------------- + + /// + /// CalcRatio_targetがvalueより小さい_比率が1未満になる: + /// value=200, target=100, updateRate=0.5 → (100/200 - 1) * 0.5 + 1 = 0.75 + /// ゲインが目標より高すぎる場合に比率が 1.0 未満(下降補正)になることを確認する + /// + [Test] + public void CalcRatio_targetがvalueより小さい_updateRate0_5_比率が1未満になる() { + float result = GainCalc.CalcRatio(200.0f, 100.0f, 0.5f); + Assert.AreEqual(0.75f, result, 1e-6f); + } + + // --------------------------------------------------------------- + // updateRate=0 のとき比率は常に 1.0 + // --------------------------------------------------------------- + + /// + /// CalcRatio_updateRate0_比率は常に1: + /// value != target でも updateRate=0 なら (ratio-1)*0+1 = 1.0 + /// 更新が停止する境界値として確認する + /// + [Test] + public void CalcRatio_updateRate0_比率は常に1() { + float result = GainCalc.CalcRatio(100.0f, 200.0f, 0.0f); + Assert.AreEqual(1.0f, result, 1e-6f); + } + + // --------------------------------------------------------------- + // ゼロ除算の挙動(現状記録) + // --------------------------------------------------------------- + + /// + /// CalcRatio_value0_戻り値はInfinityまたはNaN_例外は出ない: + /// float では 0 除算は例外にならず Infinity / NaN になる(現状挙動の固定) + /// + [Test] + public void CalcRatio_value0_戻り値はInfinityまたはNaN_例外は出ない() { + float result = GainCalc.CalcRatio(0.0f, 200.0f, 0.5f); + // float(0 除算) は例外を投げずに Infinity / NaN を返す + Assert.IsTrue(float.IsInfinity(result) || float.IsNaN(result), + "value=0 のとき戻り値は Infinity または NaN になる(例外は出ない)"); + } + } +} diff --git a/TIASshot.Tests/TIASshot.Tests.csproj b/TIASshot.Tests/TIASshot.Tests.csproj index 3e5f4d6..b2dbf0a 100644 --- a/TIASshot.Tests/TIASshot.Tests.csproj +++ b/TIASshot.Tests/TIASshot.Tests.csproj @@ -54,6 +54,7 @@ + diff --git a/TIASshot/Cameras/CameraBase.cs b/TIASshot/Cameras/CameraBase.cs index db839c2..cf2b263 100644 --- a/TIASshot/Cameras/CameraBase.cs +++ b/TIASshot/Cameras/CameraBase.cs @@ -297,12 +297,11 @@ /// /// ゲイン値の更新比率計算 /// - /// - /// + /// 現在の計測値 + /// 目標値 + /// 更新比率 protected float GetRatio(float value, float target) { - float ratio = target / value; - ratio = (ratio - 1.0f) * (value == 255.0f ? 1.0f : UpdateRate) + 1.0f; - return ratio; + return GainCalc.CalcRatio(value, target, UpdateRate); } /// diff --git a/TIASshot/Cameras/GainCalc.cs b/TIASshot/Cameras/GainCalc.cs new file mode 100644 index 0000000..2695fd5 --- /dev/null +++ b/TIASshot/Cameras/GainCalc.cs @@ -0,0 +1,21 @@ +namespace TIASshot { + /// + /// ゲイン更新比率の計算ロジック(ピュアロジック層). + /// ハードウェアに依存しない純粋な計算のみを担う. + /// + internal static class GainCalc { + /// + /// ゲイン値の更新比率を計算する. + /// value が 255.0f のときは updateRate によらず係数を 1.0 として比率を求める. + /// + /// 現在の計測値 + /// 目標値 + /// 更新レート(0 より大きい値を想定) + /// 更新比率 + internal static float CalcRatio(float value, float target, float updateRate) { + float ratio = target / value; + ratio = (ratio - 1.0f) * (value == 255.0f ? 1.0f : updateRate) + 1.0f; + return ratio; + } + } +} diff --git a/TIASshot/TIASshot.csproj b/TIASshot/TIASshot.csproj index 17d62b6..eb9c3d4 100644 --- a/TIASshot/TIASshot.csproj +++ b/TIASshot/TIASshot.csproj @@ -119,6 +119,7 @@ + diff --git "a/docs/02_ENV/ENV_02_\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_02_\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 c0c3f8e..c85f6bc 100644 --- "a/docs/02_ENV/ENV_02_\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_02_\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" @@ -25,7 +25,8 @@ ├── Cameras/ ← カメラ制御 │ ├── CameraBase.cs ← カメラ制御の基底クラス(共通処理・撮影/校正の骨格) │ ├── IScam.cs ← ImagingSource カメラの実装 -│ └── Lucam.cs ← Lumenera カメラの実装 +│ ├── Lucam.cs ← Lumenera カメラの実装 +│ └── GainCalc.cs ← ゲイン更新比率の計算(純粋ロジック) ├── ChartDetection/ ← チャート検出 │ └── ChartDetector.cs ← ArUco マーカー検出・マスク生成 ├── ColorCorrection/ ← 色補正 diff --git "a/docs/05_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" "b/docs/05_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" index d8c6ad6..d3998c6 100644 --- "a/docs/05_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" +++ "b/docs/05_TECH/TECH_01_\343\202\257\343\203\251\343\202\271\350\250\255\350\250\210.md" @@ -60,6 +60,12 @@ - ファイル I/O(CSV 読み書き・保存フォルダ生成・撮影情報出力)とサウンド再生.状態を持たない. - `CameraBase` から委譲される. +**GainCalc(静的クラス)** + +- ファイル: `Cameras/GainCalc.cs` +- ゲイン更新比率の計算(ホワイトバランス自動調整).ハードウェアに依存しない純粋計算. +- `CameraBase.GetRatio` から委譲される(テスト可能なピュアロジック層). + ## 継承関係 (Inheritance) ```text @@ -90,7 +96,7 @@ - `RunShotLoop(numImages, interval)`: 撮影ループの共通骨格(テンプレートメソッド) - `SaveImages(img, idx)`: 撮影画像の保存(RGB・sRGB 変換画像.色変換は `ColorCorrector` に委譲) - `SaveThread(numImages)`: 画像保存スレッド処理 -- `GetRatio(value, target) : float`: ゲイン更新比率の計算 +- `GetRatio(value, target) : float`: ゲイン更新比率の計算(`GainCalc.CalcRatio` に委譲) ### 主なフィールド diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 48f8b0c..c19f0a6 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -42,3 +42,9 @@ - テスト容易化のシーム(挙動不変): 本体に `[InternalsVisibleTo("TIASshot.Tests")]` 追加,`ColorCorrector` の純粋メソッドを private→internal・チャネル配列を受け取る internal コンストラクタ追加,`Config.Load(string path)` オーバーロード追加。 - ハマり所: テストでも OpenCvSharp ネイティブ DLL の **x64 ロード**が必須(`IoUtil` の CSV テストが通る=ネイティブ動作の試金石)。`.gitignore` を汎用 `bin/`・`obj/` に変更し,テストプロジェクトの成果物も除外。 - 後段: `GetRatio`(ゲイン補正式)は `CameraBase`(ハードウェア結合層)にインラインのため未テスト。抽出すればテスト候補。ENV_02 のメインソース一覧がリファクタ前構成のままドリフトしており,別途修正が必要。 + +- [x] **docs ドリフト修正(ENV_02・TECH_01)** (2026-06-07): リファクタリング(Phase 1〜5)後も旧構成のまま残っていたドキュメントを実構成に整合。ENV_02 のソース一覧をサブフォルダ構成に、TECH_01 のファイルパス・クラス一覧(`ChartDetector`/`ColorCorrector`/`IoUtil` 追加)・`CameraBase` メンバ(移動した `ConvertImage`/`_convRGB2SRGB` 削除)を修正。 + +- [x] **ゲイン補正式(GetRatio)の純粋メソッド抽出+テスト** (2026-06-07): `CameraBase` にインラインだったゲイン更新比率の計算式を,ハードウェア非依存の純粋クラス `GainCalc.CalcRatio` に抽出し,`CameraBase.GetRatio` は委譲に変更。`TIASshot.Tests` でテスト対象化(GUIDE_08 の「ピュアロジック層を分離して自動テスト」方針の実践)。 + - 方針: **挙動不変**。式・演算順序・`float` 比較・ゼロ除算挙動・`UpdateRate` の流れをすべて保持し,派生クラスの呼び出し箇所は無変更。実機で校正(ホワイトバランス自動調整)の収束を確認。 + - テスト観点: 通常比率・`updateRate` 減衰・下降補正(`target