diff --git a/TIASshot.Tests/HwTestJudgeTests.cs b/TIASshot.Tests/HwTestJudgeTests.cs index 2afcf15..fde7a16 100644 --- a/TIASshot.Tests/HwTestJudgeTests.cs +++ b/TIASshot.Tests/HwTestJudgeTests.cs @@ -1062,6 +1062,675 @@ public void ShouldKeepWorkDir_合格と前提不足_消す(int exitCode) { Assert.That(HwTestJudge.ShouldKeepWorkDir(false, exitCode), Is.False); } + + // ================================================================ + // チャートが必要な検査(--with-chart.PLAN_06 フェーズ 2) + // 仕様: PLAN_06「チャートが必要」の表と合意済みのしきい値. + // 実測の例は .verify/hwtest/run-20261008-154111/hwtest-result.txt(DFK 33UX178.17 次元)から採った + // ================================================================ + + // ---- 検査の並び・全体の上限 ---- + + [Test] + public void CheckIds_チャートあり_接続から校正撮影閉じる検査を経て後始末の順() { + Assert.That(HwTestJudge.CheckIds(true), + Is.EqualTo(new[] { "connect", "calibrate", "shot", "close-saving", "close-calibrating", "cleanup" })); + } + + [Test] + public void CheckIds_チャートなし_従来のチャート不要の検査の順() { + Assert.That(HwTestJudge.CheckIds(false), + Is.EqualTo(new[] { "connect", "preview", "exposure-gain", "light", "close-preview", "reconnect", "cleanup" })); + } + + [Test] + public void CheckIds_チャートあり_チャート不要の検査を含まない() { + Assert.That(HwTestJudge.CheckIds(true), Has.None.AnyOf("preview", "exposure-gain", "light", "close-preview", "reconnect")); + } + + [Test] + public void OverallTimeout_チャートなし_600秒() { + Assert.That(HwTestJudge.OverallTimeout(false), Is.EqualTo(TimeSpan.FromSeconds(600))); + } + + [Test] + public void OverallTimeout_チャートあり_1200秒() { + Assert.That(HwTestJudge.OverallTimeout(true), Is.EqualTo(TimeSpan.FromSeconds(1200))); + } + + [Test] + public void OverallTimeout_チャートあり_チャートありの項目の上限の合計より長い() { + var sum = HwTestThresholds.ConnectTimeout + HwTestThresholds.CalibrateTimeout + HwTestThresholds.ShotTimeout + + HwTestThresholds.CloseSavingTimeout + HwTestThresholds.CloseCalibratingTimeout + HwTestThresholds.CleanupTimeout; + Assert.That(HwTestJudge.OverallTimeout(true), Is.GreaterThan(sum)); + } + + // ---- 合意済みのしきい値 ---- + + [Test] + public void Thresholds_校正の待ち時間_60秒() { + Assert.That(HwTestThresholds.CalibrationWait, Is.EqualTo(TimeSpan.FromSeconds(60))); + } + + [Test] + public void Thresholds_色の誤差の平均の上限_5() { + Assert.That(HwTestThresholds.MaxCalibrationMeanError, Is.EqualTo(5.0)); + } + + [Test] + public void Thresholds_白色票を除いた最大の上限_10() { + Assert.That(HwTestThresholds.MaxCalibrationMaxError, Is.EqualTo(10.0)); + } + + [Test] + public void Thresholds_白色票の最大の上限_40() { + Assert.That(HwTestThresholds.MaxCalibrationWhiteMaxError, Is.EqualTo(40.0)); + } + + [Test] + public void Thresholds_撮影間隔のずれの許容_100ms() { + Assert.That(HwTestThresholds.ShotIntervalToleranceMs, Is.EqualTo(100.0)); + } + + [Test] + public void Thresholds_保存の待ち_180秒() { + Assert.That(HwTestThresholds.SaveWait, Is.EqualTo(TimeSpan.FromSeconds(180))); + } + + [Test] + public void Thresholds_校正中になるまでの待ち_60秒() { + Assert.That(HwTestThresholds.CalibratingWait, Is.EqualTo(TimeSpan.FromSeconds(60))); + } + + [Test] + public void Thresholds_閉じる処理の上限_1000ms() { + Assert.That(HwTestThresholds.CloseLimit, Is.EqualTo(TimeSpan.FromMilliseconds(1000))); + } + + [Test] + public void Thresholds_閉じた後のフレームの上限_2() { + Assert.That(HwTestThresholds.MaxFramesAfterClose, Is.EqualTo(2)); + } + + // ---- 時刻の差 ---- + + [Test] + public void TimestampDeltaMs_周波数で割ってミリ秒にする() { + Assert.That(HwTestJudge.TimestampDeltaMs(1000, 3500, 1000), Is.EqualTo(2500.0).Within(Eps)); + } + + [Test] + public void TimestampDeltaMs_高分解能の周波数_ミリ秒に換算する() { + // Stopwatch.Frequency の典型値 10 MHz で 1 秒の差 + Assert.That(HwTestJudge.TimestampDeltaMs(5000000, 15000000, 10000000), Is.EqualTo(1000.0).Within(Eps)); + } + + [Test] + public void TimestampDeltaMs_同じ時刻_0() { + Assert.That(HwTestJudge.TimestampDeltaMs(1234, 1234, 1000), Is.EqualTo(0.0).Within(Eps)); + } + + [Test] + public void TimestampDeltaMs_開始が未記録の0_NaN() { + Assert.That(HwTestJudge.TimestampDeltaMs(0, 1000, 1000), Is.NaN); + } + + [Test] + public void TimestampDeltaMs_終了が未記録の0_NaN() { + Assert.That(HwTestJudge.TimestampDeltaMs(1000, 0, 1000), Is.NaN); + } + + [Test] + public void TimestampDeltaMs_逆順_NaN() { + Assert.That(HwTestJudge.TimestampDeltaMs(2000, 1000, 1000), Is.NaN); + } + + [TestCase(0L)] + [TestCase(-1000L)] + public void TimestampDeltaMs_周波数が0以下_NaN(long frequency) { + Assert.That(HwTestJudge.TimestampDeltaMs(1000, 2000, frequency), Is.NaN); + } + + // ---- 色の誤差の判定 ---- + + private static bool JudgeColor(double meanAbs, double otherMax, double whiteMax) { + return HwTestJudge.JudgeColorError(meanAbs, otherMax, whiteMax, + HwTestThresholds.MaxCalibrationMeanError, HwTestThresholds.MaxCalibrationMaxError, HwTestThresholds.MaxCalibrationWhiteMaxError); + } + + [Test] + public void JudgeColorError_実測の値_合格() { + // 2026-10-08 15:41 の実測(17 次元): 平均 1.57・白色票を除いた最大 3.47・白色票 24.65 + Assert.That(JudgeColor(1.57, 3.47, 24.65), Is.True); + } + + [Test] + public void JudgeColorError_すべて上限ちょうど_合格() { + Assert.That(JudgeColor(5.0, 10.0, 40.0), Is.True); + } + + [Test] + public void JudgeColorError_平均が上限超え_不合格() { + Assert.That(JudgeColor(5.01, 3.47, 24.65), Is.False); + } + + [Test] + public void JudgeColorError_白色票を除いた最大が上限超え_不合格() { + Assert.That(JudgeColor(1.57, 10.01, 24.65), Is.False); + } + + [Test] + public void JudgeColorError_白色票の最大が上限超え_不合格() { + Assert.That(JudgeColor(1.57, 3.47, 40.01), Is.False); + } + + [Test] + public void JudgeColorError_白色票の誤差は白色票の上限で見る_白色票以外の上限を超えても合格() { + // 白色票の 30 は白色票以外の上限 10 を超えるが,白色票の上限 40 以内なので合格 + Assert.That(JudgeColor(1.57, 3.47, 30.0), Is.True); + } + + [TestCase(double.NaN, 3.47, 24.65)] + [TestCase(1.57, double.NaN, 24.65)] + [TestCase(1.57, 3.47, double.NaN)] + public void JudgeColorError_いずれかがNaN_不合格(double meanAbs, double otherMax, double whiteMax) { + Assert.That(JudgeColor(meanAbs, otherMax, whiteMax), Is.False); + } + + [Test] + public void JudgeColorError_上限を引数で受け取る_引数の上限で判定する() { + Assert.That(HwTestJudge.JudgeColorError(2.0, 2.0, 2.0, 1.0, 100.0, 100.0), Is.False); + } + + // ---- 撮影ログ(Info.csv)の読み取り ---- + + [Test] + public void ParseShotLog_連続撮影のログ_番号と時刻を読む() { + var lines = new[] { "Shot,Time(ms),Note", "1,0", "2,1002", "3,2006" }; + var rows = HwTestJudge.ParseShotLog(lines, out _); + Assert.That(rows.ConvertAll(r => r.TimeMs), Is.EqualTo(new[] { 0L, 1002L, 2006L })); + } + + [Test] + public void ParseShotLog_連続撮影のログ_番号を読む() { + var lines = new[] { "Shot,Time(ms),Note", "1,0", "2,1002", "3,2006" }; + var rows = HwTestJudge.ParseShotLog(lines, out _); + Assert.That(rows.ConvertAll(r => r.Shot), Is.EqualTo(new[] { 1, 2, 3 })); + } + + [Test] + public void ParseShotLog_正常なログ_errorはnull() { + var lines = new[] { "Shot,Time(ms),Note", "1,0" }; + HwTestJudge.ParseShotLog(lines, out var error); + Assert.That(error, Is.Null); + } + + [Test] + public void ParseShotLog_備考なし_備考は空() { + var rows = HwTestJudge.ParseShotLog(new[] { "Shot,Time(ms),Note", "1,0" }, out _); + Assert.That(rows[0].Note, Is.EqualTo("")); + } + + [Test] + public void ParseShotLog_見出しより前の行_読み飛ばす() { + var lines = new[] { "Name,hwtest-shot-multi", "Date,2026-10-08", "Shot,Time(ms),Note", "1,0", "2,1002" }; + var rows = HwTestJudge.ParseShotLog(lines, out var error); + Assert.That(rows.Count, Is.EqualTo(2)); + } + + [Test] + public void ParseShotLog_空行_読み飛ばす() { + var lines = new[] { "Shot,Time(ms),Note", "1,0", "", "2,1002", "" }; + var rows = HwTestJudge.ParseShotLog(lines, out var error); + Assert.That(rows.Count, Is.EqualTo(2)); + } + + [Test] + public void ParseShotLog_カンマを含むエスケープした備考_エスケープのまま残す() { + var note = "\"撮影失敗のため中止(a,b \"\"x\"\")\""; + var rows = HwTestJudge.ParseShotLog(new[] { "Shot,Time(ms),Note", "1,0", "2,1002," + note }, out _); + Assert.That(rows[1].Note, Is.EqualTo(note)); + } + + [Test] + public void ParseShotLog_見出し行が無い_errorを返す() { + HwTestJudge.ParseShotLog(new[] { "1,0", "2,1002" }, out var error); + Assert.That(error, Is.Not.Null); + } + + [Test] + public void ParseShotLog_見出し行が無い_行を返さない() { + var rows = HwTestJudge.ParseShotLog(new[] { "1,0", "2,1002" }, out _); + Assert.That(rows, Is.Empty); + } + + [Test] + public void ParseShotLog_時刻が数値でない行_errorを返す() { + HwTestJudge.ParseShotLog(new[] { "Shot,Time(ms),Note", "1,0", "2,abc" }, out var error); + Assert.That(error, Is.Not.Null); + } + + [Test] + public void ParseShotLog_番号が数値でない行_errorを返す() { + HwTestJudge.ParseShotLog(new[] { "Shot,Time(ms),Note", "x,0" }, out var error); + Assert.That(error, Is.Not.Null); + } + + [Test] + public void ParseShotLog_列が足りない行_errorを返す() { + HwTestJudge.ParseShotLog(new[] { "Shot,Time(ms),Note", "1" }, out var error); + Assert.That(error, Is.Not.Null); + } + + [Test] + public void ParseShotLog_途中で読めない行_それまでの行を返す() { + var rows = HwTestJudge.ParseShotLog(new[] { "Shot,Time(ms),Note", "1,0", "2,abc", "3,2006" }, out _); + Assert.That(rows.Count, Is.EqualTo(1)); + } + + [Test] + public void ParseShotLog_null_errorを返す() { + HwTestJudge.ParseShotLog(null, out var error); + Assert.That(error, Is.Not.Null); + } + + // ---- 撮影間隔のずれ ---- + + [Test] + public void MaxIntervalDeviationMs_実測の撮影時刻_最大のずれ() { + // 0/1002/2006: 間隔 1002・1004 → 設定 1000 との差 2・4 の最大 4 + Assert.That(HwTestJudge.MaxIntervalDeviationMs(new long[] { 0, 1002, 2006 }, 1000), Is.EqualTo(4.0).Within(Eps)); + } + + [Test] + public void MaxIntervalDeviationMs_間隔が設定より短い_差の絶対値() { + Assert.That(HwTestJudge.MaxIntervalDeviationMs(new long[] { 0, 900, 1900 }, 1000), Is.EqualTo(100.0).Within(Eps)); + } + + [Test] + public void MaxIntervalDeviationMs_間隔0の連続撮影_間隔そのものがずれ() { + Assert.That(HwTestJudge.MaxIntervalDeviationMs(new long[] { 0, 40, 73, 107, 138 }, 0), Is.EqualTo(40.0).Within(Eps)); + } + + [Test] + public void MaxIntervalDeviationMs_設定どおり_0() { + Assert.That(HwTestJudge.MaxIntervalDeviationMs(new long[] { 0, 1000, 2000 }, 1000), Is.EqualTo(0.0).Within(Eps)); + } + + [Test] + public void MaxIntervalDeviationMs_1件_NaN() { + Assert.That(HwTestJudge.MaxIntervalDeviationMs(new long[] { 0 }, 1000), Is.NaN); + } + + [Test] + public void MaxIntervalDeviationMs_0件_NaN() { + Assert.That(HwTestJudge.MaxIntervalDeviationMs(new long[0], 1000), Is.NaN); + } + + [Test] + public void MaxIntervalDeviationMs_null_NaN() { + Assert.That(HwTestJudge.MaxIntervalDeviationMs(null, 1000), Is.NaN); + } + + // ---- 保存されるファイル名 ---- + + [Test] + public void ExpectedShotFiles_単発撮影で1次元_InfoとShotとsRGBの3件() { + var names = HwTestJudge.ExpectedShotFiles("Shot{NO}.png", "sRGB{CN}_{NO}.jpg", "Info.csv", new[] { 17 }, 1); + Assert.That(names, Is.EquivalentTo(new[] { "Info.csv", "Shot0001.png", "sRGB17_0001.jpg" })); + } + + [Test] + public void ExpectedShotFiles_連続3枚で1次元_7件() { + var names = HwTestJudge.ExpectedShotFiles("Shot{NO}.png", "sRGB{CN}_{NO}.jpg", "Info.csv", new[] { 17 }, 3); + Assert.That(names.Count, Is.EqualTo(7)); + } + + [Test] + public void ExpectedShotFiles_番号は4桁の1始まり() { + var names = HwTestJudge.ExpectedShotFiles("Shot{NO}.png", "sRGB{CN}_{NO}.jpg", "Info.csv", new int[0], 3); + Assert.That(names, Is.EquivalentTo(new[] { "Info.csv", "Shot0001.png", "Shot0002.png", "Shot0003.png" })); + } + + [Test] + public void ExpectedShotFiles_項目数は2桁() { + var names = HwTestJudge.ExpectedShotFiles("Shot{NO}.png", "sRGB{CN}_{NO}.jpg", "Info.csv", new[] { 4, 17 }, 1); + Assert.That(names, Is.EquivalentTo(new[] { "Info.csv", "Shot0001.png", "sRGB04_0001.jpg", "sRGB17_0001.jpg" })); + } + + [Test] + public void ExpectedShotFiles_項目数がnull_sRGB変換画像なし() { + var names = HwTestJudge.ExpectedShotFiles("Shot{NO}.png", "sRGB{CN}_{NO}.jpg", "Info.csv", null, 2); + Assert.That(names, Is.EquivalentTo(new[] { "Info.csv", "Shot0001.png", "Shot0002.png" })); + } + + [Test] + public void ExpectedShotFiles_枚数0_Infoだけ() { + var names = HwTestJudge.ExpectedShotFiles("Shot{NO}.png", "sRGB{CN}_{NO}.jpg", "Info.csv", new[] { 17 }, 0); + Assert.That(names, Is.EquivalentTo(new[] { "Info.csv" })); + } + + [Test] + public void ExpectedShotFiles_書式がnull_ArgumentNullException() { + Assert.Throws((Action)(() => + HwTestJudge.ExpectedShotFiles(null, "sRGB{CN}_{NO}.jpg", "Info.csv", new[] { 17 }, 1))); + } + + [Test] + public void CompareFileNames_一致_足りないものも余分なものも無い() { + HwTestJudge.CompareFileNames(new[] { "Info.csv", "Shot0001.png" }, new[] { "Shot0001.png", "Info.csv" }, + out var missing, out var unexpected); + Assert.That(missing.Count + unexpected.Count, Is.EqualTo(0)); + } + + [Test] + public void CompareFileNames_大文字小文字の違い_一致とみなす() { + HwTestJudge.CompareFileNames(new[] { "Info.csv", "Shot0001.png" }, new[] { "INFO.CSV", "shot0001.PNG" }, + out var missing, out var unexpected); + Assert.That(missing.Count + unexpected.Count, Is.EqualTo(0)); + } + + [Test] + public void CompareFileNames_足りないファイル_missingに入る() { + HwTestJudge.CompareFileNames(new[] { "Info.csv", "Shot0001.png", "sRGB17_0001.jpg" }, new[] { "Info.csv" }, + out var missing, out _); + Assert.That(missing, Is.EqualTo(new[] { "Shot0001.png", "sRGB17_0001.jpg" })); + } + + [Test] + public void CompareFileNames_余分なファイル_unexpectedに入る() { + HwTestJudge.CompareFileNames(new[] { "Info.csv" }, new[] { "Info.csv", "Shot0002.png" }, + out _, out var unexpected); + Assert.That(unexpected, Is.EqualTo(new[] { "Shot0002.png" })); + } + + [Test] + public void CompareFileNames_結果は名前順() { + HwTestJudge.CompareFileNames(new[] { "c.png", "a.png", "B.png" }, new string[0], out var missing, out _); + Assert.That(missing, Is.EqualTo(new[] { "a.png", "B.png", "c.png" })); + } + + // ---- 保存中に閉じる検査 ---- + + [Test] + public void DecideSavingAttempt_保存中を観測_Ready() { + Assert.That(HwTestJudge.DecideSavingAttempt(true, 1, HwTestThresholds.CloseSavingMaxAttempts), Is.EqualTo(HwTestSavingAttempt.Ready)); + } + + [Test] + public void DecideSavingAttempt_最後の試行で保存中を観測_Ready() { + Assert.That(HwTestJudge.DecideSavingAttempt(true, 2, 2), Is.EqualTo(HwTestSavingAttempt.Ready)); + } + + [Test] + public void DecideSavingAttempt_1回目で保存中にならない_Retry() { + Assert.That(HwTestJudge.DecideSavingAttempt(false, 1, 2), Is.EqualTo(HwTestSavingAttempt.Retry)); + } + + [Test] + public void DecideSavingAttempt_上限の回で保存中にならない_GiveUp() { + Assert.That(HwTestJudge.DecideSavingAttempt(false, 2, 2), Is.EqualTo(HwTestSavingAttempt.GiveUp)); + } + + [Test] + public void Thresholds_保存中を作る試行_最初と再試行1回の2回() { + Assert.That(HwTestThresholds.CloseSavingMaxAttempts, Is.EqualTo(2)); + } + + [Test] + public void CloseSavingShotCount_1回目_5枚() { + Assert.That(HwTestJudge.CloseSavingShotCount(1), Is.EqualTo(5)); + } + + [TestCase(2)] + [TestCase(3)] + public void CloseSavingShotCount_2回目以降_10枚(int attempt) { + Assert.That(HwTestJudge.CloseSavingShotCount(attempt), Is.EqualTo(10)); + } + + private const string SavingMsg = "保存中です.保存完了後に閉じてください"; + + [Test] + public void CancelledCloseProblems_正しく取り消された_問題なし() { + Assert.That(HwTestJudge.CancelledCloseProblems(false, SavingMsg, SavingMsg, 0, false, true), Is.Empty); + } + + [Test] + public void CancelledCloseProblems_閉じると決まった_問題あり() { + Assert.That(HwTestJudge.CancelledCloseProblems(true, SavingMsg, SavingMsg, 0, false, true).Count, Is.EqualTo(1)); + } + + [Test] + public void CancelledCloseProblems_表示が違う_問題に表示を含める() { + var problems = HwTestJudge.CancelledCloseProblems(false, "全ての画像保存完了", SavingMsg, 0, false, true); + Assert.That(problems, Has.Exactly(1).Contains("全ての画像保存完了")); + } + + [Test] + public void CancelledCloseProblems_表示がnull_問題あり() { + Assert.That(HwTestJudge.CancelledCloseProblems(false, null, SavingMsg, 0, false, true).Count, Is.EqualTo(1)); + } + + [Test] + public void CancelledCloseProblems_後始末が進んだ_問題あり() { + Assert.That(HwTestJudge.CancelledCloseProblems(false, SavingMsg, SavingMsg, 1, false, true).Count, Is.EqualTo(1)); + } + + [Test] + public void CancelledCloseProblems_閉じ始めのフラグが立った_問題あり() { + Assert.That(HwTestJudge.CancelledCloseProblems(false, SavingMsg, SavingMsg, 0, true, true).Count, Is.EqualTo(1)); + } + + [Test] + public void CancelledCloseProblems_再描画タイマーが止まった_問題あり() { + Assert.That(HwTestJudge.CancelledCloseProblems(false, SavingMsg, SavingMsg, 0, false, false).Count, Is.EqualTo(1)); + } + + [Test] + public void CancelledCloseProblems_すべて異常_5件() { + Assert.That(HwTestJudge.CancelledCloseProblems(true, "x", SavingMsg, 4, true, false).Count, Is.EqualTo(5)); + } + + // ---- 後始末の順序 ---- + + [Test] + public void IsCloseSequenceSince_開始位置から正しい順_true() { + var steps = new[] { "closing", "timer", "camera", "light", "closing", "timer", "camera", "light" }; + Assert.That(HwTestJudge.IsCloseSequenceSince(steps, 4), Is.True); + } + + [Test] + public void IsCloseSequenceSince_開始位置0から正しい順_true() { + Assert.That(HwTestJudge.IsCloseSequenceSince(new[] { "closing", "timer", "camera", "light" }, 0), Is.True); + } + + [Test] + public void IsCloseSequenceSince_順序が違う_false() { + Assert.That(HwTestJudge.IsCloseSequenceSince(new[] { "closing", "camera", "timer", "light" }, 0), Is.False); + } + + [Test] + public void IsCloseSequenceSince_段階が足りない_false() { + Assert.That(HwTestJudge.IsCloseSequenceSince(new[] { "closing", "timer", "camera" }, 0), Is.False); + } + + [Test] + public void IsCloseSequenceSince_余分な段階がある_false() { + Assert.That(HwTestJudge.IsCloseSequenceSince(new[] { "closing", "timer", "camera", "light", "light" }, 0), Is.False); + } + + [Test] + public void IsCloseSequenceSince_何も進んでいない_false() { + Assert.That(HwTestJudge.IsCloseSequenceSince(new[] { "closing", "timer", "camera", "light" }, 4), Is.False); + } + + [TestCase(-1)] + [TestCase(5)] + public void IsCloseSequenceSince_開始位置が範囲外_false(int start) { + Assert.That(HwTestJudge.IsCloseSequenceSince(new[] { "closing", "timer", "camera", "light" }, start), Is.False); + } + + [Test] + public void IsCloseSequenceSince_null_false() { + Assert.That(HwTestJudge.IsCloseSequenceSince(null, 0), Is.False); + } + + [Test] + public void StepsSince_開始位置以降を返す() { + Assert.That(HwTestJudge.StepsSince(new[] { "closing", "timer", "closing", "timer" }, 2), Is.EqualTo(new[] { "closing", "timer" })); + } + + [Test] + public void StepsSince_開始位置が末尾_空() { + Assert.That(HwTestJudge.StepsSince(new[] { "closing" }, 1), Is.Empty); + } + + [Test] + public void StepsSince_開始位置が負_全部() { + Assert.That(HwTestJudge.StepsSince(new[] { "closing", "timer" }, -1), Is.EqualTo(new[] { "closing", "timer" })); + } + + [Test] + public void StepsSince_null_空() { + Assert.That(HwTestJudge.StepsSince(null, 0), Is.Empty); + } + + // ---- 色票内の画素の集計(白飛び・黒つぶれ) ---- + + [Test] + public void SummarizePatchPixels_画素の数() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 1, 2, 3, 4, 5, 6 }); + Assert.That(s.Pixels, Is.EqualTo(2)); + } + + [Test] + public void SummarizePatchPixels_チャネルごとの平均_BGRの順() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 10, 20, 30, 30, 40, 50 }); + Assert.That(s.Mean, Is.EqualTo(new[] { 20.0, 30.0, 40.0 }).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_チャネルごとの最小() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 10, 60, 30, 30, 40, 50 }); + Assert.That(s.Min, Is.EqualTo(new[] { 10.0, 40.0, 30.0 }).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_チャネルごとの最大() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 10, 60, 30, 30, 40, 50 }); + Assert.That(s.Max, Is.EqualTo(new[] { 30.0, 60.0, 50.0 }).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_255の画素_白飛びの割合に数える() { + // 4 画素のうち 1 画素が G=255(8bit の頭打ち) + var s = HwTestJudge.SummarizePatchPixels(new double[] { 100, 255, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 }); + Assert.That(s.FractionAnyAtOrAboveMax, Is.EqualTo(0.25).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_255ちょうど_超えた割合には数えない() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 255, 255, 255 }); + Assert.That(s.FractionAnyAboveMax, Is.EqualTo(0.0).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_255を超えた画素_超えた割合に数える() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 621.4, 200, 200, 100, 100, 100 }); + Assert.That(s.FractionAnyAboveMax, Is.EqualTo(0.5).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_255を超えた画素_以上の割合にも数える() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 621.4, 200, 200, 100, 100, 100 }); + Assert.That(s.FractionAnyAtOrAboveMax, Is.EqualTo(0.5).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_0の画素_黒つぶれの割合に数える() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 0, 2, 0, 100, 100, 100 }); + Assert.That(s.FractionAnyAtOrBelowMin, Is.EqualTo(0.5).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_0ちょうど_0未満の割合には数えない() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 0, 0, 0 }); + Assert.That(s.FractionAnyBelowMin, Is.EqualTo(0.0).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_0未満の画素_0未満の割合と以下の割合に数える() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { -175.6, 2, -42.3, 100, 100, 100 }); + Assert.That(new[] { s.FractionAnyBelowMin, s.FractionAnyAtOrBelowMin }, Is.EqualTo(new[] { 0.5, 0.5 }).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_1画素で複数チャネルが255_1画素として数える() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 255, 255, 255, 100, 100, 100 }); + Assert.That(s.FractionAnyAtOrAboveMax, Is.EqualTo(0.5).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_範囲内の画素だけ_割合はすべて0() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { 1, 128, 254, 100, 100, 100 }); + Assert.That(new[] { s.FractionAnyAtOrAboveMax, s.FractionAnyAtOrBelowMin, s.FractionAnyAboveMax, s.FractionAnyBelowMin }, + Is.EqualTo(new[] { 0.0, 0.0, 0.0, 0.0 })); + } + + [Test] + public void SummarizePatchPixels_空_画素数0() { + var s = HwTestJudge.SummarizePatchPixels(new double[0]); + Assert.That(s.Pixels, Is.EqualTo(0)); + } + + [Test] + public void SummarizePatchPixels_空_平均最小最大はNaN() { + var s = HwTestJudge.SummarizePatchPixels(new double[0]); + Assert.That(new[] { s.Mean[0], s.Mean[1], s.Mean[2], s.Min[0], s.Min[1], s.Min[2], s.Max[0], s.Max[1], s.Max[2] }, Has.All.NaN); + } + + [Test] + public void SummarizePatchPixels_空_割合4種はNaN() { + var s = HwTestJudge.SummarizePatchPixels(new double[0]); + Assert.That(new[] { s.FractionAnyAtOrAboveMax, s.FractionAnyAtOrBelowMin, s.FractionAnyAboveMax, s.FractionAnyBelowMin }, Has.All.NaN); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(4)] + public void SummarizePatchPixels_長さが3の倍数でない_ArgumentException(int length) { + Assert.Throws((Action)(() => HwTestJudge.SummarizePatchPixels(new double[length]))); + } + + [Test] + public void SummarizePatchPixels_null_ArgumentNullException() { + Assert.Throws((Action)(() => HwTestJudge.SummarizePatchPixels(null))); + } + + [Test] + public void SummarizePatchPixels_NaNの画素_そのチャネルの平均はNaN() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { double.NaN, 10, 10, 20, 20, 20 }); + Assert.That(s.Mean[0], Is.NaN); + } + + [Test] + public void SummarizePatchPixels_NaNの画素_他のチャネルの平均は影響しない() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { double.NaN, 10, 10, 20, 20, 20 }); + Assert.That(new[] { s.Mean[1], s.Mean[2] }, Is.EqualTo(new[] { 15.0, 15.0 }).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_NaNの画素_最小最大に数えない() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { double.NaN, 10, 10, 20, 20, 20 }); + Assert.That(new[] { s.Min[0], s.Max[0] }, Is.EqualTo(new[] { 20.0, 20.0 }).Within(Eps)); + } + + [Test] + public void SummarizePatchPixels_NaNの画素_割合に数えない() { + var s = HwTestJudge.SummarizePatchPixels(new double[] { double.NaN, double.NaN, double.NaN, 100, 100, 100 }); + Assert.That(new[] { s.FractionAnyAtOrAboveMax, s.FractionAnyAtOrBelowMin, s.FractionAnyAboveMax, s.FractionAnyBelowMin }, + Is.EqualTo(new[] { 0.0, 0.0, 0.0, 0.0 })); + } } } #endif diff --git a/TIASshot.Tests/PatchColorErrorTests.cs b/TIASshot.Tests/PatchColorErrorTests.cs new file mode 100644 index 0000000..d6e9d09 --- /dev/null +++ b/TIASshot.Tests/PatchColorErrorTests.cs @@ -0,0 +1,405 @@ +#if SELFTEST +using System; +using System.IO; +using NUnit.Framework; +using OpenCvSharp; + +namespace TIASshot.Tests { + /// + /// 色票の測定値と参照値(tcc_srgb)の差の集計(PatchColorError)のテスト(層①.純粋関数). + /// + /// 仕様: PLAN_06「チャートが必要」の calibrate(変換後の色票と参照値の色差)と合意済みの判定方法 + /// - 値の並びは 1 行 = 1 色票,列 0 = B・列 1 = G・列 2 = R(8bit 階調) + /// - 色の誤差はチャネルごとの |測定 − 参照|.平均の分母は 色票の数 × 3.NaN があれば平均・最大とも NaN + /// - 白色票は参照値の BGR の和が最大の色票(同値なら先の行).tcc_srgb.csv では 0 始まりの 12(13 番) + /// - 白色票を除いた最大(範囲外の除外指定は何も除かない.残りが無ければ NaN と -1) + /// - 参考の ΔE は CIE76(sRGB D65 → CIELAB).0〜255 の外は丸めずにガンマの式を符号つきで延長する + /// 本クラスは SELFTEST 構成(Debug|x64)でのみコンパイルされる. + /// + [TestFixture] + public class PatchColorErrorTests { + + private const double Eps = 1e-9; + + // ================================================================ + // Compare + // ================================================================ + + private static readonly double[,] Measured2 = { { 10, 20, 30 }, { 40, 50, 60 } }; + // 差の絶対値: 1, 2, 0 / 0, 0, 6 + private static readonly double[,] Reference2 = { { 11, 18, 30 }, { 40, 50, 66 } }; + + [Test] + public void Compare_色票の数() { + Assert.That(PatchColorError.Compare(Measured2, Reference2).Patches, Is.EqualTo(2)); + } + + [Test] + public void Compare_平均_分母は色票の数かける3() { + // (1 + 2 + 0 + 0 + 0 + 6) / (2 × 3) = 1.5 + Assert.That(PatchColorError.Compare(Measured2, Reference2).MeanAbs, Is.EqualTo(1.5).Within(Eps)); + } + + [Test] + public void Compare_最大_チャネルごとの差の絶対値の最大() { + Assert.That(PatchColorError.Compare(Measured2, Reference2).MaxAbs, Is.EqualTo(6.0).Within(Eps)); + } + + [Test] + public void Compare_最大の色票_0始まりの番号() { + Assert.That(PatchColorError.Compare(Measured2, Reference2).MaxAbsPatch, Is.EqualTo(1)); + } + + [Test] + public void Compare_測定が参照より小さい_差は絶対値() { + var s = PatchColorError.Compare(new double[,] { { 0, 0, 0 } }, new double[,] { { 3, 3, 3 } }); + Assert.That(s.MeanAbs, Is.EqualTo(3.0).Within(Eps)); + } + + [Test] + public void Compare_一致_平均は0() { + Assert.That(PatchColorError.Compare(Measured2, Measured2).MeanAbs, Is.EqualTo(0.0).Within(Eps)); + } + + [Test] + public void Compare_一致_DeltaEの最大は0() { + Assert.That(PatchColorError.Compare(Measured2, Measured2).MaxDeltaE, Is.EqualTo(0.0).Within(Eps)); + } + + [Test] + public void Compare_白と黒_DeltaEの平均は100() { + var s = PatchColorError.Compare(new double[,] { { 255, 255, 255 } }, new double[,] { { 0, 0, 0 } }); + Assert.That(s.MeanDeltaE, Is.EqualTo(100.0).Within(0.01)); + } + + [Test] + public void Compare_DeltaEの最大の色票_0始まりの番号() { + var s = PatchColorError.Compare(new double[,] { { 100, 100, 100 }, { 255, 255, 255 } }, + new double[,] { { 100, 100, 100 }, { 0, 0, 0 } }); + Assert.That(s.MaxDeltaEPatch, Is.EqualTo(1)); + } + + [Test] + public void Compare_NaNを含む_平均はNaN() { + var measured = new double[,] { { 10, double.NaN, 30 }, { 40, 50, 60 } }; + Assert.That(PatchColorError.Compare(measured, Reference2).MeanAbs, Is.NaN); + } + + [Test] + public void Compare_NaNを含む_最大はNaN() { + // NaN の後に大きい差(6)があっても最大は NaN(誤差を見逃さない) + var measured = new double[,] { { 10, double.NaN, 30 }, { 40, 50, 60 } }; + Assert.That(PatchColorError.Compare(measured, Reference2).MaxAbs, Is.NaN); + } + + [Test] + public void Compare_最後の色票にNaN_最大はNaN() { + var measured = new double[,] { { 0, 0, 0 }, { 40, 50, double.NaN } }; + var reference = new double[,] { { 100, 100, 100 }, { 40, 50, 60 } }; + Assert.That(PatchColorError.Compare(measured, reference).MaxAbs, Is.NaN); + } + + [Test] + public void Compare_参照値にNaN_最大はNaN() { + var reference = new double[,] { { 11, 18, 30 }, { double.NaN, 50, 66 } }; + Assert.That(PatchColorError.Compare(Measured2, reference).MaxAbs, Is.NaN); + } + + [Test] + public void Compare_列が3でない_ArgumentException() { + Assert.Throws((Action)(() => + PatchColorError.Compare(new double[2, 4], new double[2, 4]))); + } + + [Test] + public void Compare_行数が違う_ArgumentException() { + Assert.Throws((Action)(() => + PatchColorError.Compare(new double[2, 3], new double[3, 3]))); + } + + [Test] + public void Compare_0行_ArgumentException() { + Assert.Throws((Action)(() => + PatchColorError.Compare(new double[0, 3], new double[0, 3]))); + } + + [Test] + public void Compare_測定がnull_ArgumentNullException() { + Assert.Throws((Action)(() => PatchColorError.Compare(null, Reference2))); + } + + [Test] + public void Compare_参照がnull_ArgumentNullException() { + Assert.Throws((Action)(() => PatchColorError.Compare(Measured2, null))); + } + + // ================================================================ + // SrgbToLab・DeltaE76 + // ================================================================ + + [Test] + public void SrgbToLab_白_Lは100() { + PatchColorError.SrgbToLab(255, 255, 255, out var l, out _, out _); + Assert.That(l, Is.EqualTo(100.0).Within(0.01)); + } + + [Test] + public void SrgbToLab_白_aとbは0() { + PatchColorError.SrgbToLab(255, 255, 255, out _, out var a, out var b); + Assert.That(new[] { a, b }, Is.EqualTo(new[] { 0.0, 0.0 }).Within(0.01)); + } + + [Test] + public void SrgbToLab_黒_Labはすべて0() { + PatchColorError.SrgbToLab(0, 0, 0, out var l, out var a, out var b); + Assert.That(new[] { l, a, b }, Is.EqualTo(new[] { 0.0, 0.0, 0.0 }).Within(1e-6)); + } + + [Test] + public void SrgbToLab_中間の灰色128_Lは約53_59() { + // sRGB (128,128,128) → L* 53.585(D65) + PatchColorError.SrgbToLab(128, 128, 128, out var l, out _, out _); + Assert.That(l, Is.EqualTo(53.585).Within(0.01)); + } + + [Test] + public void SrgbToLab_赤_既知のLab() { + // sRGB (255,0,0) → L* 53.24, a* 80.09, b* 67.20(D65) + PatchColorError.SrgbToLab(255, 0, 0, out var l, out var a, out var b); + Assert.That(new[] { l, a, b }, Is.EqualTo(new[] { 53.24, 80.09, 67.20 }).Within(0.05)); + } + + [Test] + public void SrgbToLab_青_既知のLab() { + // sRGB (0,0,255) → L* 32.30, a* 79.19, b* -107.86(D65) + PatchColorError.SrgbToLab(0, 0, 255, out var l, out var a, out var b); + Assert.That(new[] { l, a, b }, Is.EqualTo(new[] { 32.30, 79.19, -107.86 }).Within(0.05)); + } + + [Test] + public void SrgbToLab_255を超える値_丸めずにLが100を超える() { + PatchColorError.SrgbToLab(300, 300, 300, out var l, out _, out _); + Assert.That(l, Is.GreaterThan(100.0)); + } + + [Test] + public void SrgbToLab_0未満の値_丸めずにLが負() { + PatchColorError.SrgbToLab(-10, -10, -10, out var l, out _, out _); + Assert.That(l, Is.LessThan(0.0)); + } + + [Test] + public void SrgbToLab_0未満の値_符号つきで延長する() { + // 暗部(L* の線形部分)では,符号を反転した入力の L* は符号が反転する + PatchColorError.SrgbToLab(10, 10, 10, out var lPos, out _, out _); + PatchColorError.SrgbToLab(-10, -10, -10, out var lNeg, out _, out _); + Assert.That(lNeg, Is.EqualTo(-lPos).Within(1e-9)); + } + + [Test] + public void DeltaE76_同じ色_0() { + Assert.That(PatchColorError.DeltaE76(50, 120, 200, 50, 120, 200), Is.EqualTo(0.0).Within(Eps)); + } + + [Test] + public void DeltaE76_白と黒_100() { + Assert.That(PatchColorError.DeltaE76(255, 255, 255, 0, 0, 0), Is.EqualTo(100.0).Within(0.01)); + } + + [Test] + public void DeltaE76_引数はBGRの順_赤と黒() { + // BGR (0,0,255) = 赤.Lab (53.24, 80.09, 67.20) と黒の距離 ≒ 117.34 + Assert.That(PatchColorError.DeltaE76(0, 0, 255, 0, 0, 0), Is.EqualTo(117.34).Within(0.1)); + } + + [Test] + public void DeltaE76_対称() { + Assert.That(PatchColorError.DeltaE76(10, 200, 30, 90, 80, 70), + Is.EqualTo(PatchColorError.DeltaE76(90, 80, 70, 10, 200, 30)).Within(Eps)); + } + + // ================================================================ + // ToArray + // ================================================================ + + [Test] + public void ToArray_nかける3のMat_同じ値の配列() { + using (var m = new Mat(2, 3, MatType.CV_64FC1)) { + m.Set(0, 0, 1.5); m.Set(0, 1, 2.5); m.Set(0, 2, 3.5); + m.Set(1, 0, 4.5); m.Set(1, 1, 5.5); m.Set(1, 2, 6.5); + Assert.That(PatchColorError.ToArray(m), Is.EqualTo(new double[,] { { 1.5, 2.5, 3.5 }, { 4.5, 5.5, 6.5 } })); + } + } + + [Test] + public void ToArray_列が3でない_ArgumentException() { + using (var m = new Mat(2, 4, MatType.CV_64FC1, Scalar.All(0))) { + Assert.Throws((Action)(() => PatchColorError.ToArray(m))); + } + } + + [Test] + public void ToArray_3チャネルのMat_ArgumentException() { + using (var m = new Mat(2, 3, MatType.CV_64FC3, Scalar.All(0))) { + Assert.Throws((Action)(() => PatchColorError.ToArray(m))); + } + } + + [Test] + public void ToArray_null_ArgumentNullException() { + Assert.Throws((Action)(() => PatchColorError.ToArray(null))); + } + + // ================================================================ + // WhitePatchIndex + // ================================================================ + + [Test] + public void WhitePatchIndex_BGRの和が最大の行() { + var reference = new double[,] { { 100, 100, 100 }, { 200, 210, 220 }, { 250, 10, 10 } }; + Assert.That(PatchColorError.WhitePatchIndex(reference), Is.EqualTo(1)); + } + + [Test] + public void WhitePatchIndex_和が同じ_先の行() { + var reference = new double[,] { { 10, 10, 10 }, { 200, 200, 200 }, { 100, 200, 300 } }; + Assert.That(PatchColorError.WhitePatchIndex(reference), Is.EqualTo(1)); + } + + [Test] + public void WhitePatchIndex_1行_0() { + Assert.That(PatchColorError.WhitePatchIndex(new double[,] { { 1, 2, 3 } }), Is.EqualTo(0)); + } + + [Test] + public void WhitePatchIndex_同梱のtcc_srgb_12() { + var path = Path.GetFullPath(Path.Combine(TestContext.CurrentContext.TestDirectory, "..", "..", "..", "..", "TIASshot", "tcc_srgb.csv")); + Assert.That(File.Exists(path), Is.True, $"tcc_srgb.csv が見つからない: {path}"); + using (var m = IoUtil.LoadMatFromCsv(path)) { + Assert.That(PatchColorError.WhitePatchIndex(PatchColorError.ToArray(m)), Is.EqualTo(12)); + } + } + + [Test] + public void WhitePatchIndex_null_ArgumentNullException() { + Assert.Throws((Action)(() => PatchColorError.WhitePatchIndex(null))); + } + + [Test] + public void WhitePatchIndex_列が3でない_ArgumentException() { + Assert.Throws((Action)(() => PatchColorError.WhitePatchIndex(new double[2, 2]))); + } + + [Test] + public void WhitePatchIndex_0行_ArgumentException() { + Assert.Throws((Action)(() => PatchColorError.WhitePatchIndex(new double[0, 3]))); + } + + [Test] + public void WhitePatchIndex_NaNを含む_ArgumentException() { + var reference = new double[,] { { 100, 100, 100 }, { double.NaN, 0, 0 } }; + Assert.Throws((Action)(() => PatchColorError.WhitePatchIndex(reference))); + } + + // ================================================================ + // PatchMaxAbs・MaxAbsExcluding + // ================================================================ + + // 色票ごとの差の最大: 2(0 番)・24.65(1 番=白色票に見立てる)・3.47(2 番) + private static readonly double[,] Ref3 = { { 100, 100, 100 }, { 221.5, 220.1, 228.5 }, { 50, 60, 70 } }; + private static readonly double[,] Meas3 = { { 101, 98, 100 }, { 196.85, 212.2, 211.9 }, { 50, 63.47, 69 } }; + + [Test] + public void PatchMaxAbs_その色票のチャネルごとの差の最大() { + Assert.That(PatchColorError.PatchMaxAbs(Meas3, Ref3, 1), Is.EqualTo(24.65).Within(1e-9)); + } + + [Test] + public void PatchMaxAbs_一致_0() { + Assert.That(PatchColorError.PatchMaxAbs(Ref3, Ref3, 0), Is.EqualTo(0.0)); + } + + [Test] + public void PatchMaxAbs_NaNを含む_NaN() { + var measured = new double[,] { { double.NaN, 0, 0 } }; + Assert.That(PatchColorError.PatchMaxAbs(measured, new double[,] { { 0, 0, 100 } }, 0), Is.NaN); + } + + [TestCase(-1)] + [TestCase(3)] + public void PatchMaxAbs_番号が範囲外_ArgumentOutOfRangeException(int patch) { + Assert.Throws((Action)(() => PatchColorError.PatchMaxAbs(Meas3, Ref3, patch))); + } + + [Test] + public void PatchMaxAbs_行数が違う_ArgumentException() { + Assert.Throws((Action)(() => PatchColorError.PatchMaxAbs(new double[2, 3], new double[3, 3], 0))); + } + + [Test] + public void MaxAbsExcluding_白色票を除いた最大() { + Assert.That(PatchColorError.MaxAbsExcluding(Meas3, Ref3, 1, out _), Is.EqualTo(3.47).Within(1e-9)); + } + + [Test] + public void MaxAbsExcluding_白色票を除いた最大の色票() { + PatchColorError.MaxAbsExcluding(Meas3, Ref3, 1, out var maxPatch); + Assert.That(maxPatch, Is.EqualTo(2)); + } + + [TestCase(-1)] + [TestCase(3)] + public void MaxAbsExcluding_範囲外の除外指定_何も除かない(int excluded) { + Assert.That(PatchColorError.MaxAbsExcluding(Meas3, Ref3, excluded, out _), Is.EqualTo(24.65).Within(1e-9)); + } + + [Test] + public void MaxAbsExcluding_範囲外の除外指定_最大の色票は全体の最大() { + PatchColorError.MaxAbsExcluding(Meas3, Ref3, -1, out var maxPatch); + Assert.That(maxPatch, Is.EqualTo(1)); + } + + [Test] + public void MaxAbsExcluding_除外の結果残りが無い_NaN() { + Assert.That(PatchColorError.MaxAbsExcluding(new double[,] { { 1, 2, 3 } }, new double[,] { { 0, 0, 0 } }, 0, out _), Is.NaN); + } + + [Test] + public void MaxAbsExcluding_除外の結果残りが無い_最大の色票はマイナス1() { + PatchColorError.MaxAbsExcluding(new double[,] { { 1, 2, 3 } }, new double[,] { { 0, 0, 0 } }, 0, out var maxPatch); + Assert.That(maxPatch, Is.EqualTo(-1)); + } + + [Test] + public void MaxAbsExcluding_残りにNaN_NaN() { + var measured = new double[,] { { 101, 98, 100 }, { 0, 0, 0 }, { double.NaN, 60, 70 } }; + Assert.That(PatchColorError.MaxAbsExcluding(measured, Ref3, 1, out _), Is.NaN); + } + + [Test] + public void MaxAbsExcluding_除いた色票にだけNaN_NaNにならない() { + var measured = new double[,] { { 101, 98, 100 }, { double.NaN, 0, 0 }, { 50, 63.47, 69 } }; + Assert.That(PatchColorError.MaxAbsExcluding(measured, Ref3, 1, out _), Is.EqualTo(3.47).Within(1e-9)); + } + + [Test] + public void MaxAbsExcluding_除いた色票が全体の最大_白色票と白色票以外の最大の組がCompareの最大と一致する() { + // 白色票を除いた最大と白色票の最大の大きい方 = 全体の最大 + var whole = PatchColorError.Compare(Meas3, Ref3).MaxAbs; + var other = PatchColorError.MaxAbsExcluding(Meas3, Ref3, 1, out _); + var white = PatchColorError.PatchMaxAbs(Meas3, Ref3, 1); + Assert.That(Math.Max(other, white), Is.EqualTo(whole).Within(1e-9)); + } + + [Test] + public void MaxAbsExcluding_形状が違う_ArgumentException() { + Assert.Throws((Action)(() => PatchColorError.MaxAbsExcluding(new double[2, 3], new double[2, 4], 0, out _))); + } + + [Test] + public void MaxAbsExcluding_null_ArgumentNullException() { + Assert.Throws((Action)(() => PatchColorError.MaxAbsExcluding(null, Ref3, 0, out _))); + } + } +} +#endif diff --git a/TIASshot.Tests/TIASshot.Tests.csproj b/TIASshot.Tests/TIASshot.Tests.csproj index f745aa4..a956ce0 100644 --- a/TIASshot.Tests/TIASshot.Tests.csproj +++ b/TIASshot.Tests/TIASshot.Tests.csproj @@ -101,6 +101,7 @@ +