2026-03-28 16:54:11 +11:00
|
|
|
// 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"
|
2026-03-29 22:51:39 +11:00
|
|
|
#include <json.hpp>
|
2026-03-28 16:54:11 +11:00
|
|
|
#include "NV12PreprocessHelper.h"
|
2026-03-30 09:24:04 +11:00
|
|
|
#include "engine/TRTEngineCache.h"
|
|
|
|
|
#include "engine/EnginePoolManager.h"
|
2026-03-28 16:54:11 +11:00
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <condition_variable>
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
2026-04-19 23:15:50 +10:00
|
|
|
// DebugView: filter on "[ANSOCR]" — gated by ANSCORE_DEBUGVIEW in ANSLicense.h.
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
// Handle registry with refcount — prevents use-after-free when
|
|
|
|
|
// ReleaseANSOCRHandle is called while inference is still running.
|
2026-04-19 14:47:29 +10:00
|
|
|
// 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;
|
2026-03-28 16:54:11 +11:00
|
|
|
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());
|
2026-04-19 14:47:29 +10:00
|
|
|
OCRHandleRegistry()[h] = { 1, false };
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Register: handle=%p (uint=%llu) registrySize=%zu",
|
|
|
|
|
(void*)h, (unsigned long long)(uintptr_t)h, OCRHandleRegistry().size());
|
2026-03-28 16:54:11 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ANSCENTER::ANSOCRBase* AcquireOCRHandle(ANSCENTER::ANSOCRBase* h) {
|
|
|
|
|
std::lock_guard<std::mutex> lk(OCRHandleRegistryMutex());
|
|
|
|
|
auto it = OCRHandleRegistry().find(h);
|
2026-04-19 23:15:50 +10:00
|
|
|
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;
|
|
|
|
|
}
|
2026-04-19 14:47:29 +10:00
|
|
|
it->second.refcount++;
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Acquire OK: handle=%p refcount=%d", (void*)h, it->second.refcount);
|
2026-03-28 16:54:11 +11:00
|
|
|
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;
|
2026-04-19 14:47:29 +10:00
|
|
|
it->second.refcount--;
|
|
|
|
|
if (it->second.refcount <= 0) {
|
2026-03-28 16:54:11 +11:00
|
|
|
OCRHandleRegistryCV().notify_all();
|
|
|
|
|
}
|
2026-04-19 14:47:29 +10:00
|
|
|
return false; // Only Unregister deletes.
|
2026-03-28 16:54:11 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool UnregisterOCRHandle(ANSCENTER::ANSOCRBase* h) {
|
|
|
|
|
std::unique_lock<std::mutex> lk(OCRHandleRegistryMutex());
|
|
|
|
|
auto it = OCRHandleRegistry().find(h);
|
2026-04-19 23:15:50 +10:00
|
|
|
if (it == OCRHandleRegistry().end()) {
|
|
|
|
|
ANS_DBG("ANSOCR","Unregister: handle=%p NOT in registry (already gone)", (void*)h);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-19 14:47:29 +10:00
|
|
|
if (it->second.destructionStarted) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Unregister: handle=%p already being destroyed by another thread, returning false", (void*)h);
|
2026-04-19 14:47:29 +10:00
|
|
|
return false; // Another thread already owns the delete.
|
|
|
|
|
}
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Unregister: handle=%p starting (refcount before=%d)", (void*)h, it->second.refcount);
|
2026-04-19 14:47:29 +10:00
|
|
|
it->second.destructionStarted = true;
|
|
|
|
|
it->second.refcount--;
|
2026-03-28 16:54:11 +11:00
|
|
|
bool ok = OCRHandleRegistryCV().wait_for(lk, std::chrono::seconds(5), [&]() {
|
|
|
|
|
auto it2 = OCRHandleRegistry().find(h);
|
2026-04-19 14:47:29 +10:00
|
|
|
return it2 == OCRHandleRegistry().end() || it2->second.refcount <= 0;
|
2026-03-28 16:54:11 +11:00
|
|
|
});
|
|
|
|
|
if (!ok) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","WARNING: Unregister timed out waiting for in-flight inference on handle=%p", (void*)h);
|
2026-03-28 16:54:11 +11:00
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
// 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;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
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:
|
2026-03-30 09:24:04 +11:00
|
|
|
break;
|
2026-03-28 16:54:11 +11:00
|
|
|
case DLL_PROCESS_DETACH:
|
2026-03-30 09:24:04 +11:00
|
|
|
// 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 (...) {}
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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; }
|
2026-03-28 16:54:11 +11:00
|
|
|
try {
|
|
|
|
|
// Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded
|
|
|
|
|
ANSCENTER::ANSLibsLoader::Initialize();
|
|
|
|
|
ANSCENTER::EngineType engineType = ANSCENTER::ANSLicenseHelper::CheckHardwareInformation();
|
2026-04-12 17:16:16 +10:00
|
|
|
{
|
|
|
|
|
const char* vendorTag =
|
|
|
|
|
engineType == ANSCENTER::EngineType::NVIDIA_GPU ? "NVIDIA_GPU (TensorRT OCR enabled)" :
|
|
|
|
|
engineType == ANSCENTER::EngineType::AMD_GPU ? "AMD_GPU (ONNX Runtime / DirectML, TensorRT OCR DISABLED)" :
|
|
|
|
|
engineType == ANSCENTER::EngineType::OPENVINO_GPU ? "OPENVINO_GPU (ONNX Runtime / OpenVINO, TensorRT OCR DISABLED)" :
|
|
|
|
|
"CPU (ONNX Runtime, TensorRT OCR DISABLED)";
|
|
|
|
|
char buf[192];
|
|
|
|
|
snprintf(buf, sizeof(buf),
|
|
|
|
|
"[ANSOCR] CreateANSOCRHandleEx: detected engineType=%d [%s], engineMode=%d\n",
|
|
|
|
|
static_cast<int>(engineType), vendorTag, engineMode);
|
|
|
|
|
OutputDebugStringA(buf);
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
|
2026-04-18 20:51:50 +10:00
|
|
|
// 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;
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
// 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();
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateEx: legacy model detected, allocated ANSCPUOCR=%p", (void*)*Handle);
|
2026-03-28 16:54:11 +11:00
|
|
|
}
|
|
|
|
|
else {
|
2026-04-12 17:16:16 +10:00
|
|
|
// 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);
|
2026-03-28 16:54:11 +11:00
|
|
|
switch (engineMode) {
|
|
|
|
|
case 0:// Auto-detect, always use ONNX for better compatibility, especially on AMD GPUs and high-res images
|
|
|
|
|
(*Handle) = new ANSCENTER::ANSONNXOCR();
|
|
|
|
|
break;
|
2026-04-12 17:16:16 +10:00
|
|
|
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();
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
break;
|
|
|
|
|
case 2:// CPU
|
|
|
|
|
(*Handle) = new ANSCENTER::ANSONNXOCR();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
(*Handle) = new ANSCENTER::ANSONNXOCR();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-19 23:15:50 +10:00
|
|
|
if (*Handle == nullptr) { ANS_DBG("ANSOCR","CreateEx FAIL: new returned null"); return 0; }
|
2026-03-28 16:54:11 +11:00
|
|
|
else {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateEx: allocated handle=%p (uint=%llu), calling Initialize...",
|
|
|
|
|
(void*)*Handle, (unsigned long long)(uintptr_t)*Handle);
|
2026-03-28 16:54:11 +11:00
|
|
|
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);
|
2026-04-15 09:23:05 +10:00
|
|
|
if (!result) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateEx FAIL: Initialize returned false, tearing down handle=%p", (void*)*Handle);
|
2026-04-15 09:23:05 +10:00
|
|
|
// 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;
|
|
|
|
|
}
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateEx OK: handle=%p (uint=%llu)", (void*)*Handle, (unsigned long long)(uintptr_t)*Handle);
|
2026-03-28 16:54:11 +11:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateEx EXCEPTION (std::exception): %s", e.what());
|
2026-04-15 09:23:05 +10:00
|
|
|
// 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;
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateEx EXCEPTION (unknown)");
|
2026-04-15 09:23:05 +10:00
|
|
|
if (Handle && *Handle) {
|
|
|
|
|
if (UnregisterOCRHandle(*Handle)) {
|
|
|
|
|
try { (*Handle)->Destroy(); } catch (...) {}
|
|
|
|
|
delete *Handle;
|
|
|
|
|
}
|
|
|
|
|
*Handle = nullptr;
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateANSOCRHandle: HandlePtr=%p, language=%d, engineMode=%d, gpuId=%d (delegating to CreateEx, limitSideLen=960)",
|
|
|
|
|
(void*)Handle, language, engineMode, gpuId);
|
2026-03-28 16:54:11 +11:00
|
|
|
return CreateANSOCRHandleEx(Handle, licenseKey, modelFilePath, modelFileZipPassword,
|
|
|
|
|
language, engineMode, gpuId,
|
|
|
|
|
detectorDBThreshold, detectorDBBoxThreshold, detectorDBUnclipRatio,
|
|
|
|
|
classifierThreshold, useDilation, 960);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-29 22:51:39 +11:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
extern "C" ANSOCR_API std::string RunInference(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 bufferLength) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInference: HandlePtr=%p, *Handle=%p, bufferLength=%d",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength);
|
2026-03-28 16:54:11 +11:00
|
|
|
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);
|
2026-03-29 22:51:39 +11:00
|
|
|
std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, cameraId=%s",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, cameraId ? cameraId : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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);
|
2026-03-29 22:51:39 +11:00
|
|
|
std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame);
|
2026-03-28 16:54:11 +11:00
|
|
|
frame.release();
|
|
|
|
|
outputs.clear();
|
|
|
|
|
return stResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSOCR_API int RunInferenceCV(ANSCENTER::ANSOCRBase** Handle, const cv::Mat& image, std::string& ocrResult) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceCV: HandlePtr=%p, *Handle=%p, image=%dx%d",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), image.cols, image.rows);
|
2026-03-28 16:54:11 +11:00
|
|
|
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");
|
2026-03-29 22:51:39 +11:00
|
|
|
ocrResult = SerializeOCRResults(engine, outputs, image.cols, image.rows, image);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceBinary: HandlePtr=%p, *Handle=%p, %ux%u",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height);
|
2026-03-28 16:54:11 +11:00
|
|
|
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);
|
2026-03-29 22:51:39 +11:00
|
|
|
std::string stResult = SerializeOCRResults(engine, outputs, width, height, frame);
|
2026-03-28 16:54:11 +11:00
|
|
|
frame.release();
|
|
|
|
|
outputs.clear();
|
|
|
|
|
return stResult;
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return ""; }
|
|
|
|
|
}
|
|
|
|
|
static int ReleaseANSOCRHandle_Impl(ANSCENTER::ANSOCRBase** Handle) {
|
|
|
|
|
try {
|
2026-04-19 23:15:50 +10:00
|
|
|
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);
|
2026-03-28 16:54:11 +11:00
|
|
|
*Handle = nullptr;
|
|
|
|
|
return 0; // Not in registry — already freed
|
|
|
|
|
}
|
|
|
|
|
(*Handle)->Destroy();
|
|
|
|
|
delete *Handle;
|
|
|
|
|
*Handle = nullptr;
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Release OK: handle=%p deleted, registry now has %zu entries",
|
|
|
|
|
(void*)h, OCRHandleRegistry().size());
|
2026-03-28 16:54:11 +11:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Release EXCEPTION (unknown)");
|
2026-03-28 16:54:11 +11:00
|
|
|
if (Handle) *Handle = nullptr;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSOCR_API int ReleaseANSOCRHandle(ANSCENTER::ANSOCRBase** Handle) {
|
|
|
|
|
__try {
|
|
|
|
|
return ReleaseANSOCRHandle_Impl(Handle);
|
|
|
|
|
}
|
|
|
|
|
__except (EXCEPTION_EXECUTE_HANDLER) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","ReleaseANSOCRHandle: SEH exception caught");
|
2026-03-28 16:54:11 +11:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-29 22:51:39 +11:00
|
|
|
// ── ALPR Configuration API ──────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
extern "C" ANSOCR_API int SetANSOCRMode(ANSCENTER::ANSOCRBase** Handle, int ocrMode) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","SetANSOCRMode: HandlePtr=%p, *Handle=%p, ocrMode=%d",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), ocrMode);
|
2026-03-29 22:51:39 +11:00
|
|
|
if (!Handle || !*Handle) return -1;
|
|
|
|
|
(*Handle)->SetOCRMode(static_cast<ANSCENTER::OCRMode>(ocrMode));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 15:21:32 +11:00
|
|
|
extern "C" ANSOCR_API int SetANSOCRCountry(ANSCENTER::ANSOCRBase** Handle, int country) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","SetANSOCRCountry: HandlePtr=%p, *Handle=%p, country=%d",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), country);
|
2026-03-29 22:51:39 +11:00
|
|
|
if (!Handle || !*Handle) return -1;
|
2026-03-30 15:21:32 +11:00
|
|
|
(*Handle)->SetCountry(static_cast<ANSCENTER::Country>(country));
|
2026-03-29 22:51:39 +11:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSOCR_API int SetANSOCRALPRFormat(ANSCENTER::ANSOCRBase** Handle, const char* formatJson) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","SetANSOCRALPRFormat: HandlePtr=%p, *Handle=%p, formatJson=%s",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), formatJson ? "(set)" : "(null)");
|
2026-03-29 22:51:39 +11:00
|
|
|
if (!Handle || !*Handle || !formatJson) return -1;
|
|
|
|
|
try {
|
|
|
|
|
nlohmann::json j = nlohmann::json::parse(formatJson);
|
|
|
|
|
ANSCENTER::ALPRPlateFormat fmt;
|
|
|
|
|
fmt.name = j.value("name", "CUSTOM");
|
2026-03-30 15:21:32 +11:00
|
|
|
fmt.country = static_cast<ANSCENTER::Country>(j.value("country", 99));
|
2026-03-29 22:51:39 +11:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 14:10:21 +11:00
|
|
|
// 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).
|
2026-03-31 21:52:47 +11:00
|
|
|
extern "C" ANSOCR_API int ANSOCR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int includeBOM) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","ANSOCR_ConvertUTF8ToUTF16LE: utf8Str=%s, includeBOM=%d",
|
|
|
|
|
utf8Str ? "(set)" : "(null)", includeBOM);
|
2026-03-31 14:10:21 +11:00
|
|
|
try {
|
|
|
|
|
if (!utf8Str || !result) return -1;
|
|
|
|
|
int len = (int)strlen(utf8Str);
|
|
|
|
|
if (len == 0) return 0;
|
|
|
|
|
|
2026-03-31 21:52:47 +11:00
|
|
|
// Always output UTF-16LE (required for LabVIEW "Force Unicode Text" indicators)
|
|
|
|
|
const char bom[2] = { '\xFF', '\xFE' };
|
2026-03-31 14:10:21 +11:00
|
|
|
|
2026-03-31 21:52:47 +11:00
|
|
|
// 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; }
|
2026-03-31 14:10:21 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasUnicodeEscapes) {
|
|
|
|
|
std::string utf16le;
|
2026-03-31 21:52:47 +11:00
|
|
|
if (includeBOM) utf16le.assign(bom, 2);
|
|
|
|
|
utf16le.reserve(len * 2 + 2);
|
2026-03-31 14:10:21 +11:00
|
|
|
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);
|
2026-03-31 21:52:47 +11:00
|
|
|
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));
|
2026-03-31 14:10:21 +11:00
|
|
|
if (error != noErr) return -2;
|
2026-03-31 21:52:47 +11:00
|
|
|
(*result)->cnt = totalSize;
|
|
|
|
|
if (includeBOM) memcpy((*result)->str, bom, 2);
|
|
|
|
|
memcpy((*result)->str + bomSize, wideStr.data(), dataSize);
|
2026-03-31 14:10:21 +11:00
|
|
|
return 1;
|
|
|
|
|
#else
|
|
|
|
|
return 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
catch (...) { return -1; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" ANSOCR_API int ANSOCR_ConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","ANSOCR_ConvertUTF16LEToUTF8: utf16leBytes=%s, byteLen=%d",
|
|
|
|
|
utf16leBytes ? "(set)" : "(null)", byteLen);
|
2026-03-31 14:10:21 +11:00
|
|
|
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; }
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 16:54:11 +11:00
|
|
|
extern "C" ANSOCR_API std::string RunInferenceImagePath(ANSCENTER::ANSOCRBase** Handle, const char* imageFilePath) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceImagePath: HandlePtr=%p, *Handle=%p, imageFilePath=%s",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), imageFilePath ? imageFilePath : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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);
|
2026-03-29 22:51:39 +11:00
|
|
|
std::string stResult = SerializeOCRResults(engine, outputs, frame.cols, frame.rows, frame);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceInCroppedImages: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInference_LV: HandlePtr=%p, *Handle=%p, bufferLength=%d",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInference_LVWithCamID: HandlePtr=%p, *Handle=%p, bufferLength=%d, cameraId=%s",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, cameraId ? cameraId : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceBinary_LV: HandlePtr=%p, *Handle=%p, %ux%u",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), width, height);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceImagePath_LV: HandlePtr=%p, *Handle=%p, imageFilePath=%s",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), imageFilePath ? imageFilePath : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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)
|
|
|
|
|
{
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","ANSOCRUnitTest: modelFilePath=%s, imageFilePath=%s",
|
|
|
|
|
modelFilePath ? modelFilePath : "(null)", imageFilePath ? imageFilePath : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceInCroppedImages_LV: HandlePtr=%p, *Handle=%p, bufferLength=%d, strBboxes=%s",
|
|
|
|
|
(void*)Handle, (void*)(Handle ? *Handle : nullptr), (int)bufferLength, strBboxes ? "(set)" : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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)
|
|
|
|
|
{
|
2026-04-19 23:15:50 +10:00
|
|
|
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);
|
2026-03-28 16:54:11 +11:00
|
|
|
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;
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
std::vector<ANSCENTER::OCRObject> outputs;
|
|
|
|
|
{
|
|
|
|
|
GpuFrameScope _gfs(gpuFrame);
|
|
|
|
|
outputs = engine->RunInference(localImage, cameraId);
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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;
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
{
|
|
|
|
|
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));
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInference_LV_V2: handleVal=%llu (handle=%p), bufferLength=%d",
|
|
|
|
|
(unsigned long long)handleVal, (void*)(uintptr_t)handleVal, (int)bufferLength);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceBinary_LV_V2: handleVal=%llu (handle=%p), %ux%u",
|
|
|
|
|
(unsigned long long)handleVal, (void*)(uintptr_t)handleVal, width, height);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","RunInferenceImagePath_LV_V2: handleVal=%llu (handle=%p), imageFilePath=%s",
|
|
|
|
|
(unsigned long long)handleVal, (void*)(uintptr_t)handleVal, imageFilePath ? imageFilePath : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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)
|
|
|
|
|
{
|
2026-04-19 23:15:50 +10:00
|
|
|
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);
|
2026-03-28 16:54:11 +11:00
|
|
|
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;
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
std::vector<ANSCENTER::OCRObject> outputs;
|
|
|
|
|
{
|
|
|
|
|
GpuFrameScope _gfs(gpuFrame);
|
|
|
|
|
outputs = engine->RunInference(localImage, cameraId);
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
|
|
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
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)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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;
|
|
|
|
|
|
2026-04-15 09:23:05 +10:00
|
|
|
{
|
|
|
|
|
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));
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateANSOCRHandleEx_V2: language=%d, engineMode=%d, gpuId=%d, limitSideLen=%d, modelPath=%s",
|
|
|
|
|
language, engineMode, gpuId, limitSideLen, modelFilePath ? modelFilePath : "(null)");
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","CreateANSOCRHandle_V2: language=%d, engineMode=%d, gpuId=%d (delegating to CreateEx_V2, limitSideLen=960)",
|
|
|
|
|
language, engineMode, gpuId);
|
2026-03-28 16:54:11 +11:00
|
|
|
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) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","ReleaseANSOCRHandle_V2: handleVal=%llu (handle=%p)",
|
|
|
|
|
(unsigned long long)handleVal, (void*)(uintptr_t)handleVal);
|
2026-03-28 16:54:11 +11:00
|
|
|
try {
|
|
|
|
|
ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
|
2026-04-19 23:15:50 +10:00
|
|
|
if (directHandle == nullptr) {
|
|
|
|
|
ANS_DBG("ANSOCR","Release_V2: handleVal is 0/null, no-op");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2026-03-28 16:54:11 +11:00
|
|
|
if (!UnregisterOCRHandle(directHandle)) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Release_V2: Unregister returned false (already gone or being destroyed by another thread), handle=%p", (void*)directHandle);
|
2026-03-28 16:54:11 +11:00
|
|
|
return 0; // Not in registry — already freed
|
|
|
|
|
}
|
|
|
|
|
directHandle->Destroy();
|
|
|
|
|
delete directHandle;
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Release_V2 OK: handle=%p deleted, registry now has %zu entries",
|
|
|
|
|
(void*)directHandle, OCRHandleRegistry().size());
|
2026-03-28 16:54:11 +11:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
catch (...) {
|
2026-04-19 23:15:50 +10:00
|
|
|
ANS_DBG("ANSOCR","Release_V2 EXCEPTION (unknown)");
|
2026-03-28 16:54:11 +11:00
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|