Files
ANSCORE/modules/ANSOCR/dllmain.cpp
2026-04-21 09:26:02 +10:00

1477 lines
58 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 <json.hpp>
#include "NV12PreprocessHelper.h"
#include "engine/TRTEngineCache.h"
#include "engine/EnginePoolManager.h"
#include <unordered_map>
#include <condition_variable>
#include <cstdint>
// 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<ANSCENTER::ANSOCRBase*, OCREntry>& OCRHandleRegistry() {
static std::unordered_map<ANSCENTER::ANSOCRBase*, OCREntry> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<ANSCENTER::ANSOCRBase*> leakedHandles;
{
std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
for (auto& [h, _] : OCRHandleRegistry())
leakedHandles.push_back(h);
OCRHandleRegistry().clear();
}
for (auto* h : leakedHandles) {
try { h->Destroy(); delete h; } catch (...) {}
}
try { EnginePoolManager<float>::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<int>(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 ~300600 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<ANSCENTER::OCRObject>& 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<ANSCENTER::OCRObject> 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<ANSCENTER::OCRObject> 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<ANSCENTER::OCRObject> 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<ANSCENTER::OCRObject> 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<ANSCENTER::OCRMode>(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<ANSCENTER::Country>(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<ANSCENTER::Country>(j.value("country", 99));
fmt.numRows = j.value("num_rows", 2);
fmt.rowSplitThreshold = j.value("row_split_threshold", 0.3f);
static const std::map<std::string, ANSCENTER::ALPRCharClass> 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<std::string>();
}
}
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<char>(cp & 0xFF);
utf16le += static_cast<char>((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<const wchar_t*>(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<ANSCENTER::OCRObject> 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<cv::Rect> bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
std::vector<ANSCENTER::OCRObject> 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<cv::Rect> bBoxes = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
std::vector<ANSCENTER::OCRObject> 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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<ANSCENTER::OCRObject> 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<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
ratio = static_cast<float>(newWidth) / originalWidth;
for (auto& obj : outputs) {
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
}
}
else {
for (auto& obj : outputs) {
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
obj.box.height = std::max(1, std::min(static_cast<int>(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<uchar> 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<int>(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<int>(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<ANSCENTER::OCRObject> objectDetectionResults;
std::vector<cv::Rect> bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
const int originalWidth = localImage.cols;
const int originalHeight = localImage.rows;
const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(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<int>(rect.x * scaleFactor);
scaledRect.y = static_cast<int>(rect.y * scaleFactor);
scaledRect.width = static_cast<int>(rect.width * scaleFactor);
scaledRect.height = static_cast<int>(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<ANSCENTER::OCRObject> 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<int>(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<ANSCENTER::ANSOCRBase*>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<ANSCENTER::ANSOCRBase*>(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<ANSCENTER::OCRObject> 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<int>(std::round(newWidth * static_cast<double>(originalHeight) / originalWidth));
ratio = static_cast<float>(newWidth) / originalWidth;
for (auto& obj : outputs) {
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x * ratio), newWidth - 1));
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y * ratio), newHeight - 1));
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width * ratio), newWidth - obj.box.x));
obj.box.height = std::max(1, std::min(static_cast<int>(obj.box.height * ratio), newHeight - obj.box.y));
}
}
else {
for (auto& obj : outputs) {
obj.box.x = std::max(0, std::min(static_cast<int>(obj.box.x), originalWidth - 1));
obj.box.y = std::max(0, std::min(static_cast<int>(obj.box.y), originalHeight - 1));
obj.box.width = std::max(1, std::min(static_cast<int>(obj.box.width), originalWidth - obj.box.x));
obj.box.height = std::max(1, std::min(static_cast<int>(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<uchar> 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<int>(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<int>(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<ANSCENTER::ANSOCRBase*>(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<ANSCENTER::OCRObject> objectDetectionResults;
std::vector<cv::Rect> bBox = ANSCENTER::ANSOCRUtility::GetBoundingBoxes(strBboxes);
const int originalWidth = localImage.cols;
const int originalHeight = localImage.rows;
const double scaleFactor = (maxImageSize > 0) ? static_cast<double>(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<int>(rect.x * scaleFactor);
scaledRect.y = static_cast<int>(rect.y * scaleFactor);
scaledRect.width = static_cast<int>(rect.width * scaleFactor);
scaledRect.height = static_cast<int>(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<ANSCENTER::OCRObject> 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<int>(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<uint64_t>(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<ANSCENTER::ANSOCRBase*>(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;
}
}