From 25f3d0c2c5df3501b0392d97774fad1f28eaa23d Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Tue, 4 Aug 2026 21:03:54 -0500 Subject: [PATCH 01/10] FRLG port over tests --- .../ComputerPrograms/UnitTestRunner.cpp | 2 + .../Dialogs/PokemonFRLG_BattleDialogs.cpp | 53 ++++++++++++ .../Dialogs/PokemonFRLG_BattleDialogs.h | 4 +- .../Dialogs/PokemonFRLG_DialogDetector.cpp | 68 +++++++++++++++- .../Dialogs/PokemonFRLG_DialogDetector.h | 5 +- .../PokemonFRLG_PrizeSelectDetector.cpp | 28 +++++++ .../Dialogs/PokemonFRLG_PrizeSelectDetector.h | 3 +- .../PokemonFRLG_ShinySymbolDetector.cpp | 29 +++++++ .../PokemonFRLG_ShinySymbolDetector.h | 3 +- .../Source/PokemonFRLG/PokemonFRLG_Tests.cpp | 31 +++++++ .../Source/PokemonFRLG/PokemonFRLG_Tests.h | 25 ++++++ .../Source/Tests/PokemonFRLG_Tests.cpp | 80 ------------------- .../Source/Tests/PokemonFRLG_Tests.h | 34 -------- SerialPrograms/Source/Tests/TestMap.cpp | 7 -- SerialPrograms/cmake/SourceFiles.cmake | 4 +- 15 files changed, 247 insertions(+), 129 deletions(-) create mode 100644 SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h delete mode 100644 SerialPrograms/Source/Tests/PokemonFRLG_Tests.cpp delete mode 100644 SerialPrograms/Source/Tests/PokemonFRLG_Tests.h diff --git a/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp index dfbffb6d20..c7832b0701 100644 --- a/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp @@ -13,6 +13,7 @@ #include "UnitTestRunner.h" #include "CommonTools/OCR/OCR_Tests.h" +#include "PokemonFRLG/PokemonFRLG_Tests.h" #include "PokemonHome/PokemonHome_Tests.h" #include "PokemonSwSh/PokemonSwSh_Tests.h" #include "PokemonLA/PokemonLA_Tests.h" @@ -31,6 +32,7 @@ UnitTestDatabase make_UNIT_TESTS_ALL(){ UnitTestDatabase ret; OCR::add_tests(ret); + NintendoSwitch::PokemonFRLG::add_tests(ret); NintendoSwitch::PokemonHome::add_tests(ret); NintendoSwitch::PokemonSwSh::add_tests(ret); NintendoSwitch::PokemonLA::add_tests(ret); diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp index 3c33cc87f6..ae4ff378c5 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/Globals.h" #include "CommonTools/Images/SolidColorTest.h" #include "CommonTools/Images/ImageFilter.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" @@ -282,7 +283,59 @@ bool BattleLevelUpDetector::detect(const ImageViewRGB32& screen){ return false; } +class Test_AdvanceBattleDialogDetector : public UnitTest{ +public: + + Test_AdvanceBattleDialogDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonFRLG::AdvanceBattleDialogDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + AdvanceBattleDialogDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_AdvanceBattleDialogDetector(UnitTestDatabase& database){ + //todo: gather test images for this detector +} +class Test_BattleMenuDetector : public UnitTest{ +public: + + Test_BattleMenuDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonFRLG::BattleMenuDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + BattleMenuDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_BattleMenuDetector(UnitTestDatabase& database){ + //todo: gather test images for this detector +} } } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h index 964825dd45..f3c1f6b152 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h @@ -9,6 +9,7 @@ #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/Language.h" @@ -171,7 +172,8 @@ class BattleLevelUpWatcher : public DetectorToFinder{ {} }; - +void add_tests_AdvanceBattleDialogDetector(UnitTestDatabase& database); +void add_tests_BattleMenuDetector(UnitTestDatabase& database); } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp index 9d4abd36c9..58ed276f00 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTools/ImageStats.h" @@ -11,7 +12,6 @@ #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" #include "CommonTools/Images/ImageFilter.h" -#include "CommonTools/Images/WaterfillUtilities.h" //#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" //#include "CommonFramework/VideoPipeline/VideoOverlay.h" #include "PokemonFRLG_DialogDetector.h" @@ -127,7 +127,73 @@ bool SelectionDialogDetector::detect(const ImageViewRGB32& screen){ return false; } +class Test_AdvanceWhiteDialogDetector : public UnitTest{ +public: + + Test_AdvanceWhiteDialogDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonFRLG::AdvanceWhiteDialogDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + AdvanceWhiteDialogDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_AdvanceWhiteDialogDetector(UnitTestDatabase& database){ + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/Eng-Select_False.png", false); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/English-Bulba_True.png", true); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/Ger-KennyExile-Bulba_True.png", true); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/Ger-KennyExile-Char_True.png", true); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/Ger-KennyExile-Sq_True.png", true); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/MacOS-not-ili-Char_False.png", false); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/MacOS-not-ili-Char_True.png", true); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/Spanish-AlejaKaiser-Bulba_True.png", true); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/Spanish-AlejaKaiser-Char_True.png", true); + database.add("PokemonFRLG/AdvanceWhiteDialogDetector/Spanish-AlejaKaiser-Squirtl_True.png", true); +} +class Test_SelectionDialogDetector : public UnitTest{ +public: + + Test_SelectionDialogDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonFRLG::SelectionDialogDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + SelectionDialogDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_SelectionDialogDetector(UnitTestDatabase& database){ + database.add("PokemonFRLG/SelectionDialogDetector/Eng-Select_True.png", true); + database.add("PokemonFRLG/SelectionDialogDetector/English-Bulba-720p_True.png", true); + database.add("PokemonFRLG/SelectionDialogDetector/English-Bulba_False.png", false); + database.add("PokemonFRLG/SelectionDialogDetector/Ger-KennyExile-Sq_False.png", false); + database.add("PokemonFRLG/SelectionDialogDetector/MacOS-not-ili-Char_False.png", false); + database.add("PokemonFRLG/SelectionDialogDetector/MacOS-not-ili-Char_True.png", true); +} } } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h index 37885413ac..9ba4ba069b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h @@ -9,11 +9,11 @@ #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" #include "CommonTools/VisualDetectors/BlackScreenDetector.h" -#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "PokemonFRLG/PokemonFRLG_Settings.h" namespace PokemonAutomation{ @@ -127,7 +127,8 @@ class SelectionDialogWatcher : public DetectorToFinder{ {} }; - +void add_tests_AdvanceWhiteDialogDetector(UnitTestDatabase& database); +void add_tests_SelectionDialogDetector(UnitTestDatabase& database); } } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.cpp index e041eb60f0..fcee970c3b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" @@ -48,6 +49,33 @@ bool PrizeSelectDetector::detect(const ImageViewRGB32& screen){ return false; } +class Test_PrizeSelectDetector : public UnitTest{ +public: + + Test_PrizeSelectDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonFRLG::PrizeSelectDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + PrizeSelectDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_PrizeSelectDetector(UnitTestDatabase& database){ + //todo: gather test images for this detector +} + } } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.h index 62f9c34da3..27d2f8ecd4 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.h @@ -9,6 +9,7 @@ #include #include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "Common/Cpp/Color.h" #include "CommonFramework/ImageTools/ImageBoxes.h" @@ -46,7 +47,7 @@ class PrizeSelectWatcher : public DetectorToFinder{ {} }; - +void add_tests_PrizeSelectDetector(UnitTestDatabase& database); } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.cpp index 0a3b816485..0b2e9a959c 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.cpp @@ -5,6 +5,7 @@ */ #include "Common/Cpp/Color.h" +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" @@ -59,6 +60,34 @@ bool ShinySymbolDetector::read(Logger& logger, const ImageViewRGB32& frame){ return false; } +class Test_ShinySymbolDetector : public UnitTest{ +public: + + Test_ShinySymbolDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonHome::ShinySymbolDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ShinySymbolDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.read(logger, image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_ShinySymbolDetector(UnitTestDatabase& database){ + database.add("PokemonFRLG/ShinySymbolDetector/Char_False.png", false); + database.add("PokemonFRLG/ShinySymbolDetector/Karp-coloredited_True.png", true); + database.add("PokemonFRLG/ShinySymbolDetector/Karp-snotyak_True.png", true); +} } } diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h index 5a8cb3553b..b9f6a7bba1 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonFRLG_ShinySymbolDetector_H #include "Common/Cpp/Logging/AbstractLogger.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" namespace PokemonAutomation{ @@ -27,7 +28,7 @@ class ShinySymbolDetector{ ImageFloatBox m_box_symbol; }; - +void add_tests_ShinySymbolDetector(UnitTestDatabase& database); } } diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp new file mode 100644 index 0000000000..d8ce98c4d1 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.cpp @@ -0,0 +1,31 @@ +/* Pokemon FRLG Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "Inference/Dialogs/PokemonFRLG_DialogDetector.h" +#include "Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.h" +#include "Inference/PokemonFRLG_ShinySymbolDetector.h" +#include "PokemonFRLG_Tests.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +void add_tests(UnitTestDatabase& database){ + add_tests_AdvanceWhiteDialogDetector(database); + add_tests_SelectionDialogDetector(database); + add_tests_ShinySymbolDetector(database); + add_tests_PrizeSelectDetector(database); + add_tests_AdvanceBattleDialogDetector(database); + add_tests_BattleMenuDetector(database); +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h new file mode 100644 index 0000000000..ef942df247 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Tests.h @@ -0,0 +1,25 @@ +/* Pokemon FRLG Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_Tests_H +#define PokemonAutomation_PokemonFRLG_Tests_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + + +void add_tests(UnitTestDatabase& database); + + + +} +} +} +#endif \ No newline at end of file diff --git a/SerialPrograms/Source/Tests/PokemonFRLG_Tests.cpp b/SerialPrograms/Source/Tests/PokemonFRLG_Tests.cpp deleted file mode 100644 index ba4dc437ea..0000000000 --- a/SerialPrograms/Source/Tests/PokemonFRLG_Tests.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* PokemonFRLG Tests - * - * From: https://github.com/PokemonAutomation/ - * - */ - - -#include "CommonFramework/Logging/Logger.h" -//#include "CommonFramework/Language.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -//#include "CommonFramework/Recording/StreamHistorySession.h" -//#include "NintendoSwitch/Controllers/SerialPABotBase/NintendoSwitch_SerialPABotBase_WiredController.h" -//#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" -#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" -#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" -#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.h" -#include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" -#include "PokemonFRLG_Tests.h" -#include "TestUtils.h" - -#include -using std::cout; -using std::cerr; -using std::endl; - -namespace PokemonAutomation{ - -using namespace NintendoSwitch; -using namespace NintendoSwitch::PokemonFRLG; - -int test_pokemonFRLG_AdvanceWhiteDialogDetector(const ImageViewRGB32& image, bool target){ - auto overlay = DummyVideoOverlay(); - AdvanceWhiteDialogDetector detector(COLOR_RED); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_pokemonFRLG_ShinySymbolDetector(const ImageViewRGB32& image, bool target){ - auto& logger = global_logger_command_line(); - auto overlay = DummyVideoOverlay(); - ShinySymbolDetector detector(COLOR_RED); - bool result = detector.read(logger, image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_pokemonFRLG_SelectionDialogDetector(const ImageViewRGB32& image, bool target){ - auto overlay = DummyVideoOverlay(); - SelectionDialogDetector detector(COLOR_RED); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_pokemonFRLG_AdvanceBattleDialogDetector(const ImageViewRGB32& image, bool target){ - auto overlay = DummyVideoOverlay(); - AdvanceBattleDialogDetector detector(COLOR_RED); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_pokemonFRLG_BattleMenuDetector(const ImageViewRGB32& image, bool target){ - auto overlay = DummyVideoOverlay(); - BattleMenuDetector detector(COLOR_RED); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_pokemonFRLG_PrizeSelectDetector(const ImageViewRGB32& image, bool target){ - auto overlay = DummyVideoOverlay(); - PrizeSelectDetector detector(COLOR_RED); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -} diff --git a/SerialPrograms/Source/Tests/PokemonFRLG_Tests.h b/SerialPrograms/Source/Tests/PokemonFRLG_Tests.h deleted file mode 100644 index bed8f372ce..0000000000 --- a/SerialPrograms/Source/Tests/PokemonFRLG_Tests.h +++ /dev/null @@ -1,34 +0,0 @@ -/* PokemonFRLG Tests - * - * From: https://github.com/PokemonAutomation/ - * - * - */ - - -#ifndef PokemonAutomation_Tests_PokemonFRLG_Tests_H -#define PokemonAutomation_Tests_PokemonFRLG_Tests_H - -#include -#include - -namespace PokemonAutomation{ - -class ImageViewRGB32; - - -int test_pokemonFRLG_AdvanceWhiteDialogDetector(const ImageViewRGB32& image, bool target); - -int test_pokemonFRLG_ShinySymbolDetector(const ImageViewRGB32& image, bool target); - -int test_pokemonFRLG_SelectionDialogDetector(const ImageViewRGB32& image, bool target); - -int test_pokemonFRLG_AdvanceBattleDialogDetector(const ImageViewRGB32& image, bool target); - -int test_pokemonFRLG_BattleMenuDetector(const ImageViewRGB32& image, bool target); - -int test_pokemonFRLG_PrizeSelectDetector(const ImageViewRGB32& image, bool target); - -} - -#endif diff --git a/SerialPrograms/Source/Tests/TestMap.cpp b/SerialPrograms/Source/Tests/TestMap.cpp index b685ca2c0c..d435501571 100644 --- a/SerialPrograms/Source/Tests/TestMap.cpp +++ b/SerialPrograms/Source/Tests/TestMap.cpp @@ -9,7 +9,6 @@ #include "CommonFramework_Tests.h" #include "Kernels_Tests.h" #include "NintendoSwitch_Tests.h" -#include "PokemonFRLG_Tests.h" #include "PokemonLZA_Tests_Old.h" #include "PokemonSV_Tests_Old.h" #include "TestMap.h" @@ -261,12 +260,6 @@ const std::map TEST_MAP = { {"PokemonLZA_HyperspaceCalorieDetector", std::bind(image_int_detector_helper, test_pokemonLZA_HyperspaceCalorieDetector, _1)}, {"PokemonLZA_FlavorPowerScreenDetector", test_pokemonLZA_FlavorPowerScreenDetector}, {"PokemonLZA_DonutBerriesReader", test_pokemonLZA_DonutBerriesReader}, - {"PokemonFRLG_AdvanceWhiteDialogDetector", std::bind(image_bool_detector_helper, test_pokemonFRLG_AdvanceWhiteDialogDetector, _1)}, - {"PokemonFRLG_ShinySymbolDetector", std::bind(image_bool_detector_helper, test_pokemonFRLG_ShinySymbolDetector, _1)}, - {"PokemonFRLG_SelectionDialogDetector", std::bind(image_bool_detector_helper, test_pokemonFRLG_SelectionDialogDetector, _1)}, - {"PokemonFRLG_AdvanceBattleDialogDetector", std::bind(image_bool_detector_helper, test_pokemonFRLG_AdvanceBattleDialogDetector, _1)}, - {"PokemonFRLG_BattleMenuDetector", std::bind(image_bool_detector_helper, test_pokemonFRLG_BattleMenuDetector, _1)}, - {"PokemonFRLG_PrizeSelectDetector", std::bind(image_bool_detector_helper, test_pokemonFRLG_PrizeSelectDetector, _1)}, }; TestFunction find_test_function(const std::string& test_space, const std::string& test_name){ diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 488b4f7fc8..c4040f5127 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1567,6 +1567,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/PokemonFRLG_Panels.h Source/PokemonFRLG/PokemonFRLG_Settings.cpp Source/PokemonFRLG/PokemonFRLG_Settings.h + Source/PokemonFRLG/PokemonFRLG_Tests.cpp + Source/PokemonFRLG/PokemonFRLG_Tests.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_ItemDuplication.cpp Source/PokemonFRLG/Programs/Farming/PokemonFRLG_ItemDuplication.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp @@ -2930,8 +2932,6 @@ file(GLOB LIBRARY_SOURCES Source/Tests/Kernels_Tests.h Source/Tests/NintendoSwitch_Tests.cpp Source/Tests/NintendoSwitch_Tests.h - Source/Tests/PokemonFRLG_Tests.cpp - Source/Tests/PokemonFRLG_Tests.h Source/Tests/PokemonLZA_Tests_Old.cpp Source/Tests/PokemonLZA_Tests_Old.h Source/Tests/PokemonSV_Tests_Old.cpp From 81ea1326831f040b5366fcea109f157cf996da83 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Tue, 4 Aug 2026 23:25:39 -0500 Subject: [PATCH 02/10] Port over LZA --- .../Boxes/PokemonLZA_BoxDetection.cpp | 298 +++++- .../Inference/Boxes/PokemonLZA_BoxDetection.h | 2 +- .../PokemonLZA_DonutBerriesDetector.cpp | 128 ++- .../Donuts/PokemonLZA_DonutBerriesDetector.h | 26 +- .../PokemonLZA_FlavorPowerScreenDetector.cpp | 133 +++ .../PokemonLZA_FlavorPowerScreenDetector.h | 25 +- .../Map/PokemonLZA_DirectionArrowDetector.cpp | 76 +- .../Map/PokemonLZA_DirectionArrowDetector.h | 24 +- .../Inference/Map/PokemonLZA_MapDetector.cpp | 61 +- .../Inference/Map/PokemonLZA_MapDetector.h | 24 +- .../Map/PokemonLZA_MapIconDetector.cpp | 108 ++- .../Map/PokemonLZA_MapIconDetector.h | 20 +- .../Inference/PokemonLZA_AlertEyeDetector.cpp | 48 + .../Inference/PokemonLZA_AlertEyeDetector.h | 2 + .../Inference/PokemonLZA_ButtonDetector.cpp | 41 + .../Inference/PokemonLZA_ButtonDetector.h | 2 + .../Inference/PokemonLZA_DialogDetector.cpp | 53 ++ .../PokemonLZA_HyperspaceCalorieDetector.cpp | 63 +- .../PokemonLZA_HyperspaceCalorieDetector.h | 24 +- .../Inference/PokemonLZA_MainMenuDetector.cpp | 30 +- .../Inference/PokemonLZA_MainMenuDetector.h | 1 + ...monLZA_OverworldPartySelectionDetector.cpp | 94 +- ...kemonLZA_OverworldPartySelectionDetector.h | 22 +- .../PokemonLZA_SelectionArrowDetector.cpp | 39 + .../PokemonLZA_SelectionArrowDetector.h | 2 + .../Source/PokemonLZA/PokemonLZA_Tests.cpp | 43 +- .../Source/Tests/PokemonLZA_Tests_Old.cpp | 860 ------------------ .../Source/Tests/PokemonLZA_Tests_Old.h | 55 -- SerialPrograms/Source/Tests/TestMap.cpp | 19 +- SerialPrograms/cmake/SourceFiles.cmake | 2 - 30 files changed, 1208 insertions(+), 1117 deletions(-) delete mode 100644 SerialPrograms/Source/Tests/PokemonLZA_Tests_Old.cpp delete mode 100644 SerialPrograms/Source/Tests/PokemonLZA_Tests_Old.h diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.cpp b/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.cpp index a414ae6bd2..ad3e6fd0b4 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.cpp @@ -6,6 +6,7 @@ #include "Common/Cpp/Exceptions.h" #include "CommonFramework/Exceptions/FatalProgramException.h" +#include "CommonFramework/Globals.h" #include "CommonFramework/StaticGlobals.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/ImageTypes/ImageHSV32.h" @@ -23,10 +24,12 @@ #include "Kernels/Waterfill/Kernels_Waterfill_Session.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Controllers/Procon/NintendoSwitch_ProController.h" +#include "PokemonLZA/Inference/Boxes/PokemonLZA_BoxInfoDetector.h" #include "PokemonLZA_BoxDetection.h" +#include "Tests/TestUtils.h" #include -//#include + //#include using std::cout; using std::endl; @@ -37,7 +40,7 @@ namespace PokemonLZA{ using namespace Kernels::Waterfill; namespace{ - bool debug_switch = false; +bool debug_switch = false; } @@ -119,9 +122,9 @@ class BoxCellSelectionArrowMatcher : public ImageMatch::SubObjectTemplateMatcher input_image.save("check_image_input_" + std::to_string(counter++) + ".png"); cout << "check_image() HSV validation:" << endl; cout << " Green pixels found: " << green_pixel_count << " / " << total_pixels - << " (" << (100.0 * green_pixel_count / total_pixels) << "%)" << endl; + << " (" << (100.0 * green_pixel_count / total_pixels) << "%)" << endl; cout << " Required minimum: " << min_required_green_pixels - << " (" << (100.0 * min_required_green_pixels / total_pixels) << "%)" << endl; + << " (" << (100.0 * min_required_green_pixels / total_pixels) << "%)" << endl; cout << " Hue range: [" << min_hue << ", " << max_hue << "]" << endl; cout << " Result: " << (has_green ? "PASS" : "FAIL") << endl; } @@ -266,7 +269,7 @@ bool BoxDetector::detect_at_cell(uint8_t cell_idx, const ImageViewRGB32& screen) cout << "Saving image_crop to input_image_crop.png" << endl; image_crop.save("input_image_crop.png"); } - + bool detected = false; auto& matcher = BoxCellSelectionArrowMatcher::matcher(); for (size_t i_matrix = 0; i_matrix < matrices.size(); i_matrix++){ @@ -284,11 +287,11 @@ bool BoxDetector::detect_at_cell(uint8_t cell_idx, const ImageViewRGB32& screen) object.min_y == 0 || object.max_x >= image_crop.width() || object.max_y >= image_crop.height() - ){ + ){ #if 0 cout << "object.min_x = " << object.min_x << ", object.min_y = " << object.min_y - << ", object.max_x = " << object.max_x << ", object.max_y = " << object.max_y - << " : " << image_crop.width() << " x " << image_crop.height() << endl; + << ", object.max_x = " << object.max_x << ", object.max_y = " << object.max_y + << " : " << image_crop.width() << " x " << image_crop.height() << endl; #endif continue; } @@ -303,20 +306,20 @@ bool BoxDetector::detect_at_cell(uint8_t cell_idx, const ImageViewRGB32& screen) } ImagePixelBox found_arrow_box; - + if (debug_switch){ double rmsd_value = matcher.rmsd(found_arrow_box, image_crop, object); cout << "rmsd_value: " << rmsd_value << endl; #if 0 if (!matcher.check_aspect_ratio(object.width(), object.height())){ - cout << "aspect ratio check failed" << endl; + cout << "aspect ratio check failed" << endl; } if (!matcher.check_area_ratio(object.area_ratio())){ cout << "area ratio check failed: candidate object " << object.area_ratio() << " template " << matcher.m_subobject_area_ratio << endl; } #endif } - + if (matcher.matches(found_arrow_box, image_crop, object)){ if (debug_switch){ cout << "detected!!!!!" << endl; @@ -365,7 +368,7 @@ bool BoxDetector::detect(const ImageViewRGB32& screen){ if (detected){ if (arrow_found && m_debug_mode){ cout << "Multiple box selection arrows detected! First detection (" << int(m_found_row) << ", " << int(m_found_col) << ")" - << " second detection (" << int(row) << ", " << int(col) << ")" << endl; + << " second detection (" << int(row) << ", " << int(col) << ")" << endl; throw FatalProgramException(ErrorReport::NO_ERROR_REPORT, "Multiple box selection arrows detected!", nullptr, screen.copy()); } @@ -423,7 +426,7 @@ void BoxDetector::move_cursor( } if (current.row == row && current.col == col){ -// cout << "done!" << endl; + // cout << "done!" << endl; return; } @@ -492,6 +495,275 @@ bool SomethingInBoxCellDetector::detect(const ImageViewRGB32& screen){ } + + + + +class Test_BoxCellInfoDetector : public UnitTest{ +public: + Test_BoxCellInfoDetector( + const std::string& image, + std::vector words + ) + : UnitTest("PokemonPLZA::BoxCellInfoDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_words(std::move(words)) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + // Expected filename format: <...>____.png + // Where status is one of: Empty, Shiny, Alpha, ShinyAlpha and can be followed by "Held" + // Where dex_info is one of: "None", "L" (Lumiose dex), or "H" (Hyperspace dex) + // Examples: + // test_2_3_Empty_None.png -> row 2, col 3, empty cell, no dex number + // test_2_3_Regular_L25.png -> row 2, col 3, non-shiny, non-alpha pokemon, Lumiose dex #25 + // test_2_3_Shiny_H100.png -> row 2, col 3, shiny (non-alpha), Hyperspace dex #100 + // test_2_3_Alpha_L50.png -> row 2, col 3, alpha (non-shiny), Lumiose dex #50 + // test_2_3_ShinyAlphaHeld_H75.png -> row 2, col 3, shiny alpha and holding a pokemon, Hyperspace dex #75 + + if (m_words.size() < 4){ + std::stringstream ss; + ss << "Error: filename must have at least 4 words (row, col, status, dex_info)." << endl; + return ss.str(); + } + + // Parse row from fourth-to-last word + int expected_row; + if (parse_int(m_words[m_words.size() - 4], expected_row) == false){ + std::stringstream ss; + ss << "Error: fourth-to-last word in filename should be row number (0-5)." << endl; + return ss.str(); + } + if (expected_row < 0 || expected_row > 5){ + std::stringstream ss; + ss << "Error: row must be between 0 and 5, got " << expected_row << "." << endl; + return ss.str(); + } + + // Parse col from third-to-last word + int expected_col; + if (parse_int(m_words[m_words.size() - 3], expected_col) == false){ + std::stringstream ss; + ss << "Error: third-to-last word in filename should be col number (0-5)." << endl; + return ss.str(); + } + if (expected_col < 0 || expected_col > 5){ + std::stringstream ss; + ss << "Error: col must be between 0 and 5, got " << expected_col << "." << endl; + return ss.str(); + } + + // Parse status from second-to-last word + std::string status_word = m_words[m_words.size() - 2]; + bool holding_pokemon = false; + if (status_word.ends_with("Held")){ + holding_pokemon = true; + status_word = status_word.substr(0, status_word.size() - 4); + } + bool expected_something_in_cell; + bool expected_shiny; + bool expected_alpha; + + if (status_word == "Empty"){ + expected_something_in_cell = false; + expected_shiny = false; + expected_alpha = false; + } else if (status_word == "Regular"){ + expected_something_in_cell = true; + expected_shiny = false; + expected_alpha = false; + } else if (status_word == "Shiny"){ + expected_something_in_cell = true; + expected_shiny = true; + expected_alpha = false; + } else if (status_word == "Alpha"){ + expected_something_in_cell = true; + expected_shiny = false; + expected_alpha = true; + } else if (status_word == "ShinyAlpha"){ + expected_something_in_cell = true; + expected_shiny = true; + expected_alpha = true; + } else{ + std::stringstream ss; + ss << "Error: second-to-last word must be 'Empty', 'Shiny', 'Alpha', or 'ShinyAlpha', got '" << status_word << "'." << endl; + return ss.str(); + } + + // Parse dex info from last word + std::string dex_info_word = m_words[m_words.size() - 1]; + bool expect_dex_detection = false; + DexType expected_dex_type = DexType::LUMIOSE; + uint16_t expected_dex_number = 0; + + if (dex_info_word == "None"){ + expect_dex_detection = false; + } else if (dex_info_word.size() >= 2 && (dex_info_word[0] == 'L' || dex_info_word[0] == 'H')){ + expect_dex_detection = true; + expected_dex_type = (dex_info_word[0] == 'L') ? DexType::LUMIOSE : DexType::HYPERSPACE; + + std::string number_str = dex_info_word.substr(1); + int dex_num_int; + if (parse_int(number_str, dex_num_int) == false || dex_num_int <= 0){ + std::stringstream ss; + ss << "Error: invalid dex number in '" << dex_info_word << "'. Expected format: L or H." << endl; + return ss.str(); + } + expected_dex_number = static_cast(dex_num_int); + } else{ + std::stringstream ss; + ss << "Error: last word must be 'None', 'L', or 'H', got '" << dex_info_word << "'." << endl; + return ss.str(); + } + + // Run detectors + auto overlay = DummyVideoOverlay(); + ImageRGB32 image(m_image); + + // Test BoxDetector for row and col + BoxDetector box_detector(COLOR_RED, &overlay); + box_detector.set_debug_mode(true); + box_detector.holding_pokemon(holding_pokemon); + + // #define PROFILE_BOX_DETECTION +#ifdef PROFILE_BOX_DETECTION + // Profile the template matching performance + const int num_iterations = 100; + auto time_start = current_time(); + bool in_box_system = false; + for (int i = 0; i < num_iterations; i++){ + in_box_system = box_detector.detect(image); + } + auto time_end = current_time(); + + const auto ns = std::chrono::duration_cast(time_end - time_start).count(); + const double ms_total = ns / 1000000.0; + const double ms_per_iteration = ms_total / num_iterations; + + cout << "BoxDetector::detect() performance:" << endl; + cout << " Total time for " << num_iterations << " iterations: " << ms_total << " ms" << endl; + cout << " Average time per iteration: " << ms_per_iteration << " ms" << endl; + cout << " Throughput: " << (1000.0 / ms_per_iteration) << " detections/second" << endl; +#else + bool in_box_system = box_detector.detect(image); +#endif + + if (!in_box_system){ + std::stringstream ss; + ss << "Error: BoxDetector did not detect box system view." << endl; + return ss.str(); + } + + BoxCursorCoordinates coords = box_detector.detected_location(); + if (coords.row == BoxCursorCoordinates::INVALID || coords.col == BoxCursorCoordinates::INVALID){ + std::stringstream ss; + ss << "Error: detect_location() returned INVALID coordinates." << endl; + return ss.str(); + } + + TEST_RESULT_COMPONENT_EQUAL((int)coords.row, expected_row, "row"); + TEST_RESULT_COMPONENT_EQUAL((int)coords.col, expected_col, "col"); + + // Test SomethingInBoxCellDetector + SomethingInBoxCellDetector something_detector(COLOR_RED, &overlay); + bool detected_something = something_detector.detect(image); + TEST_RESULT_COMPONENT_EQUAL(detected_something, expected_something_in_cell, "something_in_cell"); + + // Test BoxShinyDetector + BoxShinyDetector shiny_detector(COLOR_RED, &overlay); + bool detected_shiny = shiny_detector.detect(image); + TEST_RESULT_COMPONENT_EQUAL(detected_shiny, expected_shiny, "shiny"); + + // Test BoxAlphaDetector + BoxAlphaDetector alpha_detector(COLOR_RED, &overlay); + bool detected_alpha = alpha_detector.detect(image); + TEST_RESULT_COMPONENT_EQUAL(detected_alpha, expected_alpha, "alpha"); + + // Test BoxDexNumberDetector + if (expect_dex_detection){ + BoxDexNumberDetector dex_detector(global_logger_command_line()); + bool detected_dex = dex_detector.detect(image); + + if (!detected_dex){ + std::stringstream ss; + ss << "Error: BoxDexNumberDetector failed to detect dex number." << endl; + return ss.str(); + } + + DexType detected_dex_type = dex_detector.dex_type(); + uint16_t detected_dex_number = dex_detector.dex_number(); + + std::string expected_dex_type_str = (expected_dex_type == DexType::LUMIOSE) ? "Lumiose" : "Hyperspace"; + std::string detected_dex_type_str = (detected_dex_type == DexType::LUMIOSE) ? "Lumiose" : "Hyperspace"; + + if (detected_dex_type != expected_dex_type){ + std::stringstream ss; + ss << "Error: dex type mismatch. Expected " << expected_dex_type_str + << " but detected " << detected_dex_type_str << "." << endl; + return ss.str(); + } + + TEST_RESULT_COMPONENT_EQUAL((int)detected_dex_number, (int)expected_dex_number, "dex_number"); + } + + return true; + }; + +private: + std::string m_image; + std::vector m_words; + +}; + + +void add_tests_BoxCellInfoDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/BoxCellInfoDetector/french_box_gyarados_2_1_Regular_L033.jpg", std::vector{"2", "1", "Regular", "L033"}); + database.add("PokemonLZA/BoxCellInfoDetector/french_box_haunter_1_4_Shiny_L066.jpg", std::vector{"1", "4", "Shiny", "L066"}); + database.add("PokemonLZA/BoxCellInfoDetector/french_box_lopunny_1_1_Alpha_L110.jpg", std::vector{"1", "1", "Alpha", "L110"}); + database.add("PokemonLZA/BoxCellInfoDetector/french_box_ralts_1_0_ShinyAlpha_L087.jpg", std::vector{"1", "0", "ShinyAlpha", "L087"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_0_0_Empty_None.jpg", std::vector{"0", "0", "Empty", "None"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_0_4_Empty_None.jpg", std::vector{"0", "4", "Empty", "None"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_1_0_Empty_None.jpg", std::vector{"1", "0", "Empty", "None"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_1_0_Regular_H014.jpg", std::vector{"1", "0", "Regular", "H014"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_2_5_Empty_None.jpg", std::vector{"2", "5", "Empty", "None"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_4_4_Empty_None.jpg", std::vector{"4", "4", "Empty", "None"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_clauncher_3_2_Shiny_L163.jpg", std::vector{"3", "2", "Shiny", "L163"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_dragonite_0_5_Regular_L147.jpg", std::vector{"0", "5", "Regular", "L147"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_flabebe_1_0_Alpha_L038.jpg", std::vector{"1", "0", "Alpha", "L038"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_fletchling_1_5_ShinyAlpha_L010.jpg", std::vector{"1", "5", "ShinyAlpha", "L010"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_florges_5_5_Regular_L040.jpg", std::vector{"5", "5", "Regular", "L040"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_furfrou_4_2_Regular_L158.jpg", std::vector{"4", "2", "Regular", "L158"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_gardevoir_0_0_Regular_L089.jpg", std::vector{"0", "0", "Regular", "L089"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_greninja_1_5_ShinyAlpha_L211.jpg", std::vector{"1", "5", "ShinyAlpha", "L211"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_hippopotas_3_3_Alpha_L118.jpg", std::vector{"3", "3", "Alpha", "L118"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_igglybuff_2_3_Regular_H076.jpg", std::vector{"2", "3", "Regular", "H076"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_magikarp_2_0_Regular_L032.jpg", std::vector{"2", "0", "Regular", "L032"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_mareep_3_4_Shiny_L024.jpg", std::vector{"3", "4", "Shiny", "L024"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_pyroar_0_5_ShinyAlpha_L046.jpg", std::vector{"0", "5", "ShinyAlpha", "L046"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_pyroar_1_0_ShinyAlpha_L046.jpg", std::vector{"1", "0", "ShinyAlpha", "L046"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_tyrunt_1_0_Regular_L193.jpg", std::vector{"1", "0", "Regular", "L193"}); + database.add("PokemonLZA/BoxCellInfoDetector/mac_box_victreebel_1_3_Alpha_L076.jpg", std::vector{"1", "3", "Alpha", "L076"}); + database.add("PokemonLZA/BoxCellInfoDetector/Bug/box_dragalge_0_5_Alpha_L162.jpg", std::vector{"0", "5", "Alpha", "L162"}); + database.add("PokemonLZA/BoxCellInfoDetector/Bug/box_greninja_0_4_ShinyAlpha_L211.jpg", std::vector{"0", "4", "ShinyAlpha", "L211"}); + database.add("PokemonLZA/BoxCellInfoDetector/Bug/box_scolipede_0_3_Shiny_L070.jpg", std::vector{"0", "3", "Shiny", "L070"}); + database.add("PokemonLZA/BoxCellInfoDetector/dhruv/box_2_2_ShinyHeld_H067.jpg", std::vector{"2", "2", "ShinyHeld", "H067"}); + database.add("PokemonLZA/BoxCellInfoDetector/dhruv/box_tinkatonAbove_2_2_ShinyHeld_H067.jpg", std::vector{"2", "2", "ShinyHeld", "H067"}); + database.add("PokemonLZA/BoxCellInfoDetector/dolphincurry/box_tyrunt_no1_1_0_Regular_L193.jpg", std::vector{"1", "0", "Regular", "L193"}); + database.add("PokemonLZA/BoxCellInfoDetector/dolphincurry/box_tyrunt_no2_1_0_Regular_L193.jpg", std::vector{"1", "0", "Regular", "L193"}); + database.add("PokemonLZA/BoxCellInfoDetector/dolphincurry/box_tyrunt_no3_1_0_Regular_L193.jpg", std::vector{"1", "0", "Regular", "L193"}); + database.add("PokemonLZA/BoxCellInfoDetector/held/mac_box_0_0_AlphaHeld_L227.jpg", std::vector{"0", "0", "AlphaHeld", "L227"}); + database.add("PokemonLZA/BoxCellInfoDetector/held/mac_box_1_5_RegularHeld_H050.jpg", std::vector{"1", "5", "RegularHeld", "H050"}); + database.add("PokemonLZA/BoxCellInfoDetector/held/mac_box_2_1_ShinyHeld_L111.jpg", std::vector{"2", "1", "ShinyHeld", "L111"}); + database.add("PokemonLZA/BoxCellInfoDetector/held/mac_box_2_4_EmptyHeld_None.jpg", std::vector{"2", "4", "EmptyHeld", "None"}); + database.add("PokemonLZA/BoxCellInfoDetector/held/mac_box_5_5_EmptyHeld_None.jpg", std::vector{"5", "5", "EmptyHeld", "None"}); + database.add("PokemonLZA/BoxCellInfoDetector/k3lpoke/bright_box_gourgeist_4_2_ShinyAlpha_L205.jpg", std::vector{"4", "2", "ShinyAlpha", "L205"}); + database.add("PokemonLZA/BoxCellInfoDetector/k3lpoke/bright_box_pangoro_1_2_Alpha_L048.jpg", std::vector{"1", "2", "Alpha", "L048"}); + database.add("PokemonLZA/BoxCellInfoDetector/k3lpoke/bright_box_pumpkaboo_4_3_Shiny_L204.jpg", std::vector{"4", "3", "Shiny", "L204"}); + database.add("PokemonLZA/BoxCellInfoDetector/Quantum/box_2_3_ShinyHeld_L172.jpg", std::vector{"2", "3", "ShinyHeld", "L172"}); +} + + + } } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.h b/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.h index 3556fe689e..4f137fe783 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.h @@ -113,7 +113,7 @@ class SomethingInBoxCellWatcher : public DetectorToFinder //#include //using std::cout; //using std::endl; @@ -154,7 +159,7 @@ OCR::StringMatchResult DonutBerriesReader::read_berry_page_with_ocr( return read_with_ocr(screen, logger, language, m_box_berry_text[index]); } -OCR::StringMatchResult DonutBerriesReader::read_with_ocr( +OCR::StringMatchResult DonutBerriesReader::read_with_ocr( const ImageViewRGB32& screen, Logger& logger, Language language, @@ -168,9 +173,104 @@ OCR::StringMatchResult DonutBerriesReader::read_with_ocr( OCR::StringMatchResult results; results = DonutBerriesOCR::instance().read_substring(logger, language, image); - return results; -} - -} -} -} + return results; +} + + +class Test_DonutBerriesReader : public UnitTest{ +public: + Test_DonutBerriesReader(const std::string& image) + : UnitTest("PokemonPLZA::DonutBerriesReader - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + Filesystem::Path file_path(m_image); + Filesystem::Path parent_dir = file_path.parent_path(); + std::string base_name = file_path.stem().string(); + + const std::vector words = parse_words(base_name); + if (words.size() < 2){ + return "Error: not enough number of words in the filename."; + } + + std::string code = words[words.size() - 2]; + if (code == "chiSim"){ + code = "chi_sim"; + }else if (code == "chiTra"){ + code = "chi_tra"; + } + Language language = language_code_to_enum(code); + if (language == Language::None || language == Language::EndOfList){ + return "Error: invalid language word in filename."; + } + + size_t selected_berry = 0; + if (!parse_size_t(words.back(), selected_berry)){ + return "Error: selected berry word must be int of range [0, 7]."; + } + if (selected_berry >= DonutBerriesReader::BERRY_PAGE_LINES){ + return "Error: selected_berry must be in range [0, 7]."; + } + + ImageRGB32 image(m_image); + Filesystem::Path target_berries_path = parent_dir / ("_" + base_name + ".txt"); + + if (STATIC_GLOBALS.GENERATE_TEST_GOLDEN_FILES){ + std::ofstream output_file(target_berries_path.stdpath()); + if (!output_file.is_open()){ + return "Error: cannot open output file " + target_berries_path.string() + " for writing"; + } + + DonutBerriesReader reader; + for (size_t i = 0; i < DonutBerriesReader::BERRY_PAGE_LINES; ++i){ + OCR::StringMatchResult results = reader.read_berry_page_with_ocr(image, global_logger_command_line(), language, i); + output_file << (results.results.empty() ? "unknown-berry" : results.results.begin()->second.token) << std::endl; + } + return true; + } + + std::vector target_berries; + if (!load_slug_list(target_berries_path.string(), target_berries)){ + return "Cannot load slug list from " + target_berries_path.string(); + } + if (target_berries.size() != DonutBerriesReader::BERRY_PAGE_LINES){ + return "Error: need to have exactly 8 berries in " + target_berries_path.string(); + } + + DonutBerriesReader reader; + for (size_t i = 0; i < DonutBerriesReader::BERRY_PAGE_LINES; ++i){ + DonutBerriesSelectionDetector selection_detector(i); + TEST_RESULT_COMPONENT_EQUAL(selection_detector.detect(image), i == selected_berry, "selection detector : berry slot " + std::to_string(i)); + + OCR::StringMatchResult results = reader.read_berry_page_with_ocr(image, global_logger_command_line(), language, i); + if (results.results.empty()){ + return "No berry detected via OCR"; + } + TEST_RESULT_COMPONENT_EQUAL(results.results.begin()->second.token, target_berries[i], "ocr : berry slot " + std::to_string(i)); + } + + return true; + } + +private: + std::string m_image; +}; + + +void add_tests_DonutBerriesReader(UnitTestDatabase& database){ + database.add("PokemonLZA/DonutBerriesReader/20260102_01_chiSim_7.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260102_01_eng_6.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260102_02_chiSim_5.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260102_02_eng_6.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260102_03_eng_0.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260102_04_eng_7.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260102_05_eng_3.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260120_01_kor_0.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260120_02_kor_0.jpg"); + database.add("PokemonLZA/DonutBerriesReader/20260122_01_eng_0.jpg"); +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_DonutBerriesDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_DonutBerriesDetector.h index 74a1353128..43501ab1bc 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_DonutBerriesDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_DonutBerriesDetector.h @@ -5,10 +5,11 @@ */ #ifndef PokemonAutomation_PokemonLZA_DonutBerriesDetector_H -#define PokemonAutomation_PokemonLZA_DonutBerriesDetector_H - -#include -#include "Common/Cpp/Color.h" +#define PokemonAutomation_PokemonLZA_DonutBerriesDetector_H + +#include +#include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/ImageMatch/ImageMatchResult.h" #include "CommonTools/ImageMatch/CroppedImageDictionaryMatcher.h" @@ -77,7 +78,7 @@ class DonutBerriesOCR : public OCR::SmallDictionaryMatcher{ }; -class DonutBerriesReader{ +class DonutBerriesReader{ public: static constexpr double MAX_ALPHA = 180; static constexpr double ALPHA_SPREAD = 10; @@ -109,10 +110,13 @@ class DonutBerriesReader{ Color m_color; std::array m_box_berry_text; - std::array m_box_berry_icon; -}; - -} -} -} + std::array m_box_berry_icon; +}; + + +void add_tests_DonutBerriesReader(UnitTestDatabase& database); + +} +} +} #endif diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.cpp index a24ac284f2..5321ef4df4 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.cpp @@ -4,11 +4,20 @@ * */ +#include "Common/Cpp/Filesystem.h" +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/Logging/Logger.h" +#include "CommonFramework/StaticGlobals.h" #include "CommonTools/Images/SolidColorTest.h" #include "CommonFramework/VideoPipeline/VideoOverlay.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "PokemonLZA_FlavorPowerDetector.h" #include "PokemonLZA_FlavorPowerScreenDetector.h" +#include "Tests/TestUtils.h" + +#include +#include namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -46,6 +55,130 @@ bool FlavorPowerScreenDetector::detect(const ImageViewRGB32& screen){ } +class Test_FlavorPowerScreenDetector : public UnitTest{ +public: + Test_FlavorPowerScreenDetector(const std::string& image) + : UnitTest("PokemonPLZA::FlavorPowerScreenDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + Filesystem::Path file_path(m_image); + Filesystem::Path parent_dir = file_path.parent_path(); + std::string base_name = file_path.stem().string(); + + const std::vector words = parse_words(base_name); + if (words.empty()){ + return "Error: not enough number of words in the filename."; + } + + std::string code = words.back(); + if (code == "chiSim"){ + code = "chi_sim"; + }else if (code == "chiTra"){ + code = "chi_tra"; + } + Language language = language_code_to_enum(code); + if (language == Language::None || language == Language::EndOfList){ + return "Error: invalid language word in filename."; + } + + ImageRGB32 image(m_image); + FlavorPowerScreenDetector screen_detector; + if (!screen_detector.detect(image)){ + return "Error: FlavorPowerScreenDetector did not detect flavor power screen."; + } + + if (STATIC_GLOBALS.GENERATE_TEST_GOLDEN_FILES){ + Filesystem::Path target_powers_path = parent_dir / ("_" + base_name + ".txt"); + std::ofstream output_file(target_powers_path.stdpath()); + if (!output_file.is_open()){ + return "Error: cannot open output file " + target_powers_path.string() + " for writing"; + } + + for (int i = 0; i < 3; i++){ + FlavorPowerDetector power_detector(global_logger_command_line(), COLOR_RED, language, i); + std::string power_slug = power_detector.detect_power(image); + output_file << (power_slug.empty() ? "empty" : power_slug) << std::endl; + } + return true; + } + + Filesystem::Path target_powers_path = parent_dir / ("_" + base_name + ".txt"); + std::vector expected_powers; + if (!load_slug_list(target_powers_path.string(), expected_powers)){ + return "Loading slug list " + target_powers_path.string() + " failed."; + } + if (expected_powers.size() != 3){ + return "Error: need to have exactly 3 power slots in " + target_powers_path.string(); + } + + for (int i = 0; i < 3; i++){ + FlavorPowerIconDetector power_icon_detector(global_logger_command_line(), i); + FlavorPowerDetector power_detector(global_logger_command_line(), COLOR_RED, language, i); + + std::string detected_power_slug = power_detector.detect_power(image); + int detected_power_level = power_icon_detector.detect(image); + const std::string& expected_power = expected_powers[i]; + + if (expected_power == "empty"){ + if (!detected_power_slug.empty()){ + return "Error: expected empty power slot but OCR detected a power."; + } + if (detected_power_level > 0){ + return "Error: expected empty power slot but icon detector detected a level."; + } + continue; + } + + TEST_RESULT_COMPONENT_EQUAL(detected_power_slug, expected_power, "power slug for slot " + std::to_string(i)); + if (detected_power_slug.empty()){ + return "Error: OCR detected empty but expected a power."; + } + int expected_level = detected_power_slug.back() - '1' + 1; + if (detected_power_level != expected_level){ + std::ostringstream ss; + ss << "Error: Slot " << i << " - OCR reads power " << detected_power_slug + << " but power icon detector gets power level " << detected_power_level; + return ss.str(); + } + } + + return true; + } + +private: + std::string m_image; +}; + + +void add_tests_FlavorPowerScreenDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/FlavorPowerScreenDetector/gin_01_eng.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/gin_02_eng.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jimin_01_kor.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jimin_02_kor.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_01_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_02_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_03_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_04_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_05_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_06_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_07_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_08_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/jojoaman_09_chiTra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/kichi_01_eng.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/kuro_01_normal_eng.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/kuro_02_dark_eng.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/ougi_01_chiSim.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/ougi_02_chiSim.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/pif_01_fra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/pif_02_fra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/pif_03_fra.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/turboV_01_eng.jpg"); + database.add("PokemonLZA/FlavorPowerScreenDetector/turboV_02_eng.jpg"); +} + + } } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h index 6e39a29456..909a337e40 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h @@ -5,10 +5,10 @@ */ #ifndef PokemonAutomation_PokemonLZA_FlavorPowerScreenDetector_H -#define PokemonAutomation_PokemonLZA_FlavorPowerScreenDetector_H - -#include -#include "Common/Cpp/Color.h" +#define PokemonAutomation_PokemonLZA_FlavorPowerScreenDetector_H + +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" @@ -34,17 +34,20 @@ class FlavorPowerScreenDetector : public StaticScreenDetector{ const ImageFloatBox m_donut_area; }; -class FlavorPowerScreenWatcher : public DetectorToFinder{ +class FlavorPowerScreenWatcher : public DetectorToFinder{ public: FlavorPowerScreenWatcher( std::chrono::milliseconds hold_duration = std::chrono::milliseconds(100) ) : DetectorToFinder("FlavorPowerScreenWatcher", hold_duration) {} -}; - - -} -} -} +}; + + +void add_tests_FlavorPowerScreenDetector(UnitTestDatabase& database); + + +} +} +} #endif diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.cpp index 80d245fe55..c582f98d45 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.cpp @@ -4,11 +4,14 @@ * */ -#include "PokemonLZA_DirectionArrowDetector.h" -#include "CommonFramework/ImageTypes/ImageViewRGB32.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include -#include +#include "PokemonLZA_DirectionArrowDetector.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "Tests/TestUtils.h" +#include +#include +#include // #define DEBUG_DIRECTION_ARROW @@ -329,14 +332,59 @@ bool DirectionArrowDetector::detect(const ImageViewRGB32& screen){ cout << "Found angle: " << eigenvec_angle_deg << " degrees." << endl; #endif // Store result - m_detected_angle = eigenvec_angle_deg; - - return true; -} - - - - -} + m_detected_angle = eigenvec_angle_deg; + + return true; +} + + +class Test_DirectionArrowDetector : public UnitTest{ +public: + Test_DirectionArrowDetector(const std::string& image, int target_angle) + : UnitTest("PokemonPLZA::DirectionArrowDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_target_angle(target_angle) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + if (m_target_angle < 0 || m_target_angle >= 360){ + return "Error: target angle must be in range [0, 360)."; + } + + DirectionArrowDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + if (!detector.detect(image)){ + return "Error: detector failed to detect arrow in image."; + } + + double diff = std::abs(detector.detected_angle_deg() - m_target_angle); + if (diff > 180.0){ + diff = 360.0 - diff; + } + if (diff > 10.0){ + std::ostringstream ss; + ss << "Error: detected angle " << detector.detected_angle_deg() + << " differs from target angle " << m_target_angle + << " by " << diff << " degrees."; + return ss.str(); + } + + return true; + } + +private: + std::string m_image; + int m_target_angle; +}; + + +void add_tests_DirectionArrowDetector(UnitTestDatabase& database){ + //todo: Gather test images for the direction arrow detector +} + + + + +} } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.h index da1a719a1a..db7238bfca 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.h @@ -5,10 +5,11 @@ */ #ifndef PokemonAutomation_PokemonLZA_DirectionArrowDetector_H -#define PokemonAutomation_PokemonLZA_DirectionArrowDetector_H - -#include -#include "Common/Cpp/Color.h" +#define PokemonAutomation_PokemonLZA_DirectionArrowDetector_H + +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Common/Cpp/Color.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" @@ -40,7 +41,7 @@ class DirectionArrowDetector : public StaticScreenDetector{ double m_detected_angle; // in degrees [0, 360), or -1 if not detected }; -class DirectionArrowWatcher : public DetectorToFinder{ +class DirectionArrowWatcher : public DetectorToFinder{ public: DirectionArrowWatcher( Color color = COLOR_RED, @@ -48,11 +49,14 @@ class DirectionArrowWatcher : public DetectorToFinder{ ) : DetectorToFinder("DirectionArrowWatcher", hold_duration, color) {} -}; - - - - +}; + + +void add_tests_DirectionArrowDetector(UnitTestDatabase& database); + + + + } } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.cpp index faa27367fa..ba9a7a5457 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.cpp @@ -3,11 +3,13 @@ * From: https://github.com/PokemonAutomation/ * */ -#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" -#include "CommonTools/Images/WaterfillUtilities.h" -#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" -#include "PokemonLZA_MapDetector.h" -#include "PokemonLZA_MapIconDetector.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "CommonFramework/Globals.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "PokemonLZA_MapDetector.h" +#include "PokemonLZA_MapIconDetector.h" +#include "Tests/TestUtils.h" //#include //using std::cout; @@ -122,16 +124,43 @@ std::vector MapDetector::detected_map_icons() const{ return ret; } -void MapDetector::reset_state(){ - m_x_button.reset_state(); - m_y_button.reset_state(); - for (MapIconDetector* detector : m_map_icon_detectors){ - detector->reset_state(); - } -} - - - -} +void MapDetector::reset_state(){ + m_x_button.reset_state(); + m_y_button.reset_state(); + for (MapIconDetector* detector : m_map_icon_detectors){ + detector->reset_state(); + } +} + + +class Test_MapDetector : public UnitTest{ +public: + Test_MapDetector(const std::string& image, bool expected) + : UnitTest("PokemonPLZA::MapDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + DummyVideoOverlay overlay; + MapDetector detector(COLOR_RED, &overlay); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_MapDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/MapDetector/hyperspace_2_True.png", true); + database.add("PokemonLZA/MapDetector/hyperspace_True.png", true); +} + + + +} } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.h index 9ba955ba25..cc357bba88 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapDetector.h @@ -5,9 +5,10 @@ */ #ifndef PokemonAutomation_PokemonLZA_MapDetector_H -#define PokemonAutomation_PokemonLZA_MapDetector_H - -#include "CommonTools/VisualDetector.h" +#define PokemonAutomation_PokemonLZA_MapDetector_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonTools/VisualDetector.h" #include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" #include "CommonTools/DetectedBoxes.h" @@ -61,7 +62,7 @@ class MapWatcher : public DetectorToFinder{ : DetectorToFinder("MapWatcher", hold_duration, color, overlay) {} }; -class MapOverWatcher : public DetectorToFinder{ +class MapOverWatcher : public DetectorToFinder{ public: MapOverWatcher( Color color = COLOR_RED, @@ -70,12 +71,15 @@ class MapOverWatcher : public DetectorToFinder{ ) : DetectorToFinder("MapWatcher", FinderType::GONE, hold_duration, color, overlay) {} -}; - - - - - +}; + + +void add_tests_MapDetector(UnitTestDatabase& database); + + + + + diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.cpp index 7cf9024fd8..1887181439 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.cpp @@ -4,13 +4,18 @@ * */ -#include -#include "Common/Cpp/Exceptions.h" -//#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" -#include "CommonTools/Images/WaterfillUtilities.h" -#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" -#include "Pokemon/Pokemon_Strings.h" -#include "PokemonLZA_MapIconDetector.h" +#include +#include +#include +#include "Common/Cpp/Filesystem.h" +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/Globals.h" +//#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "Pokemon/Pokemon_Strings.h" +#include "PokemonLZA_MapIconDetector.h" +#include "Tests/TestUtils.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -393,15 +398,86 @@ bool MapIconDetector::detect(const ImageViewRGB32& screen){ } -const std::vector& MapIconDetector::last_detected(){ - merge_overlapping_boxes(m_last_detected); - return m_last_detected; -} - - - - - +const std::vector& MapIconDetector::last_detected(){ + merge_overlapping_boxes(m_last_detected); + return m_last_detected; +} + + + +class Test_MapIconDetector : public UnitTest{ +public: + Test_MapIconDetector(const std::string& image) + : UnitTest("PokemonPLZA::MapIconDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + Filesystem::Path file_path(m_image); + Filesystem::Path parent_dir = file_path.parent_path(); + std::string base_name = file_path.stem().string(); + Filesystem::Path target_detections_path = parent_dir / ("_" + base_name + ".txt"); + + std::map expected_counts; + std::ifstream file(target_detections_path.stdpath()); + if (!file.is_open()){ + return "Error: cannot open target detection file " + target_detections_path.string(); + } + + std::string line; + while (std::getline(file, line)){ + if (line.empty()){ + continue; + } + + size_t space_pos = line.find(' '); + if (space_pos == std::string::npos){ + return "Error: invalid line format in " + target_detections_path.string() + ": " + line; + } + + std::string type_str = line.substr(0, space_pos); + std::string count_str = line.substr(space_pos + 1); + + int count = 0; + if (!parse_int(count_str, count)){ + return "Error: invalid count in " + target_detections_path.string() + ": " + count_str; + } + + try{ + expected_counts[string_to_map_icon_type(type_str)] = count; + }catch (const std::exception&){ + return "Error: unknown MapIconType in " + target_detections_path.string() + ": " + type_str; + } + } + + ImageRGB32 image(m_image); + std::map> detected_boxes; + for (const auto& pair : expected_counts){ + MapIconDetector detector(COLOR_RED, pair.first, ImageFloatBox(0.0, 0.0, 1.0, 1.0)); + detector.detect(image); + detected_boxes[pair.first] = detector.last_detected(); + } + + for (const auto& pair : expected_counts){ + const auto& detections = detected_boxes[pair.first]; + TEST_RESULT_COMPONENT_EQUAL((int)detections.size(), pair.second, map_icon_type_to_string(pair.first)); + } + + return true; + } + +private: + std::string m_image; +}; + + +void add_tests_MapIconDetector(UnitTestDatabase& database){ +} + + + + + diff --git a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.h index cf327199cf..513134e563 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.h @@ -7,7 +7,8 @@ #ifndef PokemonAutomation_PokemonLZA_MapIconDetector_H #define PokemonAutomation_PokemonLZA_MapIconDetector_H -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/DetectedBoxes.h" #include "CommonTools/VisualDetector.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -43,7 +44,7 @@ MapIconType string_to_map_icon_type(const std::string& str); -class MapIconDetector : public StaticScreenDetector{ +class MapIconDetector : public StaticScreenDetector{ public: MapIconDetector( Color color, @@ -69,12 +70,15 @@ class MapIconDetector : public StaticScreenDetector{ std::vector m_last_detected; std::deque m_last_detected_box; -}; - - - - - +}; + + +void add_tests_MapIconDetector(UnitTestDatabase& database); + + + + + } } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.cpp index e01efc47d9..861240da6d 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.cpp @@ -5,10 +5,12 @@ */ #include "Common/Cpp/Exceptions.h" +#include "CommonFramework/Globals.h" #include "Kernels/Waterfill/Kernels_Waterfill_Types.h" #include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" #include "CommonTools/Images/WaterfillUtilities.h" #include "PokemonLZA_AlertEyeDetector.h" +#include "Tests/TestUtils.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -145,7 +147,53 @@ bool AlertEyeTracker::process_frame(const ImageViewRGB32& frame, WallClock times +class Test_AlertEyeDetector : public UnitTest{ +public: + Test_AlertEyeDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonPLZA::AlertEyeDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + DummyVideoOverlay overlay; + AlertEyeDetector detector(COLOR_RED, &overlay); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_AlertEyeDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/AlertEyeDetector/lab_beldum_alpha_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/lab_beldum_x2_alpha_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_15_gourgeist_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_15_gourgeist_pumpkaboo_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_15_green_gate_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_15_pumpkaboo_alpha_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_15_pumpkaboo_alpha_x2_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_17_burrow_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_17_burrow_chespin_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_17_chespin_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_20_carbink_barbaracle_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_20_chandelure_drampa_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_20_dedenne_alakazam_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_20_gallade_simipour_alert_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_6_alpha_houndour_1_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_6_alpha_houndour_2_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_6_houndour_1_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_6_houndour_2_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_6_houndour_3_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_6_houndour_4_True.png", true); + database.add("PokemonLZA/AlertEyeDetector/zone_6_houndour_5_True.png", true); +} } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.h index a984c10b2e..7df4179eaa 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonLZA_AlertEyeDetector_H #include "Common/Cpp/Concurrency/SpinLock.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" @@ -77,6 +78,7 @@ class AlertEyeTracker final : public AlertEyeDetector, public VisualInferenceCal }; +void add_tests_AlertEyeDetector(UnitTestDatabase& database); } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.cpp index 7643fa7f27..8504f92dfc 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.cpp @@ -6,11 +6,13 @@ #include #include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "CommonFramework/Globals.h" #include "CommonFramework/StaticGlobals.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/WaterfillUtilities.h" #include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" #include "PokemonLZA_ButtonDetector.h" +#include "Tests/TestUtils.h" // using std::cout; // using std::endl; @@ -281,7 +283,46 @@ bool ButtonDetector::detect(const ImageViewRGB32& screen){ +class Test_ButtonDetector : public UnitTest{ +public: + Test_ButtonDetector( + const std::string& image, + ButtonType expected_button, + bool expected + ) + : UnitTest("PokemonPLZA::ButtonDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected_button(expected_button) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + DummyVideoOverlay overlay; + ButtonDetector detector(COLOR_RED, m_expected_button, ImageFloatBox(0.0, 0.0, 1.0, 1.0), &overlay); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; +private: + std::string m_image; + bool m_expected; + ButtonType m_expected_button; +}; + + +void add_tests_ButtonDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/ButtonDetector/french_fossil_1_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/french_fossil_2_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/french_fossil_3_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/french_fossil_4_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/french_fossil_5_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/mac_fossil_1_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/mac_fossil_2_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/mac_fossil_3_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/mac_fossil_4_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/mac_fossil_5_ButtonA_True.png", ButtonType::ButtonA, true); + database.add("PokemonLZA/ButtonDetector/mac_fossil_6_ButtonA_True.png", ButtonType::ButtonA, true); +} diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.h index cf548d690c..ee20f974be 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_ButtonDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonLZA_ButtonDetector_H #include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -91,6 +92,7 @@ class ButtonGoneWatcher : public DetectorToFinder{ }; +void add_tests_ButtonDetector(UnitTestDatabase& database); } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_DialogDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_DialogDetector.cpp index efa3e9399a..3fd56cc695 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_DialogDetector.cpp @@ -474,6 +474,30 @@ bool LightBlueDialogDetector::detect(const ImageViewRGB32& screen){ +class Test_BlueDialogDetector : public UnitTest{ +public: + Test_BlueDialogDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonPLZA::BlueDialogDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + DummyVideoOverlay overlay; + BlueDialogDetector detector(COLOR_RED, &overlay); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + + class Test_FlatWhiteDialogDetector : public UnitTest{ public: Test_FlatWhiteDialogDetector( @@ -497,9 +521,34 @@ class Test_FlatWhiteDialogDetector : public UnitTest{ bool m_expected; }; +class Test_TransparentBattleDialogDetector : public UnitTest{ +public: + Test_TransparentBattleDialogDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonPLZA::TransparentBattleDialogDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + DummyVideoOverlay overlay; + TransparentBattleDialogDetector detector(COLOR_RED, &overlay); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; void add_tests_DialogDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/BlueDialogDetector/mac_canFastTravel_False.png", false); + database.add("PokemonLZA/BlueDialogDetector/mac_cannotFastTravel_True.png", true); + database.add("PokemonLZA/BlueDialogDetector/mac_receive_tyrunt_True.png", true); database.add("PokemonLZA/FlatWhiteDialogDetector/chao_fossil_True.png", true); database.add("PokemonLZA/FlatWhiteDialogDetector/french_fossil_1_True.png", true); database.add("PokemonLZA/FlatWhiteDialogDetector/french_fossil_2_True.png", true); @@ -515,6 +564,10 @@ void add_tests_DialogDetector(UnitTestDatabase& database){ database.add("PokemonLZA/FlatWhiteDialogDetector/mac_jacinthe_pre_battle_transparent_False.png", false); database.add("PokemonLZA/FlatWhiteDialogDetector/mac_restaurant_True.png", true); database.add("PokemonLZA/FlatWhiteDialogDetector/mac_seven_wonder_leftout_True.png", true); + database.add("PokemonLZA/TransparentBattleDialogDetector/french_jacinthe_True.png", true); + database.add("PokemonLZA/TransparentBattleDialogDetector/mac_jacinthe_False.png", false); + database.add("PokemonLZA/TransparentBattleDialogDetector/mac_jacinthe_overworld_False.png", false); + database.add("PokemonLZA/TransparentBattleDialogDetector/mac_jacinthe_True.png", true); } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.cpp index cdda4731ca..d572902cd0 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.cpp @@ -4,14 +4,15 @@ * */ -#include "CommonFramework/Globals.h" +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/Language.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/Tools/GlobalThreadPools.h" -#include "CommonTools/OCR/OCR_NumberReader.h" -#include "CommonTools/OCR/OCR_Routines.h" -#include "PokemonLZA_HyperspaceCalorieDetector.h" +#include "CommonTools/OCR/OCR_NumberReader.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include "PokemonLZA_HyperspaceCalorieDetector.h" +#include "Tests/TestUtils.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -85,7 +86,7 @@ void HyperspaceCalorieLimitWatcher::make_overlays(VideoOverlaySet& items) const{ HyperspaceCalorieDetector::make_overlays(items); } -bool HyperspaceCalorieLimitWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ +bool HyperspaceCalorieLimitWatcher::process_frame(const ImageViewRGB32& frame, WallClock timestamp){ bool detected = detect(frame); if (!detected){ m_start_of_detection = WallClock::min(); @@ -124,11 +125,51 @@ bool HyperspaceCalorieLimitWatcher::process_frame(const ImageViewRGB32& frame, W return true; } - return false; -} - - - -} + return false; +} + + +class Test_HyperspaceCalorieDetector : public UnitTest{ +public: + Test_HyperspaceCalorieDetector(const std::string& image, int expected_calorie) + : UnitTest("PokemonPLZA::HyperspaceCalorieDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected_calorie(expected_calorie) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + if (m_expected_calorie <= 0 || m_expected_calorie > 9999){ + return "Error: expected calorie must be in range [1, 9999]."; + } + + HyperspaceCalorieDetector detector(global_logger_command_line()); + ImageRGB32 image(m_image); + if (!detector.detect(image)){ + return "Error: detector failed to detect calorie number in image."; + } + + TEST_RESULT_EQUAL((int)detector.calorie_number(), m_expected_calorie); + return true; + } + +private: + std::string m_image; + int m_expected_calorie; +}; + + +void add_tests_HyperspaceCalorieDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/HyperspaceCalorieDetector/dhruv_1000.jpg", 1000); + database.add("PokemonLZA/HyperspaceCalorieDetector/dhruv_874.jpg", 874); + database.add("PokemonLZA/HyperspaceCalorieDetector/dhruv_902.jpg", 902); + database.add("PokemonLZA/HyperspaceCalorieDetector/dhruv_915.jpg", 915); + database.add("PokemonLZA/HyperspaceCalorieDetector/dhruv_923.jpg", 923); + database.add("PokemonLZA/HyperspaceCalorieDetector/dhruv_956.jpg", 956); + database.add("PokemonLZA/HyperspaceCalorieDetector/dhruv_999.jpg", 999); +} + + + +} } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h index 56439e23c9..d66cb730ae 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h @@ -5,10 +5,11 @@ */ #ifndef PokemonAutomation_PokemonLZA_HyperspaceCalorieDetector_H -#define PokemonAutomation_PokemonLZA_HyperspaceCalorieDetector_H - - -#include "CommonFramework/ImageTools/ImageBoxes.h" +#define PokemonAutomation_PokemonLZA_HyperspaceCalorieDetector_H + + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonTools/VisualDetector.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -63,7 +64,7 @@ class HyperspaceCalorieWatcher : public HyperspaceCalorieDetector, public Visual // It has some robustness logic to ensure occasional wrong number OCR won't // lead to an early stop of the inference session and also saves potential // screenshots of wrong OCR to debug folder. -class HyperspaceCalorieLimitWatcher : public HyperspaceCalorieDetector, public VisualInferenceCallback{ +class HyperspaceCalorieLimitWatcher : public HyperspaceCalorieDetector, public VisualInferenceCallback{ public: HyperspaceCalorieLimitWatcher(Logger& logger, uint16_t calorie_limit); @@ -77,11 +78,14 @@ class HyperspaceCalorieLimitWatcher : public HyperspaceCalorieDetector, public V WallClock m_start_of_detection = WallClock::min(); std::list> m_last_calorie_images; -}; - - - - +}; + + +void add_tests_HyperspaceCalorieDetector(UnitTestDatabase& database); + + + + } } } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.cpp index a9b7725048..8772e1031a 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.cpp @@ -4,11 +4,12 @@ * */ +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/VideoPipeline/VideoOverlay.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "CommonTools/Images/SolidColorTest.h" #include "PokemonLZA_MainMenuDetector.h" +#include "Tests/TestUtils.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -29,7 +30,34 @@ bool MainMenuDetector::detect(const ImageViewRGB32& screen){ return m_right_arrow_button.detect(screen) && m_b_button.detect(screen); } +class Test_MainMenuDetector : public UnitTest{ +public: + Test_MainMenuDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonPLZA::MainMenuDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + DummyVideoOverlay overlay; + MainMenuDetector detector(COLOR_RED, &overlay); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_MainMenuDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/MainMenuDetector/french_main_menu_True.png", true); + database.add("PokemonLZA/MainMenuDetector/mac_main_menu_True.png", true); +} } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.h index 725dc1dcda..58d7fcdd35 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_MainMenuDetector.h @@ -47,6 +47,7 @@ class MainMenuWatcher : public DetectorToFinder{ }; +void add_tests_MainMenuDetector(UnitTestDatabase& database); } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.cpp b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.cpp index 553cea5288..9710a6b76f 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.cpp @@ -4,13 +4,15 @@ * */ -#include "CommonFramework/StaticGlobals.h" -#include "CommonFramework/Exceptions/FatalProgramException.h" -#include "CommonFramework/ImageTools/ImageStats.h" -#include "CommonFramework/VideoPipeline/VideoOverlay.h" -#include "CommonFramework/VideoPipeline/VideoFeed.h" -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" -#include "PokemonLZA_OverworldPartySelectionDetector.h" +#include "CommonFramework/StaticGlobals.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/Exceptions/FatalProgramException.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/VideoPipeline/VideoOverlay.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "PokemonLZA_OverworldPartySelectionDetector.h" +#include "Tests/TestUtils.h" #include using std::cout; @@ -127,14 +129,70 @@ uint8_t OverworldPartySelectionDetector::selected_party_idx() const{ } -bool OverworldPartySelectionWatcher::process_frame(const VideoSnapshot& frame){ - const bool detected = process_frame(*frame.frame, frame.timestamp); - if (detected){ - m_last_detected_frame = frame.frame; - } - return detected; -} - -} -} -} +bool OverworldPartySelectionWatcher::process_frame(const VideoSnapshot& frame){ + const bool detected = process_frame(*frame.frame, frame.timestamp); + if (detected){ + m_last_detected_frame = frame.frame; + } + return detected; +} + + +class Test_OverworldPartySelectionDetector : public UnitTest{ +public: + Test_OverworldPartySelectionDetector( + const std::string& image, + int expected_up_idx, + int expected_down_idx + ) + : UnitTest("PokemonPLZA::OverworldPartySelectionDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected_up_idx(expected_up_idx) + , m_expected_down_idx(expected_down_idx) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + if (m_expected_up_idx < 0 || m_expected_up_idx > 6){ + return "Error: dpad_up_idx must be between 0 and 6."; + } + if (m_expected_down_idx < 0 || m_expected_down_idx > 6){ + return "Error: dpad_down_idx must be between 0 and 6."; + } + + DummyVideoOverlay overlay; + OverworldPartySelectionDetector detector(COLOR_RED, &overlay); + detector.set_debug_mode(true); + ImageRGB32 image(m_image); + bool detected = detector.detect(image); + + if (m_expected_up_idx == 6 && m_expected_down_idx == 6){ + return detected == false; + } + + if (!detected){ + return "Error: detector failed to detect party selection screen."; + } + + TEST_RESULT_COMPONENT_EQUAL((int)detector.dpad_up_idx(), m_expected_up_idx, "dpad_up_idx"); + TEST_RESULT_COMPONENT_EQUAL((int)detector.dpad_down_idx(), m_expected_down_idx, "dpad_down_idx"); + return true; + } + +private: + std::string m_image; + int m_expected_up_idx; + int m_expected_down_idx; +}; + + +void add_tests_OverworldPartySelectionDetector(UnitTestDatabase& database){ + database.add("PokemonLZA/OverworldPartySelectionDetector/mac_zone_10_0_5.png", 0, 5); + database.add("PokemonLZA/OverworldPartySelectionDetector/mac_zone_10_1_3.png", 1, 3); + database.add("PokemonLZA/OverworldPartySelectionDetector/mac_zone_10_2_6.png", 2, 6); + database.add("PokemonLZA/OverworldPartySelectionDetector/mac_zone_10_6_2.png", 6, 2); + database.add("PokemonLZA/OverworldPartySelectionDetector/mac_zone_10_6_5.png", 6, 5); +} + +} +} +} diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h index 0544521eba..a82cd1e587 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h @@ -5,9 +5,10 @@ */ #ifndef PokemonAutomation_PokemonLZA_OverworldPartySelectionDetector_H -#define PokemonAutomation_PokemonLZA_OverworldPartySelectionDetector_H - -#include "Common/Cpp/Color.h" +#define PokemonAutomation_PokemonLZA_OverworldPartySelectionDetector_H + +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Common/Cpp/Color.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" @@ -108,7 +109,7 @@ class OverworldPartySelectionWatcher : public DetectorToFinder m_last_detected_frame; }; -class OverworldPartySelectionOverWatcher : public DetectorToFinder{ +class OverworldPartySelectionOverWatcher : public DetectorToFinder{ public: OverworldPartySelectionOverWatcher( Color color = COLOR_WHITE, @@ -117,11 +118,14 @@ class OverworldPartySelectionOverWatcher : public DetectorToFinder("PokemonLZA/SelectionArrowDetector/french_1_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_2_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_3_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_4_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_5_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_6_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_7_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_8_Fossil_True.png", SelectionArrowType::RIGHT, true); + database.add("PokemonLZA/SelectionArrowDetector/french_9_Fossil_True.png", SelectionArrowType::RIGHT, true); +} } diff --git a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_SelectionArrowDetector.h b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_SelectionArrowDetector.h index 549db653a8..c9a56102e1 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_SelectionArrowDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_SelectionArrowDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonLZA_SelectionArrowDetector_H #include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/VisualDetector.h" @@ -66,6 +67,7 @@ class SelectionArrowWatcher : public DetectorToFinder{ +void add_tests_SelectionArrowDetector(UnitTestDatabase& database); } diff --git a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Tests.cpp b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Tests.cpp index 3974da53ef..709216b9c1 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Tests.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Tests.cpp @@ -4,20 +4,43 @@ * */ -#include "Common/Cpp/TestRunners/UnitTestDatabase.h" -#include "Inference/PokemonLZA_DialogDetector.h" - -namespace PokemonAutomation{ -namespace NintendoSwitch{ +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Inference/Donuts/PokemonLZA_DonutBerriesDetector.h" +#include "Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h" +#include "Inference/PokemonLZA_AlertEyeDetector.h" +#include "Inference/Boxes/PokemonLZA_BoxDetection.h" +#include "Inference/PokemonLZA_ButtonDetector.h" +#include "Inference/Map/PokemonLZA_DirectionArrowDetector.h" +#include "Inference/PokemonLZA_DialogDetector.h" +#include "Inference/PokemonLZA_HyperspaceCalorieDetector.h" +#include "Inference/PokemonLZA_MainMenuDetector.h" +#include "Inference/Map/PokemonLZA_MapDetector.h" +#include "Inference/Map/PokemonLZA_MapIconDetector.h" +#include "Inference/PokemonLZA_OverworldPartySelectionDetector.h" +#include "Inference/PokemonLZA_SelectionArrowDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ namespace PokemonLZA{ -void add_tests(UnitTestDatabase& database){ - add_tests_DialogDetector(database); - - -} +void add_tests(UnitTestDatabase& database){ + add_tests_AlertEyeDetector(database); + add_tests_BoxCellInfoDetector(database); + add_tests_ButtonDetector(database); + add_tests_DirectionArrowDetector(database); + add_tests_DialogDetector(database); + add_tests_DonutBerriesReader(database); + add_tests_FlavorPowerScreenDetector(database); + add_tests_HyperspaceCalorieDetector(database); + add_tests_MainMenuDetector(database); + add_tests_MapDetector(database); + add_tests_MapIconDetector(database); + add_tests_OverworldPartySelectionDetector(database); + add_tests_SelectionArrowDetector(database); + +} diff --git a/SerialPrograms/Source/Tests/PokemonLZA_Tests_Old.cpp b/SerialPrograms/Source/Tests/PokemonLZA_Tests_Old.cpp deleted file mode 100644 index 790c2b81a1..0000000000 --- a/SerialPrograms/Source/Tests/PokemonLZA_Tests_Old.cpp +++ /dev/null @@ -1,860 +0,0 @@ -/* PokemonLZA Tests -* -* From: https://github.com/PokemonAutomation/ -* -*/ - -#include "Common/Cpp/Filesystem.h" -#include "CommonFramework/StaticGlobals.h" -#include "CommonFramework/Logging/Logger.h" -#include "PokemonLZA/Inference/Donuts/PokemonLZA_DonutBerriesDetector.h" -#include "PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerDetector.h" -#include "PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_DialogDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_ButtonDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_SelectionArrowDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_AlertEyeDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_MainMenuDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h" -#include "PokemonLZA/Inference/Boxes/PokemonLZA_BoxDetection.h" -#include "PokemonLZA/Inference/Boxes/PokemonLZA_BoxInfoDetector.h" -#include "PokemonLZA/Inference/Map/PokemonLZA_MapIconDetector.h" -#include "PokemonLZA/Inference/Map/PokemonLZA_MapDetector.h" -#include "PokemonLZA/Inference/Map/PokemonLZA_DirectionArrowDetector.h" -#include "PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "PokemonLZA_Tests_Old.h" -#include "TestUtils.h" -#include -#include -#include -//#include -using std::cout; -using std::cerr; -using std::endl; - - -namespace PokemonAutomation{ - - -using namespace NintendoSwitch; -using namespace NintendoSwitch::PokemonLZA; - - - - -int test_pokemonLZA_BlueDialogDetector(const ImageViewRGB32& image, bool target){ - auto overlay = DummyVideoOverlay(); - BlueDialogDetector detector(COLOR_RED, &overlay); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_pokemonLZA_TransparentBattleDialogDetector(const ImageViewRGB32& image, bool target){ - auto overlay = DummyVideoOverlay(); - TransparentBattleDialogDetector detector(COLOR_RED, &overlay); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_pokemonLZA_ButtonDetector(const ImageViewRGB32& image, const std::vector& words){ - // two words: