From 85c463b3d76dbe4b706e153ee7654d2719cd5d56 Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 9 Aug 2026 14:54:02 -0700 Subject: [PATCH 01/13] downgrade CI's Qt version to match official builds. upload .exe file --- .github/workflows/cpp-ci-serial-programs-base.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml index e53e055c6d..6a4a6d6ea9 100644 --- a/.github/workflows/cpp-ci-serial-programs-base.yml +++ b/.github/workflows/cpp-ci-serial-programs-base.yml @@ -57,7 +57,7 @@ jobs: with: # Delete aqtsource once https://github.com/miurahr/aqtinstall/issues/1007 is fixed and released aqtsource: 'git+https://github.com/miurahr/aqtinstall' - version: '6.11.1' + version: '6.10.2' modules: 'qtmultimedia qtserialport' - name: Install dependencies (Ubuntu) @@ -107,6 +107,13 @@ jobs: name: Serial Programs (os=${{inputs.os}} - compiler=${{inputs.compiler}}) path: ${{env.UPLOAD_FOLDER}} + - name: Upload Executable Only + uses: actions/upload-artifact@v7 + if: inputs.upload-build && startsWith(inputs.os, 'windows') + with: + name: Standalone Executable (os=${{inputs.os}}) + path: ${{env.UPLOAD_FOLDER}}/SerialPrograms.exe + - name: Checkout CommandLineTests uses: actions/checkout@v7 if: inputs.run-tests From 53170f6235f24cf5ffaa382333934c6f15b41a58 Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 9 Aug 2026 18:58:49 -0700 Subject: [PATCH 02/13] add settings DEBUG.PADDLE_OCR_DEBUG --- SerialPrograms/Source/CommonFramework/StaticGlobals.cpp | 2 ++ SerialPrograms/Source/CommonFramework/StaticGlobals.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/SerialPrograms/Source/CommonFramework/StaticGlobals.cpp b/SerialPrograms/Source/CommonFramework/StaticGlobals.cpp index 698e4326c9..65bbfd77c2 100644 --- a/SerialPrograms/Source/CommonFramework/StaticGlobals.cpp +++ b/SerialPrograms/Source/CommonFramework/StaticGlobals.cpp @@ -73,6 +73,7 @@ void StaticGlobals::load_json(const JsonValue& json){ debug_obj->read_integer(BOX_SYSTEM_CELL_ROW, "BOX_SYSTEM_CELL_ROW"); debug_obj->read_integer(BOX_SYSTEM_CELL_COL, "BOX_SYSTEM_CELL_COL"); debug_obj->read_boolean(GENERATE_TEST_GOLDEN_FILES, "GENERATE_TEST_GOLDEN_FILES"); + debug_obj->read_boolean(PADDLE_OCR_DEBUG, "PADDLE_OCR_DEBUG"); } } JsonValue StaticGlobals::to_json_debug() const{ @@ -83,6 +84,7 @@ JsonValue StaticGlobals::to_json_debug() const{ debug_obj["BOX_SYSTEM_CELL_ROW"] = BOX_SYSTEM_CELL_ROW; debug_obj["BOX_SYSTEM_CELL_COL"] = BOX_SYSTEM_CELL_COL; debug_obj["GENERATE_TEST_GOLDEN_FILES"] = GENERATE_TEST_GOLDEN_FILES; + debug_obj["PADDLE_OCR_DEBUG"] = PADDLE_OCR_DEBUG; return debug_obj; } diff --git a/SerialPrograms/Source/CommonFramework/StaticGlobals.h b/SerialPrograms/Source/CommonFramework/StaticGlobals.h index 9682176bad..8cdf89a407 100644 --- a/SerialPrograms/Source/CommonFramework/StaticGlobals.h +++ b/SerialPrograms/Source/CommonFramework/StaticGlobals.h @@ -56,6 +56,8 @@ class StaticGlobals{ // then manually inspect to fix any errors. bool GENERATE_TEST_GOLDEN_FILES = false; + bool PADDLE_OCR_DEBUG = false; + }; From 3d597aacb6ae61ff663c09f77d81d2e808da49e8 Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 9 Aug 2026 18:59:11 -0700 Subject: [PATCH 03/13] add logging to PaddleOCR --- .../ML/Inference/ML_PaddleOCRPipeline.cpp | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index 3d2c198501..e991e20900 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -11,6 +11,7 @@ #include "Common/Cpp/Exceptions.h" #include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/GlobalSettingsPanel.h" +#include "CommonFramework/StaticGlobals.h" #include "CommonFramework/ImageTypes/ImageRGB32_OpenCV.h" #include "ML/Models/ML_ONNXRuntimeHelpers.h" #include "ML_PaddleOCRPipeline.h" @@ -105,9 +106,12 @@ void PaddleOCRPipeline::load_dictionary(const std::string& path){ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ + bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + // 1. Convert Image to OpenCV image (cv::mat) cv::Mat cv_image_rgb = imageviewrgb32_to_cv_mat_rgb(image); if (cv_image_rgb.empty()) { + std::clog << "[OCR-DEBUG] Input was an empty image.\n"; return ""; } @@ -115,6 +119,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ // 2. Crop tightly around the text, with small safety margin cv::Mat cropped_image = crop_to_text_region(cv_image_rgb); if (cropped_image.empty()){ + std::clog << "[OCR-DEBUG] Crop to text region returned empty image.\n"; return ""; } @@ -132,6 +137,11 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ (int)std::round(target_h * aspect_ratio) ); + if (target_w <= 0 || target_w > 8192){ + std::clog << "[OCR-ERROR] Abnormally scaled target width calculated: " << target_w << "\n"; + return ""; + } + cv::Mat resized; cv::resize( cropped_image, @@ -173,6 +183,41 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ // 6. Define Dynamic Shape std::vector input_shape = {1, 3, target_h, target_w}; + + size_t expected_elements = 1 * 3 * target_h * target_w; + if (debugging){ + std::clog << "[OCR-DEBUG] Cropped image constraints - Width: " << cropped_image.cols + << ", Height: " << cropped_image.rows + << ", Channels: " << cropped_image.channels() + << ", Total Pixels: " << cropped_image.total() << "\n"; + + size_t nan_count = 0; + size_t subnormal_count = 0; + for (float val : input_tensor_values) { + if (std::isnan(val)) { + nan_count++; + } else if (val != 0.0f && std::fpclassify(val) == FP_SUBNORMAL) { + subnormal_count++; + } + } + std::clog << "[OCR-DEBUG] Tensor payload validation - Total Floats: " << input_tensor_values.size() + << ", NaNs detected: " << nan_count + << ", Subnormal (denormal) values: " << subnormal_count << "\n"; + + // Validate expected payload sizing matches matrix dimensionality + std::clog << "[OCR-DEBUG] Shape Definition - NCHW: [" << input_shape[0] << "," << input_shape[1] + << "," << input_shape[2] << "," << input_shape[3] << "]. Expected Elements: " << expected_elements << "\n"; + + std::clog.flush(); + } + + if (input_tensor_values.size() != static_cast(expected_elements)) { + std::clog << "[OCR-ERROR] Vector length vs input_shape calculation mismatch!\n"; + std::clog << "[OCR-ERROR] Fatal memory stride mismatch. Vector size (" << input_tensor_values.size() + << ") does not match shape requirement (" << expected_elements << ")\n"; + return ""; + } + // 7. Create tensor with its own managed memory Ort::AllocatorWithDefaultOptions allocator; auto input_tensor = Ort::Value::CreateTensor( @@ -192,6 +237,11 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ const char* output_names[] = {m_output_name.c_str()}; try{ + if (debugging) { + std::clog << "[OCR-DEBUG] Calling m_rec_session.Run() now...\n"; + std::clog.flush(); // Critical: Forces log file save before potential hardware crash + } + // 8. Run the recognition session auto outputs = m_rec_session.Run( Ort::RunOptions{nullptr}, From e5c418eb3a16d227e71989932bb424f60c4d5715 Mon Sep 17 00:00:00 2001 From: jw098 Date: Sun, 9 Aug 2026 19:29:59 -0700 Subject: [PATCH 04/13] update logging --- .../ML/Inference/ML_PaddleOCRPipeline.cpp | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index e991e20900..e7b3a8dd05 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -12,6 +12,7 @@ #include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/GlobalSettingsPanel.h" #include "CommonFramework/StaticGlobals.h" +#include "CommonFramework/Logging/Logger.h" #include "CommonFramework/ImageTypes/ImageRGB32_OpenCV.h" #include "ML/Models/ML_ONNXRuntimeHelpers.h" #include "ML_PaddleOCRPipeline.h" @@ -106,12 +107,14 @@ void PaddleOCRPipeline::load_dictionary(const std::string& path){ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ + Logger& logger = global_logger_tagged(); + bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; // 1. Convert Image to OpenCV image (cv::mat) cv::Mat cv_image_rgb = imageviewrgb32_to_cv_mat_rgb(image); if (cv_image_rgb.empty()) { - std::clog << "[OCR-DEBUG] Input was an empty image.\n"; + logger.log("[OCR-DEBUG] Input was an empty image."); return ""; } @@ -119,7 +122,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ // 2. Crop tightly around the text, with small safety margin cv::Mat cropped_image = crop_to_text_region(cv_image_rgb); if (cropped_image.empty()){ - std::clog << "[OCR-DEBUG] Crop to text region returned empty image.\n"; + logger.log("[OCR-DEBUG] Crop to text region returned empty image."); return ""; } @@ -138,7 +141,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ ); if (target_w <= 0 || target_w > 8192){ - std::clog << "[OCR-ERROR] Abnormally scaled target width calculated: " << target_w << "\n"; + logger.log("[OCR-ERROR] Abnormally scaled target width calculated: " + std::to_string(target_w)); return ""; } @@ -186,10 +189,10 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ size_t expected_elements = 1 * 3 * target_h * target_w; if (debugging){ - std::clog << "[OCR-DEBUG] Cropped image constraints - Width: " << cropped_image.cols - << ", Height: " << cropped_image.rows - << ", Channels: " << cropped_image.channels() - << ", Total Pixels: " << cropped_image.total() << "\n"; + logger.log("[OCR-DEBUG] Cropped image constraints - Width: " + std::to_string(cropped_image.cols) + + ", Height: " + std::to_string(cropped_image.rows) + + ", Channels: " + std::to_string(cropped_image.channels()) + + ", Total Pixels: " + std::to_string(cropped_image.total())); size_t nan_count = 0; size_t subnormal_count = 0; @@ -200,21 +203,20 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ subnormal_count++; } } - std::clog << "[OCR-DEBUG] Tensor payload validation - Total Floats: " << input_tensor_values.size() - << ", NaNs detected: " << nan_count - << ", Subnormal (denormal) values: " << subnormal_count << "\n"; + logger.log("[OCR-DEBUG] Tensor payload validation - Total Floats: " + std::to_string(input_tensor_values.size()) + + ", NaNs detected: " + std::to_string(nan_count) + + ", Subnormal (denormal) values: " + std::to_string(subnormal_count)); // Validate expected payload sizing matches matrix dimensionality - std::clog << "[OCR-DEBUG] Shape Definition - NCHW: [" << input_shape[0] << "," << input_shape[1] - << "," << input_shape[2] << "," << input_shape[3] << "]. Expected Elements: " << expected_elements << "\n"; + logger.log("[OCR-DEBUG] Shape Definition - NCHW: [" + std::to_string(input_shape[0]) + "," + std::to_string(input_shape[1]) + + "," + std::to_string(input_shape[2]) + "," + std::to_string(input_shape[3]) + "]. Expected Elements: " + std::to_string(expected_elements)); - std::clog.flush(); } if (input_tensor_values.size() != static_cast(expected_elements)) { - std::clog << "[OCR-ERROR] Vector length vs input_shape calculation mismatch!\n"; - std::clog << "[OCR-ERROR] Fatal memory stride mismatch. Vector size (" << input_tensor_values.size() - << ") does not match shape requirement (" << expected_elements << ")\n"; + logger.log("[OCR-ERROR] Vector length vs input_shape calculation mismatch!"); + logger.log("[OCR-ERROR] Fatal memory stride mismatch. Vector size (" + std::to_string(input_tensor_values.size()) + + ") does not match shape requirement (" + std::to_string(expected_elements)); return ""; } @@ -238,8 +240,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ try{ if (debugging) { - std::clog << "[OCR-DEBUG] Calling m_rec_session.Run() now...\n"; - std::clog.flush(); // Critical: Forces log file save before potential hardware crash + logger.log("[OCR-DEBUG] Calling m_rec_session.Run() now..."); } // 8. Run the recognition session From 3d4b2dfa6e0cdccd589719063a60f3da1542d7f3 Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 10 Aug 2026 15:47:04 -0700 Subject: [PATCH 05/13] add logging to decode_CTC --- .../ML/Inference/ML_PaddleOCRPipeline.cpp | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index e7b3a8dd05..6e3cb0f055 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -393,10 +393,26 @@ std::vector preprocess_NCHW(cv::Mat& img){ } std::string decode_CTC(float* data, const std::vector& shape, const std::vector& dict){ + + Logger& logger = global_logger_tagged(); + bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + std::string text = ""; size_t seq_len = static_cast(shape[1]); int64_t num_cls = shape[2]; size_t last_index = 0; + + // Initial boundary logging configuration + if (debugging){ + logger.log("[OCR-CTC-DEBUG] Starting decode_CTC. Sequence Length: " + std::to_string(seq_len) + + ", Total Classes: " + std::to_string(num_cls) + + ", Dictionary Size: " + std::to_string(dict.size())); + } + + if (dict.empty()) { + logger.log("[OCR-CTC-ERROR] FATAL: Dictionary payload array is empty! Parsing loops will fail to resolve text indicators."); + } + for (size_t i = 0; i < seq_len; ++i){ float* row = data + i * num_cls; // 1. Get the character index with highest probability (Argmax) @@ -409,11 +425,27 @@ std::string decode_CTC(float* data, const std::vector& shape, const std // Index 1 from the model maps to the 1st line of your .txt file (Vector index 0) size_t dict_idx = argmax - 1; if (dict_idx < dict.size()){ + if (debugging) { + logger.log("[OCR-CTC-DEBUG] Step " + std::to_string(i) + + ": Predicted Argmax = " + std::to_string(argmax) + + " -> Target Dict Index = " + std::to_string(dict_idx) + + " (Character resolved: '" + dict[dict_idx] + "')"); + } text += dict[dict_idx]; + }else { + logger.log("[OCR-CTC-ERROR] Step " + std::to_string(i) + + ": Predicted Argmax = " + std::to_string(argmax) + + " -> Target Dict Index = " + std::to_string(dict_idx) + + " (ERROR: Calculated index is out of bounds for the dictionary memory layout!)"); } } last_index = argmax; } + + if (debugging) { + logger.log("[OCR-CTC-DEBUG] Complete loop execution tracking finish. Resulting string extraction: '" + text + "'"); + } + return text; } From 129097cc66423d64dfbbfa17ffe6d494210138fd Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 10 Aug 2026 18:26:51 -0700 Subject: [PATCH 06/13] add more OCR tests --- SerialPrograms/Source/CommonTools/OCR/OCR_Tests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SerialPrograms/Source/CommonTools/OCR/OCR_Tests.cpp b/SerialPrograms/Source/CommonTools/OCR/OCR_Tests.cpp index d6b0fe694f..7bf4b5cdcb 100644 --- a/SerialPrograms/Source/CommonTools/OCR/OCR_Tests.cpp +++ b/SerialPrograms/Source/CommonTools/OCR/OCR_Tests.cpp @@ -67,6 +67,7 @@ void add_tests_raw_OCR(UnitTestDatabase& database){ database.add("OCR/sentence-1-3.jpg", Language::English, "You hurry to the Pokemon Center, shielding your"); database.add("OCR/sentence-1-3-wide.jpg", Language::English, "You hurry to the Pokemon Center, shielding your"); database.add("OCR/sentence-1-3-tall.jpg", Language::English, "You hurry to the Pokemon Center, shielding your"); + database.add("OCR/german-nature-sanft.png", Language::German, "Wesen: SANFT"); } From 19bcecdb7479f579f135f41ed8d7712db2fe3132 Mon Sep 17 00:00:00 2001 From: jw098 Date: Mon, 10 Aug 2026 18:46:12 -0700 Subject: [PATCH 07/13] update preprocess_NCHW --- .../ML/Inference/ML_PaddleOCRPipeline.cpp | 32 +++++++++++++++---- .../ML/Inference/ML_PaddleOCRPipeline.h | 4 ++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index 6e3cb0f055..824e748e02 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -380,18 +380,38 @@ cv::Scalar estimate_background_color(const cv::Mat& image) { } - - std::vector preprocess_NCHW(cv::Mat& img){ - std::vector dst(img.rows * img.cols * 3); - for (int c = 0; c < 3; ++c){ - for (int i = 0; i < img.rows * img.cols; ++i){ - dst[c * img.rows * img.cols + i] = ((float*)img.data)[i * 3 + c]; + const int rows = img.rows; + const int cols = img.cols; + const int channels = 3; + + // Allocate a flat memory buffer big enough for all channels + std::vector dst(rows * cols * channels); + + // Define the size of one complete "color plane" (channel) + const int plane_size = rows * cols; + + // Loop through the image row-by-row + for (int y = 0; y < rows; ++y) { + // Safely locate the exact memory address for the start of row 'y' + const float* row_ptr = img.ptr(y); + + // Loop through every pixel column in the current row + for (int x = 0; x < cols; ++x) { + // Calculate the 1D coordinate of the pixel inside a flat 2D plane + int linear_idx = y * cols + x; + + // Extract the interleaved BGR channels explicitly + dst[0 * plane_size + linear_idx] = row_ptr[x * channels + 0]; // Channel 0 + dst[1 * plane_size + linear_idx] = row_ptr[x * channels + 1]; // Channel 1 + dst[2 * plane_size + linear_idx] = row_ptr[x * channels + 2]; // Channel 2 } } return dst; } + + std::string decode_CTC(float* data, const std::vector& shape, const std::vector& dict){ Logger& logger = global_logger_tagged(); diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h index a9babd7460..8669de95ff 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h @@ -56,7 +56,9 @@ void add_horizontal_padding(cv::Mat& image); // assumes input image is RGB cv::Scalar estimate_background_color(const cv::Mat& image); -// convert HCW (height, width, channels) to NCHW (batch N, channels C, height H, width W) +// convert HWC (height, width, channels) to NCHW (batch N, channels C, height H, width W) +// HWC: pixels are interleaved. [B,G,R] [B,G,R] [B,G,R] ... +// NCHW: [All Blue Pixels...] [All Green Pixels...] [All Red Pixels...] std::vector preprocess_NCHW(cv::Mat& img); std::string decode_CTC(float* data, const std::vector& shape, const std::vector& dict); From 9c287e8fd8ea6c1afdd4fcd2af7175d24f0727df Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 11 Aug 2026 17:46:19 -0700 Subject: [PATCH 08/13] add logger member to Paddle class. use custom Filesystem::Path class to properly convert strings to path. --- .../ML/Inference/ML_PaddleOCRPipeline.cpp | 98 ++++++++++++++----- .../ML/Inference/ML_PaddleOCRPipeline.h | 9 +- 2 files changed, 77 insertions(+), 30 deletions(-) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index 824e748e02..bb2feecd54 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -8,11 +8,13 @@ #include #include #include +#include #include "Common/Cpp/Exceptions.h" +#include "Common/Cpp/Filesystem/Filesystem.h" +#include "Common/Cpp/Logging/GlobalLogger.h" #include "CommonFramework/GlobalAutoPaths.h" #include "CommonFramework/GlobalSettingsPanel.h" #include "CommonFramework/StaticGlobals.h" -#include "CommonFramework/Logging/Logger.h" #include "CommonFramework/ImageTypes/ImageRGB32_OpenCV.h" #include "ML/Models/ML_ONNXRuntimeHelpers.h" #include "ML_PaddleOCRPipeline.h" @@ -72,9 +74,9 @@ PaddleOCRPipeline::PaddleOCRPipeline(Language language, std::string rec_path, st , m_language(language) , m_input_name(m_rec_session.GetInputNameAllocated(0, Ort::AllocatorWithDefaultOptions{}).get()) , m_output_name(m_rec_session.GetOutputNameAllocated(0, Ort::AllocatorWithDefaultOptions{}).get()) + , m_logger(global_logger_raw(), "OCR") { - load_dictionary(dict_path); - + load_dictionary(Filesystem::Path(dict_path)); } void PaddleOCRPipeline::run(const std::string& img_path){ @@ -96,25 +98,68 @@ void PaddleOCRPipeline::run(const std::string& img_path){ -void PaddleOCRPipeline::load_dictionary(const std::string& path){ - std::ifstream fs(path); +void PaddleOCRPipeline::load_dictionary(const Filesystem::Path& path){ + + const bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + + if (debugging){ + m_logger.log("[OCR-INFO] Loading dictionary from: " + path.string()); + m_logger.log("[OCR-INFO] Current working directory: " + + Filesystem::current_path().string()); + + std::error_code ec; + const auto absolute_path = std::filesystem::absolute(path, ec); + + if (!ec) { + m_logger.log("[OCR-INFO] Absolute dictionary path: " + + absolute_path.string()); + + m_logger.log("[OCR-INFO] Dictionary exists: " + + std::string(Filesystem::exists(absolute_path) ? "true" : "false")); + + if (Filesystem::exists(absolute_path)) { + const auto file_size = Filesystem::file_size(Filesystem::Path(absolute_path), ec); + + if (!ec) { + m_logger.log("[OCR-INFO] Dictionary file size: " + + std::to_string(file_size) + " bytes"); + } + } + } + } + + std::ifstream fs_file(path.stdpath()); + + if (!fs_file.is_open()) { + m_logger.log("[OCR-ERROR] Failed to open dictionary: " + path.string()); + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "PaddleOCRPipeline::load_dictionary(): Failed to open dictionary: " + path.string()); + } + std::string line; - // m_dictionary.push_back("blank"); // CTC blank index - while (std::getline(fs, line)){ + while (std::getline(fs_file, line)) { m_dictionary.push_back(line); } + + if (fs_file.bad()) { + m_logger.log("[OCR-ERROR] I/O error while reading dictionary: " + path.string()); + } + + if (debugging){ + m_logger.log("[OCR-INFO] Loaded " + + std::to_string(m_dictionary.size()) + + " dictionary entries"); + } } -std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ - Logger& logger = global_logger_tagged(); +std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ - bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + const bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; // 1. Convert Image to OpenCV image (cv::mat) cv::Mat cv_image_rgb = imageviewrgb32_to_cv_mat_rgb(image); if (cv_image_rgb.empty()) { - logger.log("[OCR-DEBUG] Input was an empty image."); + m_logger.log("[OCR-DEBUG] Input was an empty image."); return ""; } @@ -122,7 +167,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ // 2. Crop tightly around the text, with small safety margin cv::Mat cropped_image = crop_to_text_region(cv_image_rgb); if (cropped_image.empty()){ - logger.log("[OCR-DEBUG] Crop to text region returned empty image."); + m_logger.log("[OCR-DEBUG] Crop to text region returned empty image."); return ""; } @@ -141,7 +186,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ ); if (target_w <= 0 || target_w > 8192){ - logger.log("[OCR-ERROR] Abnormally scaled target width calculated: " + std::to_string(target_w)); + m_logger.log("[OCR-ERROR] Abnormally scaled target width calculated: " + std::to_string(target_w)); return ""; } @@ -189,7 +234,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ size_t expected_elements = 1 * 3 * target_h * target_w; if (debugging){ - logger.log("[OCR-DEBUG] Cropped image constraints - Width: " + std::to_string(cropped_image.cols) + m_logger.log("[OCR-DEBUG] Cropped image constraints - Width: " + std::to_string(cropped_image.cols) + ", Height: " + std::to_string(cropped_image.rows) + ", Channels: " + std::to_string(cropped_image.channels()) + ", Total Pixels: " + std::to_string(cropped_image.total())); @@ -203,19 +248,19 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ subnormal_count++; } } - logger.log("[OCR-DEBUG] Tensor payload validation - Total Floats: " + std::to_string(input_tensor_values.size()) + m_logger.log("[OCR-DEBUG] Tensor payload validation - Total Floats: " + std::to_string(input_tensor_values.size()) + ", NaNs detected: " + std::to_string(nan_count) + ", Subnormal (denormal) values: " + std::to_string(subnormal_count)); // Validate expected payload sizing matches matrix dimensionality - logger.log("[OCR-DEBUG] Shape Definition - NCHW: [" + std::to_string(input_shape[0]) + "," + std::to_string(input_shape[1]) + m_logger.log("[OCR-DEBUG] Shape Definition - NCHW: [" + std::to_string(input_shape[0]) + "," + std::to_string(input_shape[1]) + "," + std::to_string(input_shape[2]) + "," + std::to_string(input_shape[3]) + "]. Expected Elements: " + std::to_string(expected_elements)); } if (input_tensor_values.size() != static_cast(expected_elements)) { - logger.log("[OCR-ERROR] Vector length vs input_shape calculation mismatch!"); - logger.log("[OCR-ERROR] Fatal memory stride mismatch. Vector size (" + std::to_string(input_tensor_values.size()) + m_logger.log("[OCR-ERROR] Vector length vs input_shape calculation mismatch!"); + m_logger.log("[OCR-ERROR] Fatal memory stride mismatch. Vector size (" + std::to_string(input_tensor_values.size()) + ") does not match shape requirement (" + std::to_string(expected_elements)); return ""; } @@ -240,7 +285,7 @@ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ try{ if (debugging) { - logger.log("[OCR-DEBUG] Calling m_rec_session.Run() now..."); + m_logger.log("[OCR-DEBUG] Calling m_rec_session.Run() now..."); } // 8. Run the recognition session @@ -412,10 +457,9 @@ std::vector preprocess_NCHW(cv::Mat& img){ -std::string decode_CTC(float* data, const std::vector& shape, const std::vector& dict){ +std::string PaddleOCRPipeline::decode_CTC(float* data, const std::vector& shape, const std::vector& dict){ - Logger& logger = global_logger_tagged(); - bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + const bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; std::string text = ""; size_t seq_len = static_cast(shape[1]); @@ -424,13 +468,13 @@ std::string decode_CTC(float* data, const std::vector& shape, const std // Initial boundary logging configuration if (debugging){ - logger.log("[OCR-CTC-DEBUG] Starting decode_CTC. Sequence Length: " + std::to_string(seq_len) + + m_logger.log("[OCR-CTC-DEBUG] Starting decode_CTC. Sequence Length: " + std::to_string(seq_len) + ", Total Classes: " + std::to_string(num_cls) + ", Dictionary Size: " + std::to_string(dict.size())); } if (dict.empty()) { - logger.log("[OCR-CTC-ERROR] FATAL: Dictionary payload array is empty! Parsing loops will fail to resolve text indicators."); + m_logger.log("[OCR-CTC-ERROR] FATAL: Dictionary payload array is empty! Parsing loops will fail to resolve text indicators."); } for (size_t i = 0; i < seq_len; ++i){ @@ -446,14 +490,14 @@ std::string decode_CTC(float* data, const std::vector& shape, const std size_t dict_idx = argmax - 1; if (dict_idx < dict.size()){ if (debugging) { - logger.log("[OCR-CTC-DEBUG] Step " + std::to_string(i) + + m_logger.log("[OCR-CTC-DEBUG] Step " + std::to_string(i) + ": Predicted Argmax = " + std::to_string(argmax) + " -> Target Dict Index = " + std::to_string(dict_idx) + " (Character resolved: '" + dict[dict_idx] + "')"); } text += dict[dict_idx]; }else { - logger.log("[OCR-CTC-ERROR] Step " + std::to_string(i) + + m_logger.log("[OCR-CTC-ERROR] Step " + std::to_string(i) + ": Predicted Argmax = " + std::to_string(argmax) + " -> Target Dict Index = " + std::to_string(dict_idx) + " (ERROR: Calculated index is out of bounds for the dictionary memory layout!)"); @@ -463,7 +507,7 @@ std::string decode_CTC(float* data, const std::vector& shape, const std } if (debugging) { - logger.log("[OCR-CTC-DEBUG] Complete loop execution tracking finish. Resulting string extraction: '" + text + "'"); + m_logger.log("[OCR-CTC-DEBUG] Complete loop execution tracking finish. Resulting string extraction: '" + text + "'"); } return text; diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h index 8669de95ff..022408fc8a 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.h @@ -12,6 +12,7 @@ #include #include #include +#include "Common/Cpp/Logging/TaggedLogger.h" #include "CommonFramework/Language.h" #include "CommonFramework/ImageTypes/ImageViewRGB32.h" #include "CommonFramework/ImageTools/ImageBoxes.h" @@ -31,8 +32,10 @@ class PaddleOCRPipeline{ static std::pair get_paths(Language language); + std::string decode_CTC(float* data, const std::vector& shape, const std::vector& dict); + private: - void load_dictionary(const std::string& path); + void load_dictionary(const Filesystem::Path& path); Ort::Env m_env; // Ort::Session det_session; @@ -41,7 +44,8 @@ class PaddleOCRPipeline{ Language m_language; std::string m_input_name; std::string m_output_name; - std::vector m_dictionary; + std::vector m_dictionary; + TaggedLogger m_logger; }; @@ -61,7 +65,6 @@ cv::Scalar estimate_background_color(const cv::Mat& image); // NCHW: [All Blue Pixels...] [All Green Pixels...] [All Red Pixels...] std::vector preprocess_NCHW(cv::Mat& img); -std::string decode_CTC(float* data, const std::vector& shape, const std::vector& dict); cv::Mat imageviewrgb32_to_cv_mat_rgb(const ImageViewRGB32& image); From 8017d77ce3a53b535139b38ec25f93564720b86b Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 11 Aug 2026 17:57:09 -0700 Subject: [PATCH 09/13] ci: force sync trigger From cc5b5757dbbc41d36dcb949e570a6e5f95d6ff7d Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 11 Aug 2026 21:26:01 -0700 Subject: [PATCH 10/13] disable changes to ONNX threading. throw FileException when fail to open dictionary --- SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index bb2feecd54..029721edb1 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -132,7 +132,7 @@ void PaddleOCRPipeline::load_dictionary(const Filesystem::Path& path){ if (!fs_file.is_open()) { m_logger.log("[OCR-ERROR] Failed to open dictionary: " + path.string()); - throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "PaddleOCRPipeline::load_dictionary(): Failed to open dictionary: " + path.string()); + throw FileException(nullptr, PA_CURRENT_FUNCTION, "PaddleOCRPipeline::load_dictionary(): Failed to open dictionary.", path.string()); } std::string line; From be1136238bb517747c354b76e9b9d6ff41632b86 Mon Sep 17 00:00:00 2001 From: jw098 Date: Tue, 11 Aug 2026 22:37:58 -0700 Subject: [PATCH 11/13] enable debugging flag PADDLE_OCR_DEBUG --- SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index 029721edb1..6906452b72 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -100,7 +100,7 @@ void PaddleOCRPipeline::run(const std::string& img_path){ void PaddleOCRPipeline::load_dictionary(const Filesystem::Path& path){ - const bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + const bool debugging = STATIC_GLOBALS.PADDLE_OCR_DEBUG; if (debugging){ m_logger.log("[OCR-INFO] Loading dictionary from: " + path.string()); @@ -154,7 +154,7 @@ void PaddleOCRPipeline::load_dictionary(const Filesystem::Path& path){ std::string PaddleOCRPipeline::recognize(const ImageViewRGB32& image){ - const bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + const bool debugging = STATIC_GLOBALS.PADDLE_OCR_DEBUG; // 1. Convert Image to OpenCV image (cv::mat) cv::Mat cv_image_rgb = imageviewrgb32_to_cv_mat_rgb(image); @@ -459,7 +459,7 @@ std::vector preprocess_NCHW(cv::Mat& img){ std::string PaddleOCRPipeline::decode_CTC(float* data, const std::vector& shape, const std::vector& dict){ - const bool debugging = true; //STATIC_GLOBALS.PADDLE_OCR_DEBUG; + const bool debugging = STATIC_GLOBALS.PADDLE_OCR_DEBUG; std::string text = ""; size_t seq_len = static_cast(shape[1]); From cc294bf5a70cff91fb028b5b652a00d6e1b6444d Mon Sep 17 00:00:00 2001 From: jw098 Date: Wed, 12 Aug 2026 12:35:22 -0700 Subject: [PATCH 12/13] revert changes to ONNX, CI --- .github/workflows/cpp-ci-serial-programs-base.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/cpp-ci-serial-programs-base.yml b/.github/workflows/cpp-ci-serial-programs-base.yml index 6a4a6d6ea9..e53e055c6d 100644 --- a/.github/workflows/cpp-ci-serial-programs-base.yml +++ b/.github/workflows/cpp-ci-serial-programs-base.yml @@ -57,7 +57,7 @@ jobs: with: # Delete aqtsource once https://github.com/miurahr/aqtinstall/issues/1007 is fixed and released aqtsource: 'git+https://github.com/miurahr/aqtinstall' - version: '6.10.2' + version: '6.11.1' modules: 'qtmultimedia qtserialport' - name: Install dependencies (Ubuntu) @@ -107,13 +107,6 @@ jobs: name: Serial Programs (os=${{inputs.os}} - compiler=${{inputs.compiler}}) path: ${{env.UPLOAD_FOLDER}} - - name: Upload Executable Only - uses: actions/upload-artifact@v7 - if: inputs.upload-build && startsWith(inputs.os, 'windows') - with: - name: Standalone Executable (os=${{inputs.os}}) - path: ${{env.UPLOAD_FOLDER}}/SerialPrograms.exe - - name: Checkout CommandLineTests uses: actions/checkout@v7 if: inputs.run-tests From 25b8d150a019be3dc942ae0b294f5eae07cfd733 Mon Sep 17 00:00:00 2001 From: jw098 Date: Wed, 12 Aug 2026 12:46:48 -0700 Subject: [PATCH 13/13] use Filesystem instead of std::filesystem --- SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp index 6906452b72..16f83b84bb 100644 --- a/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp +++ b/SerialPrograms/Source/ML/Inference/ML_PaddleOCRPipeline.cpp @@ -5,10 +5,8 @@ * */ -#include #include #include -#include #include "Common/Cpp/Exceptions.h" #include "Common/Cpp/Filesystem/Filesystem.h" #include "Common/Cpp/Logging/GlobalLogger.h" @@ -108,7 +106,7 @@ void PaddleOCRPipeline::load_dictionary(const Filesystem::Path& path){ Filesystem::current_path().string()); std::error_code ec; - const auto absolute_path = std::filesystem::absolute(path, ec); + const auto absolute_path = Filesystem::absolute(path); if (!ec) { m_logger.log("[OCR-INFO] Absolute dictionary path: " + @@ -118,7 +116,7 @@ void PaddleOCRPipeline::load_dictionary(const Filesystem::Path& path){ std::string(Filesystem::exists(absolute_path) ? "true" : "false")); if (Filesystem::exists(absolute_path)) { - const auto file_size = Filesystem::file_size(Filesystem::Path(absolute_path), ec); + const auto file_size = Filesystem::file_size(absolute_path, ec); if (!ec) { m_logger.log("[OCR-INFO] Dictionary file size: " +