From f4844b4d3bba8516c77c26b796b388b4c0673660 Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 19 Jan 2026 11:47:42 -0800 Subject: [PATCH 01/11] cache PaddleOCR instances --- .../CommonTools/OCR/OCR_RawPaddleOCR.cpp | 218 ++++++++++++++++++ .../Source/CommonTools/OCR/OCR_RawPaddleOCR.h | 49 ++++ .../Source/CommonTools/OCR/OCR_Routines.cpp | 12 +- .../DevPrograms/TestProgramSwitch.cpp | 6 +- SerialPrograms/cmake/SourceFiles.cmake | 2 + 5 files changed, 276 insertions(+), 11 deletions(-) create mode 100644 SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp create mode 100644 SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp new file mode 100644 index 000000000..8ceb9daaf --- /dev/null +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -0,0 +1,218 @@ +/* Threadpools for PaddleOCR + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include "ML/Inference/ML_PaddleOCRPipeline.h" +#include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Concurrency/SpinLock.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/Logging/Logger.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "OCR_RawOCR.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ +namespace OCR{ + + + +// Thread-safe object pool for PaddleOCR instances for a specific language. +// Allows concurrent OCR operations by maintaining multiple PaddleOCR instances that can be +// checked out, used, and returned. Instances are created lazily on demand. +class PaddlePool{ +public: + PaddlePool(Language language) + : m_language(language) + {} + + // Perform OCR on the given image. Thread-safe - can be called concurrently. + // Checkout pattern: (1) acquire idle instance from pool (or create new one if none available), + // (2) run OCR without holding lock, (3) return instance to idle pool. + std::string run(const ImageViewRGB32& image){ + ML::PaddleOCRPipeline* instance = nullptr; + // Checkout: Try to get an idle instance, create new one if the idle pool is empty. + while (true){ + { + WriteSpinLock lg(m_lock, "PaddlePool::run()1"); + if (!m_idle.empty()){ + instance = m_idle.back(); + m_idle.pop_back(); + break; + } + } + // No idle instance available - create a new one. + add_instance(); + } + + // Perform OCR without holding the lock (allows concurrent OCR operations). +// auto start = current_time(); + std::string str = instance->recognize(image); + +// auto end = current_time(); +// cout << std::chrono::duration_cast(end - start).count() << endl; + // Checkin: Return instance to the idle pool. + { + WriteSpinLock lg(m_lock, "PaddlePool::run()2"); + m_idle.emplace_back(instance); + } + + return str.c_str() == nullptr + ? std::string() + : str.c_str(); + } + + // Create a new PaddleOCR instance and add it to the pool. + // Thread-safe - can be called concurrently (using `m_lock` when modifying the pool). + void add_instance(){ + + global_logger_tagged().log( + "Initializing PaddleOCR (" + language_data(m_language).name + "): " + ); + // Create instance outside lock (initialization is expensive). + std::unique_ptr api = std::make_unique(m_language); + + // if (!api->valid()){ + // throw InternalSystemError(nullptr, PA_CURRENT_FUNCTION, "Could not initialize PaddleOCR."); + // } + + // Add to pool under lock. + WriteSpinLock lg(m_lock, "PaddlePool::add_instance()"); + + m_instances.emplace_back(std::move(api)); + try{ + m_idle.emplace_back(m_instances.back().get()); + }catch (...){ + m_instances.pop_back(); + throw; + } + } + + // Pre-allocate a minimum number of instances to avoid lazy initialization during runtime. + // Useful for warming up the pool before heavy OCR workloads. + void ensure_instances(size_t instances){ + size_t current_instances; + while (true){ + { + ReadSpinLock lg(m_lock, "PaddlePool::ensure_instances()"); + current_instances = m_instances.size(); + } + if (current_instances >= instances){ + break; + } + add_instance(); + } + } + +#if 0 + #ifdef __APPLE__ + #ifdef UNIX_LINK_TESSERACT + ~PaddlePool(){ + for(auto& api : m_instances){ + api.release(); + } + } + #endif + #endif +#endif + +private: + const Language m_language; + + // Concurrency: m_lock protects both m_instances and m_idle vectors. + SpinLock m_lock; + // Owns all created PaddleOCR instances. + std::vector> m_instances; + // Current idle PaddleOCR instances in `m_instances`. + std::vector m_idle; +}; + +// Global singleton managing PaddlePools for all languages. +// Two-level concurrency model: +// Level 1: ocr_pool_lock protects the map (language -> pool creation/lookup) +// Level 2: Each PaddlePool has its own m_lock (instance checkout/checkin) +struct OcrGlobals{ + SpinLock ocr_pool_lock; // Protects ocr_pool map. + std::map ocr_pool; // One pool per language. + + static OcrGlobals& instance(){ + static OcrGlobals globals; + return globals; + } +}; + + +std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ +// static size_t c = 0; +// image.save("ocr-" + std::to_string(c++) + ".png"); + + if (language == Language::None){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); + } + + OcrGlobals& globals = OcrGlobals::instance(); + std::map& ocr_pool = globals.ocr_pool; + + // Get or create the pool for this language (lock only during map access). + std::map::iterator iter; + { + WriteSpinLock lg(globals.ocr_pool_lock, "paddle_ocr_read()"); + iter = ocr_pool.find(language); + if (iter == ocr_pool.end()){ + iter = ocr_pool.emplace(language, language).first; + } + } + // Delegate to pool (which has its own locking for instance management). + std::string ret = iter->second.run(image); + +// global_logger_tagged().log(ret); + + return ret; +} + + +void ensure_paddle_ocr_instances(Language language, size_t instances){ + if (language == Language::None){ + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); + } + + OcrGlobals& globals = OcrGlobals::instance(); + std::map& ocr_pool = globals.ocr_pool; + + // Get or create the pool for this language. + std::map::iterator iter; + { + WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); + iter = ocr_pool.find(language); + if (iter == ocr_pool.end()){ + iter = ocr_pool.emplace(language, language).first; + } + } + // Delegate to pool's ensure_instances (which handles its own locking). + iter->second.ensure_instances(instances); +} + +void clear_paddle_ocr_cache(){ + OcrGlobals& globals = OcrGlobals::instance(); + std::map& ocr_pool = globals.ocr_pool; + WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); + ocr_pool.clear(); // Destroys all pools and their instances. +} + + + + +} +} + + + + diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h new file mode 100644 index 000000000..3e74d655b --- /dev/null +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h @@ -0,0 +1,49 @@ +/* Threadpools for PaddleOCR + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_CommonTools_OCR_RawPaddleOCR_H +#define PokemonAutomation_CommonTools_OCR_RawPaddleOCR_H + +#include +#include "CommonFramework/Language.h" + +namespace PokemonAutomation{ + class ImageViewRGB32; +namespace OCR{ + + + + +// OCR the image in the specified language. +// Main OCR entry point. Performs OCR on the image using the specified language. +// Thread-safe: internally uses a pool of PaddleOCR instances, able to accept +// multiple concurrent calls without delay or queueing. +// It creates a new PaddleOCR instance if no available idle instance. You can +// call `ensure_instances()` to pre-warm to pool with a given number of instances. +// +std::string paddle_ocr_read( + Language language, + const ImageViewRGB32& image +); + + +// Pre-warm the PaddleOCR instance pool for a language by ensuring a minimum +// number of instances exist. +// Avoids lazy initialization delays during runtime. Thread-safe. +// Call this if you expect to need to do many OCR instances in parallel and you +// want to preload the OCR instances. +void ensure_paddle_ocr_instances(Language language, size_t instances); + +// Clear all PaddleOCR instances for all languages. Used for cleanup or +// forcing re-initialization. +// This is not safe to call while in any OCR is still running! +void clear_paddle_ocr_cache(); + + + +} +} +#endif diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp index 02d61bad7..16998b096 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_Routines.cpp @@ -8,7 +8,7 @@ #include "CommonFramework/Tools/GlobalThreadPools.h" #include "CommonFramework/GlobalSettingsPanel.h" #include "CommonTools/Images/ImageFilter.h" -#include "ML/Inference/ML_PaddleOCRPipeline.h" +#include "OCR_RawPaddleOCR.h" #include "OCR_RawOCR.h" #include "OCR_DictionaryMatcher.h" #include "OCR_Routines.h" @@ -45,11 +45,6 @@ StringMatchResult multifiltered_OCR( double pixels_inv = 1. / (image.width() * image.height()); bool use_paddle_ocr = GlobalSettings::instance().USE_PADDLE_OCR; - std::unique_ptr paddle_ocr; - if (use_paddle_ocr) { - // Initialize only if the setting is enabled - paddle_ocr = std::make_unique(language); - } // Run all the filters. SpinLock lock; @@ -60,7 +55,7 @@ StringMatchResult multifiltered_OCR( std::string text; if (use_paddle_ocr) { - text = paddle_ocr->recognize(filtered.first); + text = paddle_ocr_read(language, filtered.first); }else{ text = ocr_read(language, filtered.first, psm); } @@ -117,8 +112,7 @@ StringMatchResult dictionary_OCR( // Run all the filters. std::string text; if (GlobalSettings::instance().USE_PADDLE_OCR){ - ML::PaddleOCRPipeline paddle_ocr(language); - text = paddle_ocr.recognize(image); + text = paddle_ocr_read(language, image); }else{ text = ocr_read(language, image, psm); } diff --git a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp index 09580cdcc..d61369170 100644 --- a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp +++ b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp @@ -172,6 +172,7 @@ #include "Common/PABotBase2/PABotbase2_ReliableStreamConnection.h" #include "Common/Cpp/StreamConnections/MockDevice.h" #include "ML/Inference/ML_PaddleOCRPipeline.h" +#include "CommonTools/OCR/OCR_RawPaddleOCR.h" @@ -771,12 +772,13 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& // ImageRGB32 image1(IMAGE_PATH); auto image1 = feed.snapshot(); ImageViewRGB32 cropped = extract_box_reference(image1, ImageFloatBox{BOX.x(), BOX.y(), BOX.width(), BOX.height()}); - ML::PaddleOCRPipeline paddle_ocr(LANGUAGE); // auto snapshot = feed.snapshot(); - std::string text = paddle_ocr.recognize(cropped); + std::string text = OCR::paddle_ocr_read(LANGUAGE, image1); cout << text << endl; + + #endif #if 0 diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index a579e65cf..d626d6e6b 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -615,6 +615,8 @@ file(GLOB LIBRARY_SOURCES Source/CommonTools/OCR/OCR_LargeDictionaryMatcher.h Source/CommonTools/OCR/OCR_NumberReader.cpp Source/CommonTools/OCR/OCR_NumberReader.h + Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp + Source/CommonTools/OCR/OCR_RawPaddleOCR.h Source/CommonTools/OCR/OCR_RawOCR.cpp Source/CommonTools/OCR/OCR_RawOCR.h Source/CommonTools/OCR/OCR_Routines.cpp From 1da1934e155bd5396f2143120bafb6033f74a71b Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 19 Jan 2026 12:57:14 -0800 Subject: [PATCH 02/11] update number OCR to use PaddleOCR. --- .../CommonTools/OCR/OCR_NumberReader.cpp | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_NumberReader.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_NumberReader.cpp index 0f4ba24e1..f54909159 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_NumberReader.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_NumberReader.cpp @@ -14,10 +14,12 @@ #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonFramework/GlobalSettingsPanel.h" #include "CommonTools/Images/ImageManip.h" #include "CommonTools/Images/ImageFilter.h" #include "CommonTools/Images/BinaryImage_FilterRgb32.h" #include "OCR_RawOCR.h" +#include "OCR_RawPaddleOCR.h" #include "OCR_NumberReader.h" #include @@ -81,7 +83,14 @@ std::string run_number_normalization(const std::string& input){ int read_number(Logger& logger, const ImageViewRGB32& image, Language language){ - std::string ocr_text = OCR::ocr_read(language, image, OCR::PageSegMode::SINGLE_LINE); + bool use_paddle_ocr = false; // GlobalSettings::instance().USE_PADDLE_OCR; + std::string ocr_text; + if (use_paddle_ocr){ + ocr_text = OCR::paddle_ocr_read(language, image); + }else{ + ocr_text = OCR::ocr_read(language, image, OCR::PageSegMode::SINGLE_LINE); + } + std::string normalized = run_number_normalization(ocr_text); std::string str; @@ -167,8 +176,13 @@ std::string read_number_waterfill_no_normalization( } ImageRGB32 padded = pad_image(cropped, 1 * cropped.width(), 0xffffffff); - std::string ocr = OCR::ocr_read(Language::English, padded, OCR::PageSegMode::SINGLE_CHAR); - + bool use_paddle_ocr = false; // GlobalSettings::instance().USE_PADDLE_OCR; + std::string ocr; + if (use_paddle_ocr){ + ocr = OCR::paddle_ocr_read(Language::English, padded); + }else{ + ocr = OCR::ocr_read(Language::English, padded, OCR::PageSegMode::SINGLE_CHAR); + } // padded.save("zztest-cropped" + std::to_string(c) + "-" + std::to_string(i++) + ".png"); // std::cout << ocr[0] << std::endl; if (!ocr.empty()){ From b6fc8ae24d44be895031fb501e35ee062726a3d2 Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 19 Jan 2026 13:14:53 -0800 Subject: [PATCH 03/11] fix build --- .../Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp index d61369170..9b084b899 100644 --- a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp +++ b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp @@ -774,7 +774,7 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& ImageViewRGB32 cropped = extract_box_reference(image1, ImageFloatBox{BOX.x(), BOX.y(), BOX.width(), BOX.height()}); // auto snapshot = feed.snapshot(); - std::string text = OCR::paddle_ocr_read(LANGUAGE, image1); + std::string text = OCR::paddle_ocr_read(LANGUAGE, cropped); cout << text << endl; From 1258ef8ff1e851d0e86b12483d9a2dc0e3b1335b Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 19 Jan 2026 22:45:35 -0800 Subject: [PATCH 04/11] fix namespace issue causing memory issues and deadlock --- .../CommonTools/OCR/OCR_RawPaddleOCR.cpp | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index 8ceb9daaf..a32ac856c 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -50,7 +50,7 @@ class PaddlePool{ } } // No idle instance available - create a new one. - add_instance(); + add_paddle_instance(); } // Perform OCR without holding the lock (allows concurrent OCR operations). @@ -72,7 +72,7 @@ class PaddlePool{ // Create a new PaddleOCR instance and add it to the pool. // Thread-safe - can be called concurrently (using `m_lock` when modifying the pool). - void add_instance(){ + void add_paddle_instance(){ global_logger_tagged().log( "Initializing PaddleOCR (" + language_data(m_language).name + "): " @@ -85,7 +85,7 @@ class PaddlePool{ // } // Add to pool under lock. - WriteSpinLock lg(m_lock, "PaddlePool::add_instance()"); + WriteSpinLock lg(m_lock, "PaddlePool::add_paddle_instance()"); m_instances.emplace_back(std::move(api)); try{ @@ -98,17 +98,17 @@ class PaddlePool{ // Pre-allocate a minimum number of instances to avoid lazy initialization during runtime. // Useful for warming up the pool before heavy OCR workloads. - void ensure_instances(size_t instances){ + void ensure_paddle_instances(size_t instances){ size_t current_instances; while (true){ { - ReadSpinLock lg(m_lock, "PaddlePool::ensure_instances()"); + ReadSpinLock lg(m_lock, "PaddlePool::ensure_paddle_instances()"); current_instances = m_instances.size(); } if (current_instances >= instances){ break; } - add_instance(); + add_paddle_instance(); } } @@ -139,12 +139,12 @@ class PaddlePool{ // Two-level concurrency model: // Level 1: ocr_pool_lock protects the map (language -> pool creation/lookup) // Level 2: Each PaddlePool has its own m_lock (instance checkout/checkin) -struct OcrGlobals{ +struct PaddleOcrGlobals{ SpinLock ocr_pool_lock; // Protects ocr_pool map. std::map ocr_pool; // One pool per language. - static OcrGlobals& instance(){ - static OcrGlobals globals; + static PaddleOcrGlobals& instance(){ + static PaddleOcrGlobals globals; return globals; } }; @@ -158,7 +158,7 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); } - OcrGlobals& globals = OcrGlobals::instance(); + PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); std::map& ocr_pool = globals.ocr_pool; // Get or create the pool for this language (lock only during map access). @@ -184,7 +184,7 @@ void ensure_paddle_ocr_instances(Language language, size_t instances){ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); } - OcrGlobals& globals = OcrGlobals::instance(); + PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); std::map& ocr_pool = globals.ocr_pool; // Get or create the pool for this language. @@ -196,12 +196,12 @@ void ensure_paddle_ocr_instances(Language language, size_t instances){ iter = ocr_pool.emplace(language, language).first; } } - // Delegate to pool's ensure_instances (which handles its own locking). - iter->second.ensure_instances(instances); + // Delegate to pool's ensure_paddle_instances (which handles its own locking). + iter->second.ensure_paddle_instances(instances); } void clear_paddle_ocr_cache(){ - OcrGlobals& globals = OcrGlobals::instance(); + PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); std::map& ocr_pool = globals.ocr_pool; WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); ocr_pool.clear(); // Destroys all pools and their instances. From aa7ff866e1b561e7f33785b5565cb3c678ba4043 Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 20 Jan 2026 18:07:09 -0800 Subject: [PATCH 05/11] refactor threadpool: one paddle instance per language --- .../CommonTools/OCR/OCR_RawPaddleOCR.cpp | 320 ++++++++++-------- .../Source/CommonTools/OCR/OCR_RawPaddleOCR.h | 22 +- 2 files changed, 191 insertions(+), 151 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index a32ac856c..eed93aec0 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -25,115 +25,151 @@ namespace OCR{ -// Thread-safe object pool for PaddleOCR instances for a specific language. -// Allows concurrent OCR operations by maintaining multiple PaddleOCR instances that can be -// checked out, used, and returned. Instances are created lazily on demand. -class PaddlePool{ -public: - PaddlePool(Language language) - : m_language(language) - {} - - // Perform OCR on the given image. Thread-safe - can be called concurrently. - // Checkout pattern: (1) acquire idle instance from pool (or create new one if none available), - // (2) run OCR without holding lock, (3) return instance to idle pool. - std::string run(const ImageViewRGB32& image){ - ML::PaddleOCRPipeline* instance = nullptr; - // Checkout: Try to get an idle instance, create new one if the idle pool is empty. - while (true){ - { - WriteSpinLock lg(m_lock, "PaddlePool::run()1"); - if (!m_idle.empty()){ - instance = m_idle.back(); - m_idle.pop_back(); - break; - } - } - // No idle instance available - create a new one. - add_paddle_instance(); - } - - // Perform OCR without holding the lock (allows concurrent OCR operations). -// auto start = current_time(); - std::string str = instance->recognize(image); - -// auto end = current_time(); -// cout << std::chrono::duration_cast(end - start).count() << endl; - // Checkin: Return instance to the idle pool. - { - WriteSpinLock lg(m_lock, "PaddlePool::run()2"); - m_idle.emplace_back(instance); - } - - return str.c_str() == nullptr - ? std::string() - : str.c_str(); - } - - // Create a new PaddleOCR instance and add it to the pool. - // Thread-safe - can be called concurrently (using `m_lock` when modifying the pool). - void add_paddle_instance(){ - - global_logger_tagged().log( - "Initializing PaddleOCR (" + language_data(m_language).name + "): " - ); - // Create instance outside lock (initialization is expensive). - std::unique_ptr api = std::make_unique(m_language); - - // if (!api->valid()){ - // throw InternalSystemError(nullptr, PA_CURRENT_FUNCTION, "Could not initialize PaddleOCR."); - // } - - // Add to pool under lock. - WriteSpinLock lg(m_lock, "PaddlePool::add_paddle_instance()"); - - m_instances.emplace_back(std::move(api)); - try{ - m_idle.emplace_back(m_instances.back().get()); - }catch (...){ - m_instances.pop_back(); - throw; - } - } +// // Thread-safe object pool for PaddleOCR instances for a specific language. +// // Allows concurrent OCR operations by maintaining multiple PaddleOCR instances that can be +// // checked out, used, and returned. Instances are created lazily on demand. +// class PaddlePool{ +// public: +// PaddlePool(Language language) +// : m_language(language) +// {} + +// // Perform OCR on the given image. Thread-safe - can be called concurrently. +// // Checkout pattern: (1) acquire idle instance from pool (or create new one if none available), +// // (2) run OCR without holding lock, (3) return instance to idle pool. +// std::string run(const ImageViewRGB32& image){ +// ML::PaddleOCRPipeline* instance = nullptr; +// // Checkout: Try to get an idle instance, create new one if the idle pool is empty. +// while (true){ +// { +// WriteSpinLock lg(m_lock, "PaddlePool::run()1"); +// if (!m_idle.empty()){ +// instance = m_idle.back(); +// m_idle.pop_back(); +// break; +// } +// } +// // No idle instance available - create a new one. +// add_paddle_instance(); +// } + +// // Perform OCR without holding the lock (allows concurrent OCR operations). +// // auto start = current_time(); +// std::string str = instance->recognize(image); + +// // auto end = current_time(); +// // cout << std::chrono::duration_cast(end - start).count() << endl; +// // Checkin: Return instance to the idle pool. +// { +// WriteSpinLock lg(m_lock, "PaddlePool::run()2"); +// m_idle.emplace_back(instance); +// } + +// return str.c_str() == nullptr +// ? std::string() +// : str.c_str(); +// } + +// // Create a new PaddleOCR instance and add it to the pool. +// // Thread-safe - can be called concurrently (using `m_lock` when modifying the pool). +// void add_paddle_instance(){ + +// global_logger_tagged().log( +// "Initializing PaddleOCR (" + language_data(m_language).name + "): " +// ); +// // Create instance outside lock (initialization is expensive). +// std::unique_ptr api = std::make_unique(m_language); + +// // if (!api->valid()){ +// // throw InternalSystemError(nullptr, PA_CURRENT_FUNCTION, "Could not initialize PaddleOCR."); +// // } + +// // Add to pool under lock. +// WriteSpinLock lg(m_lock, "PaddlePool::add_paddle_instance()"); + +// m_instances.emplace_back(std::move(api)); +// try{ +// m_idle.emplace_back(m_instances.back().get()); +// }catch (...){ +// m_instances.pop_back(); +// throw; +// } +// } + +// // Pre-allocate a minimum number of instances to avoid lazy initialization during runtime. +// // Useful for warming up the pool before heavy OCR workloads. +// void ensure_paddle_instances(size_t instances){ +// size_t current_instances; +// while (true){ +// { +// ReadSpinLock lg(m_lock, "PaddlePool::ensure_paddle_instances()"); +// current_instances = m_instances.size(); +// } +// if (current_instances >= instances){ +// break; +// } +// add_paddle_instance(); +// } +// } + +// #if 0 +// #ifdef __APPLE__ +// #ifdef UNIX_LINK_TESSERACT +// ~PaddlePool(){ +// for(auto& api : m_instances){ +// api.release(); +// } +// } +// #endif +// #endif +// #endif + +// private: +// const Language m_language; + +// // Concurrency: m_lock protects both m_instances and m_idle vectors. +// SpinLock m_lock; +// // Owns all created PaddleOCR instances. +// std::vector> m_instances; +// // Current idle PaddleOCR instances in `m_instances`. +// std::vector m_idle; +// }; + +enum class LanguageGroup { + None, + English, + ChineseJapanese, + Latin, + Korean, +}; - // Pre-allocate a minimum number of instances to avoid lazy initialization during runtime. - // Useful for warming up the pool before heavy OCR workloads. - void ensure_paddle_instances(size_t instances){ - size_t current_instances; - while (true){ - { - ReadSpinLock lg(m_lock, "PaddlePool::ensure_paddle_instances()"); - current_instances = m_instances.size(); - } - if (current_instances >= instances){ - break; - } - add_paddle_instance(); - } +LanguageGroup language_to_languagegroup(Language language){ + switch(language){ + case Language::None: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); + case Language::English: + return LanguageGroup::English; + case Language::Japanese: + return LanguageGroup::ChineseJapanese; + case Language::Spanish: + return LanguageGroup::Latin; + case Language::French: + return LanguageGroup::Latin; + case Language::German: + return LanguageGroup::Latin; + case Language::Italian: + return LanguageGroup::Latin; + case Language::Korean: + return LanguageGroup::Korean; + case Language::ChineseSimplified: + return LanguageGroup::ChineseJapanese; + case Language::ChineseTraditional: + return LanguageGroup::ChineseJapanese; + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR on an unknown language."); } +} -#if 0 - #ifdef __APPLE__ - #ifdef UNIX_LINK_TESSERACT - ~PaddlePool(){ - for(auto& api : m_instances){ - api.release(); - } - } - #endif - #endif -#endif - -private: - const Language m_language; - - // Concurrency: m_lock protects both m_instances and m_idle vectors. - SpinLock m_lock; - // Owns all created PaddleOCR instances. - std::vector> m_instances; - // Current idle PaddleOCR instances in `m_instances`. - std::vector m_idle; -}; // Global singleton managing PaddlePools for all languages. // Two-level concurrency model: @@ -141,7 +177,7 @@ class PaddlePool{ // Level 2: Each PaddlePool has its own m_lock (instance checkout/checkin) struct PaddleOcrGlobals{ SpinLock ocr_pool_lock; // Protects ocr_pool map. - std::map ocr_pool; // One pool per language. + std::map ocr_pool; // One instance per language. static PaddleOcrGlobals& instance(){ static PaddleOcrGlobals globals; @@ -150,6 +186,7 @@ struct PaddleOcrGlobals{ }; + std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ // static size_t c = 0; // image.save("ocr-" + std::to_string(c++) + ".png"); @@ -158,20 +195,23 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); } + LanguageGroup language_group = language_to_languagegroup(language); + PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); - std::map& ocr_pool = globals.ocr_pool; + std::map& ocr_pool = globals.ocr_pool; // Get or create the pool for this language (lock only during map access). - std::map::iterator iter; + std::map::iterator iter; { WriteSpinLock lg(globals.ocr_pool_lock, "paddle_ocr_read()"); - iter = ocr_pool.find(language); + iter = ocr_pool.find(language_group); if (iter == ocr_pool.end()){ - iter = ocr_pool.emplace(language, language).first; + iter = ocr_pool.emplace(language_group, language).first; } } - // Delegate to pool (which has its own locking for instance management). - std::string ret = iter->second.run(image); + // Run inference with the paddle model. + // PaddleOCR with Onnx is threadsafe, so a single instance can be called by multiple threads. + std::string ret = iter->second.recognize(image); // global_logger_tagged().log(ret); @@ -179,33 +219,33 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ } -void ensure_paddle_ocr_instances(Language language, size_t instances){ - if (language == Language::None){ - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); - } - - PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); - std::map& ocr_pool = globals.ocr_pool; - - // Get or create the pool for this language. - std::map::iterator iter; - { - WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); - iter = ocr_pool.find(language); - if (iter == ocr_pool.end()){ - iter = ocr_pool.emplace(language, language).first; - } - } - // Delegate to pool's ensure_paddle_instances (which handles its own locking). - iter->second.ensure_paddle_instances(instances); -} - -void clear_paddle_ocr_cache(){ - PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); - std::map& ocr_pool = globals.ocr_pool; - WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); - ocr_pool.clear(); // Destroys all pools and their instances. -} +// void ensure_paddle_ocr_instances(Language language, size_t instances){ +// if (language == Language::None){ +// throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); +// } + +// PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); +// std::map& ocr_pool = globals.ocr_pool; + +// // Get or create the pool for this language. +// std::map::iterator iter; +// { +// WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); +// iter = ocr_pool.find(language); +// if (iter == ocr_pool.end()){ +// iter = ocr_pool.emplace(language, language).first; +// } +// } +// // Delegate to pool's ensure_paddle_instances (which handles its own locking). +// iter->second.ensure_paddle_instances(instances); +// } + +// void clear_paddle_ocr_cache(){ +// PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); +// std::map& ocr_pool = globals.ocr_pool; +// WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); +// ocr_pool.clear(); // Destroys all pools and their instances. +// } diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h index 3e74d655b..d0017b8a5 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h @@ -30,17 +30,17 @@ std::string paddle_ocr_read( ); -// Pre-warm the PaddleOCR instance pool for a language by ensuring a minimum -// number of instances exist. -// Avoids lazy initialization delays during runtime. Thread-safe. -// Call this if you expect to need to do many OCR instances in parallel and you -// want to preload the OCR instances. -void ensure_paddle_ocr_instances(Language language, size_t instances); - -// Clear all PaddleOCR instances for all languages. Used for cleanup or -// forcing re-initialization. -// This is not safe to call while in any OCR is still running! -void clear_paddle_ocr_cache(); +// // Pre-warm the PaddleOCR instance pool for a language by ensuring a minimum +// // number of instances exist. +// // Avoids lazy initialization delays during runtime. Thread-safe. +// // Call this if you expect to need to do many OCR instances in parallel and you +// // want to preload the OCR instances. +// void ensure_paddle_ocr_instances(Language language, size_t instances); + +// // Clear all PaddleOCR instances for all languages. Used for cleanup or +// // forcing re-initialization. +// // This is not safe to call while in any OCR is still running! +// void clear_paddle_ocr_cache(); From 5cf7af313985b44ecce3bfb4da6a43ebe267fc6b Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 20 Jan 2026 19:49:29 -0800 Subject: [PATCH 06/11] more refactors --- .../CommonTools/OCR/OCR_RawPaddleOCR.cpp | 171 +++--------------- .../Source/CommonTools/OCR/OCR_RawPaddleOCR.h | 22 +-- 2 files changed, 35 insertions(+), 158 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index eed93aec0..c364710c9 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -25,116 +25,6 @@ namespace OCR{ -// // Thread-safe object pool for PaddleOCR instances for a specific language. -// // Allows concurrent OCR operations by maintaining multiple PaddleOCR instances that can be -// // checked out, used, and returned. Instances are created lazily on demand. -// class PaddlePool{ -// public: -// PaddlePool(Language language) -// : m_language(language) -// {} - -// // Perform OCR on the given image. Thread-safe - can be called concurrently. -// // Checkout pattern: (1) acquire idle instance from pool (or create new one if none available), -// // (2) run OCR without holding lock, (3) return instance to idle pool. -// std::string run(const ImageViewRGB32& image){ -// ML::PaddleOCRPipeline* instance = nullptr; -// // Checkout: Try to get an idle instance, create new one if the idle pool is empty. -// while (true){ -// { -// WriteSpinLock lg(m_lock, "PaddlePool::run()1"); -// if (!m_idle.empty()){ -// instance = m_idle.back(); -// m_idle.pop_back(); -// break; -// } -// } -// // No idle instance available - create a new one. -// add_paddle_instance(); -// } - -// // Perform OCR without holding the lock (allows concurrent OCR operations). -// // auto start = current_time(); -// std::string str = instance->recognize(image); - -// // auto end = current_time(); -// // cout << std::chrono::duration_cast(end - start).count() << endl; -// // Checkin: Return instance to the idle pool. -// { -// WriteSpinLock lg(m_lock, "PaddlePool::run()2"); -// m_idle.emplace_back(instance); -// } - -// return str.c_str() == nullptr -// ? std::string() -// : str.c_str(); -// } - -// // Create a new PaddleOCR instance and add it to the pool. -// // Thread-safe - can be called concurrently (using `m_lock` when modifying the pool). -// void add_paddle_instance(){ - -// global_logger_tagged().log( -// "Initializing PaddleOCR (" + language_data(m_language).name + "): " -// ); -// // Create instance outside lock (initialization is expensive). -// std::unique_ptr api = std::make_unique(m_language); - -// // if (!api->valid()){ -// // throw InternalSystemError(nullptr, PA_CURRENT_FUNCTION, "Could not initialize PaddleOCR."); -// // } - -// // Add to pool under lock. -// WriteSpinLock lg(m_lock, "PaddlePool::add_paddle_instance()"); - -// m_instances.emplace_back(std::move(api)); -// try{ -// m_idle.emplace_back(m_instances.back().get()); -// }catch (...){ -// m_instances.pop_back(); -// throw; -// } -// } - -// // Pre-allocate a minimum number of instances to avoid lazy initialization during runtime. -// // Useful for warming up the pool before heavy OCR workloads. -// void ensure_paddle_instances(size_t instances){ -// size_t current_instances; -// while (true){ -// { -// ReadSpinLock lg(m_lock, "PaddlePool::ensure_paddle_instances()"); -// current_instances = m_instances.size(); -// } -// if (current_instances >= instances){ -// break; -// } -// add_paddle_instance(); -// } -// } - -// #if 0 -// #ifdef __APPLE__ -// #ifdef UNIX_LINK_TESSERACT -// ~PaddlePool(){ -// for(auto& api : m_instances){ -// api.release(); -// } -// } -// #endif -// #endif -// #endif - -// private: -// const Language m_language; - -// // Concurrency: m_lock protects both m_instances and m_idle vectors. -// SpinLock m_lock; -// // Owns all created PaddleOCR instances. -// std::vector> m_instances; -// // Current idle PaddleOCR instances in `m_instances`. -// std::vector m_idle; -// }; - enum class LanguageGroup { None, English, @@ -185,12 +75,7 @@ struct PaddleOcrGlobals{ } }; - - -std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ -// static size_t c = 0; -// image.save("ocr-" + std::to_string(c++) + ".png"); - +ML::PaddleOCRPipeline& ensure_paddle_ocr_instance(Language language){ if (language == Language::None){ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); } @@ -200,18 +85,29 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); std::map& ocr_pool = globals.ocr_pool; - // Get or create the pool for this language (lock only during map access). + // Get or create the Paddle instance for this language. std::map::iterator iter; { - WriteSpinLock lg(globals.ocr_pool_lock, "paddle_ocr_read()"); + WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); iter = ocr_pool.find(language_group); if (iter == ocr_pool.end()){ - iter = ocr_pool.emplace(language_group, language).first; + iter = ocr_pool.try_emplace(language_group, language).first; } } + + return iter->second; +} + + +std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ +// static size_t c = 0; +// image.save("ocr-" + std::to_string(c++) + ".png"); + + ML::PaddleOCRPipeline& paddle_instance = ensure_paddle_ocr_instance(language); + // Run inference with the paddle model. // PaddleOCR with Onnx is threadsafe, so a single instance can be called by multiple threads. - std::string ret = iter->second.recognize(image); + std::string ret = paddle_instance.recognize(image); // global_logger_tagged().log(ret); @@ -219,33 +115,14 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ } -// void ensure_paddle_ocr_instances(Language language, size_t instances){ -// if (language == Language::None){ -// throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); -// } - -// PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); -// std::map& ocr_pool = globals.ocr_pool; - -// // Get or create the pool for this language. -// std::map::iterator iter; -// { -// WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); -// iter = ocr_pool.find(language); -// if (iter == ocr_pool.end()){ -// iter = ocr_pool.emplace(language, language).first; -// } -// } -// // Delegate to pool's ensure_paddle_instances (which handles its own locking). -// iter->second.ensure_paddle_instances(instances); -// } - -// void clear_paddle_ocr_cache(){ -// PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); -// std::map& ocr_pool = globals.ocr_pool; -// WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); -// ocr_pool.clear(); // Destroys all pools and their instances. -// } + + +void clear_paddle_ocr_cache(){ + PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); + std::map& ocr_pool = globals.ocr_pool; + WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); + ocr_pool.clear(); // Destroys all pools and their instances. +} diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h index d0017b8a5..80f54567b 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h @@ -12,10 +12,16 @@ namespace PokemonAutomation{ class ImageViewRGB32; + namespace ML { + class PaddleOCRPipeline; + } namespace OCR{ - +// Pre-warm the PaddleOCR instance pool for a language. Ensure one instance exists. +// Avoids lazy initialization delays during runtime. Thread-safe. +// returns a Paddle instance +ML::PaddleOCRPipeline& ensure_paddle_ocr_instance(Language language); // OCR the image in the specified language. // Main OCR entry point. Performs OCR on the image using the specified language. @@ -30,17 +36,11 @@ std::string paddle_ocr_read( ); -// // Pre-warm the PaddleOCR instance pool for a language by ensuring a minimum -// // number of instances exist. -// // Avoids lazy initialization delays during runtime. Thread-safe. -// // Call this if you expect to need to do many OCR instances in parallel and you -// // want to preload the OCR instances. -// void ensure_paddle_ocr_instances(Language language, size_t instances); -// // Clear all PaddleOCR instances for all languages. Used for cleanup or -// // forcing re-initialization. -// // This is not safe to call while in any OCR is still running! -// void clear_paddle_ocr_cache(); +// Clear all PaddleOCR instances for all languages. Used for cleanup or +// forcing re-initialization. +// This is not safe to call while any OCR is still running! +void clear_paddle_ocr_cache(); From d5a30b6ac9920ba85a328b4a683b11d1c0a8197d Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 20 Jan 2026 22:12:58 -0800 Subject: [PATCH 07/11] more refactors to avoid creating paddle instance while under lock --- .../CommonTools/OCR/OCR_RawPaddleOCR.cpp | 39 ++++++++++++------- .../Source/CommonTools/OCR/OCR_RawPaddleOCR.h | 4 +- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index c364710c9..24c518d5c 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -66,8 +66,8 @@ LanguageGroup language_to_languagegroup(Language language){ // Level 1: ocr_pool_lock protects the map (language -> pool creation/lookup) // Level 2: Each PaddlePool has its own m_lock (instance checkout/checkin) struct PaddleOcrGlobals{ - SpinLock ocr_pool_lock; // Protects ocr_pool map. - std::map ocr_pool; // One instance per language. + std::mutex ocr_pool_lock; // Protects ocr_pool map. + std::map> ocr_pool; // One instance per language. static PaddleOcrGlobals& instance(){ static PaddleOcrGlobals globals; @@ -75,7 +75,7 @@ struct PaddleOcrGlobals{ } }; -ML::PaddleOCRPipeline& ensure_paddle_ocr_instance(Language language){ +std::shared_ptr ensure_paddle_ocr_instance(Language language){ if (language == Language::None){ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); } @@ -83,18 +83,30 @@ ML::PaddleOCRPipeline& ensure_paddle_ocr_instance(Language language){ LanguageGroup language_group = language_to_languagegroup(language); PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); - std::map& ocr_pool = globals.ocr_pool; + std::map>& ocr_pool = globals.ocr_pool; - // Get or create the Paddle instance for this language. - std::map::iterator iter; + // Check if a Paddle instance already exists for this language. { - WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); + std::map>::iterator iter; + // ReadSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances(): lookup"); + std::lock_guard lg(globals.ocr_pool_lock); iter = ocr_pool.find(language_group); - if (iter == ocr_pool.end()){ - iter = ocr_pool.try_emplace(language_group, language).first; + if (iter != ocr_pool.end()){ + return iter->second; } } + // if Paddle instance didn't already exist, create a new one outside of the lock. + std::shared_ptr new_instance = std::make_shared(language); + + std::map>::iterator iter; + { + // add to ocr_pool while under the lock + // WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances(): insertion"); + std::lock_guard lg(globals.ocr_pool_lock); + iter = ocr_pool.try_emplace(language_group, std::move(new_instance)).first; + } + return iter->second; } @@ -103,11 +115,11 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ // static size_t c = 0; // image.save("ocr-" + std::to_string(c++) + ".png"); - ML::PaddleOCRPipeline& paddle_instance = ensure_paddle_ocr_instance(language); + std::shared_ptr paddle_instance = ensure_paddle_ocr_instance(language); // Run inference with the paddle model. // PaddleOCR with Onnx is threadsafe, so a single instance can be called by multiple threads. - std::string ret = paddle_instance.recognize(image); + std::string ret = paddle_instance->recognize(image); // global_logger_tagged().log(ret); @@ -119,8 +131,9 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ void clear_paddle_ocr_cache(){ PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); - std::map& ocr_pool = globals.ocr_pool; - WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); + std::map>& ocr_pool = globals.ocr_pool; + // WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); + std::lock_guard lg(globals.ocr_pool_lock); ocr_pool.clear(); // Destroys all pools and their instances. } diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h index 80f54567b..8193e0d53 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h @@ -21,13 +21,13 @@ namespace OCR{ // Pre-warm the PaddleOCR instance pool for a language. Ensure one instance exists. // Avoids lazy initialization delays during runtime. Thread-safe. // returns a Paddle instance -ML::PaddleOCRPipeline& ensure_paddle_ocr_instance(Language language); +std::shared_ptr ensure_paddle_ocr_instance(Language language); // OCR the image in the specified language. // Main OCR entry point. Performs OCR on the image using the specified language. // Thread-safe: internally uses a pool of PaddleOCR instances, able to accept // multiple concurrent calls without delay or queueing. -// It creates a new PaddleOCR instance if no available idle instance. You can +// It creates one PaddleOCR instance for each language. You can // call `ensure_instances()` to pre-warm to pool with a given number of instances. // std::string paddle_ocr_read( From 80b3ddeb649053fba99892d796bac09e3bf0eeec Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 20 Jan 2026 22:50:30 -0800 Subject: [PATCH 08/11] revert to creating paddle instance under lock --- .../CommonTools/OCR/OCR_RawPaddleOCR.cpp | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index 24c518d5c..88911887a 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -66,7 +66,7 @@ LanguageGroup language_to_languagegroup(Language language){ // Level 1: ocr_pool_lock protects the map (language -> pool creation/lookup) // Level 2: Each PaddlePool has its own m_lock (instance checkout/checkin) struct PaddleOcrGlobals{ - std::mutex ocr_pool_lock; // Protects ocr_pool map. + SpinLock ocr_pool_lock; // Protects ocr_pool map. std::map> ocr_pool; // One instance per language. static PaddleOcrGlobals& instance(){ @@ -85,28 +85,17 @@ std::shared_ptr ensure_paddle_ocr_instance(Language langu PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); std::map>& ocr_pool = globals.ocr_pool; - // Check if a Paddle instance already exists for this language. + // Get or create the Paddle instance for this language. + std::map>::iterator iter; { - std::map>::iterator iter; - // ReadSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances(): lookup"); - std::lock_guard lg(globals.ocr_pool_lock); + WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); + // std::lock_guard lg(globals.ocr_pool_lock); iter = ocr_pool.find(language_group); - if (iter != ocr_pool.end()){ - return iter->second; + if (iter == ocr_pool.end()){ + iter = ocr_pool.try_emplace(language_group, std::make_shared(language)).first; } } - // if Paddle instance didn't already exist, create a new one outside of the lock. - std::shared_ptr new_instance = std::make_shared(language); - - std::map>::iterator iter; - { - // add to ocr_pool while under the lock - // WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances(): insertion"); - std::lock_guard lg(globals.ocr_pool_lock); - iter = ocr_pool.try_emplace(language_group, std::move(new_instance)).first; - } - return iter->second; } @@ -132,8 +121,8 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ void clear_paddle_ocr_cache(){ PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); std::map>& ocr_pool = globals.ocr_pool; - // WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); - std::lock_guard lg(globals.ocr_pool_lock); + WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); + // std::lock_guard lg(globals.ocr_pool_lock); ocr_pool.clear(); // Destroys all pools and their instances. } From c82b074a72946c4a3fd106ea5c15124675cb3114 Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 20 Jan 2026 22:56:10 -0800 Subject: [PATCH 09/11] add comments --- SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp | 2 ++ SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index 88911887a..399834834 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -92,6 +92,8 @@ std::shared_ptr ensure_paddle_ocr_instance(Language langu // std::lock_guard lg(globals.ocr_pool_lock); iter = ocr_pool.find(language_group); if (iter == ocr_pool.end()){ + // This is creating a Paddle instance while under a lock; it isn't ideal if we need to run OCR on different languages at the same time. + // In practice, however, this doesn't really happen in our code base. iter = ocr_pool.try_emplace(language_group, std::make_shared(language)).first; } } diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h index 8193e0d53..fd8359326 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h @@ -20,7 +20,7 @@ namespace OCR{ // Pre-warm the PaddleOCR instance pool for a language. Ensure one instance exists. // Avoids lazy initialization delays during runtime. Thread-safe. -// returns a Paddle instance +// returns a pointer to a Paddle instance, for the given language. std::shared_ptr ensure_paddle_ocr_instance(Language language); // OCR the image in the specified language. From b0cd8919f00e75f5540155188b95d04e9a03cfc7 Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 20 Jan 2026 23:13:39 -0800 Subject: [PATCH 10/11] add more comments --- SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index 399834834..828937522 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -61,10 +61,8 @@ LanguageGroup language_to_languagegroup(Language language){ } -// Global singleton managing PaddlePools for all languages. -// Two-level concurrency model: -// Level 1: ocr_pool_lock protects the map (language -> pool creation/lookup) -// Level 2: Each PaddlePool has its own m_lock (instance checkout/checkin) +// Global singleton managing the single PaddleOCR instance for each language. +// ocr_pool_lock protects the map struct PaddleOcrGlobals{ SpinLock ocr_pool_lock; // Protects ocr_pool map. std::map> ocr_pool; // One instance per language. From 8929716cc7bd6be1e4f08d890bc60cf0a8d1d033 Mon Sep 17 00:00:00 2001 From: jw098 Date: Wed, 21 Jan 2026 08:46:34 -0800 Subject: [PATCH 11/11] convert shared ptr back to reference --- .../Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp | 16 ++++++++-------- .../Source/CommonTools/OCR/OCR_RawPaddleOCR.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp index 828937522..c59e92d00 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.cpp @@ -65,7 +65,7 @@ LanguageGroup language_to_languagegroup(Language language){ // ocr_pool_lock protects the map struct PaddleOcrGlobals{ SpinLock ocr_pool_lock; // Protects ocr_pool map. - std::map> ocr_pool; // One instance per language. + std::map ocr_pool; // One instance per language. static PaddleOcrGlobals& instance(){ static PaddleOcrGlobals globals; @@ -73,7 +73,7 @@ struct PaddleOcrGlobals{ } }; -std::shared_ptr ensure_paddle_ocr_instance(Language language){ +ML::PaddleOCRPipeline& ensure_paddle_ocr_instance(Language language){ if (language == Language::None){ throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Attempted to call OCR without a language."); } @@ -81,10 +81,10 @@ std::shared_ptr ensure_paddle_ocr_instance(Language langu LanguageGroup language_group = language_to_languagegroup(language); PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); - std::map>& ocr_pool = globals.ocr_pool; + std::map& ocr_pool = globals.ocr_pool; // Get or create the Paddle instance for this language. - std::map>::iterator iter; + std::map::iterator iter; { WriteSpinLock lg(globals.ocr_pool_lock, "ensure_paddle_ocr_instances()"); // std::lock_guard lg(globals.ocr_pool_lock); @@ -92,7 +92,7 @@ std::shared_ptr ensure_paddle_ocr_instance(Language langu if (iter == ocr_pool.end()){ // This is creating a Paddle instance while under a lock; it isn't ideal if we need to run OCR on different languages at the same time. // In practice, however, this doesn't really happen in our code base. - iter = ocr_pool.try_emplace(language_group, std::make_shared(language)).first; + iter = ocr_pool.try_emplace(language_group, language).first; } } @@ -104,11 +104,11 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ // static size_t c = 0; // image.save("ocr-" + std::to_string(c++) + ".png"); - std::shared_ptr paddle_instance = ensure_paddle_ocr_instance(language); + ML::PaddleOCRPipeline& paddle_instance = ensure_paddle_ocr_instance(language); // Run inference with the paddle model. // PaddleOCR with Onnx is threadsafe, so a single instance can be called by multiple threads. - std::string ret = paddle_instance->recognize(image); + std::string ret = paddle_instance.recognize(image); // global_logger_tagged().log(ret); @@ -120,7 +120,7 @@ std::string paddle_ocr_read(Language language, const ImageViewRGB32& image){ void clear_paddle_ocr_cache(){ PaddleOcrGlobals& globals = PaddleOcrGlobals::instance(); - std::map>& ocr_pool = globals.ocr_pool; + std::map& ocr_pool = globals.ocr_pool; WriteSpinLock lg(globals.ocr_pool_lock, "clear_paddle_ocr_cache()"); // std::lock_guard lg(globals.ocr_pool_lock); ocr_pool.clear(); // Destroys all pools and their instances. diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h index fd8359326..90cf5524c 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_RawPaddleOCR.h @@ -21,7 +21,7 @@ namespace OCR{ // Pre-warm the PaddleOCR instance pool for a language. Ensure one instance exists. // Avoids lazy initialization delays during runtime. Thread-safe. // returns a pointer to a Paddle instance, for the given language. -std::shared_ptr ensure_paddle_ocr_instance(Language language); +ML::PaddleOCRPipeline& ensure_paddle_ocr_instance(Language language); // OCR the image in the specified language. // Main OCR entry point. Performs OCR on the image using the specified language.