// dllmain.cpp : Defines the entry point for the DLL application. #include "pch.h" #include "ANSOCRBase.h" #include "ANSCpuOCR.h" #include "ANSOnnxOCR.h" #include "ANSRtOCR.h" #include "ANSLibsLoader.h" #include "ANSGpuFrameRegistry.h" #include #include "NV12PreprocessHelper.h" #include "engine/TRTEngineCache.h" #include "engine/EnginePoolManager.h" #include #include #include // DebugView: filter on "[ANSOCR]" — gated by ANSCORE_DEBUGVIEW in ANSLicense.h. // Handle registry with refcount — prevents use-after-free when // ReleaseANSOCRHandle is called while inference is still running. // destructionStarted: set by the first Unregister caller; blocks new Acquires // and makes subsequent Unregister calls return false without deleting. // Prevents double-free when Release is raced on the same handle. struct OCREntry { int refcount; bool destructionStarted; }; static std::unordered_map& OCRHandleRegistry() { static std::unordered_map s; return s; } static std::mutex& OCRHandleRegistryMutex() { static std::mutex m; return m; } static std::condition_variable& OCRHandleRegistryCV() { static std::condition_variable cv; return cv; } static void RegisterOCRHandle(ANSCENTER::ANSOCRBase* h) { std::lock_guard lk(OCRHandleRegistryMutex()); OCRHandleRegistry()[h] = { 1, false }; ANS_DBG("ANSOCR","Register: handle=%p (uint=%llu) registrySize=%zu", (void*)h, (unsigned long long)(uintptr_t)h, OCRHandleRegistry().size()); } static ANSCENTER::ANSOCRBase* AcquireOCRHandle(ANSCENTER::ANSOCRBase* h) { std::lock_guard lk(OCRHandleRegistryMutex()); auto it = OCRHandleRegistry().find(h); if (it == OCRHandleRegistry().end()) { ANS_DBG("ANSOCR","Acquire FAIL: handle=%p (uint=%llu) NOT in registry. registrySize=%zu", (void*)h, (unsigned long long)(uintptr_t)h, OCRHandleRegistry().size()); size_t i = 0; for (auto& kv : OCRHandleRegistry()) { ANS_DBG("ANSOCR"," registry[%zu] = %p (uint=%llu) refcount=%d destructionStarted=%d", i++, (void*)kv.first, (unsigned long long)(uintptr_t)kv.first, kv.second.refcount, kv.second.destructionStarted ? 1 : 0); } return nullptr; } if (it->second.destructionStarted) { ANS_DBG("ANSOCR","Acquire FAIL: handle=%p is being destroyed (destructionStarted=true)", (void*)h); return nullptr; } it->second.refcount++; ANS_DBG("ANSOCR","Acquire OK: handle=%p refcount=%d", (void*)h, it->second.refcount); return h; } static bool ReleaseOCRHandleRef(ANSCENTER::ANSOCRBase* h) { std::lock_guard lk(OCRHandleRegistryMutex()); auto it = OCRHandleRegistry().find(h); if (it == OCRHandleRegistry().end()) return false; it->second.refcount--; if (it->second.refcount <= 0) { OCRHandleRegistryCV().notify_all(); } return false; // Only Unregister deletes. } static bool UnregisterOCRHandle(ANSCENTER::ANSOCRBase* h) { std::unique_lock lk(OCRHandleRegistryMutex()); auto it = OCRHandleRegistry().find(h); if (it == OCRHandleRegistry().end()) { ANS_DBG("ANSOCR","Unregister: handle=%p NOT in registry (already gone)", (void*)h); return false; } if (it->second.destructionStarted) { ANS_DBG("ANSOCR","Unregister: handle=%p already being destroyed by another thread, returning false", (void*)h); return false; // Another thread already owns the delete. } ANS_DBG("ANSOCR","Unregister: handle=%p starting (refcount before=%d)", (void*)h, it->second.refcount); it->second.destructionStarted = true; it->second.refcount--; bool ok = OCRHandleRegistryCV().wait_for(lk, std::chrono::seconds(5), [&]() { auto it2 = OCRHandleRegistry().find(h); return it2 == OCRHandleRegistry().end() || it2->second.refcount <= 0; }); if (!ok) { ANS_DBG("ANSOCR","WARNING: Unregister timed out waiting for in-flight inference on handle=%p", (void*)h); OutputDebugStringA("WARNING: UnregisterOCRHandle timed out waiting for in-flight inference\n"); } OCRHandleRegistry().erase(h); return true; } // RAII guard — ensures ReleaseOCRHandleRef is always called, preventing // refcount leaks that would cause UnregisterOCRHandle to deadlock. class OCRHandleGuard { ANSCENTER::ANSOCRBase* engine; public: explicit OCRHandleGuard(ANSCENTER::ANSOCRBase* e) : engine(e) {} ~OCRHandleGuard() { if (engine) ReleaseOCRHandleRef(engine); } ANSCENTER::ANSOCRBase* get() const { return engine; } explicit operator bool() const { return engine != nullptr; } OCRHandleGuard(const OCRHandleGuard&) = delete; OCRHandleGuard& operator=(const OCRHandleGuard&) = delete; }; // RAII guard — sets the per-thread current GPU frame pointer and always // clears it on scope exit, even if the wrapped inference call throws. // Without this, a throwing RunInference leaves tl_currentGpuFrame pointing // at a GpuFrameData that may be freed before the next call on this thread, // causing use-after-free or stale NV12 data on subsequent frames. class GpuFrameScope { public: explicit GpuFrameScope(GpuFrameData* f) { tl_currentGpuFrame() = f; } ~GpuFrameScope() { tl_currentGpuFrame() = nullptr; } GpuFrameScope(const GpuFrameScope&) = delete; GpuFrameScope& operator=(const GpuFrameScope&) = delete; }; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: // When lpReserved != NULL, the process is terminating via ExitProcess. // The OS has already killed all worker threads (idle timers, CUDA // threads, etc.). Set the global flag so atexit destructors skip // thread joins and CUDA/TRT cleanup that would fail on a dead context. if (lpReserved != nullptr) { g_processExiting().store(true, std::memory_order_relaxed); break; } // Dynamic FreeLibrary — threads are still alive, safe to clean up. try { std::vector leakedHandles; { std::lock_guard lk(OCRHandleRegistryMutex()); for (auto& [h, _] : OCRHandleRegistry()) leakedHandles.push_back(h); OCRHandleRegistry().clear(); } for (auto* h : leakedHandles) { try { h->Destroy(); delete h; } catch (...) {} } try { EnginePoolManager::instance().clearAll(); } catch (...) {} try { TRTEngineCache::instance().clearAll(); } catch (...) {} } catch (...) {} break; } return TRUE; } // Extended version with limitSideLen parameter — new callers should use this. extern "C" ANSOCR_API int CreateANSOCRHandleEx(ANSCENTER::ANSOCRBase** Handle, const char* licenseKey, const char* modelFilePath, const char* modelFileZipPassword, int language, int engineMode, // 0: autodetect, 1 gpu, 2 cpu int gpuId, double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio, double classifierThreshold, int useDilation, int limitSideLen) { ANS_DBG("ANSOCR","CreateEx called: HandlePtr=%p, *Handle(in)=%p, language=%d, engineMode=%d, gpuId=%d, limitSideLen=%d, modelPath=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), language, engineMode, gpuId, limitSideLen, modelFilePath ? modelFilePath : "(null)"); if (Handle == nullptr) { ANS_DBG("ANSOCR","CreateEx FAIL: Handle is null"); return 0; } try { // Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded ANSCENTER::ANSLibsLoader::Initialize(); ANSCENTER::EngineType engineType = ANSCENTER::ANSLicenseHelper::CheckHardwareInformation(); { // Describe the backend the engine-selector below will actually choose // for this (hardware, engineMode) combination. Previous versions of // this log claimed "TensorRT OCR enabled" based on hardware alone, // which was misleading because engineMode=0 (auto) unconditionally // picked ONNX — users saw the log and assumed TRT was running. const bool isNvidia = (engineType == ANSCENTER::EngineType::NVIDIA_GPU); const bool willUseTRT = isNvidia && (engineMode == 0 /* auto → TRT on NVIDIA */ || engineMode == 1 /* GPU → TRT on NVIDIA */); const char* vendorTag = engineType == ANSCENTER::EngineType::NVIDIA_GPU ? (willUseTRT ? "NVIDIA_GPU (TensorRT OCR active)" : "NVIDIA_GPU (TensorRT available, but engineMode forces ONNX)") : engineType == ANSCENTER::EngineType::AMD_GPU ? "AMD_GPU (ONNX Runtime / DirectML, TensorRT OCR unavailable)" : engineType == ANSCENTER::EngineType::OPENVINO_GPU ? "OPENVINO_GPU (ONNX Runtime / OpenVINO, TensorRT OCR unavailable)" : "CPU (ONNX Runtime, TensorRT OCR unavailable)"; char buf[192]; snprintf(buf, sizeof(buf), "[ANSOCR] CreateANSOCRHandleEx: detected engineType=%d [%s], engineMode=%d\n", static_cast(engineType), vendorTag, engineMode); OutputDebugStringA(buf); } // Pure constructor: ignore *Handle(in). LabVIEW's CLF Node marshalling // reuses the same temp buffer per call site, so *Handle(in) often holds // leftover bytes from the previous Create's output even when the actual // LabVIEW wire is a different, freshly-allocated instance. Inspecting // *Handle(in) and destroying what we "see" tears down legitimate // parallel instances. (Same reasoning as CreateANSAWSHandle.) // Trade-off: a true double-Create on the same wire leaks the prior // handle -- caller's bug; the alternative is far worse. *Handle = nullptr; // Validate limitSideLen: must be in (0, 20000], default to 960 if (limitSideLen <= 0 || limitSideLen > 20000) limitSideLen = 960; // Backward compatibility: legacy PaddleOCR model uses CPU OCR engine std::string modelPath(modelFilePath ? modelFilePath : ""); bool isLegacyModel = (modelPath.find("ANS_GenericOCR_v1.0") != std::string::npos); if (isLegacyModel) { (*Handle) = new ANSCENTER::ANSCPUOCR(); ANS_DBG("ANSOCR","CreateEx: legacy model detected, allocated ANSCPUOCR=%p", (void*)*Handle); } else { // ANSRTOCR wraps PaddleOCRV5RTEngine which is strictly NVIDIA/CUDA: // RTOCRDetector/Classifier/Recognizer use cv::cuda::GpuMat, cudaMalloc, // cudaMemcpy and link against cudart. Instantiating it on AMD, Intel // or pure-CPU machines either crashes in the CUDA runtime loader or // hangs in amdkmdag when DirectML and TRT coexist. We therefore // hard-gate the TRT path on NVIDIA_GPU and fall back to ANSONNXOCR // (which uses CPU-side NV12 conversion and ONNX Runtime's EP auto- // select, including DirectML for AMD). const bool isNvidia = (engineType == ANSCENTER::EngineType::NVIDIA_GPU); switch (engineMode) { case 0: // Auto-detect — prefer TensorRT on NVIDIA, ONNX elsewhere. // Previous policy was "always ONNX" for cross-platform safety, // but on NVIDIA that defeated the point: each ANSONNXOCR handle // allocates its own cls/dec/rec OrtSessions (no dedupe), which // wasted ~300–600 MB VRAM per extra instance and ran ~2× slower // than ANSRTOCR's shared-engine path via EnginePoolManager. if (isNvidia) { limitSideLen = 960; (*Handle) = new ANSCENTER::ANSRTOCR(); } else { // AMD / Intel / CPU — ANSRTOCR hard-requires CUDA and would // crash. ANSONNXOCR auto-picks the correct ORT EP // (DirectML on AMD, OpenVINO on Intel, CPU otherwise). (*Handle) = new ANSCENTER::ANSONNXOCR(); } break; case 1: // GPU — use TensorRT engine ONLY on NVIDIA hardware. if (isNvidia) { limitSideLen = 960; (*Handle) = new ANSCENTER::ANSRTOCR(); } else { // AMD / Intel / CPU requested GPU mode — ANSRTOCR would crash. // Fall back to ANSONNXOCR which picks the right ORT provider // (DirectML on AMD, OpenVINO/CPU on Intel, CPU otherwise). (*Handle) = new ANSCENTER::ANSONNXOCR(); } break; case 2: // CPU (*Handle) = new ANSCENTER::ANSONNXOCR(); break; default: (*Handle) = new ANSCENTER::ANSONNXOCR(); break; } } if (*Handle == nullptr) { ANS_DBG("ANSOCR","CreateEx FAIL: new returned null"); return 0; } else { ANS_DBG("ANSOCR","CreateEx: allocated handle=%p (uint=%llu), calling Initialize...", (void*)*Handle, (unsigned long long)(uintptr_t)*Handle); RegisterOCRHandle(*Handle); ANSCENTER::OCRModelConfig modelConfig; switch (language) { case 0: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::ENGLISH; break; } case 1: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::CHINESE; break; } case 2: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::FRENCH; break; } case 3: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::GERMANY; break; } case 4: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::JAPANESE; break; } case 5: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::KOREAN; break; } case 6: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::CUSTOM; break; } default: { modelConfig.ocrLanguage = ANSCENTER::OCRLanguage::ENGLISH; break; } } modelConfig.useDetector = true; modelConfig.useRecognizer = true; modelConfig.useCLS = true; modelConfig.useLayout = false; modelConfig.useTable = false; modelConfig.useTensorRT = false; modelConfig.enableMKLDNN = false; modelConfig.useDilation = false; modelConfig.useAngleCLS = false; modelConfig.gpuId = gpuId; modelConfig.detectionDBThreshold = detectorDBThreshold; modelConfig.detectionBoxThreshold = detectorDBBoxThreshold; modelConfig.detectionDBUnclipRatio = detectorDBUnclipRatio; modelConfig.clsThreshold = classifierThreshold; if (useDilation == 1)modelConfig.useDilation = true; modelConfig.limitSideLen = limitSideLen; int result = (*Handle)->Initialize(licenseKey, modelConfig, modelFilePath, modelFileZipPassword, engineMode); if (!result) { ANS_DBG("ANSOCR","CreateEx FAIL: Initialize returned false, tearing down handle=%p", (void*)*Handle); // Initialize failed — tear down the engine we just registered so // the caller, who sees a 0 return and (typically) does not call // ReleaseANSOCRHandle, does not leak the engine + registry entry. if (UnregisterOCRHandle(*Handle)) { try { (*Handle)->Destroy(); } catch (...) {} delete *Handle; } *Handle = nullptr; return 0; } ANS_DBG("ANSOCR","CreateEx OK: handle=%p (uint=%llu)", (void*)*Handle, (unsigned long long)(uintptr_t)*Handle); return result; } } catch (std::exception& e) { ANS_DBG("ANSOCR","CreateEx EXCEPTION (std::exception): %s", e.what()); // Partially-constructed engine may already be registered — unwind it // so the leak does not accumulate across repeated failed Create calls. if (Handle && *Handle) { if (UnregisterOCRHandle(*Handle)) { try { (*Handle)->Destroy(); } catch (...) {} delete *Handle; } *Handle = nullptr; } return 0; } catch (...) { ANS_DBG("ANSOCR","CreateEx EXCEPTION (unknown)"); if (Handle && *Handle) { if (UnregisterOCRHandle(*Handle)) { try { (*Handle)->Destroy(); } catch (...) {} delete *Handle; } *Handle = nullptr; } return 0; } } // Original signature (without limitSideLen) — kept for backward compatibility // with third-party apps already built against the old DLL. extern "C" ANSOCR_API int CreateANSOCRHandle(ANSCENTER::ANSOCRBase** Handle, const char* licenseKey, const char* modelFilePath, const char* modelFileZipPassword, int language, int engineMode, // 0: autodetect, 1 gpu, 2 cpu int gpuId, double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio, double classifierThreshold, int useDilation) { ANS_DBG("ANSOCR","CreateANSOCRHandle: HandlePtr=%p, language=%d, engineMode=%d, gpuId=%d (delegating to CreateEx, limitSideLen=960)", (void*)Handle, language, engineMode, gpuId); return CreateANSOCRHandleEx(Handle, licenseKey, modelFilePath, modelFileZipPassword, language, engineMode, gpuId, detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio, classifierThreshold, useDilation, 960); } // Helper: serialize OCR results with optional ALPR post-processing static std::string SerializeOCRResults(ANSCENTER::ANSOCRBase* engine, const std::vector& outputs, int imageWidth, int imageHeight, const cv::Mat& originalImage = cv::Mat()) { if (engine->GetOCRMode() == ANSCENTER::OCR_ALPR && !engine->GetALPRFormats().empty()) { auto alprResults = ANSCENTER::ANSOCRUtility::ALPRPostProcessing( outputs, engine->GetALPRFormats(), imageWidth, imageHeight, engine, originalImage); return ANSCENTER::ANSOCRUtility::ALPRResultToJsonString(alprResults); } return ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs); } extern "C" ANSOCR_API std::string RunInference(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength) { ANS_DBG("ANSOCR","RunInference: HandlePtr=%p, *Handle=%p, bufferLength=%d", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength); if (!Handle || !*Handle) return ""; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return ""; auto* engine = guard.get(); try { cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR); if (frame.empty()) return ""; std::vector outputs = engine->RunInference(frame); std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame); frame.release(); outputs.clear(); return stResult; } catch (...) { return ""; } } extern "C" ANSOCR_API std::string RunInferenceWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId) { ANS_DBG("ANSOCR","RunInferenceWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, cameraId=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, cameraId ? cameraId : "(null)"); if (!Handle || !*Handle) return ""; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return ""; auto* engine = guard.get(); try { cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR); if (frame.empty()) return ""; std::vector outputs = engine->RunInference(frame, cameraId); std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame); frame.release(); outputs.clear(); return stResult; } catch (...) { return ""; } } extern "C" ANSOCR_API int RunInferenceCV(ANSCENTER::ANSOCRBase** Handle, const cv::Mat& image, std::string& ocrResult) { ANS_DBG("ANSOCR","RunInferenceCV: HandlePtr=%p, *Handle=%p, image=%dx%d", (void*)Handle, (void*)(Handle ? *Handle : nullptr), image.cols, image.rows); if (!Handle || !*Handle) return -1; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return -3; auto* engine = guard.get(); try { if (image.empty()) return 0; std::vector outputs = engine->RunInference(image, "cameraId"); ocrResult = SerializeOCRResults(engine, outputs, image.cols, image.rows, image); return 1; } catch (...) { return -2; } } extern "C" ANSOCR_API std::string RunInferenceBinary(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height) { ANS_DBG("ANSOCR","RunInferenceBinary: HandlePtr=%p, *Handle=%p, %ux%u", (void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height); if (!Handle || !*Handle || !jpeg_bytes || width == 0 || height == 0) return ""; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return ""; auto* engine = guard.get(); try { cv::Mat frame = cv::Mat(height, width, CV_8UC3, jpeg_bytes).clone(); if (frame.empty()) return ""; std::vector outputs = engine->RunInference(frame); std::string stResult = SerializeOCRResults(engine, outputs, width, height, frame); frame.release(); outputs.clear(); return stResult; } catch (...) { return ""; } } static int ReleaseANSOCRHandle_Impl(ANSCENTER::ANSOCRBase** Handle) { try { if (!Handle || !*Handle) { ANS_DBG("ANSOCR","Release: HandlePtr or *Handle is null, no-op"); return 0; } ANSCENTER::ANSOCRBase* h = *Handle; ANS_DBG("ANSOCR","Release called: handle=%p (uint=%llu)", (void*)h, (unsigned long long)(uintptr_t)h); if (!UnregisterOCRHandle(h)) { ANS_DBG("ANSOCR","Release: Unregister returned false (already gone or being destroyed by another thread), handle=%p", (void*)h); *Handle = nullptr; return 0; // Not in registry — already freed } (*Handle)->Destroy(); delete *Handle; *Handle = nullptr; ANS_DBG("ANSOCR","Release OK: handle=%p deleted, registry now has %zu entries", (void*)h, OCRHandleRegistry().size()); return 0; } catch (...) { ANS_DBG("ANSOCR","Release EXCEPTION (unknown)"); if (Handle) *Handle = nullptr; return 1; } } extern "C" ANSOCR_API int ReleaseANSOCRHandle(ANSCENTER::ANSOCRBase** Handle) { __try { return ReleaseANSOCRHandle_Impl(Handle); } __except (EXCEPTION_EXECUTE_HANDLER) { ANS_DBG("ANSOCR","ReleaseANSOCRHandle: SEH exception caught"); return 1; } } // ── ALPR Configuration API ────────────────────────────────────────── extern "C" ANSOCR_API int SetANSOCRMode(ANSCENTER::ANSOCRBase** Handle, int ocrMode) { ANS_DBG("ANSOCR","SetANSOCRMode: HandlePtr=%p, *Handle=%p, ocrMode=%d", (void*)Handle, (void*)(Handle ? *Handle : nullptr), ocrMode); if (!Handle || !*Handle) return -1; (*Handle)->SetOCRMode(static_cast(ocrMode)); return 0; } extern "C" ANSOCR_API int SetANSOCRCountry(ANSCENTER::ANSOCRBase** Handle, int country) { ANS_DBG("ANSOCR","SetANSOCRCountry: HandlePtr=%p, *Handle=%p, country=%d", (void*)Handle, (void*)(Handle ? *Handle : nullptr), country); if (!Handle || !*Handle) return -1; (*Handle)->SetCountry(static_cast(country)); return 0; } extern "C" ANSOCR_API int SetANSOCRALPRFormat(ANSCENTER::ANSOCRBase** Handle, const char* formatJson) { ANS_DBG("ANSOCR","SetANSOCRALPRFormat: HandlePtr=%p, *Handle=%p, formatJson=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), formatJson ? "(set)" : "(null)"); if (!Handle || !*Handle || !formatJson) return -1; try { nlohmann::json j = nlohmann::json::parse(formatJson); ANSCENTER::ALPRPlateFormat fmt; fmt.name = j.value("name", "CUSTOM"); fmt.country = static_cast(j.value("country", 99)); fmt.numRows = j.value("num_rows", 2); fmt.rowSplitThreshold = j.value("row_split_threshold", 0.3f); static const std::map classMap = { {"digit", ANSCENTER::CHAR_DIGIT}, {"latin_alpha", ANSCENTER::CHAR_LATIN_ALPHA}, {"alphanumeric", ANSCENTER::CHAR_ALPHANUMERIC}, {"hiragana", ANSCENTER::CHAR_HIRAGANA}, {"katakana", ANSCENTER::CHAR_KATAKANA}, {"kanji", ANSCENTER::CHAR_KANJI}, {"cjk_any", ANSCENTER::CHAR_CJK_ANY}, {"any", ANSCENTER::CHAR_ANY} }; for (const auto& zj : j["zones"]) { ANSCENTER::ALPRZone zone; zone.name = zj.value("name", ""); zone.row = zj.value("row", 0); zone.col = zj.value("col", 0); std::string ccStr = zj.value("char_class", "any"); auto it = classMap.find(ccStr); zone.charClass = (it != classMap.end()) ? it->second : ANSCENTER::CHAR_ANY; zone.minLength = zj.value("min_length", 1); zone.maxLength = zj.value("max_length", 10); zone.validationRegex = zj.value("regex", ""); if (zj.contains("corrections")) { for (auto& [key, val] : zj["corrections"].items()) { zone.corrections[key] = val.get(); } } fmt.zones.push_back(zone); } (*Handle)->SetALPRFormat(fmt); return 0; } catch (...) { return -2; } } // Unicode conversion utilities for LabVIEW wrapper classes // Converts input string to UTF-16LE. Handles both: // - JSON Unicode escapes (\uXXXX) from ensure_ascii=true output // - Raw UTF-8 encoded strings // Pure ASCII input is passed through directly (no conversion overhead). extern "C" ANSOCR_API int ANSOCR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int includeBOM) { ANS_DBG("ANSOCR","ANSOCR_ConvertUTF8ToUTF16LE: utf8Str=%s, includeBOM=%d", utf8Str ? "(set)" : "(null)", includeBOM); try { if (!utf8Str || !result) return -1; int len = (int)strlen(utf8Str); if (len == 0) return 0; // Always output UTF-16LE (required for LabVIEW "Force Unicode Text" indicators) const char bom[2] = { '\xFF', '\xFE' }; // Check if input contains \uXXXX escapes bool hasUnicodeEscapes = false; for (int i = 0; i + 1 < len; i++) { if (utf8Str[i] == '\\' && utf8Str[i + 1] == 'u') { hasUnicodeEscapes = true; break; } } if (hasUnicodeEscapes) { std::string utf16le; if (includeBOM) utf16le.assign(bom, 2); utf16le.reserve(len * 2 + 2); for (int i = 0; i < len; ) { if (i + 5 < len && utf8Str[i] == '\\' && utf8Str[i + 1] == 'u') { char hex[5] = { utf8Str[i + 2], utf8Str[i + 3], utf8Str[i + 4], utf8Str[i + 5], 0 }; uint16_t cp = (uint16_t)strtoul(hex, nullptr, 16); utf16le += static_cast(cp & 0xFF); utf16le += static_cast((cp >> 8) & 0xFF); i += 6; } else { utf16le += utf8Str[i]; utf16le += '\0'; i++; } } int size = (int)utf16le.size(); MgErr error = DSSetHandleSize(result, sizeof(int32) + size * sizeof(uChar)); if (error != noErr) return -2; (*result)->cnt = size; memcpy((*result)->str, utf16le.data(), size); return 1; } #ifdef _WIN32 int wideLen = MultiByteToWideChar(CP_UTF8, 0, utf8Str, len, nullptr, 0); if (wideLen <= 0) return 0; std::wstring wideStr(wideLen, 0); MultiByteToWideChar(CP_UTF8, 0, utf8Str, len, &wideStr[0], wideLen); int dataSize = wideLen * (int)sizeof(wchar_t); int bomSize = includeBOM ? 2 : 0; int totalSize = bomSize + dataSize; MgErr error = DSSetHandleSize(result, sizeof(int32) + totalSize * sizeof(uChar)); if (error != noErr) return -2; (*result)->cnt = totalSize; if (includeBOM) memcpy((*result)->str, bom, 2); memcpy((*result)->str + bomSize, wideStr.data(), dataSize); return 1; #else return 0; #endif } catch (...) { return -1; } } extern "C" ANSOCR_API int ANSOCR_ConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result) { ANS_DBG("ANSOCR","ANSOCR_ConvertUTF16LEToUTF8: utf16leBytes=%s, byteLen=%d", utf16leBytes ? "(set)" : "(null)", byteLen); try { if (!utf16leBytes || byteLen <= 0 || !result) return -1; // Check if input is already pure ASCII (no high bytes, or not valid UTF-16LE) // If all bytes < 0x80 and byteLen has no null bytes in odd positions pattern, // treat as already-UTF8 ASCII and pass through bool isAlreadyAscii = true; bool isUtf16le = (byteLen >= 2 && byteLen % 2 == 0); if (isUtf16le) { // Check if all high bytes (odd indices) are 0x00 — means pure ASCII in UTF-16LE for (int i = 1; i < byteLen; i += 2) { if (utf16leBytes[i] != 0x00) { isAlreadyAscii = false; break; } } if (isAlreadyAscii) { // Extract just the low bytes (ASCII characters) int asciiLen = byteLen / 2; MgErr error = DSSetHandleSize(result, sizeof(int32) + asciiLen * sizeof(uChar)); if (error != noErr) return -2; (*result)->cnt = asciiLen; for (int i = 0; i < asciiLen; i++) { (*result)->str[i] = utf16leBytes[i * 2]; } return 1; } } #ifdef _WIN32 int wideLen = byteLen / (int)sizeof(wchar_t); const wchar_t* wideStr = reinterpret_cast(utf16leBytes); int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, nullptr, 0, nullptr, nullptr); if (utf8Len <= 0) return 0; std::string utf8Str(utf8Len, 0); WideCharToMultiByte(CP_UTF8, 0, wideStr, wideLen, &utf8Str[0], utf8Len, nullptr, nullptr); MgErr error = DSSetHandleSize(result, sizeof(int32) + utf8Len * sizeof(uChar)); if (error != noErr) return -2; (*result)->cnt = utf8Len; memcpy((*result)->str, utf8Str.data(), utf8Len); return 1; #else return 0; #endif } catch (...) { return -1; } } extern "C" ANSOCR_API std::string RunInferenceImagePath(ANSCENTER::ANSOCRBase** Handle, const char* imageFilePath) { ANS_DBG("ANSOCR","RunInferenceImagePath: HandlePtr=%p, *Handle=%p, imageFilePath=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), imageFilePath ? imageFilePath : "(null)"); if (!Handle || !*Handle) return ""; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return ""; auto* engine = guard.get(); try { std::string stImageFileName(imageFilePath); cv::Mat frame = cv::imread(stImageFileName, cv::ImreadModes::IMREAD_COLOR); if (frame.empty()) return ""; std::vector outputs = engine->RunInference(frame); std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame); frame.release(); outputs.clear(); return stResult; } catch (...) { return ""; } } extern "C" ANSOCR_API std::string RunInferenceInCroppedImages(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes) { ANS_DBG("ANSOCR","RunInferenceInCroppedImages: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)"); if (!Handle || !*Handle) return ""; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return ""; auto* engine = guard.get(); try { cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR); if (frame.empty()) return ""; std::vector bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes); std::vector outputs = engine->RunInference(frame, bBoxes); std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs); frame.release(); outputs.clear(); return stResult; } catch (...) { return ""; } } extern "C" ANSOCR_API std::string RunInferenceInCroppedImagesWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId) { ANS_DBG("ANSOCR","RunInferenceInCroppedImagesWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s, cameraId=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)", cameraId ? cameraId : "(null)"); if (!Handle || !*Handle) return ""; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return ""; auto* engine = guard.get(); try { cv::Mat frame = cv::imdecode(cv::Mat(1, bufferLength, CV_8UC1, jpeg_string), cv::IMREAD_COLOR); if (frame.empty()) return ""; std::vector bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes); std::vector outputs = engine->RunInference(frame, bBoxes, cameraId); std::string stResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs); frame.release(); outputs.clear(); return stResult; } catch (...) { return ""; } } //// For LabVIEW API extern "C" ANSOCR_API int RunInference_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInference_LV: HandlePtr=%p, *Handle=%p, bufferLength=%d", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength); try { std::string st = RunInference(Handle, jpeg_string, bufferLength); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error; error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (std::exception& e) { return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInference_LVWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInference_LVWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, cameraId=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, cameraId ? cameraId : "(null)"); try { std::string st = RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error; error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (std::exception& e) { return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceBinary_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceBinary_LV: HandlePtr=%p, *Handle=%p, %ux%u", (void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height); try { std::string st = RunInferenceBinary(Handle, jpeg_bytes, width, height); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error; error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (std::exception& e) { return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceImagePath_LV(ANSCENTER::ANSOCRBase** Handle, const char* imageFilePath, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceImagePath_LV: HandlePtr=%p, *Handle=%p, imageFilePath=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), imageFilePath ? imageFilePath : "(null)"); try { std::string st = RunInferenceImagePath(Handle, imageFilePath); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error; error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (std::exception& e) { return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int ANSOCRUnitTest(const char* modelFilePath, const char* imageFilePath, LStrHandle detectionResult) { ANS_DBG("ANSOCR","ANSOCRUnitTest: modelFilePath=%s, imageFilePath=%s", modelFilePath ? modelFilePath : "(null)", imageFilePath ? imageFilePath : "(null)"); try { ANSCENTER::ANSOCRBase* infHandle; std::string licenseKey = ""; int language = 0;// English int engine = 0; int createResult = CreateANSOCRHandle(&infHandle, licenseKey.c_str(), modelFilePath, "", language, engine); std::string st = RunInferenceImagePath(&infHandle, imageFilePath); ReleaseANSOCRHandle(&infHandle); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error; error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (std::exception& e) { return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceInCroppedImages_LV(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LV: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)"); try { std::string st = RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error; error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (std::exception& e) { return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceInCroppedImages_LVWithCamID(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LVWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s, cameraId=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)", cameraId ? cameraId : "(null)"); try { std::string st = RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error; error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (std::exception& e) { return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceComplete_LV( ANSCENTER::ANSOCRBase** Handle, cv::Mat** cvImage, const char* cameraId, int getJpegString, int jpegImageSize, LStrHandle detectionResult, LStrHandle imageStr) { ANS_DBG("ANSOCR","RunInferenceComplete_LV: HandlePtr=%p, *Handle=%p, cameraId=%s, getJpegString=%d, jpegImageSize=%d", (void*)Handle, (void*)(Handle ? *Handle : nullptr), cameraId ? cameraId : "(null)", getJpegString, jpegImageSize); if (!Handle || !*Handle) return -1; if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return -3; auto* engine = guard.get(); try { // Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*) GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage); cv::Mat localImage = (**cvImage).clone(); int originalWidth = localImage.cols; int originalHeight = localImage.rows; if (originalWidth == 0 || originalHeight == 0) return -2; std::vector outputs; { GpuFrameScope _gfs(gpuFrame); outputs = engine->RunInference(localImage, cameraId); } bool getJpeg = (getJpegString == 1); std::string stImage; int maxImageSize = originalWidth; bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize); float ratio = 1.0f; int newWidth = originalWidth; int newHeight = originalHeight; if (resizeNeeded) { newWidth = jpegImageSize; newHeight = static_cast(std::round(newWidth * static_cast(originalHeight) / originalWidth)); ratio = static_cast(newWidth) / originalWidth; for (auto& obj : outputs) { obj.box.x = std::max(0, std::min(static_cast(obj.box.x * ratio), newWidth - 1)); obj.box.y = std::max(0, std::min(static_cast(obj.box.y * ratio), newHeight - 1)); obj.box.width = std::max(1, std::min(static_cast(obj.box.width * ratio), newWidth - obj.box.x)); obj.box.height = std::max(1, std::min(static_cast(obj.box.height * ratio), newHeight - obj.box.y)); } } else { for (auto& obj : outputs) { obj.box.x = std::max(0, std::min(static_cast(obj.box.x), originalWidth - 1)); obj.box.y = std::max(0, std::min(static_cast(obj.box.y), originalHeight - 1)); obj.box.width = std::max(1, std::min(static_cast(obj.box.width), originalWidth - obj.box.x)); obj.box.height = std::max(1, std::min(static_cast(obj.box.height), originalHeight - obj.box.y)); } } if (getJpeg) { cv::Mat processedImage = localImage; if (resizeNeeded) { cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA); } std::vector buf; if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) { stImage.assign(buf.begin(), buf.end()); } } std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs); if (stDetectionResult.empty()) return 0; int size = static_cast(stDetectionResult.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error != noErr) return 0; (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, stDetectionResult.c_str(), size); if (getJpeg) { if (stImage.empty()) return 0; size = static_cast(stImage.length()); error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar)); if (error != noErr) return 0; (*imageStr)->cnt = size; memcpy((*imageStr)->str, stImage.c_str(), size); } return 1; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferencesComplete_LV(ANSCENTER::ANSOCRBase** Handle, cv::Mat** cvImage, const char* cameraId, int maxImageSize, const char* strBboxes, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferencesComplete_LV: HandlePtr=%p, *Handle=%p, cameraId=%s, maxImageSize=%d, strBboxes=%s", (void*)Handle, (void*)(Handle ? *Handle : nullptr), cameraId ? cameraId : "(null)", maxImageSize, strBboxes ? "(set)" : "(null)"); if (!Handle || !*Handle) return -1; if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2; OCRHandleGuard guard(AcquireOCRHandle(*Handle)); if (!guard) return -3; auto* engine = guard.get(); try { // Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*) GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage); cv::Mat localImage = (**cvImage).clone(); std::vector objectDetectionResults; std::vector bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes); const int originalWidth = localImage.cols; const int originalHeight = localImage.rows; const double scaleFactor = (maxImageSize > 0) ? static_cast(originalWidth) / maxImageSize : 1.0; { GpuFrameScope _gfs(gpuFrame); if (bBox.empty()) { objectDetectionResults = engine->RunInference(localImage, cameraId); } else { for (const auto& rect : bBox) { cv::Rect scaledRect; scaledRect.x = static_cast(rect.x * scaleFactor); scaledRect.y = static_cast(rect.y * scaleFactor); scaledRect.width = static_cast(rect.width * scaleFactor); scaledRect.height = static_cast(rect.height * scaleFactor); scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight); if (scaledRect.width <= 0 || scaledRect.height <= 0) continue; const cv::Mat croppedImage = localImage(scaledRect); std::vector croppedDetectionResults = engine->RunInference(croppedImage, cameraId); for (auto& obj : croppedDetectionResults) { obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor; obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor; obj.box.width /= scaleFactor; obj.box.height /= scaleFactor; objectDetectionResults.push_back(std::move(obj)); } } } } std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(objectDetectionResults); if (stDetectionResult.empty()) return 0; const int size = static_cast(stDetectionResult.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error != noErr) return 0; (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, stDetectionResult.c_str(), size); return 1; } catch (...) { return 0; } } // ============================================================================ // V2 LabVIEW API — Accept handle as uint64_t by value. // Eliminates Handle** pointer-to-pointer instability when LabVIEW calls // concurrently from multiple tasks. // LabVIEW CLFN: set Handle parameter to Numeric / Unsigned Pointer-sized Integer / Pass: Value // ============================================================================ // Helper: cast uint64_t handle to ANSOCRBase** for delegation to existing functions #define OCR_V2_HANDLE_SETUP(handleVal) \ ANSCENTER::ANSOCRBase* _v2Direct = reinterpret_cast(handleVal); \ if (_v2Direct == nullptr) return 0; \ ANSCENTER::ANSOCRBase* _v2Arr[1] = { _v2Direct }; \ ANSCENTER::ANSOCRBase** Handle = &_v2Arr[0]; extern "C" ANSOCR_API int RunInference_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInference_LV_V2: handleVal=%llu (handle=%p), bufferLength=%d", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength); try { OCR_V2_HANDLE_SETUP(handleVal); std::string st = RunInference(Handle, jpeg_string, bufferLength); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInference_LVWithCamID_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* cameraId, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInference_LVWithCamID_V2: handleVal=%llu (handle=%p), bufferLength=%d, cameraId=%s", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength, cameraId ? cameraId : "(null)"); try { OCR_V2_HANDLE_SETUP(handleVal); std::string st = RunInferenceWithCamID(Handle, jpeg_string, bufferLength, cameraId); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceBinary_LV_V2(uint64_t handleVal, unsigned char* jpeg_bytes, unsigned int width, unsigned int height, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceBinary_LV_V2: handleVal=%llu (handle=%p), %ux%u", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, width, height); try { OCR_V2_HANDLE_SETUP(handleVal); std::string st = RunInferenceBinary(Handle, jpeg_bytes, width, height); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceImagePath_LV_V2(uint64_t handleVal, const char* imageFilePath, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceImagePath_LV_V2: handleVal=%llu (handle=%p), imageFilePath=%s", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, imageFilePath ? imageFilePath : "(null)"); try { OCR_V2_HANDLE_SETUP(handleVal); std::string st = RunInferenceImagePath(Handle, imageFilePath); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceInCroppedImages_LV_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LV_V2: handleVal=%llu (handle=%p), bufferLength=%d, strBboxes=%s", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength, strBboxes ? "(set)" : "(null)"); try { OCR_V2_HANDLE_SETUP(handleVal); std::string st = RunInferenceInCroppedImages(Handle, jpeg_string, bufferLength, strBboxes); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceInCroppedImages_LVWithCamID_V2(uint64_t handleVal, unsigned char* jpeg_string, int32 bufferLength, const char* strBboxes, const char* cameraId, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LVWithCamID_V2: handleVal=%llu (handle=%p), bufferLength=%d, strBboxes=%s, cameraId=%s", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength, strBboxes ? "(set)" : "(null)", cameraId ? cameraId : "(null)"); try { OCR_V2_HANDLE_SETUP(handleVal); std::string st = RunInferenceInCroppedImagesWithCamID(Handle, jpeg_string, bufferLength, strBboxes, cameraId); if (st.empty()) return 0; int size = static_cast(st.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error == noErr) { (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, st.c_str(), size); return 1; } else return 0; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferenceComplete_LV_V2( uint64_t handleVal, cv::Mat** cvImage, const char* cameraId, int getJpegString, int jpegImageSize, LStrHandle detectionResult, LStrHandle imageStr) { ANS_DBG("ANSOCR","RunInferenceComplete_LV_V2: handleVal=%llu (handle=%p), cameraId=%s, getJpegString=%d, jpegImageSize=%d", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, cameraId ? cameraId : "(null)", getJpegString, jpegImageSize); ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast(handleVal); if (directHandle == nullptr) return -1; if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2; OCRHandleGuard guard(AcquireOCRHandle(directHandle)); if (!guard) return -3; auto* engine = guard.get(); try { // Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*) GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage); cv::Mat localImage = (**cvImage).clone(); int originalWidth = localImage.cols; int originalHeight = localImage.rows; if (originalWidth == 0 || originalHeight == 0) return -2; std::vector outputs; { GpuFrameScope _gfs(gpuFrame); outputs = engine->RunInference(localImage, cameraId); } bool getJpeg = (getJpegString == 1); std::string stImage; int maxImageSize = originalWidth; bool resizeNeeded = (jpegImageSize > 0) && (jpegImageSize < maxImageSize); float ratio = 1.0f; int newWidth = originalWidth; int newHeight = originalHeight; if (resizeNeeded) { newWidth = jpegImageSize; newHeight = static_cast(std::round(newWidth * static_cast(originalHeight) / originalWidth)); ratio = static_cast(newWidth) / originalWidth; for (auto& obj : outputs) { obj.box.x = std::max(0, std::min(static_cast(obj.box.x * ratio), newWidth - 1)); obj.box.y = std::max(0, std::min(static_cast(obj.box.y * ratio), newHeight - 1)); obj.box.width = std::max(1, std::min(static_cast(obj.box.width * ratio), newWidth - obj.box.x)); obj.box.height = std::max(1, std::min(static_cast(obj.box.height * ratio), newHeight - obj.box.y)); } } else { for (auto& obj : outputs) { obj.box.x = std::max(0, std::min(static_cast(obj.box.x), originalWidth - 1)); obj.box.y = std::max(0, std::min(static_cast(obj.box.y), originalHeight - 1)); obj.box.width = std::max(1, std::min(static_cast(obj.box.width), originalWidth - obj.box.x)); obj.box.height = std::max(1, std::min(static_cast(obj.box.height), originalHeight - obj.box.y)); } } if (getJpeg) { cv::Mat processedImage = localImage; if (resizeNeeded) { cv::resize(localImage, processedImage, cv::Size(newWidth, newHeight), 0, 0, cv::INTER_AREA); } std::vector buf; if (cv::imencode(".jpg", processedImage, buf, { cv::IMWRITE_JPEG_QUALITY, 50 })) { stImage.assign(buf.begin(), buf.end()); } } std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(outputs); if (stDetectionResult.empty()) return 0; int size = static_cast(stDetectionResult.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error != noErr) return 0; (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, stDetectionResult.c_str(), size); if (getJpeg) { if (stImage.empty()) return 0; size = static_cast(stImage.length()); error = DSSetHandleSize(imageStr, sizeof(int32) + size * sizeof(uChar)); if (error != noErr) return 0; (*imageStr)->cnt = size; memcpy((*imageStr)->str, stImage.c_str(), size); } return 1; } catch (...) { return 0; } } extern "C" ANSOCR_API int RunInferencesComplete_LV_V2(uint64_t handleVal, cv::Mat** cvImage, const char* cameraId, int maxImageSize, const char* strBboxes, LStrHandle detectionResult) { ANS_DBG("ANSOCR","RunInferencesComplete_LV_V2: handleVal=%llu (handle=%p), cameraId=%s, maxImageSize=%d, strBboxes=%s", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal, cameraId ? cameraId : "(null)", maxImageSize, strBboxes ? "(set)" : "(null)"); ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast(handleVal); if (directHandle == nullptr) return -1; if (!cvImage || !(*cvImage) || (*cvImage)->empty()) return -2; OCRHandleGuard guard(AcquireOCRHandle(directHandle)); if (!guard) return -3; auto* engine = guard.get(); try { // Lookup NV12 frame BEFORE cloning (clone creates new cv::Mat*) GpuFrameData* gpuFrame = ANSGpuFrameRegistry::instance().lookup(*cvImage); cv::Mat localImage = (**cvImage).clone(); std::vector objectDetectionResults; std::vector bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes); const int originalWidth = localImage.cols; const int originalHeight = localImage.rows; const double scaleFactor = (maxImageSize > 0) ? static_cast(originalWidth) / maxImageSize : 1.0; { GpuFrameScope _gfs(gpuFrame); if (bBox.empty()) { objectDetectionResults = engine->RunInference(localImage, cameraId); } else { for (const auto& rect : bBox) { cv::Rect scaledRect; scaledRect.x = static_cast(rect.x * scaleFactor); scaledRect.y = static_cast(rect.y * scaleFactor); scaledRect.width = static_cast(rect.width * scaleFactor); scaledRect.height = static_cast(rect.height * scaleFactor); scaledRect &= cv::Rect(0, 0, originalWidth, originalHeight); if (scaledRect.width <= 0 || scaledRect.height <= 0) continue; const cv::Mat croppedImage = localImage(scaledRect); std::vector croppedDetectionResults = engine->RunInference(croppedImage, cameraId); for (auto& obj : croppedDetectionResults) { obj.box.x = (obj.box.x + scaledRect.x) / scaleFactor; obj.box.y = (obj.box.y + scaledRect.y) / scaleFactor; obj.box.width /= scaleFactor; obj.box.height /= scaleFactor; objectDetectionResults.push_back(std::move(obj)); } } } } std::string stDetectionResult = ANSCENTER::ANSOCRUtility::OCRDetectionToJsonString(objectDetectionResults); if (stDetectionResult.empty()) return 0; const int size = static_cast(stDetectionResult.length()); MgErr error = DSSetHandleSize(detectionResult, sizeof(int32) + size * sizeof(uChar)); if (error != noErr) return 0; (*detectionResult)->cnt = size; memcpy((*detectionResult)->str, stDetectionResult.c_str(), size); return 1; } catch (...) { return 0; } } // ============================================================================ // V2 Create / Release — handle returned/passed as uint64_t by value. // ============================================================================ extern "C" ANSOCR_API uint64_t CreateANSOCRHandleEx_V2(const char* licenseKey, const char* modelFilePath, const char* modelFileZipPassword, int language, int engineMode, int gpuId, double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio, double classifierThreshold, int useDilation, int limitSideLen) { ANS_DBG("ANSOCR","CreateANSOCRHandleEx_V2: language=%d, engineMode=%d, gpuId=%d, limitSideLen=%d, modelPath=%s", language, engineMode, gpuId, limitSideLen, modelFilePath ? modelFilePath : "(null)"); try { ANSCENTER::ANSOCRBase* handle = nullptr; int result = CreateANSOCRHandleEx(&handle, licenseKey, modelFilePath, modelFileZipPassword, language, engineMode, gpuId, detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio, classifierThreshold, useDilation, limitSideLen); if (result == 0 || handle == nullptr) return 0; return reinterpret_cast(handle); } catch (...) { return 0; } } extern "C" ANSOCR_API uint64_t CreateANSOCRHandle_V2(const char* licenseKey, const char* modelFilePath, const char* modelFileZipPassword, int language, int engineMode, int gpuId, double detectorDBThreshold, double detectorDBBoxThreshold, double detectorDBUnclipRatio, double classifierThreshold, int useDilation) { ANS_DBG("ANSOCR","CreateANSOCRHandle_V2: language=%d, engineMode=%d, gpuId=%d (delegating to CreateEx_V2, limitSideLen=960)", language, engineMode, gpuId); return CreateANSOCRHandleEx_V2(licenseKey, modelFilePath, modelFileZipPassword, language, engineMode, gpuId, detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio, classifierThreshold, useDilation, 960); } extern "C" ANSOCR_API int ReleaseANSOCRHandle_V2(uint64_t handleVal) { ANS_DBG("ANSOCR","ReleaseANSOCRHandle_V2: handleVal=%llu (handle=%p)", (unsigned long long)handleVal, (void*)(uintptr_t)handleVal); try { ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast(handleVal); if (directHandle == nullptr) { ANS_DBG("ANSOCR","Release_V2: handleVal is 0/null, no-op"); return 0; } if (!UnregisterOCRHandle(directHandle)) { ANS_DBG("ANSOCR","Release_V2: Unregister returned false (already gone or being destroyed by another thread), handle=%p", (void*)directHandle); return 0; // Not in registry — already freed } directHandle->Destroy(); delete directHandle; ANS_DBG("ANSOCR","Release_V2 OK: handle=%p deleted, registry now has %zu entries", (void*)directHandle, OCRHandleRegistry().size()); return 0; } catch (...) { ANS_DBG("ANSOCR","Release_V2 EXCEPTION (unknown)"); return 1; } }