diff --git a/core/ANSLicensingSystem/ANSLicense.cpp b/core/ANSLicensingSystem/ANSLicense.cpp index 1cc75ab..31d900b 100644 --- a/core/ANSLicensingSystem/ANSLicense.cpp +++ b/core/ANSLicensingSystem/ANSLicense.cpp @@ -315,26 +315,30 @@ namespace ANSCENTER EngineType ANSCENTER::ANSLicenseHelper::CheckHardwareInformation() { + ANS_DBG("HWInfo", "CheckHardwareInformation: starting hardware detection"); bool cpuIntelAvailable = false; bool gpuNvidiaAvailable = false; bool gpuAMDAvailable = false; bool gpuOpenVINOAvailable = false; // ---------------------------------------------------------------- - // CPU � Intel socket detection (OpenVINO fallback) + // CPU — Intel socket detection (OpenVINO fallback) // ---------------------------------------------------------------- auto sockets = hwinfo::getAllSockets(); for (auto& socket : sockets) { const auto& cpu = socket.CPU(); + ANS_DBG("HWInfo", "CPU vendor=%s model=%s", cpu.vendor().c_str(), cpu.modelName().c_str()); if (cpu.vendor() == "GenuineIntel") cpuIntelAvailable = true; } // ---------------------------------------------------------------- - // GPU � classify all adapters by vendor string + // GPU — classify all adapters by vendor string // ---------------------------------------------------------------- auto gpus = hwinfo::getAllGPUs(); + ANS_DBG("HWInfo", "Found %zu GPU adapter(s)", gpus.size()); for (auto& gpu : gpus) { std::string vendor = gpu.vendor(); + ANS_DBG("HWInfo", "GPU: name=%s vendor=%s", gpu.name().c_str(), vendor.c_str()); std::cout << "[HWInfo] Detected GPU: " << gpu.name() << " (Vendor: " << vendor << ")" << std::endl; // Normalize to uppercase for reliable substring matching std::transform(vendor.begin(), vendor.end(), @@ -345,12 +349,18 @@ namespace ANSCENTER if (vendor.find("INTEL") != std::string::npos) gpuOpenVINOAvailable = true; // AMD cards report as "AMD", "Advanced Micro Devices", or - // legacy "ATI" branding + // legacy "ATI Technologies" branding. + // NOTE: "ATI" alone is too broad — "INTEL CORPORATION" contains + // "ATI" inside "CORPOR-ATI-ON", causing a false positive. + // Use "ATI " (with trailing space) to match "ATI Technologies" + // or "ATI Radeon" without matching "CORPORATION". if (vendor.find("AMD") != std::string::npos || - vendor.find("ADVANCED") != std::string::npos || - vendor.find("ATI") != std::string::npos) + vendor.find("ADVANCED MICRO") != std::string::npos || + vendor.find("ATI ") != std::string::npos) gpuAMDAvailable = true; } + ANS_DBG("HWInfo", "Detection: cpuIntel=%d gpuNvidia=%d gpuAMD=%d gpuOpenVINO=%d", + cpuIntelAvailable, gpuNvidiaAvailable, gpuAMDAvailable, gpuOpenVINOAvailable); // ---------------------------------------------------------------- // Priority: NVIDIA > AMD > Intel OpenVINO > CPU @@ -361,19 +371,23 @@ namespace ANSCENTER // ---------------------------------------------------------------- if (gpuNvidiaAvailable) { std::cout << "[HWInfo] Engine: NVIDIA GPU -> CUDA" << std::endl; + ANS_DBG("HWInfo", "Selected: NVIDIA_GPU (CUDA)"); return EngineType::NVIDIA_GPU; //return EngineType::OPENVINO_GPU; } if (gpuAMDAvailable) { std::cout << "[HWInfo] Engine: AMD GPU -> DirectML" << std::endl; + ANS_DBG("HWInfo", "Selected: AMD_GPU (DirectML)"); return EngineType::AMD_GPU; } if (gpuOpenVINOAvailable) { std::cout << "[HWInfo] Engine: Intel CPU -> OpenVINO" << std::endl; + ANS_DBG("HWInfo", "Selected: OPENVINO_GPU (OpenVINO)"); return EngineType::OPENVINO_GPU; } std::cout << "[HWInfo] Engine: CPU (fallback)" << std::endl; + ANS_DBG("HWInfo", "Selected: CPU (fallback)"); return EngineType::CPU; } diff --git a/core/ANSLicensingSystem/ANSLicense.h b/core/ANSLicensingSystem/ANSLicense.h index 7497409..b55fbf1 100644 --- a/core/ANSLicensingSystem/ANSLicense.h +++ b/core/ANSLicensingSystem/ANSLicense.h @@ -8,7 +8,7 @@ // Set to 0 for production builds to eliminate all debug output overhead. // ============================================================================ #ifndef ANSCORE_DEBUGVIEW -#define ANSCORE_DEBUGVIEW 0 // 1 = enabled (debug), 0 = disabled (production) +#define ANSCORE_DEBUGVIEW 1 // 1 = enabled (debug), 0 = disabled (production) #endif // ANS_DBG: Debug logging macro for DebugView (OutputDebugStringA on Windows). diff --git a/modules/ANSFR/ANSFR.cpp b/modules/ANSFR/ANSFR.cpp index 34a03b3..cc53785 100644 --- a/modules/ANSFR/ANSFR.cpp +++ b/modules/ANSFR/ANSFR.cpp @@ -372,6 +372,7 @@ namespace ANSCENTER { bool ANSFacialRecognition::LoadEngine() { try { + ANS_DBG("ANSFR", "LoadEngine: starting"); // Unload existing engine (handles its own locking) UnloadEngine(); @@ -379,17 +380,23 @@ namespace ANSCENTER { { std::lock_guard lock(_configMutex); + ANS_DBG("ANSFR", "LoadEngine: InitializeDetector..."); if (!InitializeDetector()) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Failed to initialize detector"); + ANS_DBG("ANSFR", "LoadEngine: InitializeDetector FAILED"); return false; } + ANS_DBG("ANSFR", "LoadEngine: InitializeDetector OK"); + ANS_DBG("ANSFR", "LoadEngine: InitializeRecognizer..."); if (!InitializeRecognizer()) { LogThreadSafe("ANSFacialRecognition::LoadEngine", "Failed to initialize recognizer"); + ANS_DBG("ANSFR", "LoadEngine: InitializeRecognizer FAILED"); return false; } + ANS_DBG("ANSFR", "LoadEngine: InitializeRecognizer OK"); _recognizerModelFolder = _recognizer->GetModelFolder(); } @@ -402,7 +409,9 @@ namespace ANSCENTER { } // Configure device + ANS_DBG("ANSFR", "LoadEngine: getting OpenVINO device..."); std::string deviceName = GetOpenVINODevice(); + ANS_DBG("ANSFR", "LoadEngine: OpenVINO device=%s", deviceName.c_str()); if (deviceName == "NPU") { // Configure NPU with GPU fallback @@ -795,6 +804,7 @@ namespace ANSCENTER { } bool ANSFacialRecognition::InitializeRecognizer() { try { + ANS_DBG("ANSFR", "InitializeRecognizer: starting, modelPath=%s", _recognizerFilePath.c_str()); // Create recognizer instance _recognizer = std::make_unique(); _recognizer->SetMaxSlotsPerGpu(m_maxSlotsPerGpu); @@ -810,6 +820,9 @@ namespace ANSCENTER { recognizerConfig.modelConfThreshold = 0.48f; recognizerConfig.unknownPersonThreshold = _config._knownPersonThreshold; + ANS_DBG("ANSFR", "InitializeRecognizer: knownPersonThreshold=%.3f NMS=%.3f bbox=%.3f", + _config._knownPersonThreshold, _config._detThresholdNMS, _config._detThresholdBbox); + // LOCK DURING INITIALIZATION std::string labelMap; bool initSuccess; @@ -817,6 +830,7 @@ namespace ANSCENTER { { std::lock_guard lock(_recognitionMutex); + ANS_DBG("ANSFR", "InitializeRecognizer: calling _recognizer->Initialize..."); initSuccess = _recognizer->Initialize( _licenseKey, recognizerConfig, @@ -829,12 +843,15 @@ namespace ANSCENTER { if (!initSuccess) { LogThreadSafe("ANSFacialRecognition::InitializeRecognizer", "Failed to initialize recognizer - check file path: " + _recognizerFilePath); + ANS_DBG("ANSFR", "InitializeRecognizer: FAILED - path=%s", _recognizerFilePath.c_str()); return false; } + ANS_DBG("ANSFR", "InitializeRecognizer: SUCCESS"); return true; } catch (const std::exception& e) { + ANS_DBG("ANSFR", "InitializeRecognizer EXCEPTION: %s", e.what()); LogThreadSafe("ANSFacialRecognition::InitializeRecognizer", "Failed to initialize: " + std::string(e.what())); return false; @@ -2674,13 +2691,19 @@ namespace ANSCENTER { std::string ANSFacialRecognition::GetOpenVINODevice() { ov::Core core; std::vector available_devices = core.get_available_devices(); + ANS_DBG("ANSFR", "GetOpenVINODevice: %zu devices available", available_devices.size()); + for (const auto& d : available_devices) { + ANS_DBG("ANSFR", " OpenVINO device: %s", d.c_str()); + } // Prioritize devices: NPU > GPU > CPU std::vector priority_devices = { "NPU","GPU","CPU" }; for (const auto& device : priority_devices) { if (std::find(available_devices.begin(), available_devices.end(), device) != available_devices.end()) { + ANS_DBG("ANSFR", "GetOpenVINODevice: selected %s", device.c_str()); return device; // Return the first available device based on priority } } + ANS_DBG("ANSFR", "GetOpenVINODevice: fallback to CPU"); return "CPU"; } diff --git a/modules/ANSFR/ANSFR.h b/modules/ANSFR/ANSFR.h index d73f446..31aa6ad 100644 --- a/modules/ANSFR/ANSFR.h +++ b/modules/ANSFR/ANSFR.h @@ -363,6 +363,10 @@ extern "C" ANSFR_API int GetFaces(ANSCENTER::ANSFacialRecognition * *Handle,i extern "C" ANSFR_API int DeleteFacesByUser(ANSCENTER::ANSFacialRecognition * *Handle,int userId); extern "C" ANSFR_API double BlurCalculation(unsigned char* jpeg_string, unsigned int bufferLength); +// LStrHandle-based InsertUser/UpdateUser — handles UTF-16LE and UTF-8 input from LabVIEW +extern "C" ANSFR_API int InsertUser_LV(ANSCENTER::ANSFacialRecognition * *Handle, const char* userCode, LStrHandle userName); +extern "C" ANSFR_API int UpdateUser_LV(ANSCENTER::ANSFacialRecognition * *Handle, int userId, const char* userCode, LStrHandle userName); + // Unicode conversion utilities for LabVIEW wrapper classes extern "C" ANSFR_API int ANSFR_ConvertUTF8ToUTF16LE(const char* utf8Str, LStrHandle result, int includeBOM = 1); extern "C" ANSFR_API int ANSFR_ConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result); diff --git a/modules/ANSFR/ANSFaceRecognizer.cpp b/modules/ANSFR/ANSFaceRecognizer.cpp index 22391a4..007fbe2 100644 --- a/modules/ANSFR/ANSFaceRecognizer.cpp +++ b/modules/ANSFR/ANSFaceRecognizer.cpp @@ -22,12 +22,19 @@ namespace ANSCENTER { ov::Core core; std::vector available_devices = core.get_available_devices(); + ANS_DBG("FaceRecognizer", "OpenVINO available devices: %zu", available_devices.size()); + for (const auto& d : available_devices) { + ANS_DBG("FaceRecognizer", " OpenVINO device: %s", d.c_str()); + } + std::vector priority_devices = { "GPU", "CPU" }; for (const auto& device : priority_devices) { if (std::find(available_devices.begin(), available_devices.end(), device) != available_devices.end()) { + ANS_DBG("FaceRecognizer", "OpenVINO selected device: %s", device.c_str()); return device; } } + ANS_DBG("FaceRecognizer", "OpenVINO fallback to CPU"); return "CPU"; } @@ -37,12 +44,18 @@ namespace ANSCENTER { const std::string& modelZipPassword, std::string& labelMap) { + ANS_DBG("FaceRecognizer", "Initialize: modelZip=%s", modelZipFilePath.c_str()); bool result = ANSFRBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap); - if (!result) return false; + if (!result) { + ANS_DBG("FaceRecognizer", "ANSFRBase::Initialize FAILED"); + return false; + } #ifdef CPU_MODE engineType = EngineType::CPU; + ANS_DBG("FaceRecognizer", "CPU_MODE forced: engineType=CPU"); #else engineType = ANSLicenseHelper::CheckHardwareInformation(); + ANS_DBG("FaceRecognizer", "HW detection: engineType=%d", static_cast(engineType)); #endif try { _modelConfig = modelConfig; @@ -52,6 +65,12 @@ namespace ANSCENTER { m_knownPersonThresh = _modelConfig.unknownPersonThreshold; if (m_knownPersonThresh == 0.0f) m_knownPersonThresh = 0.35f; + ANS_DBG("FaceRecognizer", "engineType=%d (NVIDIA=%d, OPENVINO=%d, AMD=%d, CPU=%d)", + static_cast(engineType), + static_cast(EngineType::NVIDIA_GPU), + static_cast(EngineType::OPENVINO_GPU), + static_cast(EngineType::AMD_GPU), + static_cast(EngineType::CPU)); if (engineType == EngineType::NVIDIA_GPU) { // 1. Load ONNX model std::string onnxfile = CreateFilePath(_modelFolder, "ansfacerecognizer.onnx"); @@ -97,6 +116,11 @@ namespace ANSCENTER { _modelFilePath, __FILE__, __LINE__); return false; } + + // Create CUDA stream for GPU preprocessing (lazy init) + if (!m_gpuStream) { + m_gpuStream = std::make_unique(); + } } } else { @@ -112,10 +136,13 @@ namespace ANSCENTER { _modelFilePath, __FILE__, __LINE__); return false; } - faceRecognizer = std::make_unique(faceidModel); + ANS_DBG("FaceRecognizer", "Creating GlintArcFace with engineType=%d model=%s", + static_cast(engineType), faceidModel.c_str()); + faceRecognizer = std::make_unique(faceidModel, engineType); if (!faceRecognizer) { _logger.LogFatal("ANSFaceRecognizer::Initialize", "Failed to initialize ONNX face recognizer", __FILE__, __LINE__); + ANS_DBG("FaceRecognizer", "FAILED: GlintArcFace returned null"); return false; } #else @@ -151,6 +178,7 @@ namespace ANSCENTER { return true; } catch (const std::exception& e) { + ANS_DBG("FaceRecognizer", "Initialize EXCEPTION: %s", e.what()); _logger.LogFatal("ANSFaceRecognizer::Initialize", e.what(), __FILE__, __LINE__); return false; } @@ -688,8 +716,8 @@ namespace ANSCENTER { } cv::Mat cpuRGB; cv::cvtColor(cpuResized, cpuRGB, cv::COLOR_BGR2RGB); - m_gpuRgb.upload(cpuRGB, m_gpuStream); - m_gpuStream.waitForCompletion(); + m_gpuRgb.upload(cpuRGB, *m_gpuStream); + m_gpuStream->waitForCompletion(); // Prepare inference inputs std::vector inputVec; @@ -703,7 +731,7 @@ namespace ANSCENTER { bool succ = m_trtEngine->runInference(inputs, featureVectors); // Synchronize stream - m_gpuStream.waitForCompletion(); + m_gpuStream->waitForCompletion(); if (!succ) { _logger.LogError("ANSFaceRecognizer::RunArcFace", @@ -783,11 +811,11 @@ namespace ANSCENTER { cv::cuda::GpuMat d_img = gpuFaceROIs[i]; // already on GPU if (d_img.cols != GPU_FACE_WIDTH || d_img.rows != GPU_FACE_HEIGHT) { cv::cuda::GpuMat d_resized; - cv::cuda::resize(d_img, d_resized, targetSize, 0, 0, cv::INTER_LINEAR, m_gpuStream); + cv::cuda::resize(d_img, d_resized, targetSize, 0, 0, cv::INTER_LINEAR, *m_gpuStream); d_img = d_resized; } cv::cuda::GpuMat d_rgb; - cv::cuda::cvtColor(d_img, d_rgb, cv::COLOR_BGR2RGB, 0, m_gpuStream); + cv::cuda::cvtColor(d_img, d_rgb, cv::COLOR_BGR2RGB, 0, *m_gpuStream); batchGpu.emplace_back(std::move(d_rgb)); } else { const auto& roi = faceROIs[i]; @@ -807,7 +835,7 @@ namespace ANSCENTER { cv::Mat cpuRGB; cv::cvtColor(cpuResized, cpuRGB, cv::COLOR_BGR2RGB); cv::cuda::GpuMat d_rgb; - d_rgb.upload(cpuRGB, m_gpuStream); + d_rgb.upload(cpuRGB, *m_gpuStream); batchGpu.emplace_back(std::move(d_rgb)); } } @@ -823,7 +851,7 @@ namespace ANSCENTER { FR_START_TIMER(trt_infer); std::vector>> featureVectors; bool succ = m_trtEngine->runInference(inputs, featureVectors); - m_gpuStream.waitForCompletion(); + m_gpuStream->waitForCompletion(); FR_END_TIMER(trt_infer, "RunArcFaceBatch TRT inference (batch=" + std::to_string(actualCount) + ")"); if (!succ) { diff --git a/modules/ANSFR/ANSFaceRecognizer.h b/modules/ANSFR/ANSFaceRecognizer.h index b3f2240..e24266e 100644 --- a/modules/ANSFR/ANSFaceRecognizer.h +++ b/modules/ANSFR/ANSFaceRecognizer.h @@ -112,7 +112,9 @@ namespace ANSCENTER { const int CPU_FACE_HEIGHT = 160; #endif // Pooled GPU buffers to avoid per-frame allocation (Fix #8) - cv::cuda::Stream m_gpuStream; + // Lazy-initialized: only created when engineType == NVIDIA_GPU + // to avoid triggering CUDA init on non-NVIDIA systems. + std::unique_ptr m_gpuStream; cv::cuda::GpuMat m_gpuImg; cv::cuda::GpuMat m_gpuResized; cv::cuda::GpuMat m_gpuRgb; diff --git a/modules/ANSODEngine/ANSFaceDetectorEngine.cpp b/modules/ANSODEngine/ANSFaceDetectorEngine.cpp index ec5b75d..5ab32ad 100644 --- a/modules/ANSODEngine/ANSFaceDetectorEngine.cpp +++ b/modules/ANSODEngine/ANSFaceDetectorEngine.cpp @@ -1396,35 +1396,53 @@ namespace ANSCENTER break; } + // NPU availability is probed once per process to avoid + // repeated "Failed to load shared library" errors. + static bool s_npuProbed = false; + static bool s_npuAvailable = false; + const std::string precision = "FP16"; const std::string numberOfThreads = "8"; const std::string numberOfStreams = "8"; - std::vector> try_configs = { - { {"device_type","AUTO:NPU,GPU"}, {"precision",precision}, - {"num_of_threads",numberOfThreads}, {"num_streams",numberOfStreams}, - {"enable_opencl_throttling","False"}, {"enable_qdq_optimizer","True"} }, - { {"device_type","GPU.0"}, {"precision",precision}, - {"num_of_threads",numberOfThreads}, {"num_streams",numberOfStreams}, - {"enable_opencl_throttling","False"}, {"enable_qdq_optimizer","True"} }, - { {"device_type","GPU.1"}, {"precision",precision}, - {"num_of_threads",numberOfThreads}, {"num_streams",numberOfStreams}, - {"enable_opencl_throttling","False"}, {"enable_qdq_optimizer","True"} }, - { {"device_type","AUTO:GPU,CPU"}, {"precision",precision}, - {"num_of_threads",numberOfThreads}, {"num_streams",numberOfStreams}, - {"enable_opencl_throttling","False"}, {"enable_qdq_optimizer","True"} } + auto makeConfig = [&](const std::string& device) { + return std::unordered_map{ + {"device_type", device}, {"precision", precision}, + {"num_of_threads", numberOfThreads}, {"num_streams", numberOfStreams}, + {"enable_opencl_throttling", "False"}, {"enable_qdq_optimizer", "True"} + }; }; + std::vector> try_configs; + if (!s_npuProbed || s_npuAvailable) { + try_configs.push_back(makeConfig("AUTO:NPU,GPU")); + } + try_configs.push_back(makeConfig("GPU.0")); + try_configs.push_back(makeConfig("GPU.1")); + try_configs.push_back(makeConfig("AUTO:GPU,CPU")); + for (const auto& config : try_configs) { try { _ortLivenessSessionOptions->AppendExecutionProvider_OpenVINO_V2(config); std::cout << "[ANSFDBase] OpenVINO EP attached (" << config.at("device_type") << ")." << std::endl; attached = true; + if (config.at("device_type").find("NPU") != std::string::npos) { + s_npuProbed = true; + s_npuAvailable = true; + } break; } catch (const Ort::Exception& e) { - this->_logger.LogError("ANSFDBase::LoadLivenessModel", e.what(), __FILE__, __LINE__); + if (config.at("device_type").find("NPU") != std::string::npos) { + if (!s_npuProbed) { + std::cout << "[ANSFDBase] NPU not available — skipping NPU configs for subsequent models." << std::endl; + } + s_npuProbed = true; + s_npuAvailable = false; + } else { + this->_logger.LogError("ANSFDBase::LoadLivenessModel", e.what(), __FILE__, __LINE__); + } } } diff --git a/modules/ANSUtilities/ANSUtilities.cpp b/modules/ANSUtilities/ANSUtilities.cpp index 2c47659..74f4c57 100644 --- a/modules/ANSUtilities/ANSUtilities.cpp +++ b/modules/ANSUtilities/ANSUtilities.cpp @@ -1003,6 +1003,131 @@ namespace ANSCENTER #endif } + std::vector ANSUtilities::RepairLabVIEWUTF16LE(const unsigned char* data, int len) { + std::vector repaired; + if (!data || len <= 0) return repaired; + repaired.reserve(len + 32); + + // Helper: emit a BMP codepoint as UTF-16LE pair + auto emitU16 = [&](uint16_t cp) { + repaired.push_back(static_cast(cp & 0xFF)); + repaired.push_back(static_cast((cp >> 8) & 0xFF)); + }; + + for (int i = 0; i < len; ) { + unsigned char b = data[i]; + + // --- 1. Detect embedded UTF-8 multi-byte sequences --- + // LabVIEW text controls may mix UTF-8 encoded characters into a + // UTF-16LE stream. UTF-8 lead bytes (C2-F4) followed by valid + // continuation bytes (80-BF) are a strong signal. + // We decode the UTF-8 codepoint and re-encode as UTF-16LE. + + // 2-byte UTF-8: 110xxxxx 10xxxxxx (U+0080 .. U+07FF) + if (b >= 0xC2 && b <= 0xDF && i + 1 < len) { + unsigned char b1 = data[i + 1]; + if ((b1 & 0xC0) == 0x80) { + uint32_t cp = ((b & 0x1F) << 6) | (b1 & 0x3F); + emitU16(static_cast(cp)); + i += 2; + continue; + } + } + // 3-byte UTF-8: 1110xxxx 10xxxxxx 10xxxxxx (U+0800 .. U+FFFF) + if (b >= 0xE0 && b <= 0xEF && i + 2 < len) { + unsigned char b1 = data[i + 1]; + unsigned char b2 = data[i + 2]; + if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80) { + uint32_t cp = ((b & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F); + // Reject overlong encodings and surrogates + if (cp >= 0x0800 && (cp < 0xD800 || cp > 0xDFFF)) { + emitU16(static_cast(cp)); + i += 3; + continue; + } + } + } + // 4-byte UTF-8: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (U+10000 .. U+10FFFF) + if (b >= 0xF0 && b <= 0xF4 && i + 3 < len) { + unsigned char b1 = data[i + 1]; + unsigned char b2 = data[i + 2]; + unsigned char b3 = data[i + 3]; + if ((b1 & 0xC0) == 0x80 && (b2 & 0xC0) == 0x80 && (b3 & 0xC0) == 0x80) { + uint32_t cp = ((b & 0x07) << 18) | ((b1 & 0x3F) << 12) + | ((b2 & 0x3F) << 6) | (b3 & 0x3F); + if (cp >= 0x10000 && cp <= 0x10FFFF) { + // Surrogate pair + cp -= 0x10000; + emitU16(static_cast(0xD800 + (cp >> 10))); + emitU16(static_cast(0xDC00 + (cp & 0x3FF))); + i += 4; + continue; + } + } + } + + // --- 2. Normal UTF-16LE pair (low byte + 0x00 high byte) --- + if (i + 1 < len && data[i + 1] == 0x00) { + repaired.push_back(data[i]); + repaired.push_back(0x00); + i += 2; + } + // --- 3. Lone space byte — LabVIEW dropped the 0x00 high byte --- + else if (b == 0x20 && (i + 1 >= len || data[i + 1] != 0x00)) { + repaired.push_back(0x20); + repaired.push_back(0x00); + i += 1; + } + // --- 4. Non-ASCII UTF-16LE pair (e.g. ễ = C5 1E) --- + else if (i + 1 < len) { + repaired.push_back(data[i]); + repaired.push_back(data[i + 1]); + i += 2; + } + // --- 5. Trailing odd byte — skip --- + else { + i++; + } + } + return repaired; + } + + bool ANSUtilities::IsValidUTF8(const unsigned char* data, int len) { + if (!data || len <= 0) return false; + bool hasMultiByte = false; + for (int i = 0; i < len; ) { + unsigned char b = data[i]; + if (b <= 0x7F) { + // ASCII — valid, but alone doesn't prove UTF-8 + i++; + } else if (b >= 0xC2 && b <= 0xDF) { + // 2-byte sequence + if (i + 1 >= len || (data[i + 1] & 0xC0) != 0x80) return false; + hasMultiByte = true; + i += 2; + } else if (b >= 0xE0 && b <= 0xEF) { + // 3-byte sequence + if (i + 2 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80) return false; + uint32_t cp = ((b & 0x0F) << 12) | ((data[i + 1] & 0x3F) << 6) | (data[i + 2] & 0x3F); + if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF)) return false; // overlong or surrogate + hasMultiByte = true; + i += 3; + } else if (b >= 0xF0 && b <= 0xF4) { + // 4-byte sequence + if (i + 3 >= len || (data[i + 1] & 0xC0) != 0x80 || (data[i + 2] & 0xC0) != 0x80 || (data[i + 3] & 0xC0) != 0x80) return false; + uint32_t cp = ((b & 0x07) << 18) | ((data[i + 1] & 0x3F) << 12) | ((data[i + 2] & 0x3F) << 6) | (data[i + 3] & 0x3F); + if (cp < 0x10000 || cp > 0x10FFFF) return false; + hasMultiByte = true; + i += 4; + } else { + return false; // invalid lead byte (C0, C1, F5-FF) + } + } + // Only confirm UTF-8 if we found at least one multi-byte sequence. + // Pure ASCII is ambiguous — let the caller decide. + return hasMultiByte; + } + std::string ANSUtilities::ConvertUTF16LEToUnicodeEscapes(const char* utf16leBytes, int byteLen) { if (!utf16leBytes || byteLen <= 0) return ""; int offset = 0; @@ -1013,13 +1138,18 @@ namespace ANSCENTER offset = 2; } int remaining = byteLen - offset; - if (remaining <= 0 || remaining % 2 != 0) return ""; + if (remaining <= 0) return ""; + // Drop trailing odd byte if present (e.g. null terminator appended by LabVIEW) + if (remaining % 2 != 0) remaining--; + int endPos = offset + remaining; // safe end position (even-aligned) std::string result; result.reserve(remaining * 3); - for (int i = offset; i + 1 < byteLen; i += 2) { + for (int i = offset; i + 1 < endPos; i += 2) { uint16_t codepoint = static_cast(utf16leBytes[i]) | (static_cast(utf16leBytes[i + 1]) << 8); + // Pass through printable ASCII including space (0x20-0x7E) + // Escape control characters and non-ASCII as \uXXXX if (codepoint >= 0x20 && codepoint <= 0x7E) { result += static_cast(codepoint); } else { @@ -1038,10 +1168,16 @@ namespace ANSCENTER size_t i = 0; while (i < utf8Str.size()) { unsigned char c = static_cast(utf8Str[i]); - if (c <= 0x7F) { - // ASCII byte -- pass through as-is (including \r, \n, \t, space, etc.) + if (c >= 0x20 && c <= 0x7E) { + // Printable ASCII including space (0x20-0x7E) — pass through as-is result += utf8Str[i]; i++; + } else if (c <= 0x7F) { + // Control chars (0x00-0x1F), DEL (0x7F) — escape as \uXXXX + char buf[7]; + snprintf(buf, sizeof(buf), "\\u%04x", c); + result += buf; + i++; } else { // Multi-byte UTF-8 sequence -- decode to Unicode codepoint uint32_t codepoint = 0; diff --git a/modules/ANSUtilities/ANSUtilities.h b/modules/ANSUtilities/ANSUtilities.h index d261d38..94218ff 100644 --- a/modules/ANSUtilities/ANSUtilities.h +++ b/modules/ANSUtilities/ANSUtilities.h @@ -5,6 +5,7 @@ #include "LabVIEWHeader/extcode.h" #include #include +#include #include "ANSLicense.h" #include #include "CkAuthGoogle.h" @@ -118,6 +119,19 @@ namespace ANSCENTER { // Useful for encoding Unicode text into JSON-safe ASCII. static std::string ConvertUTF8ToUnicodeEscapes(const std::string& utf8Str); + // Repair mixed-encoding input from LabVIEW text controls. + // LabVIEW may produce a mix of UTF-16LE pairs and UTF-8 multi-byte + // sequences in a single byte stream. It may also insert space (0x20) as + // a single byte without the 0x00 high byte. + // This function normalizes everything to proper UTF-16LE pairs. + // Input: raw bytes from LStrHandle (BOM should already be stripped). + // Output: clean UTF-16LE with proper 2-byte alignment. + static std::vector RepairLabVIEWUTF16LE(const unsigned char* data, int len); + + // Check if a byte sequence is valid UTF-8. + // Returns true if all bytes form valid UTF-8 sequences. + static bool IsValidUTF8(const unsigned char* data, int len); + // Double-escape \uXXXX sequences: \u1ee7 becomes \\u1ee7. // Useful when the string will be embedded in JSON and the literal \uXXXX must survive parsing. static std::string DoubleEscapeUnicode(const std::string& str); @@ -276,6 +290,9 @@ extern "C" ANSULT_API int ANSDecodeJsonUnicodeToUTF16LE(const char* escapedStr, extern "C" ANSULT_API int ANSConvertUTF16LEToUTF8(const unsigned char* utf16leBytes, int byteLen, LStrHandle result); extern "C" ANSULT_API int ANSConvertUTF16LEToUnicodeEscapes(const unsigned char* utf16leBytes, int byteLen, LStrHandle result); extern "C" ANSULT_API int ANSConvertUnicodeEscapesToUTF8(const char* escapedStr, LStrHandle result); +// LStrHandle-safe versions: input is LStrHandle (preserves null bytes in UTF-16LE data) +extern "C" ANSULT_API int ANSConvertUTF16LEToUTF8_LV(LStrHandle input, LStrHandle result); +extern "C" ANSULT_API int ANSConvertUTF16LEToUnicodeEscapes_LV(LStrHandle input, LStrHandle result); extern "C" ANSULT_API int ANSConvertUTF8ToUnicodeEscapes(const char* utf8Str, LStrHandle result); extern "C" ANSULT_API int ANSDoubleEscapeUnicode(const char* str, LStrHandle result); extern "C" ANSULT_API int ANSConvertUTF8ToDoubleEscapedUnicode(const char* utf8Str, LStrHandle result); diff --git a/tests/ANSFR-UnitTest/ANSFR-UnitTest.cpp b/tests/ANSFR-UnitTest/ANSFR-UnitTest.cpp index 7afc9bc..0faa15f 100644 --- a/tests/ANSFR-UnitTest/ANSFR-UnitTest.cpp +++ b/tests/ANSFR-UnitTest/ANSFR-UnitTest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "ANSFR.h" #include "fastdeploy/vision.h" #include "ANSFilePlayer.h" @@ -820,6 +821,317 @@ int ANSVISTest() { return 0; } +// ANSVISTestCPU — CPU-only variant of ANSVISTest for Intel CPU PCs without NVIDIA GPU. +// Uses CPU face detector model and forces precision=0 (FP32) for ONNX Runtime / OpenVINO. +int ANSVISTestCPU() { + boost::property_tree::ptree pt; + + std::string databaseFilePath = "C:\\ProgramData\\ANSCENTER\\ANSVIS Server\\ANSFR\\ANSFR.db"; + std::string recognizerFilePath = "C:\\ProgramData\\ANSCENTER\\ANSVIS Server\\ANSFR\\ANS_FaceRecognizer_v1.1.zip"; + std::string facedetectorFilePath = "C:\\ProgramData\\ANSCENTER\\ANSVIS Server\\ANSFDET\\ANS_GenericFD(CPU)_v1.0.zip"; + std::string videoFilePath = "C:\\ProgramData\\ANSCENTER\\Shared\\classroom.mp4"; + + const char* configFilePath = ""; + ANSCENTER::ANSFacialRecognition* infHandle = nullptr; + std::string licenseKey = ""; + int enableHeadPose = 1; + int enableFaceLiveness = 1; + int enableAgeGender = 1; + int enableEmotion = 1; + int enableAntispoofing = 1; + int precision = 0; // FP32 + + std::cout << "=== ANSVISTestCPU ===" << std::endl; + std::cout << "Database: " << databaseFilePath << std::endl; + std::cout << "Recognizer: " << recognizerFilePath << std::endl; + std::cout << "FaceDetector: " << facedetectorFilePath << std::endl; + std::cout << "Video: " << videoFilePath << std::endl; + + // Step 1: Create handle + std::cout << "[CPU Test] Step 1: Creating handle..." << std::endl; + int result = CreateANSRFHandle(&infHandle, + licenseKey.c_str(), + configFilePath, + databaseFilePath.c_str(), + recognizerFilePath.c_str(), + facedetectorFilePath.c_str(), + precision, + 0.25, enableAgeGender, enableEmotion, enableHeadPose, 30, 0.55, enableFaceLiveness, enableAntispoofing); + std::cout << "[CPU Test] CreateANSRFHandle result: " << result << std::endl; + if (result < 0) { + std::cerr << "[CPU Test] FAILED: CreateANSRFHandle returned " << result << std::endl; + return -1; + } + + // Step 2: Load engine + std::cout << "[CPU Test] Step 2: Loading engine..." << std::endl; + int loadEngineResult = LoadANSRFEngine(&infHandle); + std::cout << "[CPU Test] LoadANSRFEngine result: " << loadEngineResult << std::endl; + if (loadEngineResult != 1) { + std::cerr << "[CPU Test] FAILED: LoadANSRFEngine returned " << loadEngineResult << std::endl; + ReleaseANSRFHandle(&infHandle); + return -2; + } + + // Step 3: Reload database + std::cout << "[CPU Test] Step 3: Reloading database..." << std::endl; + Reload(&infHandle); + + // Step 4: Open video + std::cout << "[CPU Test] Step 4: Opening video..." << std::endl; + cv::VideoCapture capture(videoFilePath); + if (!capture.isOpened()) { + std::cerr << "[CPU Test] FAILED: Could not open video: " << videoFilePath << std::endl; + ReleaseANSRFHandle(&infHandle); + return -3; + } + + int totalFrames = static_cast(capture.get(cv::CAP_PROP_FRAME_COUNT)); + std::cout << "[CPU Test] Video opened: " << totalFrames << " frames" << std::endl; + + // Step 5: Run inference on video frames + std::cout << "[CPU Test] Step 5: Running inference..." << std::endl; + int frameIndex = 0; + int totalDetections = 0; + double totalInferenceMs = 0.0; + int maxFrames = 200; // Process up to 200 frames for the test + + while (frameIndex < maxFrames) { + cv::Mat frame; + if (!capture.read(frame)) { + std::cout << "[CPU Test] End of video at frame " << frameIndex << std::endl; + break; + } + frameIndex++; + + unsigned int bufferLength = 0; + unsigned char* jpeg_string = ANSCENTER::ANSUtilityHelper::CVMatToBytes(frame, bufferLength); + int height = frame.rows; + int width = frame.cols; + + auto start = std::chrono::system_clock::now(); + string detectionResult = RunANSRFInferenceBinary(&infHandle, jpeg_string, width, height); + auto end = std::chrono::system_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start); + totalInferenceMs += static_cast(elapsed.count()); + + delete[] jpeg_string; + + if (!detectionResult.empty()) { + try { + pt.clear(); + std::stringstream ss; + ss << detectionResult; + boost::property_tree::read_json(ss, pt); + int detCount = 0; + BOOST_FOREACH(const boost::property_tree::ptree::value_type& child, pt.get_child("results")) { + const boost::property_tree::ptree& r = child.second; + const auto class_id = GetData(r, "user_id"); + const auto class_name = GetData(r, "user_name"); + const auto x = GetData(r, "x"); + const auto y = GetData(r, "y"); + const auto w = GetData(r, "width"); + const auto h = GetData(r, "height"); + const auto sim = GetData(r, "similarity"); + detCount++; + + cv::rectangle(frame, cv::Rect(x, y, w, h), cv::Scalar(0, 255, 0), 2); + cv::putText(frame, cv::format("%s:%d-%.2f", class_name.c_str(), class_id, sim), + cv::Point(x, y - 5), 0, 0.6, cv::Scalar(0, 0, 255), 1, cv::LINE_AA); + } + totalDetections += detCount; + } + catch (const std::exception& e) { + std::cerr << "[CPU Test] JSON parse error at frame " << frameIndex << ": " << e.what() << std::endl; + } + } + + // Show every 10th frame progress + if (frameIndex % 10 == 0) { + std::cout << "[CPU Test] Frame " << frameIndex << "/" << maxFrames + << " | Time: " << elapsed.count() << "ms" + << " | Detections: " << totalDetections << std::endl; + } + + cv::imshow("ANS CPU Test", frame); + if (cv::waitKey(1) == 27) { + std::cout << "[CPU Test] ESC pressed, stopping." << std::endl; + break; + } + } + + // Step 6: Print summary + double avgMs = (frameIndex > 0) ? (totalInferenceMs / frameIndex) : 0.0; + std::cout << "\n=== CPU Test Summary ===" << std::endl; + std::cout << "Frames processed: " << frameIndex << std::endl; + std::cout << "Total detections: " << totalDetections << std::endl; + std::cout << "Avg inference: " << avgMs << " ms/frame" << std::endl; + std::cout << "Total time: " << totalInferenceMs << " ms" << std::endl; + + if (frameIndex == 0) { + std::cerr << "[CPU Test] FAILED: No frames processed" << std::endl; + } else { + std::cout << "[CPU Test] PASSED" << std::endl; + } + + // Cleanup + capture.release(); + cv::destroyAllWindows(); + ReleaseANSRFHandle(&infHandle); + std::cout << "[CPU Test] Done." << std::endl; + return (frameIndex > 0) ? 0 : -4; +} + +// ANSVISTestCPU_Lightweight — Detection + Recognition only, no attribute models. +// Measures baseline face detection + recognition speed on Intel CPU/iGPU. +int ANSVISTestCPU_Lightweight() { + boost::property_tree::ptree pt; + + std::string databaseFilePath = "C:\\ProgramData\\ANSCENTER\\ANSVIS Server\\ANSFR\\ANSFR.db"; + std::string recognizerFilePath = "C:\\ProgramData\\ANSCENTER\\ANSVIS Server\\ANSFR\\ANS_FaceRecognizer_v1.1.zip"; + std::string facedetectorFilePath = "C:\\ProgramData\\ANSCENTER\\ANSVIS Server\\ANSFDET\\ANS_GenericFD(CPU)_v1.0.zip"; + std::string videoFilePath = "C:\\ProgramData\\ANSCENTER\\Shared\\classroom.mp4"; + + const char* configFilePath = ""; + ANSCENTER::ANSFacialRecognition* infHandle = nullptr; + std::string licenseKey = ""; + + // All attribute models DISABLED for lightweight test + int enableHeadPose = 0; + int enableFaceLiveness = 0; + int enableAgeGender = 0; + int enableEmotion = 0; + int enableAntispoofing = 0; + int precision = 0; // FP32 + + std::cout << "\n=== ANSVISTestCPU_Lightweight ===" << std::endl; + std::cout << "Mode: Detection + Recognition ONLY (no attributes)" << std::endl; + std::cout << "Database: " << databaseFilePath << std::endl; + std::cout << "Recognizer: " << recognizerFilePath << std::endl; + std::cout << "FaceDetector: " << facedetectorFilePath << std::endl; + std::cout << "Video: " << videoFilePath << std::endl; + + // Step 1: Create handle + std::cout << "[Lightweight] Step 1: Creating handle..." << std::endl; + int result = CreateANSRFHandle(&infHandle, + licenseKey.c_str(), + configFilePath, + databaseFilePath.c_str(), + recognizerFilePath.c_str(), + facedetectorFilePath.c_str(), + precision, + 0.25, enableAgeGender, enableEmotion, enableHeadPose, 30, 0.55, enableFaceLiveness, enableAntispoofing); + std::cout << "[Lightweight] CreateANSRFHandle result: " << result << std::endl; + if (result < 0) { + std::cerr << "[Lightweight] FAILED: CreateANSRFHandle returned " << result << std::endl; + return -1; + } + + // Step 2: Load engine + std::cout << "[Lightweight] Step 2: Loading engine..." << std::endl; + int loadEngineResult = LoadANSRFEngine(&infHandle); + std::cout << "[Lightweight] LoadANSRFEngine result: " << loadEngineResult << std::endl; + if (loadEngineResult != 1) { + std::cerr << "[Lightweight] FAILED: LoadANSRFEngine returned " << loadEngineResult << std::endl; + ReleaseANSRFHandle(&infHandle); + return -2; + } + + // Step 3: Reload database + std::cout << "[Lightweight] Step 3: Reloading database..." << std::endl; + Reload(&infHandle); + + // Step 4: Open video + std::cout << "[Lightweight] Step 4: Opening video..." << std::endl; + cv::VideoCapture capture(videoFilePath); + if (!capture.isOpened()) { + std::cerr << "[Lightweight] FAILED: Could not open video: " << videoFilePath << std::endl; + ReleaseANSRFHandle(&infHandle); + return -3; + } + + int totalFrames = static_cast(capture.get(cv::CAP_PROP_FRAME_COUNT)); + std::cout << "[Lightweight] Video opened: " << totalFrames << " frames" << std::endl; + + // Step 5: Run inference + std::cout << "[Lightweight] Step 5: Running inference..." << std::endl; + int frameIndex = 0; + int totalDetections = 0; + double totalInferenceMs = 0.0; + int maxFrames = 200; + + while (frameIndex < maxFrames) { + cv::Mat frame; + if (!capture.read(frame)) { + std::cout << "[Lightweight] End of video at frame " << frameIndex << std::endl; + break; + } + frameIndex++; + + unsigned int bufferLength = 0; + unsigned char* jpeg_string = ANSCENTER::ANSUtilityHelper::CVMatToBytes(frame, bufferLength); + int height = frame.rows; + int width = frame.cols; + + auto start = std::chrono::system_clock::now(); + string detectionResult = RunANSRFInferenceBinary(&infHandle, jpeg_string, width, height); + auto end = std::chrono::system_clock::now(); + auto elapsed = std::chrono::duration_cast(end - start); + totalInferenceMs += static_cast(elapsed.count()); + + delete[] jpeg_string; + + if (!detectionResult.empty()) { + try { + pt.clear(); + std::stringstream ss; + ss << detectionResult; + boost::property_tree::read_json(ss, pt); + int detCount = 0; + BOOST_FOREACH(const boost::property_tree::ptree::value_type& child, pt.get_child("results")) { + const boost::property_tree::ptree& r = child.second; + const auto x = GetData(r, "x"); + const auto y = GetData(r, "y"); + const auto w = GetData(r, "width"); + const auto h = GetData(r, "height"); + detCount++; + cv::rectangle(frame, cv::Rect(x, y, w, h), cv::Scalar(0, 255, 0), 2); + } + totalDetections += detCount; + } + catch (...) {} + } + + if (frameIndex % 10 == 0) { + double avgSoFar = totalInferenceMs / frameIndex; + std::cout << "[Lightweight] Frame " << frameIndex << "/" << maxFrames + << " | Time: " << elapsed.count() << "ms" + << " | Avg: " << static_cast(avgSoFar) << "ms" + << " | FPS: " << std::fixed << std::setprecision(1) << (1000.0 / avgSoFar) + << " | Faces: " << totalDetections << std::endl; + } + + cv::imshow("ANS CPU Lightweight", frame); + if (cv::waitKey(1) == 27) break; + } + + // Summary + double avgMs = (frameIndex > 0) ? (totalInferenceMs / frameIndex) : 0.0; + double fps = (avgMs > 0) ? (1000.0 / avgMs) : 0.0; + std::cout << "\n=== Lightweight Test Summary ===" << std::endl; + std::cout << "Frames processed: " << frameIndex << std::endl; + std::cout << "Total detections: " << totalDetections << std::endl; + std::cout << "Avg inference: " << avgMs << " ms/frame" << std::endl; + std::cout << "Avg FPS: " << std::fixed << std::setprecision(1) << fps << std::endl; + std::cout << "Total time: " << totalInferenceMs << " ms" << std::endl; + std::cout << (frameIndex > 0 ? "[Lightweight] PASSED" : "[Lightweight] FAILED") << std::endl; + + capture.release(); + cv::destroyAllWindows(); + ReleaseANSRFHandle(&infHandle); + return (frameIndex > 0) ? 0 : -4; +} + // ANSVISTestFilePlayer — Same as ANSVISTest but uses ANSFILEPLAYER (HW decode + NV12 registry) // instead of cv::VideoCapture. This enables NV12 fast-path testing for the FR pipeline: // - SCRFD face detection uses fused NV12→RGB center-letterbox kernel @@ -1321,16 +1633,16 @@ int TestCompleteFR1() { } int main() { - + //FaceDetectorTest(); //TestFaceRecognition(); //TestCompleteFR1(); - + //ANSVISImageTest(); - ANSVISTest(); + //ANSVISTest(); //ANSVISTestFilePlayer(); // ANSVISRecordingTest(); - //FRStressTest(); + //FRStressTest(); //for (int i = 0; i < 20; i++) { // ANSVISTest(); //} @@ -1341,6 +1653,8 @@ int main() // TestCompleteFR(); //TestFaceRecognition(); //FaceRecognitionBenchmark(); + ANSVISTestCPU_Lightweight(); + ANSVISTestCPU(); std::cin.get(); }