diff --git a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.cpp b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.cpp index f48304e8c7..dd9dc82c11 100644 --- a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.cpp +++ b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonTools/Images/SolidColorTest.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" @@ -72,4 +73,29 @@ bool BlackBorderDetector::detect(const ImageViewRGB32& screen){ } +class Test_BlackBorderDetector : public UnitTest{ +public: + Test_BlackBorderDetector(const std::string& image, bool expected) + : UnitTest("CommonFramework::BlackBorderDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + BlackBorderDetector detector; + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_BlackBorderDetector(UnitTestDatabase& database){ + database.add("CommonFramework/BlackBorderDetector/Dark_SV_Crystal_False.png", false); +} + + } diff --git a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.h b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.h index 623310733f..15689a1f43 100644 --- a/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.h +++ b/SerialPrograms/Source/CommonTools/VisualDetectors/BlackBorderDetector.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_CommonTools_BlackBorderDetector_H #define PokemonAutomation_CommonTools_BlackBorderDetector_H +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/VisualDetector.h" @@ -29,6 +30,9 @@ class BlackBorderDetector : public StaticScreenDetector{ }; +void add_tests_BlackBorderDetector(UnitTestDatabase& database); + + } #endif diff --git a/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp b/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp index dfbffb6d20..b3a6be8ee0 100644 --- a/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp +++ b/SerialPrograms/Source/ComputerPrograms/UnitTestRunner.cpp @@ -10,9 +10,15 @@ #include "CommonFramework/Globals.h" #include "CommonFramework/ProgramStats/StatsTracking.h" #include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonTools/VisualDetectors/BlackBorderDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.h" +#include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" #include "UnitTestRunner.h" #include "CommonTools/OCR/OCR_Tests.h" +#include "Kernels/Kernels_Tests.h" +#include "PokemonFRLG/PokemonFRLG_Tests.h" #include "PokemonHome/PokemonHome_Tests.h" #include "PokemonSwSh/PokemonSwSh_Tests.h" #include "PokemonLA/PokemonLA_Tests.h" @@ -30,7 +36,13 @@ namespace ComputerPrograms{ UnitTestDatabase make_UNIT_TESTS_ALL(){ UnitTestDatabase ret; + add_tests_BlackBorderDetector(ret); OCR::add_tests(ret); + Kernels::add_tests(ret); + NintendoSwitch::add_tests_CheckOnlineDetector(ret); + NintendoSwitch::add_tests_FailedToConnectDetector(ret); + NintendoSwitch::add_tests_UpdatePopupDetector(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/Kernels/BinaryMatrix/Kernels_BinaryMatrix_Tests.cpp b/SerialPrograms/Source/Kernels/BinaryMatrix/Kernels_BinaryMatrix_Tests.cpp new file mode 100644 index 0000000000..1f8f406bdb --- /dev/null +++ b/SerialPrograms/Source/Kernels/BinaryMatrix/Kernels_BinaryMatrix_Tests.cpp @@ -0,0 +1,426 @@ +/* BinaryMatrix Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Color.h" +#include "Common/Cpp/Time.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix.h" +#ifdef PA_AutoDispatch_arm64_20_M1 + #include "Kernels/BinaryMatrix/Kernels_BinaryMatrixTile_64x8_arm64_NEON.h" + #include "Kernels/PartialWordAccess/Kernels_PartialWordAccess_arm64_NEON.h" + #include "Kernels/Waterfill/Kernels_Waterfill_Core_64x8_arm64_NEON.h" +#endif +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix_Arch_64xH_Default.h" +#include "Kernels/BinaryImageFilters/Kernels_BinaryImage_BasicFilters.h" +#include "Kernels_BinaryMatrix_Tests.h" + +#include +#include +#include "CommonFramework/Globals.h" +using std::cout; +using std::cerr; +using std::endl; +using std::flush; + +namespace PokemonAutomation{ +namespace Kernels{ + +// Additional tests on binary matrix tile implementation +template int test_binary_matrix_tile_t(){ + size_t num_iters = 100000; + size_t sum = 0; + WallClock time_start, time_end; + size_t ns = 0; + double ms = 0; + + Tile tile; + // BinaryTile_64x8_arm64_NEON tile; + // BinaryTile_64xH_Default<8, BinaryMatrixType::arm64x8_x64_NEON> tile; + for (size_t height = 0; height < tile.HEIGHT; ++height){ + for (size_t width = 0; width < tile.WIDTH; ++width){ + tile.set_zero(); + tile.set_ones(width, height); + for (size_t y = 0; y < tile.HEIGHT; ++y){ + for (size_t x = 0; x < tile.WIDTH; ++x){ + bool gt = y < height && x < width; + if (gt != tile.get_bit(x, y)){ + cout << "Tile::set_ones(width = " << width << ", height = " << height << "), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be " << gt << endl; + return 1; + } + } + } + } + } + + sum = 0; + time_start = current_time(); + for (size_t i = 0; i < num_iters; ++i){ + tile.set_ones(32, 4); + sum += tile.row(0) + tile.row(1) + tile.row(2) + tile.row(3); + } + time_end = current_time(); + ns = std::chrono::duration_cast(time_end - time_start).count(); + ms = ns / 1000000.; + cout << "Execution enforcer: " << sum << endl; + cout << "Running " << num_iters << " iters, Tile::set_ones() time: " << ms / num_iters << " ms" << endl; + + for (size_t height = 0; height < tile.HEIGHT; ++height){ + for (size_t width = 0; width < tile.WIDTH; ++width){ + tile.set_ones(); + tile.clear_padding(width, height); + for (size_t y = 0; y < tile.HEIGHT; ++y){ + for (size_t x = 0; x < tile.WIDTH; ++x){ + bool gt = y < height && x < width; + if (gt != tile.get_bit(x, y)){ + cout << "Tile::clear_padding(width = " << width << ", height = " << height << "), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be " << gt << endl; + return 1; + } + } + } + } + } + sum = 0; + time_start = current_time(); + for (size_t i = 0; i < num_iters; ++i){ + tile.clear_padding(32, 4); + sum += tile.row(0) + tile.row(1) + tile.row(2) + tile.row(3); + } + time_end = current_time(); + ns = std::chrono::duration_cast(time_end - time_start).count(); + ms = ns / 1000000.; + cout << "Execution enforcer: " << sum << endl; + cout << "Running " << num_iters << " iters, Tile::clear_padding() time: " << ms / num_iters << " ms" << endl; + + tile.set_ones(35, 3); + tile.invert(); + for (size_t y = 0; y < tile.HEIGHT; ++y){ + for (size_t x = 0; x < tile.WIDTH; ++x){ + bool gt = !(x < 35 && y < 3); + if (gt != tile.get_bit(x, y)){ + cout << "Tile::invert(), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be " << gt << endl; + return 1; + } + } + } + + auto tile2 = tile; + tile.set_ones(35, 3); + tile2.set_ones(13, 6); + tile.andnot(tile2); + for (size_t y = 0; y < tile.HEIGHT; ++y){ + for (size_t x = 0; x < tile.WIDTH; ++x){ + bool gt = !(x < 13 && y < 6) && (x < 35 && y < 3); + if (gt != tile.get_bit(x, y)){ + cout << "Tile::andnot(), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be " << gt << endl; + return 1; + } + } + } + + tile.set_ones(35, 3); + { + uint64_t top_row = tile.top(); + for (size_t x = 0; x < tile.WIDTH; ++x){ + uint64_t bit = ((top_row >> x) & 1); + uint64_t gt = uint64_t(x < 35); + if (bit != gt){ + cout << "Tile::top(), tile (x = " << + x << "), wrong bit. Should be " << gt << endl; + return 1; + } + } + } + { + tile.set_zero(); + uint64_t& top_row = tile.top(); + top_row = 0xFFFFFFFFFFFFFFFF; + uint64_t top = tile.top(); + for (size_t x = 0; x < tile.WIDTH; ++x){ + uint64_t bit = ((top >> x) & 1); + if (bit != 1){ + cout << "& Tile::top(), tile (x = " << + x << "), wrong bit. Should be 1" << endl; + return 1; + } + } + } + { + tile.set_zero(); + uint64_t& bottom_row = tile.bottom(); + bottom_row = 0xFFFFFFFFFFFFFFFF; + uint64_t bottom = tile.bottom(); + for (size_t x = 0; x < tile.WIDTH; ++x){ + uint64_t bit = ((bottom >> x) & 1); + if (bit != 1){ + cout << "& Tile::bottom(), tile (x = " << + x << "), wrong bit. Should be 1" << endl; + return 1; + } + } + } + tile.set_zero(); + for (size_t y = 0; y < tile.HEIGHT; ++y){ + for (size_t x = 0; x < tile.WIDTH; ++x){ + tile.set_bit(x, y); + if (1 != tile.get_bit(x, y)){ + cout << "Tile::set_bit(), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be 1" << endl; + return 1; + } + tile.set_bit(x, y, 1); + if (1 != tile.get_bit(x, y)){ + cout << "Tile::set_bit(), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be 1" << endl; + return 1; + } + tile.set_bit(x, y, 0); + if (0 != tile.get_bit(x, y)){ + cout << "Tile::set_bit(), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be 0" << endl; + return 1; + } + } + } + + std::srand(0); + auto src_tile = tile; + auto dst_tile = tile; + for (size_t num_tests = 0; num_tests < 30; ++num_tests){ + src_tile.set_zero(); + dst_tile.set_zero(); + for (size_t y = 0; y < tile.HEIGHT; ++y){ + for (size_t x = 0; x < tile.WIDTH; ++x){ + src_tile.set_bit(x, y, std::rand() % 2); + dst_tile.set_bit(x, y, std::rand() % 2); + } + } + + auto test_copy_to_shift = [&](size_t shift_x, size_t shift_y, const std::string& function_name, + std::function gt_test, + std::function get_src_bit_test){ + // check against GT + for (size_t y = 0; y < tile.HEIGHT; ++y){ + for (size_t x = 0; x < tile.WIDTH; ++x){ + bool gt = false; + if (gt_test(shift_x, shift_y, x, y)){ + gt = dst_tile.get_bit(x, y) || get_src_bit_test(shift_x, shift_y, x, y); + // cout << "set the source bit with ||" << endl; + } else{ + // cout << "Use the original dest bit" << endl; + gt = dst_tile.get_bit(x, y); + } + if (tile.get_bit(x, y) != gt){ + cout << "Tile::" << function_name << "(shift_x=" << shift_x << ", shift_y=" << shift_y << "), tile (x = " << + x << ", y = " << y << "), wrong bit. Should be " << gt << endl; + cout << src_tile.dump() << endl; + cout << dst_tile.dump() << endl; + cout << tile.dump() << endl; + return 1; + } + } + } + return 0; + }; + + for (size_t shift_y = 0; shift_y < tile.HEIGHT; ++shift_y){ + for (size_t shift_x = 0; shift_x < tile.WIDTH; ++shift_x){ + tile = dst_tile; + src_tile.copy_to_shift_pp(tile, shift_x, shift_y); + auto gt_test_pp = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ + return x + shift_x < tile.WIDTH && y + shift_y < tile.HEIGHT; + }; + auto get_src_bit_test_pp = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ + return src_tile.get_bit(x + shift_x, y + shift_y); + }; + if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_pp", gt_test_pp, get_src_bit_test_pp) != 0){ + return 1; + } + + tile = dst_tile; + src_tile.copy_to_shift_np(tile, shift_x, shift_y); + auto gt_test_np = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ + return x >= shift_x && y + shift_y < tile.HEIGHT; + }; + auto get_src_bit_test_np = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ + return src_tile.get_bit(x - shift_x, y + shift_y); + }; + if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_np", gt_test_np, get_src_bit_test_np) != 0){ + return 1; + } + + tile = dst_tile; + src_tile.copy_to_shift_pn(tile, shift_x, shift_y); + auto gt_test_pn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ + return x + shift_x < tile.WIDTH && y >= shift_y; + }; + auto get_src_bit_test_pn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ + return src_tile.get_bit(x + shift_x, y - shift_y); + }; + if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_pn", gt_test_pn, get_src_bit_test_pn) != 0){ + return 1; + } + + tile = dst_tile; + src_tile.copy_to_shift_nn(tile, shift_x, shift_y); + auto gt_test_nn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ + return x >= shift_x && y >= shift_y; + }; + auto get_src_bit_test_nn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ + return src_tile.get_bit(x - shift_x, y - shift_y); + }; + if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_nn", gt_test_nn, get_src_bit_test_nn) != 0){ + return 1; + } + } + } + } + + cout << "Tile tests passed" << endl; + return 0; +} + +int test_binary_matrix_tile(){ +#ifdef PA_AutoDispatch_arm64_20_M1 + if (test_binary_matrix_tile_t() != 0){ + return 1; + } + + for (size_t num_bytes = 0; num_bytes <= 16; ++num_bytes){ + PartialWordAccess_arm64_NEON partial(num_bytes); + + uint8_t buffer[48]; + for (int i = 0; i < 48; i++){ + buffer[i] = 99; + } + for (uint8_t i = 0; i < 16; i++){ + buffer[i + 16] = i; + } + + // uint8x16_t x = partial.load(buffer+16); + uint8x16_t x = partial.load_int_no_read_past_end(buffer + 16); + for (size_t i = 0; i < num_bytes; ++i){ + if (x[i] != buffer[i + 16]){ + cout << "Error: PartialWordAccess_arm64_NEON(" << num_bytes << ")::load_int_no_read_past_end(), i = " << i << " is " << int(x[i]) + << ", but should be " << int(buffer[i + 16]) << endl; + return 1; + } + } + x = partial.load_int_no_read_before_ptr(buffer + 16); + for (size_t i = 0; i < num_bytes; ++i){ + if (x[i] != buffer[i + 16]){ + cout << "Error: PartialWordAccess_arm64_NEON(" << num_bytes << ")::load_int_no_read_before_ptr(), i = " << i << " is " << int(x[i]) + << ", but should be " << int(buffer[i + 16]) << endl; + return 1; + } + } + for (int i = 0; i < 48; i++){ + buffer[i] = 99; + } + partial.store_int_no_past_end(buffer + 16, x); + for (size_t i = 0; i < num_bytes; ++i){ + if (x[i] != buffer[i + 16]){ + cout << "Error: PartialWordAccess_arm64_NEON(" << num_bytes << ")::store_int_no_past_end(), i = " << i << " is " << int(buffer[i + 16]) + << ", but should be " << int(x[i + 16]) << endl; + return 1; + } + } + } +#endif + return 0; +} + + +class Test_BinaryMatrix : public UnitTest{ +public: + Test_BinaryMatrix( + const std::string& image + ) + : UnitTest("Kernels::BinaryMatrix - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + + if (test_binary_matrix_tile() != 0){ + return false; + } + + const size_t width = image.width(), height = image.height(); + + const Color min_color(0, 0, 0), max_color(63, 63, 63); + const uint32_t mins = uint32_t(min_color), maxs = uint32_t(max_color); + + auto binary_matrix = make_PackedBinaryMatrix(get_BinaryMatrixType(), width, height); + + auto time_start = current_time(); + compress_rgb32_to_binary_range( + image.data(), image.bytes_per_row(), *binary_matrix, mins, maxs + ); + auto time_end = current_time(); + size_t ns = std::chrono::duration_cast(time_end - time_start).count(); + double ms = ns / 1000000.; + cout << "One binary matrix creation. time: " << ms << " ms" << endl; + + size_t error_count = 0; + for (size_t y = 0; y < height; y++){ + for (size_t x = 0; x < width; x++){ + const Color color(image.pixel(x, y)); + bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); + in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); + in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); + in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); + + const bool v_default = binary_matrix->get(x, y); + + if (error_count < 10){ + if (v_default != in_range){ + cout << "Error: matrix (" << x << ", " << y << ") got " + << v_default << " but GT is " << in_range << endl; + ++error_count; + } + } + } + } + if (error_count){ + return false; + } + + + // We try to wait for three seconds: + const size_t num_iters = size_t(3000 / ms); + time_start = current_time(); + for (size_t i = 0; i < num_iters; i++){ + compress_rgb32_to_binary_range( + image.data(), image.bytes_per_row(), *binary_matrix, mins, maxs + ); + } + time_end = current_time(); + ms = (double)std::chrono::duration_cast(time_end - time_start).count(); + cout << "Running " << num_iters << " iters, average creation impl. time: " << ms / (double)num_iters << " ms" << endl; + + // cout << binary_matrix->dump() << flush; + + return true; + }; + +private: + std::string m_image; +}; + +void add_tests_BinaryMatrix(UnitTestDatabase& database){ + +} + + + +} +} diff --git a/SerialPrograms/Source/Kernels/BinaryMatrix/Kernels_BinaryMatrix_Tests.h b/SerialPrograms/Source/Kernels/BinaryMatrix/Kernels_BinaryMatrix_Tests.h new file mode 100644 index 0000000000..3f80bc2bea --- /dev/null +++ b/SerialPrograms/Source/Kernels/BinaryMatrix/Kernels_BinaryMatrix_Tests.h @@ -0,0 +1,23 @@ +/* BinaryMatrix Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_BinaryMatrix_Tests_H +#define PokemonAutomation_Kernels_BinaryMatrix_Tests_H + +#include "Common/Cpp/TestRunners/UnitTest.h" + +namespace PokemonAutomation{ +namespace Kernels{ + + + +void add_tests_BinaryMatrix(UnitTestDatabase& database); + + + +} +} +#endif diff --git a/SerialPrograms/Source/Kernels/ImageFilters/Kernels_ImageFilter_Tests.cpp b/SerialPrograms/Source/Kernels/ImageFilters/Kernels_ImageFilter_Tests.cpp new file mode 100644 index 0000000000..81c69be44c --- /dev/null +++ b/SerialPrograms/Source/Kernels/ImageFilters/Kernels_ImageFilter_Tests.cpp @@ -0,0 +1,598 @@ +/* Image Filters Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Color.h" +#include "Common/Cpp/Time.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTypes/BinaryImage.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix.h" +#ifdef PA_AutoDispatch_arm64_20_M1 +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrixTile_64x8_arm64_NEON.h" +#include "Kernels/PartialWordAccess/Kernels_PartialWordAccess_arm64_NEON.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Core_64x8_arm64_NEON.h" +#endif +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix_Arch_64xH_Default.h" +#include "Kernels/BinaryImageFilters/Kernels_BinaryImage_BasicFilters.h" +#include "Kernels/ImageFilters/RGB32_Range/Kernels_ImageFilter_RGB32_Range.h" +#include "Kernels/ImageFilters/RGB32_EuclideanDistance/Kernels_ImageFilter_RGB32_Euclidean.h" +#include "Kernels_ImageFilter_Tests.h" +#include "Tests/TestUtils.h" + +#include +using std::cout; +using std::cerr; +using std::endl; +using std::flush; +namespace PokemonAutomation{ +namespace Kernels{ + +class Test_ImageFilterRGB32Range : public UnitTest{ +public: + Test_ImageFilterRGB32Range( + const std::string& image + ) + : UnitTest("Kernels::ImageFilterRGB32Range - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + + const size_t width = image.width(), height = image.height(); + cout << "Testing filter_rgb32_range(), image size " << width << " x " << height << endl; + + Color min_color(0, 0, 0); + Color max_color(63, 63, 63); + // Color max_color(238, 24, 42); + + const uint32_t mins = uint32_t(min_color); + const uint32_t maxs = uint32_t(max_color); + + ImageRGB32 image_out(image.width(), image.height()); + ImageRGB32 image_out_2(image.width(), image.height()); + size_t pixels_in_range = 0; + + const bool replace_color_within_range = true; + auto time_start = current_time(); + // auto new_image = filter_rgb32_range(image, mins, maxs, COLOR_WHITE, replace_color_within_range); + pixels_in_range = Kernels::filter_rgb32_range( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out.data(), image_out.bytes_per_row(), + (uint32_t)COLOR_WHITE, replace_color_within_range, + mins, maxs + ); + auto time_end = current_time(); + auto ns = std::chrono::duration_cast(time_end - time_start).count(); + auto ms = ns / 1000000.; + cout << "One filter time: " << ms << " ms" << endl; + + size_t pixels_in_range_2 = Kernels::filter_rgb32_range( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out_2.data(), image_out_2.bytes_per_row(), + (uint32_t)COLOR_WHITE, !replace_color_within_range, + mins, maxs + ); + + TEST_RESULT_EQUAL(pixels_in_range, pixels_in_range_2); + + size_t actual_num_pixels_in_range = 0; + size_t error_count = 0; + for (size_t y = 0; y < height; y++){ + for (size_t x = 0; x < width; x++){ + const Color color(image.pixel(x, y)); + const Color new_color(image_out.pixel(x, y)); + const Color new_color_2(image_out_2.pixel(x, y)); + bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); + in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); + in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); + in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); + actual_num_pixels_in_range += in_range; + if (error_count < 10){ + // Print first 10 errors: + if (in_range && new_color != COLOR_WHITE){ + cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should be in range but not found by the function" << endl; + ++error_count; + } else if (in_range == false && new_color != color){ + cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should not be changed by the function" << endl; + ++error_count; + } + + if (in_range && new_color_2 != color){ + cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should not be changed by the function" << endl; + ++error_count; + } else if (in_range == false && new_color_2 != COLOR_WHITE){ + cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should not be in range but not found by the function" << endl; + ++error_count; + } + } + } + } + cout << "Found " << actual_num_pixels_in_range << " pixels in range" << endl; + if (pixels_in_range != actual_num_pixels_in_range){ + cout << "Error: wrong pixels in range: " << pixels_in_range << " actual: " << actual_num_pixels_in_range << endl; + return false; + } + + if (error_count){ + return false; + } + + // We try to wait for three seconds: + const size_t num_iters = size_t(3000 / ms); + time_start = current_time(); + for (size_t i = 0; i < num_iters; i++){ + Kernels::filter_rgb32_range( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out.data(), image_out.bytes_per_row(), + (uint32_t)COLOR_WHITE, replace_color_within_range, + mins, maxs + ); + } + time_end = current_time(); + ms = (double)std::chrono::duration_cast(time_end - time_start).count(); + cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; + + return true; + }; + +private: + std::string m_image; +}; + + +class Test_ImageFilterRGB32Euclidean : public UnitTest{ +public: + Test_ImageFilterRGB32Euclidean( + const std::string& image + ) + : UnitTest("Kernels::ImageFilterRGB32Euclidean - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + + const size_t width = image.width(); + const size_t height = image.height(); + cout << "Testing test_kernels_FilterRGB32Euclidean(), image size " << width << " x " << height << endl; + + Color middle_color = Color(image.pixel(width / 2, height / 2)); + cout << "Expected color: " << middle_color.to_string() << endl; + + double max_dist = 50.0; + size_t max_dist2 = size_t(max_dist * max_dist); + + ImageRGB32 image_out(image.width(), image.height()); + ImageRGB32 image_out_2(image.width(), image.height()); + size_t pixels_in_range = 0; + + const bool replace_color_within_range = true; + auto time_start = current_time(); + pixels_in_range = Kernels::filter_rgb32_euclidean( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out.data(), image_out.bytes_per_row(), + (uint32_t)COLOR_WHITE, replace_color_within_range, + uint32_t(middle_color), max_dist + ); + auto time_end = current_time(); + auto ns = std::chrono::duration_cast(time_end - time_start).count(); + auto ms = ns / 1000000.; + cout << "One filter time: " << ms << " ms" << endl; + + size_t pixels_in_range_2 = Kernels::filter_rgb32_euclidean( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out_2.data(), image_out_2.bytes_per_row(), + (uint32_t)COLOR_WHITE, !replace_color_within_range, + uint32_t(middle_color), max_dist + ); + + TEST_RESULT_EQUAL(pixels_in_range, pixels_in_range_2); + + size_t actual_num_pixels_in_range = 0; + size_t error_count = 0; + for (size_t y = 0; y < height; y++){ + for (size_t x = 0; x < width; x++){ + const Color color(image.pixel(x, y)); + const Color new_color(image_out.pixel(x, y)); + const Color new_color_2(image_out_2.pixel(x, y)); + int red_dif = (color.red() - middle_color.red()); + int green_dif = (color.green() - middle_color.green()); + int blue_dif = (color.blue() - middle_color.blue()); + size_t color_dist2 = red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif; + bool in_range = color_dist2 <= max_dist2; + actual_num_pixels_in_range += in_range; + if (error_count < 10){ + // Print first 10 errors: + if (in_range && new_color != COLOR_WHITE){ + cout << "Error: wrong filter result: old color " << color.to_string() + << ", (x,y) = (" << x << ", " << y << ")" + << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 + << ", should be in range but not found by the function" << endl; + ++error_count; + } else if (in_range == false && new_color != color){ + cout << "Error: wrong filter result: old color " << color.to_string() + << ", (x,y) = (" << x << ", " << y << ")" + << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 + << ", should not be changed by the function" << endl; + ++error_count; + } + + if (in_range && new_color_2 != color){ + cout << "Error: wrong inverse filter result: old color " << color.to_string() + << ", (x,y) = (" << x << ", " << y << ")" + << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 + << ", should not be changed by the function" << endl; + ++error_count; + } else if (in_range == false && new_color_2 != COLOR_WHITE){ + cout << "Error: wrong inverse filter result: old color " << color.to_string() + << ", (x,y) = (" << x << ", " << y << ")" + << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 + << ", should not be in range but not found by the function" << endl; + ++error_count; + } + } + } + } + cout << "Found " << actual_num_pixels_in_range << " pixels in range" << endl; + if (pixels_in_range != actual_num_pixels_in_range){ + cout << "Error: wrong pixels in range: " << pixels_in_range << " actual: " << actual_num_pixels_in_range << endl; + return false; + } + + if (error_count){ + return false; + } + + // We try to wait for three seconds: + const size_t num_iters = size_t(3000 / ms); + time_start = current_time(); + for (size_t i = 0; i < num_iters; i++){ + pixels_in_range = Kernels::filter_rgb32_euclidean( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out.data(), image_out.bytes_per_row(), + (uint32_t)COLOR_WHITE, replace_color_within_range, + uint32_t(middle_color), max_dist + ); + } + time_end = current_time(); + ms = (double)std::chrono::duration_cast(time_end - time_start).count(); + cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; + + return true; + + }; + +private: + std::string m_image; +}; + + +class Test_ImageToBlackWhiteRGB32Range : public UnitTest{ +public: + Test_ImageToBlackWhiteRGB32Range( + const std::string& image + ) + : UnitTest("Kernels::ImageToBlackWhiteRGB32Range - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + + const size_t width = image.width(); + const size_t height = image.height(); + cout << "Testing to_black_white_rgb32_range(), image size " << width << " x " << height << endl; + + Color min_color(0, 0, 0); + // Color min_color(0); + + Color max_color(63, 63, 63); + // Color max_color(255, 255, 255); + // Color max_color(238, 24, 42); + cout << "min color: " << min_color.to_string() << " max color: " << max_color.to_string() << endl; + + const uint32_t mins = uint32_t(min_color); + const uint32_t maxs = uint32_t(max_color); + + ImageRGB32 image_out(image.width(), image.height()); + ImageRGB32 image_out_2(image.width(), image.height()); + size_t pixels_in_range = 0; + + const bool in_range_black = true; + auto time_start = current_time(); + // auto new_image = filter_rgb32_range(image, mins, maxs, COLOR_WHITE, replace_color_within_range); + pixels_in_range = Kernels::to_blackwhite_rgb32_range( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out.data(), image_out.bytes_per_row(), + in_range_black, + mins, maxs + ); + auto time_end = current_time(); + auto ns = std::chrono::duration_cast(time_end - time_start).count(); + auto ms = ns / 1000000.; + cout << "One filter time: " << ms << " ms" << endl; + + size_t pixels_in_range_2 = Kernels::to_blackwhite_rgb32_range( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out_2.data(), image_out_2.bytes_per_row(), + !in_range_black, + mins, maxs + ); + + TEST_RESULT_EQUAL(pixels_in_range, pixels_in_range_2); + + size_t actual_num_pixels_in_range = 0; + size_t error_count = 0; + for (size_t y = 0; y < height; y++){ + for (size_t x = 0; x < width; x++){ + const Color color(image.pixel(x, y)); + const Color new_color(image_out.pixel(x, y)); + const Color new_color_2(image_out_2.pixel(x, y)); + bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); + in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); + in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); + in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); + actual_num_pixels_in_range += in_range; + if (error_count < 10){ + // Print first 10 errors: + if (in_range && new_color != COLOR_BLACK){ + cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should be black due to in range but not so" << endl; + ++error_count; + } else if (in_range == false && new_color != COLOR_WHITE){ + cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should be white due to out of range but not so" << endl; + ++error_count; + } + + if (in_range && new_color_2 != COLOR_WHITE){ + cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should be white due to in range but not so" << endl; + ++error_count; + } else if (in_range == false && new_color_2 != COLOR_BLACK){ + cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should be black due to out of range but not so" << endl; + ++error_count; + } + } + } + } + cout << "Found " << actual_num_pixels_in_range << " pixels in range" << endl; + if (pixels_in_range != actual_num_pixels_in_range){ + cout << "Error: wrong pixels in range: " << pixels_in_range << " actual: " << actual_num_pixels_in_range << endl; + return false; + } + + if (error_count){ + return false; + } + + // We try to wait for three seconds: + const size_t num_iters = size_t(3000 / ms); + time_start = current_time(); + for (size_t i = 0; i < num_iters; i++){ + Kernels::to_blackwhite_rgb32_range( + image.data(), image.bytes_per_row(), image.width(), image.height(), + image_out.data(), image_out.bytes_per_row(), + in_range_black, + mins, maxs + ); + } + time_end = current_time(); + ms = (double)std::chrono::duration_cast(time_end - time_start).count(); + cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; + + return true; + + }; + +private: + std::string m_image; +}; + + + +class Test_ImageFilterByMask : public UnitTest{ +public: + Test_ImageFilterByMask( + const std::string& image + ) + : UnitTest("Kernels::ImageFilterByMask - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + + const size_t width = image.width(), height = image.height(); + cout << "Image width " << width << " height " << height << endl; + + const Color min_color(0, 0, 0), max_color(63, 63, 63); + const uint32_t mins = uint32_t(min_color), maxs = uint32_t(max_color); + + auto binary_matrix = make_PackedBinaryMatrix(get_BinaryMatrixType(), width, height); + compress_rgb32_to_binary_range( + image.data(), image.bytes_per_row(), *binary_matrix, mins, maxs + ); + ImageRGB32 new_image = image.copy(); + ImageRGB32 new_image_2 = image.copy(); + + Color replacement_color = COLOR_WHITE; + bool replace_zero_bits = true; + + auto time_start = current_time(); + filter_by_mask(*binary_matrix, new_image.data(), new_image.bytes_per_row(), uint32_t(replacement_color), replace_zero_bits); + auto time_end = current_time(); + auto ns = std::chrono::duration_cast(time_end - time_start).count(); + auto ms = ns / 1000000.; + cout << "One Filter by mask call. time: " << ms << " ms" << endl; + + filter_by_mask(*binary_matrix, new_image_2.data(), new_image_2.bytes_per_row(), uint32_t(replacement_color), !replace_zero_bits); + + size_t error_count = 0; + + for (size_t y = 0; y < height; y++){ + for (size_t x = 0; x < width; x++){ + const Color color(image.pixel(x, y)); + const Color new_color(new_image.pixel(x, y)); + const Color new_color_2(new_image_2.pixel(x, y)); + bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); + in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); + in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); + in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); + + if (error_count <= 10){ + if (in_range && new_color != color){ + cout << "Error: wrong filter(replace_zero_bits) result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should not be changed due to being one bit but not so" << endl; + ++error_count; + } else if (!in_range && new_color != replacement_color){ + cout << "Error: wrong filter(replace_zero_bits) result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should be changed due to being zero bit but not so" << endl; + ++error_count; + } + + if (in_range && new_color_2 != replacement_color){ + cout << "Error: wrong filter(replace_one_bits) result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should be changed due to being one bit but not so" << endl; + ++error_count; + } else if (!in_range && new_color_2 != color){ + cout << "Error: wrong filter(replace_one_bits) result: old color " << color.to_string() << ", (x,y) = " + << x << ", " << y << ", should not be changed due to being zero bit but not so" << endl; + ++error_count; + } + } + } + } + + if (error_count){ + return false; + } + + // We try to wait for three seconds: + const size_t num_iters = size_t(3000 / ms); + time_start = current_time(); + for (size_t i = 0; i < num_iters; i++){ + filter_by_mask(*binary_matrix, new_image.data(), new_image.bytes_per_row(), uint32_t(replacement_color), replace_zero_bits); + } + time_end = current_time(); + ms = (double)std::chrono::duration_cast(time_end - time_start).count(); + cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; + + return true; + + }; + +private: + std::string m_image; +}; + + +class Test_CompressRGB32ToBinaryEuclidean : public UnitTest{ +public: + Test_CompressRGB32ToBinaryEuclidean( + const std::string& image + ) + : UnitTest("Kernels::CompressRGB32ToBinaryEuclidean - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + + const size_t width = image.width(); + const size_t height = image.height(); + cout << "Testing test_kernels_CompressRGB32ToBinaryEuclidean(), image size " << width << " x " << height << endl; + + Color middle_color = Color(image.pixel(width / 2, height / 2)); + cout << "Expected color: " << middle_color.to_string() << endl; + + double max_dist = 50.0; + size_t max_dist2 = size_t(max_dist * max_dist); + + PackedBinaryMatrix matrix(image.width(), image.height()); + + auto time_start = current_time(); + Kernels::compress_rgb32_to_binary_euclidean( + image.data(), image.bytes_per_row(), matrix, + uint32_t(middle_color), max_dist + ); + auto time_end = current_time(); + auto ns = std::chrono::duration_cast(time_end - time_start).count(); + auto ms = ns / 1000000.; + cout << "One filter time: " << ms << " ms" << endl; + + size_t error_count = 0; + for (size_t y = 0; y < height; y++){ + for (size_t x = 0; x < width; x++){ + const Color color(image.pixel(x, y)); + int red_dif = (color.red() - middle_color.red()); + int green_dif = (color.green() - middle_color.green()); + int blue_dif = (color.blue() - middle_color.blue()); + size_t color_dist2 = red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif; + bool in_range = color_dist2 <= max_dist2; + if (error_count < 10){ + // Print first 10 errors: + if (in_range && matrix.get(x, y) == false){ + cout << "Error: wrong filter result: old color " << color.to_string() + << ", (x,y) = (" << x << ", " << y << ")" + << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 + << ", should be in range but not set on matrix" << endl; + ++error_count; + } else if (in_range == false && matrix.get(x, y) == true){ + cout << "Error: wrong filter result: old color " << color.to_string() + << ", (x,y) = (" << x << ", " << y << ")" + << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 + << ", should not be in range but set on matrix" << endl; + ++error_count; + } + } + } + } + if (error_count){ + return false; + } + + // We try to wait for three seconds: + const size_t num_iters = size_t(3000 / ms); + time_start = current_time(); + for (size_t i = 0; i < num_iters; i++){ + Kernels::compress_rgb32_to_binary_euclidean( + image.data(), image.bytes_per_row(), matrix, + uint32_t(middle_color), max_dist + ); + } + time_end = current_time(); + ms = (double)std::chrono::duration_cast(time_end - time_start).count(); + cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; + + return true; + }; + +private: + std::string m_image; +}; + + + + + +void add_tests_ImageFilters(UnitTestDatabase& database){ + + + +} + + + +} +} diff --git a/SerialPrograms/Source/Kernels/ImageFilters/Kernels_ImageFilter_Tests.h b/SerialPrograms/Source/Kernels/ImageFilters/Kernels_ImageFilter_Tests.h new file mode 100644 index 0000000000..db37a23881 --- /dev/null +++ b/SerialPrograms/Source/Kernels/ImageFilters/Kernels_ImageFilter_Tests.h @@ -0,0 +1,23 @@ +/* Image Filters Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_ImageFilter_Tests_H +#define PokemonAutomation_Kernels_ImageFilter_Tests_H + +#include "Common/Cpp/TestRunners/UnitTest.h" + +namespace PokemonAutomation{ +namespace Kernels{ + + + +void add_tests_ImageFilters(UnitTestDatabase& database); + + + +} +} +#endif diff --git a/SerialPrograms/Source/Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.cpp b/SerialPrograms/Source/Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.cpp new file mode 100644 index 0000000000..3a8f906837 --- /dev/null +++ b/SerialPrograms/Source/Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.cpp @@ -0,0 +1,61 @@ +/* Image Scale Brightness Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Color.h" +#include "Common/Cpp/Time.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix_Arch_64xH_Default.h" +#include "Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness.h" +#include "Kernels_ImageScaleBrightness_Tests.h" + +#include +#include "CommonFramework/Globals.h" +using std::cout; +using std::endl; + +namespace PokemonAutomation{ +namespace Kernels{ + +class Test_ImageScaleBrightness : public UnitTest{ +public: + Test_ImageScaleBrightness( + const std::string& image + ) + : UnitTest("Kernels::ImageScaleBrightness - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 new_image(m_image); + + int num_iterations = 500; + auto time_start = current_time(); + for (int i = 0; i < num_iterations; i++){ + scale_brightness(new_image.width(), new_image.height(), new_image.data(), new_image.bytes_per_row(), 1.2f, 1.3f, 0.5f); + // break; + } + auto time_end = current_time(); + const auto ms = std::chrono::duration_cast(time_end - time_start).count(); + cout << "Time: " << ms << " ms, " << ms / 1000. << " s" << endl; + + // new_image.save("./output.png"); + + return true; + }; + +private: + std::string m_image; +}; + +void add_tests_ImageScaleBrightness(UnitTestDatabase& database){ + +} + + + +} +} diff --git a/SerialPrograms/Source/Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.h b/SerialPrograms/Source/Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.h new file mode 100644 index 0000000000..54bc229d1b --- /dev/null +++ b/SerialPrograms/Source/Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.h @@ -0,0 +1,23 @@ +/* Image Scale Brightness Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_ImageScaleBrightness_Tests_H +#define PokemonAutomation_Kernels_ImageScaleBrightness_Tests_H + +#include "Common/Cpp/TestRunners/UnitTest.h" + +namespace PokemonAutomation{ +namespace Kernels{ + + + +void add_tests_ImageScaleBrightness(UnitTestDatabase& database); + + + +} +} +#endif diff --git a/SerialPrograms/Source/Kernels/Kernels_Tests.cpp b/SerialPrograms/Source/Kernels/Kernels_Tests.cpp new file mode 100644 index 0000000000..dbb65849c8 --- /dev/null +++ b/SerialPrograms/Source/Kernels/Kernels_Tests.cpp @@ -0,0 +1,28 @@ +/* Kernels Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Kernels_Tests.h" +#include "BinaryMatrix/Kernels_BinaryMatrix_Tests.h" +#include "ImageFilters/Kernels_ImageFilter_Tests.h" +#include "ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.h" +#include "Waterfill/Kernels_Waterfill_Tests.h" + +namespace PokemonAutomation{ +namespace Kernels{ + + + +void add_tests(UnitTestDatabase& database){ + add_tests_BinaryMatrix(database); + add_tests_ImageFilters(database); + add_tests_ImageScaleBrightness(database); + add_tests_Waterfill(database); +} + + + +} +} diff --git a/SerialPrograms/Source/Kernels/Kernels_Tests.h b/SerialPrograms/Source/Kernels/Kernels_Tests.h new file mode 100644 index 0000000000..8fe4f0123f --- /dev/null +++ b/SerialPrograms/Source/Kernels/Kernels_Tests.h @@ -0,0 +1,23 @@ +/* Kernels Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_Tests_H +#define PokemonAutomation_Kernels_Tests_H + +#include "Common/Cpp/TestRunners/UnitTest.h" + +namespace PokemonAutomation{ +namespace Kernels{ + + + +void add_tests(UnitTestDatabase& database); + + + +} +} +#endif \ No newline at end of file diff --git a/SerialPrograms/Source/Kernels/Waterfill/Kernels_Waterfill_Tests.cpp b/SerialPrograms/Source/Kernels/Waterfill/Kernels_Waterfill_Tests.cpp new file mode 100644 index 0000000000..a43d8bd606 --- /dev/null +++ b/SerialPrograms/Source/Kernels/Waterfill/Kernels_Waterfill_Tests.cpp @@ -0,0 +1,136 @@ +/* Waterfill Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Color.h" +#include "Common/Cpp/CpuId/CpuId.h" +#include "Common/Cpp/Time.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/ImageTypes/BinaryImage.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#ifdef PA_AutoDispatch_arm64_20_M1 + #include "Kernels/BinaryMatrix/Kernels_BinaryMatrixTile_64x8_arm64_NEON.h" + #include "Kernels/PartialWordAccess/Kernels_PartialWordAccess_arm64_NEON.h" + #include "Kernels/Waterfill/Kernels_Waterfill_Core_64x8_arm64_NEON.h" +#endif +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix.h" +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix_Arch_64xH_Default.h" +#include "Kernels/BinaryMatrix/Kernels_BinaryMatrixTile_64x4_Default.h" +#include "Kernels/BinaryImageFilters/Kernels_BinaryImage_BasicFilters.h" +#include "Kernels/Waterfill/Kernels_Waterfill.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Core_64xH_Default.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Routines.h" +#include "Kernels_Waterfill_Tests.h" +#include "Tests/TestUtils.h" + +#include +using std::cout; +using std::endl; + + +namespace PokemonAutomation{ +namespace Kernels{ + +class Test_Waterfill : public UnitTest{ +public: + Test_Waterfill( + const std::string& image + ) + : UnitTest("Kernels::Waterfill - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + + const size_t width = image.width(); + const size_t height = image.height(); + cout << "Testing test_kernels_Waterfill(), image size " << width << " x " << height << endl; + + PackedBinaryMatrix matrix(width, height); + uint32_t mins = combine_rgb(0, 0, 0); + // uint32_t maxs = combine_rgb(255, 255, 255); + uint32_t maxs = combine_rgb(63, 63, 63); + Kernels::compress_rgb32_to_binary_range( + image.data(), image.bytes_per_row(), + matrix, mins, maxs + ); + + PackedBinaryMatrix source_matrix = matrix.copy(); + + PackedBinaryMatrix gt_matrix = matrix.copy(); + Kernels::PackedBinaryMatrix_IB& gt_matrix_ib = gt_matrix; + + size_t min_area = 10; + std::vector gt_objects; + bool gt_computed = false; + +#ifdef PA_AutoDispatch_arm64_20_M1 + if (CPU_CAPABILITY_CURRENT.OK_M1){ + using Waterfill_64x8_Default = Kernels::Waterfill::Waterfill_64xH_Default; + gt_objects = Kernels::Waterfill::find_objects_inplace( + static_cast(gt_matrix_ib).get(), + min_area + ); + gt_computed = true; + } +#endif + if (gt_computed == false){ + using Waterfill_64x4_Default = Kernels::Waterfill::Waterfill_64xH_Default; + gt_objects = Kernels::Waterfill::find_objects_inplace( + static_cast(gt_matrix_ib).get(), + min_area + ); + } + cout << "num objects: " << gt_objects.size() << endl; + + auto time_start = current_time(); + std::vector objects = Kernels::Waterfill::find_objects_inplace(matrix, min_area); + auto time_end = current_time(); + auto ns = std::chrono::duration_cast(time_end - time_start).count(); + auto ms = ns / 1000000.; + cout << "One waterfill time: " << ms << " ms" << endl; + + for (size_t i = 0; i < objects.size(); ++i){ + TEST_RESULT_COMPONENT_EQUAL(objects[i].area, gt_objects[i].area, "object " + std::to_string(i) + " area"); + TEST_RESULT_COMPONENT_EQUAL(objects[i].min_x, gt_objects[i].min_x, "object " + std::to_string(i) + " min_x"); + TEST_RESULT_COMPONENT_EQUAL(objects[i].min_y, gt_objects[i].min_y, "object " + std::to_string(i) + " min_y"); + TEST_RESULT_COMPONENT_EQUAL(objects[i].max_x, gt_objects[i].max_x, "object " + std::to_string(i) + " max_x"); + TEST_RESULT_COMPONENT_EQUAL(objects[i].max_y, gt_objects[i].max_y, "object " + std::to_string(i) + " max_y"); + } + + // We try to wait for three seconds: + const size_t num_iters = size_t(3000 / ms); + time_start = current_time(); + for (size_t i = 0; i < num_iters; i++){ + matrix = source_matrix.copy(); + objects = Kernels::Waterfill::find_objects_inplace(matrix, min_area); + } + time_end = current_time(); + ms = (double)std::chrono::duration_cast(time_end - time_start).count(); + cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; + + + return true; + }; + +private: + std::string m_image; +}; + + + + +void add_tests_Waterfill(UnitTestDatabase& database){ + + + +} + + + +} +} diff --git a/SerialPrograms/Source/Kernels/Waterfill/Kernels_Waterfill_Tests.h b/SerialPrograms/Source/Kernels/Waterfill/Kernels_Waterfill_Tests.h new file mode 100644 index 0000000000..a399facb44 --- /dev/null +++ b/SerialPrograms/Source/Kernels/Waterfill/Kernels_Waterfill_Tests.h @@ -0,0 +1,23 @@ +/* Waterfill Tests + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Kernels_Waterfill_Tests_H +#define PokemonAutomation_Kernels_Waterfill_Tests_H + +#include "Common/Cpp/TestRunners/UnitTest.h" + +namespace PokemonAutomation{ +namespace Kernels{ + + + +void add_tests_Waterfill(UnitTestDatabase& database); + + + +} +} +#endif diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.cpp b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.cpp index 60daa50cbc..a5b769371d 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/Globals.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" #include "NintendoSwitch_CheckOnlineDetector.h" @@ -100,6 +101,34 @@ bool CheckOnlineDetector::detect(const ImageViewRGB32& screen){ } +class Test_CheckOnlineDetector : public UnitTest{ +public: + Test_CheckOnlineDetector(const std::string& image, bool expected) + : UnitTest("NintendoSwitch::CheckOnlineDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + CheckOnlineDetector detector{}; + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_CheckOnlineDetector(UnitTestDatabase& database){ + database.add("NintendoSwitch/CheckOnlineDetector/French_blackBackground_False.png", false); + database.add("NintendoSwitch/CheckOnlineDetector/French_blackBackground_True.png", true); + database.add("NintendoSwitch/CheckOnlineDetector/French_whiteBackground_False.png", false); + database.add("NintendoSwitch/CheckOnlineDetector/French_whiteBackground_True.png", true); +} + + } } diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.h b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.h index a3f219208b..970d77519a 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.h +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_NintendoSwitch_CheckOnlineDetector_H #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/VisualDetector.h" @@ -42,6 +43,7 @@ class CheckOnlineWatcher : public DetectorToFinder{ }; +void add_tests_CheckOnlineDetector(UnitTestDatabase& database); } } diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.cpp b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.cpp index 16001c46c2..4553405f48 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.cpp @@ -4,6 +4,7 @@ * */ +#include "CommonFramework/Globals.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" #include "NintendoSwitch_FailedToConnectDetector.h" @@ -57,6 +58,34 @@ bool FailedToConnectDetector::detect(const ImageViewRGB32& screen){ } +class Test_FailedToConnectDetector : public UnitTest{ +public: + Test_FailedToConnectDetector(const std::string& image, bool expected) + : UnitTest("NintendoSwitch::FailedToConnectDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + FailedToConnectDetector detector{}; + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_FailedToConnectDetector(UnitTestDatabase& database){ + database.add("NintendoSwitch/FailedToConnectDetector/French_blackBackground_False.png", false); + database.add("NintendoSwitch/FailedToConnectDetector/French_blackBackground_True.png", true); + database.add("NintendoSwitch/FailedToConnectDetector/French_whiteBackground_False.png", false); + database.add("NintendoSwitch/FailedToConnectDetector/French_whiteBackground_True.png", true); +} + + } } diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.h b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.h index b47c78b940..8ca7f352fa 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.h +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_NintendoSwitch_FailedToConnectDetector_H #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/VisualDetector.h" @@ -39,6 +40,7 @@ class FailedToConnectWatcher : public DetectorToFinder{ }; +void add_tests_FailedToConnectDetector(UnitTestDatabase& database); } } diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.cpp b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.cpp index 03f1a1e84d..1f629964de 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.cpp +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.cpp @@ -5,10 +5,17 @@ */ #include "Common/Cpp/Exceptions.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/Recording/StreamHistorySession.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/SolidColorTest.h" +#include "NintendoSwitch/Controllers/SerialPABotBase/NintendoSwitch_SerialPABotBase_WiredController.h" #include "NintendoSwitch_UpdatePopupDetector.h" - +#include "Tests/TestUtils.h" +#include "Controllers/NullController.h" +#include "Common/Compiler.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" //#include //using std::cout; //using std::endl; @@ -258,6 +265,44 @@ bool UpdatePopupDetector_Switch2::detect(const ImageViewRGB32& screen){ } +class Test_UpdatePopupDetector : public UnitTest{ +public: + Test_UpdatePopupDetector(const std::string& image, bool expected) + : UnitTest("NintendoSwitch::UpdatePopupDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + NullController controller(logger); + DummyVideoFeed video_feed; + DummyVideoOverlay video_overlay; + DummyAudioFeed audio_feed; + StreamHistorySession history(logger); + ConsoleHandle console(0, logger, controller, video_feed, video_overlay, audio_feed, history); + + UpdatePopupDetector detector(console); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_UpdatePopupDetector(UnitTestDatabase& database){ + database.add("NintendoSwitch/UpdateMenuDetector/English_1_True.png", true); + database.add("NintendoSwitch/UpdateMenuDetector/English_2_True.png", true); + database.add("NintendoSwitch/UpdateMenuDetector/English_White_1_True.png", true); + database.add("NintendoSwitch/UpdateMenuDetector/English_White_2_True.png", true); + database.add("NintendoSwitch/UpdateMenuDetector/German_1_True.jpg", true); + database.add("NintendoSwitch/UpdateMenuDetector/German_2_True.png", true); + database.add("NintendoSwitch/UpdateMenuDetector/Home_False.png", false); +} + + diff --git a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h index faf4bbe8ab..ca70122ec9 100644 --- a/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h +++ b/SerialPrograms/Source/NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_NintendoSwitch_UpdatePopupDetector_H #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" @@ -80,6 +81,7 @@ class UpdateMenuWatcher : public DetectorToFinder{ }; +void add_tests_UpdatePopupDetector(UnitTestDatabase& database); 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/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; @@ -171,6 +176,101 @@ OCR::StringMatchResult DonutBerriesReader::read_with_ocr( 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..3e5e7914b9 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_DonutBerriesDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_DonutBerriesDetector.h @@ -9,6 +9,7 @@ #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" @@ -112,6 +113,9 @@ class DonutBerriesReader{ std::array m_box_berry_icon; }; + +void add_tests_DonutBerriesReader(UnitTestDatabase& database); + } } } 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..057f9ea4c4 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/Donuts/PokemonLZA_FlavorPowerScreenDetector.h @@ -8,7 +8,7 @@ #define PokemonAutomation_PokemonLZA_FlavorPowerScreenDetector_H #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" @@ -44,6 +44,9 @@ class FlavorPowerScreenWatcher : public DetectorToFinder -#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..6158fb4743 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; + ButtonType m_expected_button; + bool m_expected; +}; + + +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..901af40136 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.cpp @@ -12,6 +12,7 @@ #include "CommonTools/OCR/OCR_NumberReader.h" #include "CommonTools/OCR/OCR_Routines.h" #include "PokemonLZA_HyperspaceCalorieDetector.h" +#include "Tests/TestUtils.h" namespace PokemonAutomation{ namespace NintendoSwitch{ @@ -128,6 +129,46 @@ bool HyperspaceCalorieLimitWatcher::process_frame(const ImageViewRGB32& frame, W } +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..5982c9ba84 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_HyperspaceCalorieDetector.h @@ -8,6 +8,7 @@ #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" @@ -80,6 +81,9 @@ class HyperspaceCalorieLimitWatcher : public HyperspaceCalorieDetector, public V }; +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..1540b8d884 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.cpp +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.cpp @@ -5,12 +5,14 @@ */ #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; @@ -135,6 +137,62 @@ bool OverworldPartySelectionWatcher::process_frame(const VideoSnapshot& 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..05f61ad2cf 100644 --- a/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h +++ b/SerialPrograms/Source/PokemonLZA/Inference/PokemonLZA_OverworldPartySelectionDetector.h @@ -7,6 +7,7 @@ #ifndef PokemonAutomation_PokemonLZA_OverworldPartySelectionDetector_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" @@ -120,6 +121,9 @@ 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..f67f1164eb 100644 --- a/SerialPrograms/Source/PokemonLZA/PokemonLZA_Tests.cpp +++ b/SerialPrograms/Source/PokemonLZA/PokemonLZA_Tests.cpp @@ -5,7 +5,19 @@ */ #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{ @@ -14,8 +26,19 @@ namespace PokemonLZA{ 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/PokemonSV/Inference/Battles/PokemonSV_NormalBattleMenus.cpp b/SerialPrograms/Source/PokemonSV/Inference/Battles/PokemonSV_NormalBattleMenus.cpp index b76668c275..4c3192a688 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Battles/PokemonSV_NormalBattleMenus.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Battles/PokemonSV_NormalBattleMenus.cpp @@ -377,7 +377,27 @@ bool WipeoutDetector::detect(const ImageViewRGB32& screen){ +class Test_SwapMenuDetector : public UnitTest{ +public: + Test_SwapMenuDetector( + const std::string& image, + bool expected + ) + : UnitTest("PokemonSV::SwapMenuDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + SwapMenuDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + }; + +private: + std::string m_image; + bool m_expected; +}; class Test_TerastallizingDetector : public UnitTest{ public: @@ -404,11 +424,6 @@ class Test_TerastallizingDetector : public UnitTest{ - - - - - void add_tests_NormalBattleMenus(UnitTestDatabase& database){ database.add("PokemonSV/TerastallizingDetector/720p_tera_eleTerrainOverlap2_True.png", true); database.add("PokemonSV/TerastallizingDetector/French_Already_Did_False.jpg", false); @@ -417,8 +432,6 @@ void add_tests_NormalBattleMenus(UnitTestDatabase& database){ database.add("PokemonSV/TerastallizingDetector/tera_eleTerrainOverlap_Sandstorm_True.png", true); database.add("PokemonSV/TerastallizingDetector/tera_eleTerrainOverlap1_False.png", false); database.add("PokemonSV/TerastallizingDetector/tera_eleTerrainOverlap2_False.png", false); - - } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxDetection.cpp b/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxDetection.cpp index dd323a5c22..071843df6c 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxDetection.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxDetection.cpp @@ -492,6 +492,50 @@ class Test_BoxInfoDetector : public UnitTest{ const std::vector m_words; }; +class Test_SomethingInBoxSlotDetector : public UnitTest{ +public: + Test_SomethingInBoxSlotDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::SomethingInBoxSlotDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + SomethingInBoxSlotDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + +class Test_BoxBottomButtonDetector : public UnitTest{ +public: + Test_BoxBottomButtonDetector(const std::string& image, bool target_y, bool target_b) + : UnitTest("PokemonSV::BoxBottomButtonDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_target_y(target_y) + , m_target_b(target_b) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ImageRGB32 image(m_image); + BoxBottomButtonYDetector y_detector; + TEST_RESULT_COMPONENT_EQUAL(y_detector.detect(image), m_target_y, "button Y"); + + BoxBottomButtonBDetector b_detector; + TEST_RESULT_COMPONENT_EQUAL(b_detector.detect(image), m_target_b, "button B"); + return true; + } + +private: + std::string m_image; + bool m_target_y; + bool m_target_b; +}; + void add_tests_BoxInfoDetector(UnitTestDatabase& database){ @@ -527,6 +571,16 @@ void add_tests_BoxInfoDetector(UnitTestDatabase& database){ "PokemonSV/BoxPokemonInfoDetector/Tauros_False_1.png", std::vector{"Tauros", "False", "1"} ); + database.add("PokemonSV/SomethingInBoxSlotDetector/Yellow_Background_2_False.png", false); + database.add("PokemonSV/SomethingInBoxSlotDetector/Yellow_Background_False.png", false); + database.add("PokemonSV/BoxBottomButtonDetector/Empty_True_True.png", true, true); + database.add("PokemonSV/BoxBottomButtonDetector/Korean_Flamingo_False_True.png", false, true); + database.add("PokemonSV/BoxBottomButtonDetector/Korean_Flamingo_True_True.png", true, true); + database.add("PokemonSV/BoxBottomButtonDetector/Talonflame_box_selection_2_False_True.png", false, true); + database.add("PokemonSV/BoxBottomButtonDetector/Talonflame_box_selection_False_True.png", false, true); + database.add("PokemonSV/BoxBottomButtonDetector/Talonflame_True_True.png", true, true); + database.add("PokemonSV/BoxBottomButtonDetector/Volcarona_False_True.png", false, true); + database.add("PokemonSV/BoxBottomButtonDetector/Volcarona_True_True.png", true, true); } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.cpp index b26e310fb6..a2b7b5f237 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.cpp @@ -6,6 +6,7 @@ #include "Common/Cpp/Containers/FixedLimitVector.tpp" #include "Common/Cpp/Exceptions.h" +#include "CommonFramework/Globals.h" #include "Kernels/Waterfill/Kernels_Waterfill_Types.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/ImageTools/ImageStats.h" @@ -159,6 +160,61 @@ uint8_t BoxEggPartyColumnWatcher::num_non_egg_pokemon_found() const{ } +class Test_BoxCurrentEggDetector : public UnitTest{ +public: + Test_BoxCurrentEggDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::BoxCurrentEggDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + BoxCurrentEggDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + +class Test_BoxPartyEggDetector : public UnitTest{ +public: + Test_BoxPartyEggDetector(const std::string& image, int target) + : UnitTest("PokemonSV::BoxPartyEggDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_target(target) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + uint8_t num_eggs = 0; + ImageRGB32 image(m_image); + for (uint8_t i = 0; i < 5; i++){ + BoxEggDetector detector(BoxCursorLocation::PARTY, i + 1, 0); + if (detector.detect(image)){ + num_eggs++; + } + } + return (int)num_eggs == m_target; + } + +private: + std::string m_image; + int m_target; +}; + + +void add_tests_BoxEggDetector(UnitTestDatabase& database){ + database.add("PokemonSV/BoxEggDetector/default_False.png", false); + database.add("PokemonSV/BoxEggDetector/default_True.png", true); + database.add("PokemonSV/BoxPartyEggDetector/Cursor_1_Eggs_5.png", 5); + database.add("PokemonSV/BoxPartyEggDetector/Dark_screen_5.png", 5); + database.add("PokemonSV/BoxPartyEggDetector/Ditto_Sprigatito_Egg_1.png", 1); + database.add("PokemonSV/BoxPartyEggDetector/Talonflame_egg_5.png", 5); +} + + } } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.h index e050554a64..1e521b1770 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Boxes/PokemonSV_BoxEggDetector.h @@ -9,6 +9,7 @@ #include "Common/Cpp/Color.h" #include "Common/Cpp/Containers/FixedLimitVector.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" @@ -74,6 +75,7 @@ class BoxEggPartyColumnWatcher : public VisualInferenceCallback{ }; +void add_tests_BoxEggDetector(UnitTestDatabase& database); } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.cpp index 63c56c283a..1bb4f33624 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.cpp @@ -5,6 +5,7 @@ */ #include "CommonTools/Images/SolidColorTest.h" +#include "CommonFramework/Globals.h" #include "PokemonSV_DialogArrowDetector.h" #include "PokemonSV_GradientArrowDetector.h" #include "PokemonSV_DialogDetector.h" @@ -124,6 +125,50 @@ bool PromptDialogDetector::detect(const ImageViewRGB32& screen){ } +class Test_AdvanceDialogDetector : public UnitTest{ +public: + Test_AdvanceDialogDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::AdvanceDialogDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + AdvanceDialogDetector detector(COLOR_RED); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + +class Test_DialogBoxDetector : public UnitTest{ +public: + Test_DialogBoxDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::DialogBoxDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + DialogBoxDetector detector(COLOR_RED); + 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("PokemonSV/DialogBoxDetector/French_True.png", true); +} + + diff --git a/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.h index 3a53794a9b..51da16b4a7 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonSV_DialogDetector_H #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" @@ -114,6 +115,7 @@ class PromptDialogWatcher : public DetectorToFinder{ }; +void add_tests_DialogDetector(UnitTestDatabase& database); diff --git a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.cpp index 31589ef349..4ab0de8779 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.cpp @@ -5,6 +5,7 @@ */ #include "Common/Cpp/Containers/FixedLimitVector.tpp" +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/WaterfillUtilities.h" @@ -133,6 +134,38 @@ bool FastTravelWatcher::process_frame(const ImageViewRGB32& screen, WallClock ti } +class Test_FastTravelDetector : public UnitTest{ +public: + Test_FastTravelDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::FastTravelDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + FastTravelDetector detector(COLOR_RED, MINIMAP_AREA); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_FastTravelDetector(UnitTestDatabase& database){ + database.add("PokemonSV/FastTravelDetector/720p_fastTravel_NorthDown_True.jpg", true); + database.add("PokemonSV/FastTravelDetector/720p_radarbeam_fastTravel_NorthDown_False.jpg", false); + database.add("PokemonSV/FastTravelDetector/fastTravel_blueberry1_True.png", true); + database.add("PokemonSV/FastTravelDetector/fastTravel_blueberry2_True.png", true); + database.add("PokemonSV/FastTravelDetector/fastTravel_map1_blueberry_False.png", false); + database.add("PokemonSV/FastTravelDetector/fastTravel_map2_blueberry_True.png", true); + database.add("PokemonSV/FastTravelDetector/fasttravel_northLeft_True.png", true); + database.add("PokemonSV/FastTravelDetector/fastTravel_partialRadarBeam1_False.png", false); + database.add("PokemonSV/FastTravelDetector/pokeCenter1_False.png", false); + database.add("PokemonSV/FastTravelDetector/pokeCenter2_False.png", false); +} diff --git a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.h index ac68e42cc8..52b9da96e5 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.h @@ -16,6 +16,7 @@ #include #include "Common/Cpp/Color.h" #include "Common/Cpp/Containers/FixedLimitVector.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -68,6 +69,7 @@ class FastTravelWatcher : public VisualInferenceCallback{ }; +void add_tests_FastTravelDetector(UnitTestDatabase& database); } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.cpp index 69bce41d22..c4112f8bae 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.cpp @@ -5,10 +5,12 @@ */ #include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/Globals.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonTools/Images/SolidColorTest.h" #include "PokemonSV_MapMenuDetector.h" +#include "Tests/TestUtils.h" //#include //using std::cout; @@ -100,6 +102,40 @@ bool MapDestinationMenuWatcher::process_frame(const ImageViewRGB32& screen, Wall } +class Test_MapFlyMenuDetector : public UnitTest{ +public: + Test_MapFlyMenuDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::MapFlyMenuDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + MapFlyMenuDetector fly_menu(COLOR_RED); + MapDestinationMenuDetector dest_menu(COLOR_RED); + ImageRGB32 image(m_image); + TEST_RESULT_EQUAL(fly_menu.detect(image), m_expected); + TEST_RESULT_EQUAL(dest_menu.detect(image), !m_expected); + return true; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_MapMenuDetector(UnitTestDatabase& database){ + database.add("PokemonSV/MapFlyMenuDetector/ABitOutsideSouthGate_True.png", true); + database.add("PokemonSV/MapFlyMenuDetector/AtSouthGate_True.png", true); + database.add("PokemonSV/MapFlyMenuDetector/FraKitakamiForest_False.png", false); + database.add("PokemonSV/MapFlyMenuDetector/FraKitakamiTown_True.png", true); + database.add("PokemonSV/MapFlyMenuDetector/SouthGate_False.png", false); + database.add("PokemonSV/MapFlyMenuDetector/SouthGate_True.png", true); + database.add("PokemonSV/MapFlyMenuDetector/WestOfSouthGate_False.png", false); +} + + diff --git a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.h index 8afc04462a..9a7afc91ee 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapMenuDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonSV_MapMenuDetector_H #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -85,6 +86,7 @@ class MapDestinationMenuWatcher : public VisualInferenceCallback{ }; +void add_tests_MapMenuDetector(UnitTestDatabase& database); } } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.cpp index e25963a64b..2ed78a2e9d 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.cpp @@ -5,11 +5,13 @@ */ #include "Common/Cpp/Containers/FixedLimitVector.tpp" +#include "CommonFramework/Globals.h" #include "Kernels/Waterfill/Kernels_Waterfill_Types.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/WaterfillUtilities.h" #include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "PokemonSV_MapDetector.h" #include "PokemonSV_MapPokeCenterIconDetector.h" //#include @@ -113,6 +115,41 @@ bool MapPokeCenterIconWatcher::process_frame(const ImageViewRGB32& screen, WallC } +class Test_MapPokeCenterIconDetector : public UnitTest{ +public: + Test_MapPokeCenterIconDetector(const std::string& image, int target) + : UnitTest("PokemonSV::MapPokeCenterIconDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_target(target) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + MapPokeCenterIconDetector detector(COLOR_RED, MAP_READABLE_AREA); + ImageRGB32 image(m_image); + const auto result = detector.detect_all(image); + return (int)result.size() == m_target; + } + +private: + std::string m_image; + int m_target; +}; + + +void add_tests_MapPokeCenterIconDetector(UnitTestDatabase& database){ + database.add("PokemonSV/MapPokeCenterIconDetector/CursorOnMesagoza_7.png", 7); + database.add("PokemonSV/MapPokeCenterIconDetector/dark-capture-card_1.png", 1); + database.add("PokemonSV/MapPokeCenterIconDetector/FraMesagozaSouthGate_7.png", 7); + database.add("PokemonSV/MapPokeCenterIconDetector/PortoMarinada_1.png", 1); + database.add("PokemonSV/MapPokeCenterIconDetector/ZeroGate_2.png", 2); + database.add("PokemonSV/MapPokeCenterIconDetector/macOS_bright/Cascarraf_1.png", 1); + database.add("PokemonSV/MapPokeCenterIconDetector/macOS_bright/MesagozaSouthGate_5.png", 5); + database.add("PokemonSV/MapPokeCenterIconDetector/macOS_bright/SouthEastOfSouthGate_1.png", 1); + database.add("PokemonSV/MapPokeCenterIconDetector/macOS_bright/West2OfSouthGate_1.png", 1); + database.add("PokemonSV/MapPokeCenterIconDetector/macOS_bright/WestOfSouthGate_1.png", 1); +} + + diff --git a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.h index 3cf0a17f90..69c3913844 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Map/PokemonSV_MapPokeCenterIconDetector.h @@ -10,6 +10,7 @@ #include #include "Common/Cpp/Color.h" #include "Common/Cpp/Containers/FixedLimitVector.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" @@ -60,6 +61,9 @@ class MapPokeCenterIconWatcher : public VisualInferenceCallback{ }; +void add_tests_MapPokeCenterIconDetector(UnitTestDatabase& database); + + } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.cpp index a44a561fca..12a3831b0f 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.cpp @@ -210,6 +210,37 @@ bool LetsGoKillWatcher::process_frame(const ImageViewRGB32& frame, WallClock tim } +class Test_LetsGoKillDetector : public UnitTest{ +public: + Test_LetsGoKillDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::LetsGoKillDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + LetsGoKillDetector detector(COLOR_RED, {0.23, 0.23, 0.04, 0.20}); + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_LetsGoKillDetector(UnitTestDatabase& database){ + database.add("PokemonSV/RecentlyBattledDetector/Complete_True.png", true); + database.add("PokemonSV/RecentlyBattledDetector/Error_Report_231017_True.png", true); + database.add("PokemonSV/RecentlyBattledDetector/Error_Report_240811_True.png", true); + database.add("PokemonSV/RecentlyBattledDetector/Error_Report_240819_True.png", true); + database.add("PokemonSV/RecentlyBattledDetector/Incomplete_Bottom_False.png", false); + database.add("PokemonSV/RecentlyBattledDetector/Incomplete_False.png", false); + database.add("PokemonSV/RecentlyBattledDetector/Incomplete_True.png", true); +} + + LetsGoKillSoundDetector::LetsGoKillSoundDetector(Logger& logger, DetectedCallback detected_callback) diff --git a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.h index 832f826c0a..dde7152198 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_LetsGoKillDetector.h @@ -10,6 +10,7 @@ #include #include #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/Audio/AudioPerSpectrumDetectorBase.h" #include "CommonTools/VisualDetector.h" @@ -81,6 +82,9 @@ class LetsGoKillSoundDetector : public AudioPerSpectrumDetectorBase{ }; +void add_tests_LetsGoKillDetector(UnitTestDatabase& database); + + } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.cpp index 10b204e638..ed1761c8ba 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.cpp @@ -216,6 +216,35 @@ bool OverworldWatcher::process_frame(const VideoSnapshot& frame){ } +class Test_OverworldDetector : public UnitTest{ +public: + Test_OverworldDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::OverworldDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + OverworldDetector detector; + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_OverworldDetector(UnitTestDatabase& database){ + database.add("PokemonSV/OverworldDetector/ZeroGateDay_2_True.png", true); + database.add("PokemonSV/OverworldDetector/ZeroGateDay_True.png", true); + database.add("PokemonSV/OverworldDetector/ZeroGateNight_True.png", true); + database.add("PokemonSV/OverworldDetector/ZeroGateNightBike_1_True.png", true); + database.add("PokemonSV/OverworldDetector/ZeroGateNightBike_2_True.png", true); +} + + diff --git a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.h index 39058946b2..a56738e9b0 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonSV_OverworldDetector_H #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -58,6 +59,9 @@ class OverworldWatcher : public OverworldDetector, public VisualInferenceCallbac }; +void add_tests_OverworldDetector(UnitTestDatabase& database); + + } } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.cpp index ddd541cc0b..723d1412d5 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.cpp @@ -4,6 +4,9 @@ * */ +#include "Common/Cpp/Filesystem.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/Logging/Logger.h" #include "CommonFramework/ImageTools/ImageStats.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" @@ -14,6 +17,8 @@ #include "PokemonSV/Resources/PokemonSV_Ingredients.h" #include "PokemonSV_SandwichIngredientDetector.h" +#include "Tests/TestUtils.h" + //#include //using std::cout; //using std::endl; @@ -409,6 +414,148 @@ OCR::StringMatchResult SandwichIngredientReader::read_with_ocr( return results; } + +class Test_SandwichIngredientsDetector : public UnitTest{ +public: + Test_SandwichIngredientsDetector(const std::string& image, std::vector words) + : UnitTest("PokemonSV::SandwichIngredientsDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_words(std::move(words)) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + if (m_words.size() < 3){ + return "Error: not enough number of words in the filename."; + } + + std::string target_type = m_words[m_words.size() - 3]; + bool is_condiments = target_type == "Condiments"; + bool is_picks = target_type == "Picks"; + if (!is_condiments && !is_picks && target_type != "Fillings"){ + return "Error: invalid ingredient page type."; + } + + int num_fillings = 0; + int num_condiments = 0; + if (!parse_int(m_words[m_words.size() - 2], num_fillings)){ + return "Error: invalid fillings count."; + } + if (!parse_int(m_words[m_words.size() - 1], num_condiments)){ + return "Error: invalid condiments count."; + } + + ImageRGB32 image(m_image); + SandwichCondimentsPageDetector condiments_detector; + SandwichPicksPageDetector picks_detector; + + TEST_RESULT_COMPONENT_EQUAL(condiments_detector.detect(image), is_condiments, "condiments Page"); + TEST_RESULT_COMPONENT_EQUAL(picks_detector.detect(image), is_picks, "picks Page"); + + for (int i = 0; i < 10; i++){ + auto type = (i < 6 ? SandwichIngredientType::FILLING : SandwichIngredientType::CONDIMENT); + size_t index = (i < 6 ? i : i - 6); + DeterminedSandwichIngredientDetector determined_detector(type, index); + bool target = (i < 6 ? i < num_fillings : i - 6 < num_condiments); + TEST_RESULT_COMPONENT_EQUAL(determined_detector.detect(image), target, "ingredient slot " + std::to_string(i)); + } + return true; + } + +private: + std::string m_image; + std::vector m_words; +}; + +class Test_SandwichIngredientReader : public UnitTest{ +public: + Test_SandwichIngredientReader(const std::string& image) + : UnitTest("PokemonSV::SandwichIngredientReader - " + 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() < 3){ + return "Error: not enough number of words in the filename."; + } + + SandwichIngredientType sandwich_type; + if (words[words.size() - 3] == "Fillings"){ + sandwich_type = SandwichIngredientType::FILLING; + }else if (words[words.size() - 3] == "Condiments"){ + sandwich_type = SandwichIngredientType::CONDIMENT; + }else{ + return "Error: invalid sandwich type in filename."; + } + + Language language = language_code_to_enum(words[words.size() - 2]); + if (language == Language::None || language == Language::EndOfList){ + return "Error: invalid language word in filename."; + } + + size_t selected_ingredient = 0; + if (!parse_size_t(words[words.size() - 1], selected_ingredient)){ + return "Error: invalid selected ingredient index."; + } + + Filesystem::Path target_ingredients_path = parent_dir / ("_" + base_name + ".txt"); + std::vector target_ingredients; + if (!load_slug_list(target_ingredients_path.string(), target_ingredients)){ + return "Error: failed to load target ingredients."; + } + if (target_ingredients.size() != 10){ + return "Error: need exactly 10 ingredients in golden file."; + } + + ImageRGB32 image(m_image); + SandwichIngredientReader reader(sandwich_type); + for (size_t i = 0; i < 10; ++i){ + if (selected_ingredient == i){ + ImageMatch::ImageMatchResult results = reader.read_ingredient_page_with_icon_matcher(image, i); + if (results.results.empty()){ + return "No ingredient detected via icon matcher"; + } + TEST_RESULT_COMPONENT_EQUAL(results.results.begin()->second, target_ingredients[i], "image matcher : ingredient slot " + std::to_string(i)); + } + + OCR::StringMatchResult results = reader.read_ingredient_page_with_ocr(image, global_logger_command_line(), language, i); + if (results.results.empty()){ + return "No ingredient detected via text"; + } + TEST_RESULT_COMPONENT_EQUAL(results.results.begin()->second.token, target_ingredients[i], "ocr : ingredient slot " + std::to_string(i)); + } + + return true; + } + +private: + std::string m_image; +}; + + +void add_tests_SandwichIngredientDetector(UnitTestDatabase& database){ + database.add("PokemonSV/SandwichIngredientsDetector/Scarlet_Lettuce_Fillings_0_0.png", std::vector{"Scarlet", "Lettuce", "Fillings", "0", "0"}); + database.add("PokemonSV/SandwichIngredientsDetector/Scarlet_Silver_Picks_1_1.png", std::vector{"Scarlet", "Silver", "Picks", "1", "1"}); + database.add("PokemonSV/SandwichIngredientsDetector/Scarlet_Sweet_Herb_Condiments_1_0.png", std::vector{"Scarlet", "Sweet", "Herb", "Condiments", "1", "0"}); + database.add("PokemonSV/SandwichIngredientsDetector/Scarlet_Vinegar_Condiments_1_0.png", std::vector{"Scarlet", "Vinegar", "Condiments", "1", "0"}); + database.add("PokemonSV/SandwichIngredientsDetector/Scarlet_Vinegar_Condiments_1_1.png", std::vector{"Scarlet", "Vinegar", "Condiments", "1", "1"}); + database.add("PokemonSV/SandwichIngredientsDetector/Violet_Silver_Picks_1_1.png", std::vector{"Violet", "Silver", "Picks", "1", "1"}); + database.add("PokemonSV/SandwichIngredientsDetector/Violet_Silver_Picks_6_4.png", std::vector{"Violet", "Silver", "Picks", "6", "4"}); + database.add("PokemonSV/SandwichIngredientsDetector/Violet_Sweet_Herb_Condiments_1_2.png", std::vector{"Violet", "Sweet", "Herb", "Condiments", "1", "2"}); + database.add("PokemonSV/SandwichIngredientsDetector/Violet_Sweet_Herb_Condiments_1_3.png", std::vector{"Violet", "Sweet", "Herb", "Condiments", "1", "3"}); + + database.add("PokemonSV/SandwichIngredientReader/Brighter_Condiments_eng_5.png"); + database.add("PokemonSV/SandwichIngredientReader/Condiments_eng_9.png"); + database.add("PokemonSV/SandwichIngredientReader/Condiments_fra_0.png"); + database.add("PokemonSV/SandwichIngredientReader/Darker_Condiments_eng_5.png"); + database.add("PokemonSV/SandwichIngredientReader/Fillings_eng_8.png"); + database.add("PokemonSV/SandwichIngredientReader/Fillings_fra_0.png"); +} + } } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.h index 40dc881533..4a31da4b91 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.h @@ -8,6 +8,7 @@ #define PokemonAutomation_PokemonSV_SandwichIngredientDetector_H #include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "Common/Cpp/Color.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/ImageMatch/ImageMatchResult.h" @@ -215,6 +216,9 @@ class SandwichIngredientReader{ std::array m_box_confirmed; }; + +void add_tests_SandwichIngredientDetector(UnitTestDatabase& database); + } } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.cpp index 99a713a4dc..5d01b2f972 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.cpp @@ -5,12 +5,16 @@ */ +#include "Common/Cpp/Containers/FixedLimitVector.h" +#include "Common/Cpp/Containers/FixedLimitVector.tpp" #include "Common/Cpp/Exceptions.h" +#include "CommonFramework/Globals.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/Images/ImageFilter.h" #include "PokemonSV_SandwichPlateDetector.h" #include "PokemonSV/Inference/Picnics/PokemonSV_SandwichIngredientDetector.h" +#include "Tests/TestUtils.h" // #include "CommonFramework/Tools/DebugDumper.h" //#include @@ -128,6 +132,61 @@ bool SandwichPlateWatcher::process_frame(const ImageViewRGB32& screen, WallClock } +class Test_SandwichPlateDetector : public UnitTest{ +public: + Test_SandwichPlateDetector(const std::string& image, std::vector words) + : UnitTest("PokemonSV::SandwichPlateDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_words(std::move(words)) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + if (m_words.size() < 4){ + return "Error: not enough number of words in the filename."; + } + + Language language = language_code_to_enum(m_words[m_words.size() - 4]); + if (language == Language::None || language == Language::EndOfList){ + return "Error: invalid language word in filename."; + } + + auto& unit_logger = global_logger_command_line(); + FixedLimitVector detectors(3); + detectors.emplace_back(unit_logger, COLOR_RED, language, SandwichPlateDetector::Side::LEFT); + detectors.emplace_back(unit_logger, COLOR_RED, language, SandwichPlateDetector::Side::MIDDLE); + detectors.emplace_back(unit_logger, COLOR_RED, language, SandwichPlateDetector::Side::RIGHT); + + std::string sides[3] = {"left", "middle", "right"}; + ImageRGB32 image(m_image); + for (int i = 0; i < 3; i++){ + bool is_yellow = detectors[i].is_label_yellow(image); + std::string target = m_words[m_words.size() - 3 + i]; + if (target == "Yellow"){ + TEST_RESULT_COMPONENT_EQUAL(is_yellow, true, "yellow label detection at side: " + sides[i]); + }else{ + std::string filling = detectors[i].detect_filling_name(image); + if (target == "none"){ + target.clear(); + } + TEST_RESULT_COMPONENT_EQUAL(filling, target, "side: " + sides[i]); + } + } + + return true; + } + +private: + std::string m_image; + std::vector m_words; +}; + + +void add_tests_SandwichPlateDetector(UnitTestDatabase& database){ + database.add("PokemonSV/SandwichPlateDetector/Violet_eng_cucumber_cherry-tomatoes_pickle.png", std::vector{"Violet", "eng", "cucumber", "cherry-tomatoes", "pickle"}); + database.add("PokemonSV/SandwichPlateDetector/Violet_eng_Yellow_lettuce_lettuce.png", std::vector{"Violet", "eng", "Yellow", "lettuce", "lettuce"}); +} + + diff --git a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.h b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.h index 915e23ee99..ed34b57861 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/Picnics/PokemonSV_SandwichPlateDetector.h @@ -9,6 +9,7 @@ #include "Common/Cpp/Logging/AbstractLogger.h" #include "Common/Cpp/Color.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" @@ -78,6 +79,9 @@ class SandwichPlateWatcher : public VisualInferenceCallback{ }; +void add_tests_SandwichPlateDetector(UnitTestDatabase& database); + + } diff --git a/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.cpp index 32422d08a1..4c3df47544 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.cpp +++ b/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.cpp @@ -5,6 +5,7 @@ */ #include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonFramework/Globals.h" #include "CommonTools/Images/SolidColorTest.h" #include "PokemonSV_ESPEmotionDetector.h" @@ -125,6 +126,32 @@ bool ESPPressedEmotionDetector::process_frame(const ImageViewRGB32& frame, WallC return detect(frame); } + +class Test_ESPPressedEmotionDetector : public UnitTest{ +public: + Test_ESPPressedEmotionDetector(const std::string& image, bool expected) + : UnitTest("PokemonSV::ESPPressedEmotionDetector - " + image) + , m_image(UNIT_TEST_RESOURCE_PATH() + image) + , m_expected(expected) + {} + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override{ + ESPPressedEmotionDetector detector; + ImageRGB32 image(m_image); + return detector.detect(image) == m_expected; + } + +private: + std::string m_image; + bool m_expected; +}; + + +void add_tests_ESPEmotionDetector(UnitTestDatabase& database){ + database.add("PokemonSV/ESPPressedEmotionDetector/ESPPressedEmotionDetector_False.png", false); + database.add("PokemonSV/ESPPressedEmotionDetector/ESPPressedEmotionDetector_True.png", true); +} + } } } diff --git a/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.h b/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.h index 6493b94bfb..6378185ff2 100644 --- a/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.h +++ b/SerialPrograms/Source/PokemonSV/Inference/PokemonSV_ESPEmotionDetector.h @@ -5,10 +5,11 @@ */ #ifndef PokemonAutomation_PokemonSV_ESPEmotionDetector_H -#define PokemonAutomation_PokemonSV_ESPEmotionDetector_H - -#include -#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#define PokemonAutomation_PokemonSV_ESPEmotionDetector_H + +#include +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" #include "CommonTools/VisualDetector.h" @@ -85,7 +86,7 @@ class ESPShowNewEmotionDetector : public VisualInferenceCallback{ }; // Check that an emotion is pressed by looking for yellow in the lower right interface -class ESPPressedEmotionDetector : public VisualInferenceCallback{ +class ESPPressedEmotionDetector : public VisualInferenceCallback{ public: ESPPressedEmotionDetector(); @@ -98,11 +99,14 @@ class ESPPressedEmotionDetector : public VisualInferenceCallback{ ImageFloatBox m_left_box; ImageFloatBox m_right_box; ImageFloatBox m_top_box; - ImageFloatBox m_bottom_box; -}; - - -} + ImageFloatBox m_bottom_box; +}; + + +void add_tests_ESPEmotionDetector(UnitTestDatabase& database); + + +} } } diff --git a/SerialPrograms/Source/PokemonSV/PokemonSV_Tests.cpp b/SerialPrograms/Source/PokemonSV/PokemonSV_Tests.cpp index 337a98e3d3..d966bfe133 100644 --- a/SerialPrograms/Source/PokemonSV/PokemonSV_Tests.cpp +++ b/SerialPrograms/Source/PokemonSV/PokemonSV_Tests.cpp @@ -7,12 +7,22 @@ #include "Common/Cpp/TestRunners/UnitTestDatabase.h" #include "Inference/PokemonSV_WhiteButtonDetector.h" #include "Inference/Boxes/PokemonSV_BoxDetection.h" +#include "Inference/Boxes/PokemonSV_BoxEggDetector.h" #include "Inference/Map/PokemonSV_MapDetector.h" +#include "Inference/Overworld/PokemonSV_OverworldDetector.h" #include "Inference/Picnics/PokemonSV_PicnicDetector.h" #include "Inference/Battles/PokemonSV_NormalBattleMenus.h" #include "Inference/Tera/PokemonSV_TeraCardDetector.h" #include "Inference/Tera/PokemonSV_TeraSilhouetteReader.h" #include "Inference/Tera/PokemonSV_TeraTypeReader.h" +#include "Inference/Dialogs/PokemonSV_DialogDetector.h" +#include "Inference/Map/PokemonSV_FastTravelDetector.h" +#include "Inference/Map/PokemonSV_MapMenuDetector.h" +#include "Inference/Map/PokemonSV_MapPokeCenterIconDetector.h" +#include "Inference/PokemonSV_ESPEmotionDetector.h" +#include "Inference/Picnics/PokemonSV_SandwichIngredientDetector.h" +#include "Inference/Picnics/PokemonSV_SandwichPlateDetector.h" +#include "Inference/Overworld/PokemonSV_LetsGoKillDetector.h" #include "Inference/Picnics/PokemonSV_SandwichRecipeDetector.h" #include "Inference/Picnics/PokemonSV_SandwichHandDetector.h" #include "PokemonSV_Tests.h" @@ -26,14 +36,24 @@ namespace PokemonSV{ void add_tests(UnitTestDatabase& database){ add_tests_WhiteButtonDetector(database); add_tests_BoxInfoDetector(database); + add_tests_BoxEggDetector(database); + add_tests_DialogDetector(database); + add_tests_ESPEmotionDetector(database); + add_tests_FastTravelDetector(database); add_tests_MapDetector(database); + add_tests_MapMenuDetector(database); + add_tests_MapPokeCenterIconDetector(database); + add_tests_OverworldDetector(database); add_tests_PicnicDetector(database); + add_tests_SandwichIngredientDetector(database); + add_tests_SandwichPlateDetector(database); add_tests_SandwichRecipeDetector(database); add_tests_SandwichHandDetector(database); add_tests_TeraCardDetector(database); add_tests_TeraSilhouetteReader(database); add_tests_TeraTypeReader(database); add_tests_NormalBattleMenus(database); + add_tests_LetsGoKillDetector(database); } diff --git a/SerialPrograms/Source/Tests/CommonFramework_Tests.cpp b/SerialPrograms/Source/Tests/CommonFramework_Tests.cpp deleted file mode 100644 index 7122d959eb..0000000000 --- a/SerialPrograms/Source/Tests/CommonFramework_Tests.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* Common Framework Tests - * - * From: https://github.com/PokemonAutomation/ - * - */ - - -#include "CommonFramework/ImageTypes/ImageViewRGB32.h" -#include "CommonTools/VisualDetectors/BlackBorderDetector.h" -#include "CommonFramework_Tests.h" -#include "TestUtils.h" - - -//#include -//using std::cout; -//using std::cerr; -//using std::endl; - -namespace PokemonAutomation{ - -int test_CommonFramework_BlackBorderDetector(const ImageViewRGB32& image, bool target){ - BlackBorderDetector detector; - - bool result = detector.detect(image); - - TEST_RESULT_EQUAL(result, target); - - return 0; -} - - -} diff --git a/SerialPrograms/Source/Tests/CommonFramework_Tests.h b/SerialPrograms/Source/Tests/CommonFramework_Tests.h deleted file mode 100644 index dcbe39d4c5..0000000000 --- a/SerialPrograms/Source/Tests/CommonFramework_Tests.h +++ /dev/null @@ -1,20 +0,0 @@ -/* Common Framework Tests - * - * From: https://github.com/PokemonAutomation/ - * - * - */ - - -#ifndef PokemonAutomation_Tests_CommonFramework_Tests_H -#define PokemonAutomation_Tests_CommonFramework_Tests_H - -namespace PokemonAutomation{ - -class ImageViewRGB32; - -int test_CommonFramework_BlackBorderDetector(const ImageViewRGB32& image, bool target); - -} - -#endif diff --git a/SerialPrograms/Source/Tests/Kernels_Tests.cpp b/SerialPrograms/Source/Tests/Kernels_Tests.cpp deleted file mode 100644 index d68f156392..0000000000 --- a/SerialPrograms/Source/Tests/Kernels_Tests.cpp +++ /dev/null @@ -1,995 +0,0 @@ -/* Kernels Tests - * - * From: https://github.com/PokemonAutomation/ - * - */ - - -#include "Common/Compiler.h" -#include "Common/Cpp/Color.h" -#include "Common/Cpp/CpuId/CpuId.h" -#include "Common/Cpp/Time.h" -#include "CommonFramework/ImageTypes/BinaryImage.h" -#include "CommonFramework/ImageTools/ImageBoxes.h" -#include "CommonFramework/ImageTypes/ImageRGB32.h" -#include "CommonFramework/ImageTypes/ImageViewRGB32.h" -#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix.h" -#ifdef PA_AutoDispatch_arm64_20_M1 - #include "Kernels/BinaryMatrix/Kernels_BinaryMatrixTile_64x8_arm64_NEON.h" - #include "Kernels/PartialWordAccess/Kernels_PartialWordAccess_arm64_NEON.h" - #include "Kernels/Waterfill/Kernels_Waterfill_Core_64x8_arm64_NEON.h" -#endif -#include "Kernels/BinaryMatrix/Kernels_BinaryMatrix_Arch_64xH_Default.h" -#include "Kernels/BinaryMatrix/Kernels_BinaryMatrixTile_64x4_Default.h" -#include "Kernels/BinaryMatrix/Kernels_BinaryMatrixTile_64xH_Default.h" -#include "Kernels/BinaryImageFilters/Kernels_BinaryImage_BasicFilters.h" -#include "Kernels/ImageFilters/Kernels_ImageFilter_Basic.h" -#include "Kernels/ImageFilters/RGB32_Range/Kernels_ImageFilter_RGB32_Range.h" -#include "Kernels/ImageFilters/RGB32_EuclideanDistance/Kernels_ImageFilter_RGB32_Euclidean.h" -#include "Kernels/ImageScaleBrightness/Kernels_ImageScaleBrightness.h" -#include "Kernels/Waterfill/Kernels_Waterfill.h" -#include "Kernels/Waterfill/Kernels_Waterfill_Session.h" -#include "Kernels/Waterfill/Kernels_Waterfill_Core_64xH_Default.h" -#include "Kernels/Waterfill/Kernels_Waterfill_Routines.h" -#include "Kernels_Tests.h" -#include "TestUtils.h" - -#include -#include -using std::cout; -using std::cerr; -using std::endl; -using std::flush; - -namespace PokemonAutomation{ - -using namespace Kernels; - -namespace Kernels{} - -namespace{ - - - -} - -int test_binary_matrix_tile(); - - -int test_kernels_ImageScaleBrightness(const ImageViewRGB32& image){ - ImageRGB32 new_image = image.copy(); - - int num_iterations = 500; - auto time_start = current_time(); - for (int i = 0; i < num_iterations; i++){ - scale_brightness(new_image.width(), new_image.height(), new_image.data(), new_image.bytes_per_row(), 1.2f, 1.3f, 0.5f); - // break; - } - auto time_end = current_time(); - const auto ms = std::chrono::duration_cast(time_end - time_start).count(); - cout << "Time: " << ms << " ms, " << ms / 1000. << " s" << endl; - - // new_image.save("./output.png"); - - return 0; -} - - -int test_kernels_BinaryMatrix(const ImageViewRGB32& image){ - - if (test_binary_matrix_tile() != 0){ - return 1; - } - - const size_t width = image.width(), height = image.height(); - - const Color min_color(0, 0, 0), max_color(63, 63, 63); - const uint32_t mins = uint32_t(min_color), maxs = uint32_t(max_color); - - auto binary_matrix = make_PackedBinaryMatrix(get_BinaryMatrixType(), width, height); - - auto time_start = current_time(); - compress_rgb32_to_binary_range( - image.data(), image.bytes_per_row(), *binary_matrix, mins, maxs - ); - auto time_end = current_time(); - size_t ns = std::chrono::duration_cast(time_end - time_start).count(); - double ms = ns / 1000000.; - cout << "One binary matrix creation. time: " << ms << " ms" << endl; - - size_t error_count = 0; - for (size_t y = 0; y < height; y++){ - for (size_t x = 0; x < width; x++){ - const Color color(image.pixel(x, y)); - bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); - in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); - in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); - in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); - - const bool v_default = binary_matrix->get(x, y); - - if (error_count < 10){ - if (v_default != in_range){ - cout << "Error: matrix (" << x << ", " << y << ") got " - << v_default << " but GT is " << in_range << endl; - ++error_count; - } - } - } - } - if (error_count){ - return 1; - } - - - // We try to wait for three seconds: - const size_t num_iters = size_t(3000 / ms); - time_start = current_time(); - for (size_t i = 0; i < num_iters; i++){ - compress_rgb32_to_binary_range( - image.data(), image.bytes_per_row(), *binary_matrix, mins, maxs - ); - } - time_end = current_time(); - ms = (double)std::chrono::duration_cast(time_end - time_start).count(); - cout << "Running " << num_iters << " iters, average creation impl. time: " << ms / (double)num_iters << " ms" << endl; - - // cout << binary_matrix->dump() << flush; - - return 0; -} - -int test_kernels_FilterRGB32Range(const ImageViewRGB32& image){ - const size_t width = image.width(), height = image.height(); - cout << "Testing filter_rgb32_range(), image size " << width << " x " << height << endl; - - Color min_color(0, 0, 0); - Color max_color(63, 63, 63); - // Color max_color(238, 24, 42); - - const uint32_t mins = uint32_t(min_color); - const uint32_t maxs = uint32_t(max_color); - - ImageRGB32 image_out(image.width(), image.height()); - ImageRGB32 image_out_2(image.width(), image.height()); - size_t pixels_in_range = 0; - - const bool replace_color_within_range = true; - auto time_start = current_time(); - // auto new_image = filter_rgb32_range(image, mins, maxs, COLOR_WHITE, replace_color_within_range); - pixels_in_range = Kernels::filter_rgb32_range( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out.data(), image_out.bytes_per_row(), - (uint32_t)COLOR_WHITE, replace_color_within_range, - mins, maxs - ); - auto time_end = current_time(); - auto ns = std::chrono::duration_cast(time_end - time_start).count(); - auto ms = ns / 1000000.; - cout << "One filter time: " << ms << " ms" << endl; - - size_t pixels_in_range_2 = Kernels::filter_rgb32_range( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out_2.data(), image_out_2.bytes_per_row(), - (uint32_t)COLOR_WHITE, !replace_color_within_range, - mins, maxs - ); - - TEST_RESULT_EQUAL(pixels_in_range, pixels_in_range_2); - - size_t actual_num_pixels_in_range = 0; - size_t error_count = 0; - for (size_t y = 0; y < height; y++){ - for (size_t x = 0; x < width; x++){ - const Color color(image.pixel(x, y)); - const Color new_color(image_out.pixel(x, y)); - const Color new_color_2(image_out_2.pixel(x, y)); - bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); - in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); - in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); - in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); - actual_num_pixels_in_range += in_range; - if (error_count < 10){ - // Print first 10 errors: - if (in_range && new_color != COLOR_WHITE){ - cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should be in range but not found by the function" << endl; - ++error_count; - }else if (in_range == false && new_color != color){ - cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should not be changed by the function" << endl; - ++error_count; - } - - if (in_range && new_color_2 != color){ - cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should not be changed by the function" << endl; - ++error_count; - }else if (in_range == false && new_color_2 != COLOR_WHITE){ - cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should not be in range but not found by the function" << endl; - ++error_count; - } - } - } - } - cout << "Found " << actual_num_pixels_in_range << " pixels in range" << endl; - if (pixels_in_range != actual_num_pixels_in_range){ - cout << "Error: wrong pixels in range: " << pixels_in_range << " actual: " << actual_num_pixels_in_range << endl; - return 1; - } - - if (error_count){ - return 1; - } - - // We try to wait for three seconds: - const size_t num_iters = size_t(3000 / ms); - time_start = current_time(); - for (size_t i = 0; i < num_iters; i++){ - Kernels::filter_rgb32_range( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out.data(), image_out.bytes_per_row(), - (uint32_t)COLOR_WHITE, replace_color_within_range, - mins, maxs - ); - } - time_end = current_time(); - ms = (double)std::chrono::duration_cast(time_end - time_start).count(); - cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; - - return 0; -} - - -int test_kernels_FilterRGB32Euclidean(const ImageViewRGB32& image){ - const size_t width = image.width(); - const size_t height = image.height(); - cout << "Testing test_kernels_FilterRGB32Euclidean(), image size " << width << " x " << height << endl; - - Color middle_color = Color(image.pixel(width/2, height/2)); - cout << "Expected color: " << middle_color.to_string() << endl; - - double max_dist = 50.0; - size_t max_dist2 = size_t(max_dist * max_dist); - - ImageRGB32 image_out(image.width(), image.height()); - ImageRGB32 image_out_2(image.width(), image.height()); - size_t pixels_in_range = 0; - - const bool replace_color_within_range = true; - auto time_start = current_time(); - pixels_in_range = Kernels::filter_rgb32_euclidean( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out.data(), image_out.bytes_per_row(), - (uint32_t)COLOR_WHITE, replace_color_within_range, - uint32_t(middle_color), max_dist - ); - auto time_end = current_time(); - auto ns = std::chrono::duration_cast(time_end - time_start).count(); - auto ms = ns / 1000000.; - cout << "One filter time: " << ms << " ms" << endl; - - size_t pixels_in_range_2 = Kernels::filter_rgb32_euclidean( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out_2.data(), image_out_2.bytes_per_row(), - (uint32_t)COLOR_WHITE, !replace_color_within_range, - uint32_t(middle_color), max_dist - ); - - TEST_RESULT_EQUAL(pixels_in_range, pixels_in_range_2); - - size_t actual_num_pixels_in_range = 0; - size_t error_count = 0; - for (size_t y = 0; y < height; y++){ - for (size_t x = 0; x < width; x++){ - const Color color(image.pixel(x, y)); - const Color new_color(image_out.pixel(x, y)); - const Color new_color_2(image_out_2.pixel(x, y)); - int red_dif = (color.red() - middle_color.red()); - int green_dif = (color.green() - middle_color.green()); - int blue_dif = (color.blue() - middle_color.blue()); - size_t color_dist2 = red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif; - bool in_range = color_dist2 <= max_dist2; - actual_num_pixels_in_range += in_range; - if (error_count < 10){ - // Print first 10 errors: - if (in_range && new_color != COLOR_WHITE){ - cout << "Error: wrong filter result: old color " << color.to_string() - << ", (x,y) = (" << x << ", " << y << ")" - << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 - << ", should be in range but not found by the function" << endl; - ++error_count; - }else if (in_range == false && new_color != color){ - cout << "Error: wrong filter result: old color " << color.to_string() - << ", (x,y) = (" << x << ", " << y << ")" - << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 - << ", should not be changed by the function" << endl; - ++error_count; - } - - if (in_range && new_color_2 != color){ - cout << "Error: wrong inverse filter result: old color " << color.to_string() - << ", (x,y) = (" << x << ", " << y << ")" - << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 - << ", should not be changed by the function" << endl; - ++error_count; - }else if (in_range == false && new_color_2 != COLOR_WHITE){ - cout << "Error: wrong inverse filter result: old color " << color.to_string() - << ", (x,y) = (" << x << ", " << y << ")" - << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 - << ", should not be in range but not found by the function" << endl; - ++error_count; - } - } - } - } - cout << "Found " << actual_num_pixels_in_range << " pixels in range" << endl; - if (pixels_in_range != actual_num_pixels_in_range){ - cout << "Error: wrong pixels in range: " << pixels_in_range << " actual: " << actual_num_pixels_in_range << endl; - return 1; - } - - if (error_count){ - return 1; - } - - // We try to wait for three seconds: - const size_t num_iters = size_t(3000 / ms); - time_start = current_time(); - for (size_t i = 0; i < num_iters; i++){ - pixels_in_range = Kernels::filter_rgb32_euclidean( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out.data(), image_out.bytes_per_row(), - (uint32_t)COLOR_WHITE, replace_color_within_range, - uint32_t(middle_color), max_dist - ); - } - time_end = current_time(); - ms = (double)std::chrono::duration_cast(time_end - time_start).count(); - cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; - - return 0; -} - -int test_kernels_ToBlackWhiteRGB32Range(const ImageViewRGB32& image){ - const size_t width = image.width(); - const size_t height = image.height(); - cout << "Testing to_black_white_rgb32_range(), image size " << width << " x " << height << endl; - - Color min_color(0, 0, 0); - // Color min_color(0); - - Color max_color(63, 63, 63); - // Color max_color(255, 255, 255); - // Color max_color(238, 24, 42); - cout << "min color: " << min_color.to_string() << " max color: " << max_color.to_string() << endl; - - const uint32_t mins = uint32_t(min_color); - const uint32_t maxs = uint32_t(max_color); - - ImageRGB32 image_out(image.width(), image.height()); - ImageRGB32 image_out_2(image.width(), image.height()); - size_t pixels_in_range = 0; - - const bool in_range_black = true; - auto time_start = current_time(); - // auto new_image = filter_rgb32_range(image, mins, maxs, COLOR_WHITE, replace_color_within_range); - pixels_in_range = Kernels::to_blackwhite_rgb32_range( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out.data(), image_out.bytes_per_row(), - in_range_black, - mins, maxs - ); - auto time_end = current_time(); - auto ns = std::chrono::duration_cast(time_end - time_start).count(); - auto ms = ns / 1000000.; - cout << "One filter time: " << ms << " ms" << endl; - - size_t pixels_in_range_2 = Kernels::to_blackwhite_rgb32_range( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out_2.data(), image_out_2.bytes_per_row(), - !in_range_black, - mins, maxs - ); - - TEST_RESULT_EQUAL(pixels_in_range, pixels_in_range_2); - - size_t actual_num_pixels_in_range = 0; - size_t error_count = 0; - for (size_t y = 0; y < height; y++){ - for (size_t x = 0; x < width; x++){ - const Color color(image.pixel(x, y)); - const Color new_color(image_out.pixel(x, y)); - const Color new_color_2(image_out_2.pixel(x, y)); - bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); - in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); - in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); - in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); - actual_num_pixels_in_range += in_range; - if (error_count < 10){ - // Print first 10 errors: - if (in_range && new_color != COLOR_BLACK){ - cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should be black due to in range but not so" << endl; - ++error_count; - }else if (in_range == false && new_color != COLOR_WHITE){ - cout << "Error: wrong filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should be white due to out of range but not so" << endl; - ++error_count; - } - - if (in_range && new_color_2 != COLOR_WHITE){ - cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should be white due to in range but not so" << endl; - ++error_count; - }else if (in_range == false && new_color_2 != COLOR_BLACK){ - cout << "Error: wrong inverse filter result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should be black due to out of range but not so" << endl; - ++error_count; - } - } - } - } - cout << "Found " << actual_num_pixels_in_range << " pixels in range" << endl; - if (pixels_in_range != actual_num_pixels_in_range){ - cout << "Error: wrong pixels in range: " << pixels_in_range << " actual: " << actual_num_pixels_in_range << endl; - return 1; - } - - if (error_count){ - return 1; - } - - // We try to wait for three seconds: - const size_t num_iters = size_t(3000 / ms); - time_start = current_time(); - for (size_t i = 0; i < num_iters; i++){ - Kernels::to_blackwhite_rgb32_range( - image.data(), image.bytes_per_row(), image.width(), image.height(), - image_out.data(), image_out.bytes_per_row(), - in_range_black, - mins, maxs - ); - } - time_end = current_time(); - ms = (double)std::chrono::duration_cast(time_end - time_start).count(); - cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; - - return 0; -} - -int test_kernels_FilterByMask(const ImageViewRGB32& image){ - const size_t width = image.width(), height = image.height(); - cout << "Image width " << width << " height " << height << endl; - - const Color min_color(0, 0, 0), max_color(63, 63, 63); - const uint32_t mins = uint32_t(min_color), maxs = uint32_t(max_color); - - auto binary_matrix = make_PackedBinaryMatrix(get_BinaryMatrixType(), width, height); - compress_rgb32_to_binary_range( - image.data(), image.bytes_per_row(), *binary_matrix, mins, maxs - ); - ImageRGB32 new_image = image.copy(); - ImageRGB32 new_image_2 = image.copy(); - - Color replacement_color = COLOR_WHITE; - bool replace_zero_bits = true; - - auto time_start = current_time(); - filter_by_mask(*binary_matrix, new_image.data(), new_image.bytes_per_row(), uint32_t(replacement_color), replace_zero_bits); - auto time_end = current_time(); - auto ns = std::chrono::duration_cast(time_end - time_start).count(); - auto ms = ns / 1000000.; - cout << "One Filter by mask call. time: " << ms << " ms" << endl; - - filter_by_mask(*binary_matrix, new_image_2.data(), new_image_2.bytes_per_row(), uint32_t(replacement_color), !replace_zero_bits); - - size_t error_count = 0; - - for (size_t y = 0; y < height; y++){ - for (size_t x = 0; x < width; x++){ - const Color color(image.pixel(x, y)); - const Color new_color(new_image.pixel(x, y)); - const Color new_color_2(new_image_2.pixel(x, y)); - bool in_range = (min_color.alpha() <= color.alpha() && color.alpha() <= max_color.alpha()); - in_range = in_range && (min_color.red() <= color.red() && color.red() <= max_color.red()); - in_range = in_range && (min_color.green() <= color.green() && color.green() <= max_color.green()); - in_range = in_range && (min_color.blue() <= color.blue() && color.blue() <= max_color.blue()); - - if (error_count <= 10){ - if (in_range && new_color != color){ - cout << "Error: wrong filter(replace_zero_bits) result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should not be changed due to being one bit but not so" << endl; - ++error_count; - }else if (!in_range && new_color != replacement_color){ - cout << "Error: wrong filter(replace_zero_bits) result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should be changed due to being zero bit but not so" << endl; - ++error_count; - } - - if (in_range && new_color_2 != replacement_color){ - cout << "Error: wrong filter(replace_one_bits) result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should be changed due to being one bit but not so" << endl; - ++error_count; - }else if (!in_range && new_color_2 != color){ - cout << "Error: wrong filter(replace_one_bits) result: old color " << color.to_string() << ", (x,y) = " - << x << ", " << y << ", should not be changed due to being zero bit but not so" << endl; - ++error_count; - } - } - } - } - - if (error_count){ - return 1; - } - - // We try to wait for three seconds: - const size_t num_iters = size_t(3000 / ms); - time_start = current_time(); - for (size_t i = 0; i < num_iters; i++){ - filter_by_mask(*binary_matrix, new_image.data(), new_image.bytes_per_row(), uint32_t(replacement_color), replace_zero_bits); - } - time_end = current_time(); - ms = (double)std::chrono::duration_cast(time_end - time_start).count(); - cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; - - return 0; -} - -int test_kernels_CompressRGB32ToBinaryEuclidean(const ImageViewRGB32& image){ - const size_t width = image.width(); - const size_t height = image.height(); - cout << "Testing test_kernels_CompressRGB32ToBinaryEuclidean(), image size " << width << " x " << height << endl; - - Color middle_color = Color(image.pixel(width/2, height/2)); - cout << "Expected color: " << middle_color.to_string() << endl; - - double max_dist = 50.0; - size_t max_dist2 = size_t(max_dist * max_dist); - - PackedBinaryMatrix matrix(image.width(), image.height()); - - auto time_start = current_time(); - Kernels::compress_rgb32_to_binary_euclidean( - image.data(), image.bytes_per_row(), matrix, - uint32_t(middle_color), max_dist - ); - auto time_end = current_time(); - auto ns = std::chrono::duration_cast(time_end - time_start).count(); - auto ms = ns / 1000000.; - cout << "One filter time: " << ms << " ms" << endl; - - size_t error_count = 0; - for (size_t y = 0; y < height; y++){ - for (size_t x = 0; x < width; x++){ - const Color color(image.pixel(x, y)); - int red_dif = (color.red() - middle_color.red()); - int green_dif = (color.green() - middle_color.green()); - int blue_dif = (color.blue() - middle_color.blue()); - size_t color_dist2 = red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif; - bool in_range = color_dist2 <= max_dist2; - if (error_count < 10){ - // Print first 10 errors: - if (in_range && matrix.get(x, y) == false){ - cout << "Error: wrong filter result: old color " << color.to_string() - << ", (x,y) = (" << x << ", " << y << ")" - << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 - << ", should be in range but not set on matrix" << endl; - ++error_count; - }else if (in_range == false && matrix.get(x, y) == true){ - cout << "Error: wrong filter result: old color " << color.to_string() - << ", (x,y) = (" << x << ", " << y << ")" - << ", dist2 " << color_dist2 << ", max dist2 " << max_dist2 - << ", should not be in range but set on matrix" << endl; - ++error_count; - } - } - } - } - if (error_count){ - return 1; - } - - // We try to wait for three seconds: - const size_t num_iters = size_t(3000 / ms); - time_start = current_time(); - for (size_t i = 0; i < num_iters; i++){ - Kernels::compress_rgb32_to_binary_euclidean( - image.data(), image.bytes_per_row(), matrix, - uint32_t(middle_color), max_dist - ); - } - time_end = current_time(); - ms = (double)std::chrono::duration_cast(time_end - time_start).count(); - cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; - - return 0; -} - - - - -int test_kernels_Waterfill(const ImageViewRGB32& image){ - const size_t width = image.width(); - const size_t height = image.height(); - cout << "Testing test_kernels_Waterfill(), image size " << width << " x " << height << endl; - - PackedBinaryMatrix matrix(width, height); - uint32_t mins = combine_rgb(0, 0, 0); - // uint32_t maxs = combine_rgb(255, 255, 255); - uint32_t maxs = combine_rgb(63, 63, 63); - Kernels::compress_rgb32_to_binary_range( - image.data(), image.bytes_per_row(), - matrix, mins, maxs - ); - - PackedBinaryMatrix source_matrix = matrix.copy(); - - PackedBinaryMatrix gt_matrix = matrix.copy(); - Kernels::PackedBinaryMatrix_IB& gt_matrix_ib = gt_matrix; - - size_t min_area = 10; - std::vector gt_objects; - bool gt_computed = false; - -#ifdef PA_AutoDispatch_arm64_20_M1 - if (CPU_CAPABILITY_CURRENT.OK_M1){ - using Waterfill_64x8_Default = Kernels::Waterfill::Waterfill_64xH_Default; - gt_objects = Kernels::Waterfill::find_objects_inplace( - static_cast(gt_matrix_ib).get(), - min_area - ); - gt_computed = true; - } -#endif - if (gt_computed == false){ - using Waterfill_64x4_Default = Kernels::Waterfill::Waterfill_64xH_Default; - gt_objects = Kernels::Waterfill::find_objects_inplace( - static_cast(gt_matrix_ib).get(), - min_area - ); - } - cout << "num objects: " << gt_objects.size() << endl; - - auto time_start = current_time(); - std::vector objects = Kernels::Waterfill::find_objects_inplace(matrix, min_area); - auto time_end = current_time(); - auto ns = std::chrono::duration_cast(time_end - time_start).count(); - auto ms = ns / 1000000.; - cout << "One waterfill time: " << ms << " ms" << endl; - - for (size_t i = 0; i < objects.size(); ++i){ - TEST_RESULT_COMPONENT_EQUAL(objects[i].area, gt_objects[i].area, "object " + std::to_string(i) + " area"); - TEST_RESULT_COMPONENT_EQUAL(objects[i].min_x, gt_objects[i].min_x, "object " + std::to_string(i) + " min_x"); - TEST_RESULT_COMPONENT_EQUAL(objects[i].min_y, gt_objects[i].min_y, "object " + std::to_string(i) + " min_y"); - TEST_RESULT_COMPONENT_EQUAL(objects[i].max_x, gt_objects[i].max_x, "object " + std::to_string(i) + " max_x"); - TEST_RESULT_COMPONENT_EQUAL(objects[i].max_y, gt_objects[i].max_y, "object " + std::to_string(i) + " max_y"); - } - - // We try to wait for three seconds: - const size_t num_iters = size_t(3000 / ms); - time_start = current_time(); - for (size_t i = 0; i < num_iters; i++){ - matrix = source_matrix.copy(); - objects = Kernels::Waterfill::find_objects_inplace(matrix, min_area); - } - time_end = current_time(); - ms = (double)std::chrono::duration_cast(time_end - time_start).count(); - cout << "Running " << num_iters << " iters, avg filter time: " << ms / num_iters << " ms" << endl; - - - - return 0; -} - -// Additional tests on binary matrix tile implementation -template int test_binary_matrix_tile_t(){ - size_t num_iters = 100000; - size_t sum = 0; - WallClock time_start, time_end; - size_t ns = 0; - double ms = 0; - - Tile tile; - // BinaryTile_64x8_arm64_NEON tile; - // BinaryTile_64xH_Default<8, BinaryMatrixType::arm64x8_x64_NEON> tile; - for (size_t height = 0; height < tile.HEIGHT; ++height){ - for (size_t width = 0; width < tile.WIDTH; ++width){ - tile.set_zero(); - tile.set_ones(width, height); - for (size_t y = 0; y < tile.HEIGHT; ++y){ - for (size_t x = 0; x < tile.WIDTH; ++x){ - bool gt = y < height && x < width; - if (gt != tile.get_bit(x, y)){ - cout << "Tile::set_ones(width = " << width << ", height = " << height << "), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be " << gt << endl; - return 1; - } - } - } - } - } - - sum = 0; - time_start = current_time(); - for (size_t i = 0; i < num_iters; ++i){ - tile.set_ones(32, 4); - sum += tile.row(0) + tile.row(1) + tile.row(2) + tile.row(3); - } - time_end = current_time(); - ns = std::chrono::duration_cast(time_end - time_start).count(); - ms = ns / 1000000.; - cout << "Execution enforcer: " << sum << endl; - cout << "Running " << num_iters << " iters, Tile::set_ones() time: " << ms / num_iters << " ms" << endl; - - for (size_t height = 0; height < tile.HEIGHT; ++height){ - for (size_t width = 0; width < tile.WIDTH; ++width){ - tile.set_ones(); - tile.clear_padding(width, height); - for (size_t y = 0; y < tile.HEIGHT; ++y){ - for (size_t x = 0; x < tile.WIDTH; ++x){ - bool gt = y < height && x < width; - if (gt != tile.get_bit(x, y)){ - cout << "Tile::clear_padding(width = " << width << ", height = " << height << "), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be " << gt << endl; - return 1; - } - } - } - } - } - sum = 0; - time_start = current_time(); - for (size_t i = 0; i < num_iters; ++i){ - tile.clear_padding(32, 4); - sum += tile.row(0) + tile.row(1) + tile.row(2) + tile.row(3); - } - time_end = current_time(); - ns = std::chrono::duration_cast(time_end - time_start).count(); - ms = ns / 1000000.; - cout << "Execution enforcer: " << sum << endl; - cout << "Running " << num_iters << " iters, Tile::clear_padding() time: " << ms / num_iters << " ms" << endl; - - tile.set_ones(35, 3); - tile.invert(); - for (size_t y = 0; y < tile.HEIGHT; ++y){ - for (size_t x = 0; x < tile.WIDTH; ++x){ - bool gt = !(x < 35 && y < 3); - if (gt != tile.get_bit(x, y)){ - cout << "Tile::invert(), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be " << gt << endl; - return 1; - } - } - } - - auto tile2 = tile; - tile.set_ones(35, 3); - tile2.set_ones(13, 6); - tile.andnot(tile2); - for (size_t y = 0; y < tile.HEIGHT; ++y){ - for (size_t x = 0; x < tile.WIDTH; ++x){ - bool gt = !(x < 13 && y < 6) && (x < 35 && y < 3); - if (gt != tile.get_bit(x, y)){ - cout << "Tile::andnot(), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be " << gt << endl; - return 1; - } - } - } - - tile.set_ones(35, 3); - { - uint64_t top_row = tile.top(); - for (size_t x = 0; x < tile.WIDTH; ++x){ - uint64_t bit = ((top_row >> x) & 1); - uint64_t gt = uint64_t(x < 35); - if (bit != gt){ - cout << "Tile::top(), tile (x = " << - x << "), wrong bit. Should be " << gt << endl; - return 1; - } - } - } - { - tile.set_zero(); - uint64_t& top_row = tile.top(); - top_row = 0xFFFFFFFFFFFFFFFF; - uint64_t top = tile.top(); - for (size_t x = 0; x < tile.WIDTH; ++x){ - uint64_t bit = ((top >> x) & 1); - if (bit != 1){ - cout << "& Tile::top(), tile (x = " << - x << "), wrong bit. Should be 1" << endl; - return 1; - } - } - } - { - tile.set_zero(); - uint64_t& bottom_row = tile.bottom(); - bottom_row = 0xFFFFFFFFFFFFFFFF; - uint64_t bottom = tile.bottom(); - for (size_t x = 0; x < tile.WIDTH; ++x){ - uint64_t bit = ((bottom >> x) & 1); - if (bit != 1){ - cout << "& Tile::bottom(), tile (x = " << - x << "), wrong bit. Should be 1" << endl; - return 1; - } - } - } - tile.set_zero(); - for (size_t y = 0; y < tile.HEIGHT; ++y){ - for (size_t x = 0; x < tile.WIDTH; ++x){ - tile.set_bit(x, y); - if (1 != tile.get_bit(x, y)){ - cout << "Tile::set_bit(), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be 1" << endl; - return 1; - } - tile.set_bit(x, y, 1); - if (1 != tile.get_bit(x, y)){ - cout << "Tile::set_bit(), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be 1" << endl; - return 1; - } - tile.set_bit(x, y, 0); - if (0 != tile.get_bit(x, y)){ - cout << "Tile::set_bit(), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be 0" << endl; - return 1; - } - } - } - - std::srand(0); - auto src_tile = tile; - auto dst_tile = tile; - for (size_t num_tests = 0; num_tests < 30; ++num_tests){ - src_tile.set_zero(); - dst_tile.set_zero(); - for (size_t y = 0; y < tile.HEIGHT; ++y){ - for (size_t x = 0; x < tile.WIDTH; ++x){ - src_tile.set_bit(x, y, std::rand() % 2); - dst_tile.set_bit(x, y, std::rand() % 2); - } - } - - auto test_copy_to_shift = [&](size_t shift_x, size_t shift_y, const std::string& function_name, - std::function gt_test, - std::function get_src_bit_test){ - // check against GT - for (size_t y = 0; y < tile.HEIGHT; ++y){ - for (size_t x = 0; x < tile.WIDTH; ++x){ - bool gt = false; - if (gt_test(shift_x, shift_y, x, y)){ - gt = dst_tile.get_bit(x, y) || get_src_bit_test(shift_x, shift_y, x, y); - // cout << "set the source bit with ||" << endl; - }else{ - // cout << "Use the original dest bit" << endl; - gt = dst_tile.get_bit(x, y); - } - if (tile.get_bit(x, y) != gt){ - cout << "Tile::" << function_name << "(shift_x=" << shift_x << ", shift_y=" << shift_y << "), tile (x = " << - x << ", y = " << y << "), wrong bit. Should be " << gt << endl; - cout << src_tile.dump() << endl; - cout << dst_tile.dump() << endl; - cout << tile.dump() << endl; - return 1; - } - } - } - return 0; - }; - - for (size_t shift_y = 0; shift_y < tile.HEIGHT; ++shift_y){ - for (size_t shift_x = 0; shift_x < tile.WIDTH; ++shift_x){ - tile = dst_tile; - src_tile.copy_to_shift_pp(tile, shift_x, shift_y); - auto gt_test_pp = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ - return x + shift_x < tile.WIDTH && y + shift_y < tile.HEIGHT; - }; - auto get_src_bit_test_pp = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ - return src_tile.get_bit(x + shift_x, y + shift_y); - }; - if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_pp", gt_test_pp, get_src_bit_test_pp) != 0){ - return 1; - } - - tile = dst_tile; - src_tile.copy_to_shift_np(tile, shift_x, shift_y); - auto gt_test_np = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ - return x >= shift_x && y + shift_y < tile.HEIGHT; - }; - auto get_src_bit_test_np = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ - return src_tile.get_bit(x - shift_x, y + shift_y); - }; - if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_np", gt_test_np, get_src_bit_test_np) != 0){ - return 1; - } - - tile = dst_tile; - src_tile.copy_to_shift_pn(tile, shift_x, shift_y); - auto gt_test_pn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ - return x + shift_x < tile.WIDTH && y >= shift_y; - }; - auto get_src_bit_test_pn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ - return src_tile.get_bit(x + shift_x, y - shift_y); - }; - if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_pn", gt_test_pn, get_src_bit_test_pn) != 0){ - return 1; - } - - tile = dst_tile; - src_tile.copy_to_shift_nn(tile, shift_x, shift_y); - auto gt_test_nn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y) -> bool{ - return x >= shift_x && y >= shift_y; - }; - auto get_src_bit_test_nn = [&](size_t shift_x, size_t shift_y, size_t x, size_t y){ - return src_tile.get_bit(x - shift_x, y - shift_y); - }; - if (test_copy_to_shift(shift_x, shift_y, "copy_to_shift_nn", gt_test_nn, get_src_bit_test_nn) != 0){ - return 1; - } - } - } - } - - cout << "Tile tests passed" << endl; - return 0; -} - -int test_binary_matrix_tile(){ -#ifdef PA_AutoDispatch_arm64_20_M1 - if (test_binary_matrix_tile_t() != 0){ - return 1; - } - - for (size_t num_bytes = 0; num_bytes <= 16; ++num_bytes){ - PartialWordAccess_arm64_NEON partial(num_bytes); - - uint8_t buffer[48]; - for (int i = 0; i < 48; i++){ - buffer[i] = 99; - } - for (uint8_t i = 0; i < 16; i++){ - buffer[i+16] = i; - } - - // uint8x16_t x = partial.load(buffer+16); - uint8x16_t x = partial.load_int_no_read_past_end(buffer+16); - for (size_t i = 0; i < num_bytes; ++i){ - if (x[i] != buffer[i+16]){ - cout << "Error: PartialWordAccess_arm64_NEON(" << num_bytes << ")::load_int_no_read_past_end(), i = " << i << " is " << int(x[i]) - << ", but should be " << int(buffer[i+16]) << endl; - return 1; - } - } - x = partial.load_int_no_read_before_ptr(buffer+16); - for (size_t i = 0; i < num_bytes; ++i){ - if (x[i] != buffer[i+16]){ - cout << "Error: PartialWordAccess_arm64_NEON(" << num_bytes << ")::load_int_no_read_before_ptr(), i = " << i << " is " << int(x[i]) - << ", but should be " << int(buffer[i+16]) << endl; - return 1; - } - } - for (int i = 0; i < 48; i++){ - buffer[i] = 99; - } - partial.store_int_no_past_end(buffer+16, x); - for (size_t i = 0; i < num_bytes; ++i){ - if (x[i] != buffer[i+16]){ - cout << "Error: PartialWordAccess_arm64_NEON(" << num_bytes << ")::store_int_no_past_end(), i = " << i << " is " << int(buffer[i+16]) - << ", but should be " << int(x[i+16]) << endl; - return 1; - } - } - } -#endif - return 0; -} - -} diff --git a/SerialPrograms/Source/Tests/Kernels_Tests.h b/SerialPrograms/Source/Tests/Kernels_Tests.h deleted file mode 100644 index 6cf4bd66a3..0000000000 --- a/SerialPrograms/Source/Tests/Kernels_Tests.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Kernels Tests - * - * From: https://github.com/PokemonAutomation/ - * - * - */ - - -#ifndef PokemonAutomation_Tests_Kernels_Tests_H -#define PokemonAutomation_Tests_Kernels_Tests_H - -namespace PokemonAutomation{ - -class ImageViewRGB32; - -int test_kernels_ImageScaleBrightness(const ImageViewRGB32& image); - -int test_kernels_BinaryMatrix(const ImageViewRGB32& image); - -int test_kernels_FilterRGB32Range(const ImageViewRGB32& image); - -int test_kernels_FilterRGB32Euclidean(const ImageViewRGB32& image); - -int test_kernels_FilterRGB32Euclidean(const ImageViewRGB32& image); - -int test_kernels_ToBlackWhiteRGB32Range(const ImageViewRGB32& image); - -int test_kernels_FilterByMask(const ImageViewRGB32& image); - -int test_kernels_CompressRGB32ToBinaryEuclidean(const ImageViewRGB32& image); - -int test_kernels_Waterfill(const ImageViewRGB32& image); - - -} - -#endif diff --git a/SerialPrograms/Source/Tests/NintendoSwitch_Tests.cpp b/SerialPrograms/Source/Tests/NintendoSwitch_Tests.cpp deleted file mode 100644 index 75eca46c9b..0000000000 --- a/SerialPrograms/Source/Tests/NintendoSwitch_Tests.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* NintendoSwitch Tests - * - * From: https://github.com/PokemonAutomation/ - * - */ - - -#include "Common/Compiler.h" -#include "Common/Cpp/Time.h" -#include "CommonFramework/Logging/Logger.h" -#include "CommonFramework/ImageTypes/ImageRGB32.h" -#include "CommonFramework/ImageTypes/ImageViewRGB32.h" -#include "CommonFramework/Recording/StreamHistorySession.h" -#include "NintendoSwitch/Controllers/SerialPABotBase/NintendoSwitch_SerialPABotBase_WiredController.h" -#include "NintendoSwitch/Inference/NintendoSwitch_CheckOnlineDetector.h" -#include "NintendoSwitch/Inference/NintendoSwitch_FailedToConnectDetector.h" -#include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" -#include "NintendoSwitch_Tests.h" -#include "TestUtils.h" - -#include -using std::cout; -using std::cerr; -using std::endl; - -namespace PokemonAutomation{ - -using namespace NintendoSwitch; - -int test_NintendoSwitch_CheckOnlineDetector(const ImageViewRGB32& image, bool target){ - CheckOnlineDetector detector{}; - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_NintendoSwitch_FailedToConnectDetector(const ImageViewRGB32& image, bool target){ - FailedToConnectDetector detector{}; - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - -int test_NintendoSwitch_UpdatePopupDetector(const ImageViewRGB32& image, bool target){ - auto& logger = global_logger_command_line(); - DummyBotBase botbase(logger); - SerialPABotBase::SerialPABotBase_Connection connection(logger, ""); - SerialPABotBase_WiredController controller( - logger, connection, - ControllerType::NintendoSwitch_WiredController - ); - DummyVideoFeed video_feed; - DummyVideoOverlay video_overlay; - DummyAudioFeed audio_feed; - StreamHistorySession history(logger); - - ConsoleHandle console(0, logger, controller, video_feed, video_overlay, audio_feed, history); - UpdatePopupDetector detector(console); - bool result = detector.detect(image); - TEST_RESULT_EQUAL(result, target); - return 0; -} - - - -} diff --git a/SerialPrograms/Source/Tests/NintendoSwitch_Tests.h b/SerialPrograms/Source/Tests/NintendoSwitch_Tests.h deleted file mode 100644 index fdffef5712..0000000000 --- a/SerialPrograms/Source/Tests/NintendoSwitch_Tests.h +++ /dev/null @@ -1,22 +0,0 @@ -/* Nintendo Switch Tests - * - * From: https://github.com/PokemonAutomation/ - * - * - */ - - -#ifndef PokemonAutomation_Tests_NintendoSwitch_Tests_H -#define PokemonAutomation_Tests_NintendoSwitch_Tests_H - -namespace PokemonAutomation{ - -class ImageViewRGB32; - -int test_NintendoSwitch_CheckOnlineDetector(const ImageViewRGB32& image, bool target); -int test_NintendoSwitch_FailedToConnectDetector(const ImageViewRGB32& image, bool target); -int test_NintendoSwitch_UpdatePopupDetector(const ImageViewRGB32& image, bool target); - -} - -#endif 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/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: