From 52f05829b05e2146f01871d59de5324f31d1e7cb Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Fri, 28 Aug 2026 20:20:02 -0500 Subject: [PATCH 1/2] Move Alpha, Shiny, and Text dections to their own files. This way they can be reused and tested. --- .../Inference/PokemonHome_AlphaDetector.cpp | 65 ++++++ .../Inference/PokemonHome_AlphaDetector.h | 41 ++++ .../Inference/PokemonHome_ShinyDetector.cpp | 65 ++++++ .../Inference/PokemonHome_ShinyDetector.h | 41 ++++ .../Inference/PokemonHome_SummaryReader.cpp | 212 ++++++++++++++++++ .../Inference/PokemonHome_SummaryReader.h | 49 ++++ .../Source/PokemonHome/PokemonHome_Tests.cpp | 8 +- .../Programs/PokemonHome_BoxNavigation.cpp | 95 ++------ SerialPrograms/cmake/SourceFiles.cmake | 6 + 9 files changed, 503 insertions(+), 79 deletions(-) create mode 100644 SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.cpp create mode 100644 SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.h create mode 100644 SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.cpp create mode 100644 SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.h create mode 100644 SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp create mode 100644 SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.h diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.cpp new file mode 100644 index 0000000000..9e6ca8be01 --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.cpp @@ -0,0 +1,65 @@ +/* Alpha Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "Tests/TestUtils.h" +#include "PokemonHome_AlphaDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +AlphaDetector::AlphaDetector(Color color, VideoOverlay* overlay) + : m_color(color) + , m_overlay(overlay) + , m_box(0.787, 0.095, 0.024, 0.046) +{} + +void AlphaDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_box); +} + +bool AlphaDetector::detect(const ImageViewRGB32& screen){ + const bool found = image_stddev(extract_box_reference(screen, m_box)).sum() > 40; + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, m_box, COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + return found; +} + +class Test_AlphaDetector : public UnitTest{ +public: + Test_AlphaDetector(const std::string& image, bool expected) + : UnitTest("PokemonHome::AlphaDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + AlphaDetector detector; + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_AlphaDetector(UnitTestDatabase& database){ +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.h b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.h new file mode 100644 index 0000000000..8630bc3ac2 --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_AlphaDetector.h @@ -0,0 +1,41 @@ +/* Alpha Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonHome_AlphaDetector_H +#define PokemonAutomation_PokemonHome_AlphaDetector_H + +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +class AlphaDetector : public StaticScreenDetector{ +public: + AlphaDetector(Color color = COLOR_RED, VideoOverlay* overlay = nullptr); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + VideoOverlay* m_overlay; + ImageFloatBox m_box; + std::optional m_last_detected_box; +}; + +void add_tests_AlphaDetector(UnitTestDatabase& database); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.cpp new file mode 100644 index 0000000000..ac22064f3c --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.cpp @@ -0,0 +1,65 @@ +/* Shiny Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "Tests/TestUtils.h" +#include "PokemonHome_ShinyDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +ShinyDetector::ShinyDetector(Color color, VideoOverlay* overlay) + : m_color(color) + , m_overlay(overlay) + , m_box(0.702, 0.09, 0.04, 0.06) +{} + +void ShinyDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_box); +} + +bool ShinyDetector::detect(const ImageViewRGB32& screen){ + const bool found = image_stddev(extract_box_reference(screen, m_box)).sum() > 30; + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, m_box, COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + return found; +} + +class Test_ShinyDetector : public UnitTest{ +public: + Test_ShinyDetector(const std::string& image, bool expected) + : UnitTest("PokemonHome::ShinyDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + ShinyDetector detector; + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + +void add_tests_ShinyDetector(UnitTestDatabase& database){ +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.h b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.h new file mode 100644 index 0000000000..4610f9b689 --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_ShinyDetector.h @@ -0,0 +1,41 @@ +/* Shiny Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonHome_ShinyDetector_H +#define PokemonAutomation_PokemonHome_ShinyDetector_H + +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +class ShinyDetector : public StaticScreenDetector{ +public: + ShinyDetector(Color color = COLOR_RED, VideoOverlay* overlay = nullptr); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + Color m_color; + VideoOverlay* m_overlay; + ImageFloatBox m_box; + std::optional m_last_detected_box; +}; + +void add_tests_ShinyDetector(UnitTestDatabase& database); + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp new file mode 100644 index 0000000000..c892517781 --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp @@ -0,0 +1,212 @@ +/* Pokemon Home Summary Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "Common/Cpp/CancellableScope.h" +#include "Common/Cpp/Strings/Unicode.h" +#include "CommonFramework/GlobalAutoPaths.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/OCR/OCR_NumberReader.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include "CommonTools/OCR/OCR_StringNormalization.h" +#include "PokemonHome_SummaryReader.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + +namespace{ + +const std::vector>& white_number_filters(){ + static const std::vector> filters = { + {0xff808080, 0xffffffff}, + {0xff909090, 0xffffffff}, + }; + return filters; +} + +const std::vector>& gray_number_filters(){ + static const std::vector> filters = { + {0xff5c6460, 0xffe1e8e4}, + }; + return filters; +} + +const std::vector& white_text_filters(){ + static const std::vector filters = []{ + std::vector ret; + for (const OCR::TextColorRange& filter : OCR::WHITE_TEXT_FILTERS()) + ret.push_back({ true, filter.mins, filter.maxs }); + return ret; + }(); + return filters; +} + +const std::vector& gray_text_filters(){ + static const std::vector filters = { + {true, 0xff5c6460, 0xffe1e8e4}, + }; + return filters; +} + +std::string read_text( + Language language, + const ImageViewRGB32& screen, const ImageFloatBox& box, + const std::vector& filters +){ + if (language == Language::None) + return ""; + + std::string best_raw; + for (auto& [image, pixel_count] : to_blackwhite_rgb32_range(extract_box_reference(screen, box), filters)){ + if (pixel_count == 0) + continue; + std::string candidate = OCR::ocr_read(language, image, OCR::PageSegMode::SINGLE_LINE); + if (!candidate.empty()){ + best_raw = candidate; + break; + } + } + return utf32_to_str(OCR::normalize_utf32(best_raw)); +} + +int read_number( + Logger& logger, const ImageViewRGB32& screen, const ImageFloatBox& box, + const std::vector>& filters +){ + return OCR::read_number_waterfill_multifilter( + logger, + GlobalThreadPools::computation_normal(), + extract_box_reference(screen, box), + filters + ); +} + +} //namespace + +SummaryReader::SummaryReader(Color color) + : m_color(color) + , m_national_dex_number_box(0.448, 0.245, 0.049, 0.04) + , m_level_box(0.546, 0.099, 0.044, 0.041) + , m_original_trainer_id_box(0.782, 0.719, 0.193, 0.046) + , m_original_trainer_name_box(0.492, 0.719, 0.165, 0.049) + , m_nature_box(0.157, 0.783, 0.212, 0.042) + , m_ability_box(0.158, 0.838, 0.213, 0.042) +{} + +void SummaryReader::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_national_dex_number_box, "national dex"); + items.add(m_color, m_level_box, "level"); + items.add(m_color, m_original_trainer_id_box, "original trainer id"); + items.add(m_color, m_original_trainer_name_box, "original trainer name"); + items.add(m_color, m_nature_box, "nature"); + items.add(m_color, m_ability_box, "ability"); +} + +int SummaryReader::read_national_dex(Logger& logger, const ImageViewRGB32& screen) const{ + return read_number(logger, screen, m_national_dex_number_box, white_number_filters()); +} + +int SummaryReader::read_original_trainer_id(Logger& logger, const ImageViewRGB32& screen) const{ + return read_number(logger, screen, m_original_trainer_id_box, white_number_filters()); +} + +std::string SummaryReader::read_original_trainer_name(Language language, const ImageViewRGB32& screen) const{ + return read_text(language, screen, m_original_trainer_name_box, white_text_filters()); +} + +std::string SummaryReader::read_nature(Language language, const ImageViewRGB32& screen) const{ + return read_text(language, screen, m_nature_box, gray_text_filters()); +} + +std::string SummaryReader::read_ability(Language language, const ImageViewRGB32& screen) const{ + return read_text(language, screen, m_ability_box, gray_text_filters()); +} + +int SummaryReader::read_level(Logger& logger, const ImageViewRGB32& screen) const{ + return read_number(logger, screen, m_level_box, gray_number_filters()); +} + + + +class Test_SummaryReader_Numbers : public UnitTest{ +public: + Test_SummaryReader_Numbers(const std::string& image, int expected_dex, int expected_ot_id, int expected_level) + : UnitTest("PokemonHome::SummaryReader_Numbers - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected_dex(expected_dex) + , m_expected_ot_id(expected_ot_id) + , m_expected_level(expected_level) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + SummaryReader reader; + return reader.read_national_dex(logger, image) == m_expected_dex + && reader.read_original_trainer_id(logger, image) == m_expected_ot_id + && reader.read_level(logger, image) == m_expected_level; + } + +private: + std::string m_image; + int m_expected_dex; + int m_expected_ot_id; + int m_expected_level; +}; + +class Test_SummaryReader_Text : public UnitTest{ +public: + Test_SummaryReader_Text(const std::string& image, std::string expected_nature, std::string expected_ability, Language language) + : UnitTest("PokemonHome::SummaryReader_Text - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected_nature(expected_nature) + , m_expected_ability(expected_ability) + , m_language(language) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + SummaryReader reader; + return reader.read_nature(m_language, image) == m_expected_nature + && reader.read_ability(m_language, image) == m_expected_ability; + } + +private: + std::string m_image; + std::string m_expected_nature; + std::string m_expected_ability; + Language m_language; +}; + +class Test_SummaryReader_OtName : public UnitTest{ +public: + Test_SummaryReader_OtName(const std::string& image, std::string expected_ot_name, Language language) + : UnitTest("PokemonHome::SummaryReader_OtName - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected_ot_name(expected_ot_name) + , m_language(language) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + SummaryReader reader; + return reader.read_original_trainer_name(m_language, image) == m_expected_ot_name; + } + +private: + std::string m_image; + std::string m_expected_ot_name; + Language m_language; +}; + +void add_tests_SummaryReader(UnitTestDatabase& database){} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.h b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.h new file mode 100644 index 0000000000..e37d0c5970 --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.h @@ -0,0 +1,49 @@ +/* Pokemon Home Summary Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonHome_SummaryReader_H +#define PokemonAutomation_PokemonHome_SummaryReader_H + +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/Language.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "Common/Cpp/Logging/AbstractLogger.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + +class SummaryReader{ +public: + SummaryReader(Color color = COLOR_RED); + + void make_overlays(VideoOverlaySet& items) const; + + int read_national_dex(Logger& logger, const ImageViewRGB32& screen) const; + int read_original_trainer_id(Logger& logger, const ImageViewRGB32& screen) const; + std::string read_original_trainer_name(Language language, const ImageViewRGB32& screen) const; + std::string read_nature(Language language, const ImageViewRGB32& screen) const; + std::string read_ability(Language language, const ImageViewRGB32& screen) const; + int read_level(Logger& logger, const ImageViewRGB32& screen) const; + +private: + Color m_color; + ImageFloatBox m_national_dex_number_box; + ImageFloatBox m_level_box; + ImageFloatBox m_original_trainer_id_box; + ImageFloatBox m_original_trainer_name_box; + ImageFloatBox m_nature_box; + ImageFloatBox m_ability_box; +}; + +void add_tests_SummaryReader(UnitTestDatabase& database); + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonHome/PokemonHome_Tests.cpp b/SerialPrograms/Source/PokemonHome/PokemonHome_Tests.cpp index b2d1641242..0ac5e1bd97 100644 --- a/SerialPrograms/Source/PokemonHome/PokemonHome_Tests.cpp +++ b/SerialPrograms/Source/PokemonHome/PokemonHome_Tests.cpp @@ -5,6 +5,7 @@ */ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Inference/PokemonHome_AlphaDetector.h" #include "Inference/PokemonHome_BallReader.h" #include "Inference/PokemonHome_BoxGenderDetector.h" #include "Inference/PokemonHome_BoxViewDetector.h" @@ -12,6 +13,8 @@ #include "Inference/PokemonHome_GigantamaxDetector.h" #include "Inference/PokemonHome_OriginMarkReader.h" #include "Inference/PokemonHome_SelectionArrowDetector.h" +#include "Inference/PokemonHome_ShinyDetector.h" +#include "Inference/PokemonHome_SummaryReader.h" #include "Inference/PokemonHome_SummaryScreenDetector.h" #include "Inference/PokemonHome_TeraTypeReader.h" #include "PokemonHome_Tests.h" @@ -22,13 +25,16 @@ namespace PokemonHome{ void add_tests(UnitTestDatabase& database){ + add_tests_AlphaDetector(database); add_tests_BallReader(database); - add_tests_ButtonDetector(database); add_tests_BoxGenderDetector(database); add_tests_BoxViewDetector(database); + add_tests_ButtonDetector(database); add_tests_GigantamaxDetector(database); add_tests_OriginMarkReader(database); add_tests_SelectionArrowDetector(database); + add_tests_ShinyDetector(database); + add_tests_SummaryReader(database); add_tests_SummaryScreenDetector(database); add_tests_TeraTypeReader(database); } diff --git a/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxNavigation.cpp b/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxNavigation.cpp index 215601d3ff..9af9a160ae 100644 --- a/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxNavigation.cpp +++ b/SerialPrograms/Source/PokemonHome/Programs/PokemonHome_BoxNavigation.cpp @@ -8,28 +8,26 @@ #include #include #include -#include "Common/Cpp/Strings/Unicode.h" #include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/Notifications/ProgramInfo.h" -#include "CommonFramework/Tools/GlobalThreadPools.h" #include "CommonTools/Images/ImageFilter.h" -#include "CommonTools/OCR/OCR_Routines.h" -#include "CommonTools/OCR/OCR_StringNormalization.h" #include "CommonFramework/Tools/ErrorDumper.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Async/InferenceRoutines.h" -#include "CommonTools/OCR/OCR_NumberReader.h" #include "CommonTools/VisualDetectors/FrozenImageDetector.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "Pokemon/Resources/Pokemon_PokemonSlugs.h" #include "Pokemon/Inference/Pokemon_TypeReader.h" #include "PokemonHome/Inference/PokemonHome_BallReader.h" +#include "PokemonHome/Inference/PokemonHome_AlphaDetector.h" #include "PokemonHome/Inference/PokemonHome_BoxGenderDetector.h" #include "PokemonHome/Inference/PokemonHome_GigantamaxDetector.h" #include "PokemonHome/Inference/PokemonHome_OriginMarkReader.h" +#include "PokemonHome/Inference/PokemonHome_ShinyDetector.h" +#include "PokemonHome/Inference/PokemonHome_SummaryReader.h" #include "PokemonHome/Inference/PokemonHome_TeraTypeReader.h" #include "PokemonHome_BoxNavigation.h" @@ -227,33 +225,23 @@ void read_summary_screen( ) { VideoOverlaySet video_overlay_set(env.console); - ImageFloatBox national_dex_number_box(0.448, 0.245, 0.049, 0.04); //pokemon national dex number pos - ImageFloatBox shiny_symbol_box(0.702, 0.09, 0.04, 0.06); // shiny symbol pos ImageFloatBox gmax_tera_symbol_box(0.463, 0.09, 0.04, 0.06); // gmax OR tera symbol pos ImageFloatBox origin_symbol_box(0.617, 0.084, 0.044, 0.069); // origin symbol pos ImageFloatBox pokemon_box(0.69, 0.18, 0.28, 0.46); // pokemon render pos - ImageFloatBox level_box(0.546, 0.099, 0.044, 0.041); // Level - ImageFloatBox ot_id_box(0.782, 0.719, 0.193, 0.046); // OT ID - ImageFloatBox ot_box(0.492, 0.719, 0.165, 0.049); // OT - ImageFloatBox nature_box(0.157, 0.783, 0.212, 0.042); // Nature - ImageFloatBox ability_box(0.158, 0.838, 0.213, 0.042); // Ability - ImageFloatBox alpha_box(0.787, 0.095, 0.024, 0.046); // Alpha symbol ImageFloatBox type_box(0.615, 0.240, 0.071, 0.057); // Type symbols - video_overlay_set.add(COLOR_WHITE, national_dex_number_box); - video_overlay_set.add(COLOR_BLUE, shiny_symbol_box); video_overlay_set.add(COLOR_RED, gmax_tera_symbol_box); - video_overlay_set.add(COLOR_RED, alpha_box); + ShinyDetector shiny_detector(COLOR_RED, &env.console.overlay()); + AlphaDetector alpha_detector(COLOR_RED, &env.console.overlay()); + shiny_detector.make_overlays(video_overlay_set); + alpha_detector.make_overlays(video_overlay_set); video_overlay_set.add(COLOR_DARKGREEN, origin_symbol_box); video_overlay_set.add(COLOR_DARK_BLUE, pokemon_box); - video_overlay_set.add(COLOR_RED, level_box); - video_overlay_set.add(COLOR_RED, ot_id_box); - video_overlay_set.add(COLOR_RED, ot_box); - video_overlay_set.add(COLOR_RED, nature_box); - video_overlay_set.add(COLOR_RED, ability_box); video_overlay_set.add(COLOR_RED, type_box); BoxGenderDetector::make_overlays(video_overlay_set); + SummaryReader summary_reader(COLOR_RED); + summary_reader.make_overlays(video_overlay_set); // Wait for the summary screen transition to end @@ -263,20 +251,7 @@ void read_summary_screen( VideoSnapshot screen = env.console.video().snapshot(); - const std::vector> white_number_filters = { - {0xff808080, 0xffffffff}, - {0xff909090, 0xffffffff}, - }; - - const int dex_number = OCR::read_number_waterfill_multifilter( - env.console, - GlobalThreadPools::computation_normal(), - extract_box_reference(screen, national_dex_number_box), - white_number_filters, - true, - true, - 20 - ); + const int dex_number = summary_reader.read_national_dex(env.console, screen); if (dex_number <= 0 || dex_number > static_cast(NATIONAL_DEX_SLUGS().size())) { OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, @@ -284,23 +259,19 @@ void read_summary_screen( env.console ); } - cur_pokemon_info.dex_number = (uint16_t)dex_number; + cur_pokemon_info.dex_number = static_cast(dex_number); cur_pokemon_info.name_slug = NATIONAL_DEX_SLUGS()[dex_number - 1]; - const int shiny_stddev_value = (int)image_stddev(extract_box_reference(screen, shiny_symbol_box)).sum(); - const bool is_shiny = shiny_stddev_value > 30; + const bool is_shiny = shiny_detector.detect(screen); cur_pokemon_info.shiny = is_shiny; - env.console.log("Shiny detection stddev:" + std::to_string(shiny_stddev_value) + " is shiny:" + std::to_string(is_shiny)); GigantamaxDetector gmax_detector(COLOR_RED, &env.console.overlay(), gmax_tera_symbol_box); cur_pokemon_info.gmax = gmax_detector.detect(screen); cur_pokemon_info.tera_type = read_pokemon_tera_type(screen, gmax_tera_symbol_box); - const int alpha_stddev_value = (int)image_stddev(extract_box_reference(screen, alpha_box)).sum(); - const bool is_alpha = alpha_stddev_value > 40; + const bool is_alpha = alpha_detector.detect(screen); cur_pokemon_info.alpha = is_alpha; - env.console.log("Alpha detection stddev:" + std::to_string(alpha_stddev_value) + " is alpha:" + std::to_string(is_alpha)); BallReader ball_reader(env.console); cur_pokemon_info.ball_slug = ball_reader.read_ball(screen); @@ -309,12 +280,7 @@ void read_summary_screen( env.console.log("Gender: " + gender_to_string(gender), COLOR_GREEN); cur_pokemon_info.gender = gender; - const int ot_id = OCR::read_number_waterfill_multifilter( - env.console, - GlobalThreadPools::computation_normal(), - extract_box_reference(screen, ot_id_box), - white_number_filters - ); + const int ot_id = summary_reader.read_original_trainer_id(env.console, screen); if (ot_id < 0 || ot_id > 999'999) { dump_image(env.console, ProgramInfo(), "ReadSummary_OT", screen); } @@ -325,32 +291,9 @@ void read_summary_screen( cur_pokemon_info.primary_type = primary_type; cur_pokemon_info.secondary_type = secondary_type; - if (ot_name_language != Language::None){ - const std::vector& text_filters = OCR::WHITE_TEXT_FILTERS(); - std::vector bw; - bw.reserve(text_filters.size()); - for (const auto& f : text_filters){ - bw.push_back({ true, f.mins, f.maxs }); - } - - auto filtered_images = to_blackwhite_rgb32_range(extract_box_reference(screen, ot_box), bw); - - std::string best_raw; - for (auto& [img, px_count] : filtered_images){ - if (px_count == 0){ - continue; - } - std::string candidate = OCR::ocr_read(ot_name_language, img, OCR::PageSegMode::SINGLE_LINE); - if (!candidate.empty() && best_raw.empty()){ - best_raw = candidate; - } - } - - env.log("Raw trainer name: " + best_raw); - std::string normalized = utf32_to_str(OCR::normalize_utf32(best_raw)); - env.log("Normalized trainer name: " + normalized); - cur_pokemon_info.ot_name = normalized; - } + cur_pokemon_info.ot_name = summary_reader.read_original_trainer_name( + ot_name_language, screen + ); cur_pokemon_info.origin_mark = OriginMarkReader().read_mark(screen, origin_symbol_box); @@ -359,10 +302,6 @@ void read_summary_screen( // NOTE edit when adding new struct members (detections go here likely) - // level_box - // nature_box - // ability_box - // Press button R to go to next summary screen pbf_press_button(context, BUTTON_R, 80ms, 300ms); context.wait_for_all_requests(); diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 4a81f692d9..f6da07157c 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1713,6 +1713,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h Source/PokemonFRLG/Resources/PokemonFRLG_PokemonSprites.cpp Source/PokemonFRLG/Resources/PokemonFRLG_PokemonSprites.h + Source/PokemonHome/Inference/PokemonHome_AlphaDetector.cpp + Source/PokemonHome/Inference/PokemonHome_AlphaDetector.h Source/PokemonHome/Inference/PokemonHome_BallReader.cpp Source/PokemonHome/Inference/PokemonHome_BallReader.h Source/PokemonHome/Inference/PokemonHome_BoxGenderDetector.cpp @@ -1727,6 +1729,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.h Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.cpp Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.h + Source/PokemonHome/Inference/PokemonHome_ShinyDetector.cpp + Source/PokemonHome/Inference/PokemonHome_ShinyDetector.h + Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp + Source/PokemonHome/Inference/PokemonHome_SummaryReader.h Source/PokemonHome/Inference/PokemonHome_SummaryScreenDetector.cpp Source/PokemonHome/Inference/PokemonHome_SummaryScreenDetector.h Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.cpp From 089d17229cfe0ef6c4c7b45634eae14eb83cc167 Mon Sep 17 00:00:00 2001 From: Dalton-V Date: Fri, 28 Aug 2026 21:38:21 -0500 Subject: [PATCH 2/2] Expand text filters --- .../Inference/PokemonHome_SummaryReader.cpp | 18 ++++--- .../PokemonHome_ReadSummaryScreen.cpp | 51 ++++++++++++++++++- .../PokemonHome_ReadSummaryScreen.h | 5 ++ 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp index c892517781..e4a656a453 100644 --- a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SummaryReader.cpp @@ -26,13 +26,15 @@ const std::vector>& white_number_filters(){ static const std::vector> filters = { {0xff808080, 0xffffffff}, {0xff909090, 0xffffffff}, + {0xffa0a0a0, 0xffffffff}, + {0xffc0c0c0, 0xffffffff}, }; return filters; } const std::vector>& gray_number_filters(){ static const std::vector> filters = { - {0xff5c6460, 0xffe1e8e4}, + {0x323232, 0xffe1e8e4}, }; return filters; } @@ -49,7 +51,7 @@ const std::vector& white_text_filters(){ const std::vector& gray_text_filters(){ static const std::vector filters = { - {true, 0xff5c6460, 0xffe1e8e4}, + {true, 0x323232, 0xffe1e8e4}, }; return filters; } @@ -100,12 +102,12 @@ SummaryReader::SummaryReader(Color color) {} void SummaryReader::make_overlays(VideoOverlaySet& items) const{ - items.add(m_color, m_national_dex_number_box, "national dex"); - items.add(m_color, m_level_box, "level"); - items.add(m_color, m_original_trainer_id_box, "original trainer id"); - items.add(m_color, m_original_trainer_name_box, "original trainer name"); - items.add(m_color, m_nature_box, "nature"); - items.add(m_color, m_ability_box, "ability"); + items.add(m_color, m_national_dex_number_box); + items.add(m_color, m_level_box); + items.add(m_color, m_original_trainer_id_box); + items.add(m_color, m_original_trainer_name_box); + items.add(m_color, m_nature_box); + items.add(m_color, m_ability_box); } int SummaryReader::read_national_dex(Logger& logger, const ImageViewRGB32& screen) const{ diff --git a/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.cpp b/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.cpp index 056e916316..bb12ceffe9 100644 --- a/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.cpp +++ b/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.cpp @@ -6,8 +6,10 @@ #include #include +#include "CommonFramework/VideoPipeline/VideoFeed.h" #include "Pokemon/Pokemon_CollectedPokemonInfo.h" #include "Pokemon/Pokemon_Strings.h" +#include "PokemonHome/Inference/PokemonHome_SummaryReader.h" #include "PokemonHome/Programs/PokemonHome_BoxNavigation.h" #include "PokemonHome_ReadSummaryScreen.h" @@ -28,14 +30,59 @@ ReadSummaryScreen_Descriptor::ReadSummaryScreen_Descriptor() ) {} -ReadSummaryScreen::ReadSummaryScreen(){} +ReadSummaryScreen::ReadSummaryScreen() + : OT_NAME_LANGUAGE( + "Original Trainer Name Language:", + { + Language::English, + Language::Japanese, + Language::Spanish, + Language::French, + Language::German, + Language::Italian, + Language::Korean, + Language::ChineseSimplified, + Language::ChineseTraditional, + }, + LockMode::LOCK_WHILE_RUNNING, + false + ) + , HOME_LANGUAGE( + "Home Language:", + { + Language::English, + Language::Japanese, + Language::Spanish, + Language::French, + Language::German, + Language::Italian, + Language::Korean, + Language::ChineseSimplified, + Language::ChineseTraditional, + }, + LockMode::LOCK_WHILE_RUNNING + ) +{ + PA_ADD_OPTION(OT_NAME_LANGUAGE); + PA_ADD_OPTION(HOME_LANGUAGE); +} void ReadSummaryScreen::program( SingleSwitchProgramEnvironment& env, ProControllerContext& context ){ + SummaryReader reader; + VideoOverlaySet overlays(env.console); + reader.make_overlays(overlays); + + VideoSnapshot screen = env.console.video().snapshot(); + + env.log("Level: " + std::to_string(reader.read_level(env.console, screen))); + env.log("Ability: " + reader.read_ability(HOME_LANGUAGE, screen)); + env.log("Nature: " + reader.read_nature(HOME_LANGUAGE, screen)); + CollectedPokemonInfo pokemon_info{}; - read_summary_screen(env, context, pokemon_info); + read_summary_screen(env, context, pokemon_info, OT_NAME_LANGUAGE); std::optional logged_info = pokemon_info; std::ostringstream ss; diff --git a/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.h b/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.h index 345387dbbb..e1d23906fe 100644 --- a/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.h +++ b/SerialPrograms/Source/PokemonHome/Programs/TestPrograms/PokemonHome_ReadSummaryScreen.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_PokemonHome_ReadSummaryScreen_H #define PokemonAutomation_PokemonHome_ReadSummaryScreen_H +#include "CommonTools/Options/LanguageOCROption.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" namespace PokemonAutomation{ @@ -23,6 +24,10 @@ class ReadSummaryScreen : public SingleSwitchProgramInstance{ ReadSummaryScreen(); virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; + +private: + OCR::LanguageOCROption OT_NAME_LANGUAGE; + OCR::LanguageOCROption HOME_LANGUAGE; }; }