Files
ANSCORE/modules/ANSOCR/dllmain.cpp

1333 lines
47 KiB
C++
Raw Normal View History

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"
#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>
// 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-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);
if (it == OCRHandleRegistry().end()) return nullptr;
2026-04-19 14:47:29 +10:00
if (it->second.destructionStarted) return nullptr;
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);
if (it == OCRHandleRegistry().end()) return false;
2026-04-19 14:47:29 +10:00
if (it->second.destructionStarted) {
return false; // Another thread already owns the delete.
}
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) {
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) {
try {
// Ensure all shared DLLs (OpenCV, OpenVINO, TRT, ORT) are pre-loaded
ANSCENTER::ANSLibsLoader::Initialize();
ANSCENTER::EngineType engineType = ANSCENTER::ANSLicenseHelper::CheckHardwareInformation();
{
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
// 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();
}
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);
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;
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;
}
}
if (*Handle == nullptr) return 0;
else {
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) {
// 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-03-28 16:54:11 +11:00
return result;
}
}
catch (std::exception& e) {
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-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) {
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);
}
2026-03-28 16:54:11 +11:00
extern "C" ANSOCR_API std::string RunInference(ANSCENTER::ANSOCRBase** Handle, unsigned char* jpeg_string, int32 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);
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) {
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);
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) {
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);
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) {
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);
2026-03-28 16:54:11 +11:00
frame.release();
outputs.clear();
return stResult;
}
catch (...) { return ""; }
}
static int ReleaseANSOCRHandle_Impl(ANSCENTER::ANSOCRBase** Handle) {
try {
if (!Handle || !*Handle) return 0;
if (!UnregisterOCRHandle(*Handle)) {
*Handle = nullptr;
return 0; // Not in registry — already freed
}
(*Handle)->Destroy();
delete *Handle;
*Handle = nullptr;
return 0;
}
catch (...) {
if (Handle) *Handle = nullptr;
return 1;
}
}
extern "C" ANSOCR_API int ReleaseANSOCRHandle(ANSCENTER::ANSOCRBase** Handle) {
__try {
return ReleaseANSOCRHandle_Impl(Handle);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return 1;
}
}
// ── ALPR Configuration API ──────────────────────────────────────────
extern "C" ANSOCR_API int SetANSOCRMode(ANSCENTER::ANSOCRBase** Handle, int 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) {
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) {
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;
}
}
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).
extern "C" ANSOCR_API int ANSOCR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int 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;
// 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
// 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;
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);
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;
(*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) {
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) {
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);
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) {
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) {
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) {
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) {
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) {
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) {
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)
{
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) {
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) {
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)
{
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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)
{
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) {
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) {
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) {
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) {
try {
ANSCENTER::ANSOCRBase* directHandle = reinterpret_cast<ANSCENTER::ANSOCRBase*>(handleVal);
if (directHandle == nullptr) return 0;
if (!UnregisterOCRHandle(directHandle)) {
return 0; // Not in registry — already freed
}
directHandle->Destroy();
delete directHandle;
return 0;
}
catch (...) {
return 1;
}
}