diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e69de29..af377d6 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -0,0 +1,19 @@ +{ + "permissions": { + "allow": [ + "Bash(grep -v \"\\\\[EzMemoryManagerPlugin\\\\]\\\\|filesystem_win\\\\|at System\\\\|at NationalInstruments\\\\|Memory Leak Tracking\\\\|System\\\\.Argument\\\\|DNError\\\\|Parameter name\\\\|Inner Exception\\\\|^[0-9]*\t[0-9.]*\t[0-9]*\t$\" \"C:/Users/nghia/Downloads/ANSLEGION1.log\")", + "Bash(grep -v \"^$\")", + "Bash(grep -v \"^[^\t]*\t[^\t]*\t18924\t *$\")", + "Bash(dumpbin //EXPORTS \"C:/ProgramData/ANSCENTER/Shared/ANSLicensingSystem.dll\")", + "Bash(\"/c/Program Files \\(x86\\)/Windows Kits/10/bin/10.0.26100.0/x64/dumpbin.exe\" //EXPORTS \"C:/ProgramData/ANSCENTER/Shared/ANSLicensingSystem.dll\")", + "Bash(\"/c/Program Files \\(x86\\)/Windows Kits/10/bin/10.0.26100.0/x64/dumpbin.exe\" /EXPORTS \"C:/ProgramData/ANSCENTER/Shared/ANSLicensingSystem.dll\")", + "Bash(\"C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.16.27023/bin/HostX64/x64/dumpbin.exe\" /EXPORTS \"C:/ProgramData/ANSCENTER/Shared/ANSLicensingSystem.dll\")", + "Bash(\"C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.16.27023/bin/HostX64/x64/dumpbin.exe\" //EXPORTS \"C:/ProgramData/ANSCENTER/Shared/ANSLicensingSystem.dll\")", + "Read(//c/Program Files/ANSCENTER/**)", + "Bash(powershell.exe -NoProfile -Command \"[System.Environment]::GetEnvironmentVariable\\('PATH','Machine'\\) -split ';' | Select-String -Pattern 'ANSCENTER|Shared'\")", + "Bash(cmd.exe //c 'dir /AL \"C:\\\\Program Files\\\\ANSCENTER\\\\ANSVIS\\\\data\" 2>&1 | findstr /i \"junction symlink\"')", + "Bash(cmd.exe //c 'dir /AL \"C:\\\\Program Files\\\\ANSCENTER\\\\ANSVIS\\\\data\"')", + "PowerShell(Get-ChildItem \"C:\\\\Program Files\\\\ANSCENTER\\\\ANSVIS\\\\data\" -Force | Where-Object { $_.LinkType } | Select-Object Name, LinkType, Target | Format-Table -AutoSize)" + ] + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 95dd4b5..3b8f4af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,28 +73,27 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") endif() endif() -# ── DebugView logging toggle ──────────────────────────────────── -# When ON, every ANS_DBG(...) call across the whole tree expands to an -# OutputDebugStringA() call visible in Sysinternals DebugView (Dbgview.exe). -# This is the single switch for verbose runtime diagnostics in ANSLPR, -# ANSCV (RTSP lifecycle, HW decoder auto-config), ANSODEngine (NV12 fast -# path, ORT/TRT engine selection), ANSFR (face recognizer state), etc. +# ── DebugView logging toggle (runtime-gated) ──────────────────── +# ANS_DBG is compiled into every build by default and gated at runtime by +# ANSCENTER_IsDebugViewEnabled() (see ANSLicense.h/.cpp). Users toggle +# logging without a rebuild by creating or deleting +# C:\ProgramData\ANSCENTER\ansvisdebugview.txt (Windows), or +# /tmp/ansvisdebugview.txt (POSIX), +# or by setting env var ANSCENTER_DBGVIEW=1/0. Effect propagates within ~2 s. # -# Enable it to diagnose field issues (e.g. "ALPR worked for a while then -# stopped"), then turn it back OFF for production because every ANS_DBG -# call adds a kernel round-trip and string formatting cost. +# This CMake option is a HARD KILL-SWITCH only — set it ON to strip ANS_DBG +# to ((void)0) at compile time. Use it only when even the cached atomic-load +# cost is unacceptable (tight inner loops). Default OFF = runtime-gated. # # Usage: -# cmake -B build -DANSCORE_DEBUGVIEW=ON # enable -# cmake -B build -DANSCORE_DEBUGVIEW=OFF # disable (default) -# -# Or toggle in CLion/VS: edit the cache variable ANSCORE_DEBUGVIEW. -option(ANSCORE_DEBUGVIEW "Enable ANS_DBG OutputDebugString logging for DebugView" OFF) -if(ANSCORE_DEBUGVIEW) - add_compile_definitions(ANSCORE_DEBUGVIEW=1) - message(STATUS "ANSCORE_DEBUGVIEW = ON — ANS_DBG verbose logging ENABLED (DebugView)") +# cmake -B build # default: runtime-gated +# cmake -B build -DANSCORE_DEBUGVIEW_STRIP=ON # hard-strip all ANS_DBG +option(ANSCORE_DEBUGVIEW_STRIP "Hard-strip ANS_DBG calls at compile time (removes runtime toggle)" OFF) +if(ANSCORE_DEBUGVIEW_STRIP) + add_compile_definitions(ANSCORE_DEBUGVIEW=0) + message(STATUS "ANSCORE_DEBUGVIEW_STRIP = ON — ANS_DBG compiled OUT (no runtime toggle)") else() - message(STATUS "ANSCORE_DEBUGVIEW = OFF — ANS_DBG verbose logging disabled (production)") + message(STATUS "ANSCORE_DEBUGVIEW_STRIP = OFF — ANS_DBG compiled IN, gated at runtime (sentinel file / env var)") endif() # ── Vendored libyuv (submodule: 3rdparty/libyuv) ──────────────── diff --git a/core/ANSLicensingSystem/ANSLicense.cpp b/core/ANSLicensingSystem/ANSLicense.cpp index 31d900b..1176828 100644 --- a/core/ANSLicensingSystem/ANSLicense.cpp +++ b/core/ANSLicensingSystem/ANSLicense.cpp @@ -14,6 +14,11 @@ #include "base64.h" #include #include +#include +#include +#include +#include +#include #define MAX_CUSTOMER_NAME_LEN 100 #define FEATURE_1 0x1 @@ -2963,4 +2968,45 @@ namespace ANSCENTER return logger.Release(); } +} + +// ============================================================================ +// Runtime DebugView toggle — gates ANS_DBG without requiring a rebuild. +// +// Precedence per poll: env var ANSCENTER_DBGVIEW wins over the sentinel file. +// Poll interval is 2 s, so toggling takes effect within that window. Hot-path +// cost on a cache hit is a single relaxed atomic load + branch. +// ============================================================================ +extern "C" ANSLICENSE_API int ANSCENTER_IsDebugViewEnabled(void) { + using clock = std::chrono::steady_clock; + static std::atomic s_enabled{0}; + static std::atomic s_nextCheckTick{0}; + + const long long now = clock::now().time_since_epoch().count(); + const long long next = s_nextCheckTick.load(std::memory_order_relaxed); + if (now < next) { + return s_enabled.load(std::memory_order_relaxed); + } + + int enabled = 0; + const char* env = std::getenv("ANSCENTER_DBGVIEW"); + if (env && env[0] == '1' && env[1] == '\0') { + enabled = 1; + } else if (env && env[0] == '0' && env[1] == '\0') { + enabled = 0; + } else { +#ifdef _WIN32 + const wchar_t* kSentinel = L"C:\\ProgramData\\ANSCENTER\\ansvisdebugview.txt"; +#else + const char* kSentinel = "/tmp/ansvisdebugview.txt"; +#endif + std::error_code ec; + enabled = std::filesystem::exists(kSentinel, ec) ? 1 : 0; + } + + s_enabled.store(enabled, std::memory_order_relaxed); + const long long deadline = now + + std::chrono::duration_cast(std::chrono::seconds(2)).count(); + s_nextCheckTick.store(deadline, std::memory_order_relaxed); + return enabled; } \ No newline at end of file diff --git a/core/ANSLicensingSystem/ANSLicense.h b/core/ANSLicensingSystem/ANSLicense.h index c03c43a..35e4164 100644 --- a/core/ANSLicensingSystem/ANSLicense.h +++ b/core/ANSLicensingSystem/ANSLicense.h @@ -1,20 +1,40 @@ #ifndef ANSLICENSE_H #define ANSLICENSE_H +#ifdef ANSLICENSE_EXPORTS +#define ANSLICENSE_API __declspec(dllexport) +#else +#define ANSLICENSE_API __declspec(dllimport) +#endif + // ============================================================================ -// Global debug toggle for DebugView (DbgView) logging. -// Define ANSCORE_DEBUGVIEW=1 to enable verbose OutputDebugStringA logging -// across all ANSCORE modules (ANSCV, ANSODEngine, TensorRT engine, etc.). -// Set to 0 for production builds to eliminate all debug output overhead. +// DebugView (DbgView) logging toggle. +// +// ANS_DBG is compiled into every build; visibility is chosen at runtime by +// ANSCENTER_IsDebugViewEnabled() — a cached check (~2 s) that reads: +// 1. env var ANSCENTER_DBGVIEW (="1" forces on, ="0" forces off), else +// 2. existence of C:\ProgramData\ANSCENTER\ansvisdebugview.txt (Windows) or +// /tmp/ansvisdebugview.txt (POSIX). +// +// Drop the sentinel file to turn logging on; delete it to turn it off. No +// rebuild, no restart — takes effect within ~2 s. ACLs on ProgramData are +// world-writable by default so any user can toggle. +// +// Hard kill-switch: compile with -DANSCORE_DEBUGVIEW=0 to strip ANS_DBG to +// ((void)0) — use only when even the atomic-load cost is unacceptable. // ============================================================================ #ifndef ANSCORE_DEBUGVIEW -#define ANSCORE_DEBUGVIEW 1 // 1 = enabled (debug), 0 = disabled (production) +#define ANSCORE_DEBUGVIEW 1 // 1 = runtime-gated (default), 0 = hard-off (stripped) #endif +// Runtime gate. Implemented in ANSLicense.cpp. Returns non-zero when logging +// should be emitted. Cheap on the hot path: one atomic load + branch; the +// filesystem check runs at most once per ~2 s. +extern "C" ANSLICENSE_API int ANSCENTER_IsDebugViewEnabled(void); + // ANS_DBG: Debug logging macro for DebugView (OutputDebugStringA on Windows). // Usage: ANS_DBG("MyModule", "value=%d ptr=%p", val, ptr); // Output: [MyModule] value=42 ptr=0x1234 -// When ANSCORE_DEBUGVIEW=0, compiles to nothing (zero overhead). // NOTE: We avoid #include here to prevent winsock.h/winsock2.h // conflicts. Instead, forward-declare OutputDebugStringA directly. #if ANSCORE_DEBUGVIEW && defined(_WIN32) @@ -24,30 +44,28 @@ extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* l // without needing to attach DebugView. fputs + fflush keeps lines atomic even // when multiple threads log concurrently. #define ANS_DBG(tag, fmt, ...) do { \ - char _ans_dbg_buf[1024]; \ - snprintf(_ans_dbg_buf, sizeof(_ans_dbg_buf), "[" tag "] " fmt "\n", ##__VA_ARGS__); \ - OutputDebugStringA(_ans_dbg_buf); \ - fputs(_ans_dbg_buf, stderr); \ - fflush(stderr); \ + if (ANSCENTER_IsDebugViewEnabled()) { \ + char _ans_dbg_buf[1024]; \ + snprintf(_ans_dbg_buf, sizeof(_ans_dbg_buf), "[" tag "] " fmt "\n", ##__VA_ARGS__); \ + OutputDebugStringA(_ans_dbg_buf); \ + fputs(_ans_dbg_buf, stderr); \ + fflush(stderr); \ + } \ } while(0) #elif ANSCORE_DEBUGVIEW // Non-Windows: stderr only. #define ANS_DBG(tag, fmt, ...) do { \ - char _ans_dbg_buf[1024]; \ - snprintf(_ans_dbg_buf, sizeof(_ans_dbg_buf), "[" tag "] " fmt "\n", ##__VA_ARGS__); \ - fputs(_ans_dbg_buf, stderr); \ - fflush(stderr); \ + if (ANSCENTER_IsDebugViewEnabled()) { \ + char _ans_dbg_buf[1024]; \ + snprintf(_ans_dbg_buf, sizeof(_ans_dbg_buf), "[" tag "] " fmt "\n", ##__VA_ARGS__); \ + fputs(_ans_dbg_buf, stderr); \ + fflush(stderr); \ + } \ } while(0) #else #define ANS_DBG(tag, fmt, ...) ((void)0) #endif -#ifdef ANSLICENSE_EXPORTS -#define ANSLICENSE_API __declspec(dllexport) -#else -#define ANSLICENSE_API __declspec(dllimport) -#endif - #pragma once #include diff --git a/tests/ANSLPR-UnitTest/ANSLPR-UnitTest.cpp b/tests/ANSLPR-UnitTest/ANSLPR-UnitTest.cpp index 28eb584..1cbf20b 100644 --- a/tests/ANSLPR-UnitTest/ANSLPR-UnitTest.cpp +++ b/tests/ANSLPR-UnitTest/ANSLPR-UnitTest.cpp @@ -559,6 +559,130 @@ int ANSLPR_OD_Inferences_FileTest() { } +// Run ALPR_OD inference over every image file in a given folder and log all +// detections to the console. Mirrors ANSLPR_OD_Inferences_FileTest but batches +// over a directory instead of a single image and does not open a display window. +int ANSLPR_OD_FolderInferences_Test() { + std::filesystem::path currentPath = std::filesystem::current_path(); + std::cout << "Current working directory: " << currentPath << std::endl; + + boost::property_tree::ptree pt; + ANSCENTER::ANSALPR* infHandle = nullptr; + std::string licenseKey = ""; + std::string modelZipFile = "C:\\ProgramData\\ANSCENTER\\ANSVIS Server\\ANSALPR\\ServerOptimised\\ANS_ALPR_v1.2_NVIDIAGeForceRTX4070LaptopGPU.zip"; + std::string folderPath = "E:\\Programs\\DemoAssets\\ImageSeries\\ALPR character M\\EventDebug_20260419_150142.997"; + + int engineType = 1; + double detectionThreshold = 0.3; + double ocrThreshold = 0.5; + double colourThreshold = 0.5; + + int result = CreateANSALPRHandle(&infHandle, licenseKey.c_str(), modelZipFile.c_str(), "", + engineType, detectionThreshold, ocrThreshold, colourThreshold); + std::cout << "Loading ANSLPR: " << result << std::endl; + + auto startLoad = std::chrono::system_clock::now(); + int loadEngine = LoadANSALPREngineHandle(&infHandle); + auto endLoad = std::chrono::system_clock::now(); + auto loadMs = std::chrono::duration_cast(endLoad - startLoad).count(); + std::cout << "Init Result: " << loadEngine << " (engine load " << loadMs << " ms)" << std::endl; + + if (!std::filesystem::exists(folderPath) || !std::filesystem::is_directory(folderPath)) { + std::cout << "Folder does not exist or is not a directory: " << folderPath << std::endl; + ReleaseANSALPRHandle(&infHandle); + return -1; + } + + std::vector imageFiles; + const std::set imageExts = { ".jpg", ".jpeg", ".png", ".bmp" }; + for (const auto& entry : std::filesystem::directory_iterator(folderPath)) { + if (!entry.is_regular_file()) continue; + std::string ext = entry.path().extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (imageExts.count(ext)) imageFiles.push_back(entry.path()); + } + std::sort(imageFiles.begin(), imageFiles.end()); + + std::cout << "Scanning folder: " << folderPath << std::endl; + std::cout << "Found " << imageFiles.size() << " image(s)." << std::endl; + + int totalDetections = 0; + long long totalInferMs = 0; + for (size_t i = 0; i < imageFiles.size(); ++i) { + const auto& filePath = imageFiles[i]; + cv::Mat input = cv::imread(filePath.string(), cv::IMREAD_COLOR); + if (input.empty()) { + std::cout << "[" << (i + 1) << "/" << imageFiles.size() << "] " + << filePath.filename().string() << " " << std::endl; + continue; + } + + std::string lpnResult; + std::string jpegImage; + cv::Mat* image = new cv::Mat(input); + + auto t0 = std::chrono::system_clock::now(); + ANSALPR_RunInferenceComplete_CPP(&infHandle, &image, "MyCam", 0, 0, lpnResult, jpegImage); + auto t1 = std::chrono::system_clock::now(); + auto inferMs = std::chrono::duration_cast(t1 - t0).count(); + totalInferMs += inferMs; + + std::cout << "\n[" << (i + 1) << "/" << imageFiles.size() << "] " + << filePath.filename().string() + << " (" << input.cols << "x" << input.rows << ")" + << " infer=" << inferMs << "ms" << std::endl; + std::cout << " Raw JSON: " << lpnResult << std::endl; + + int numDetections = 0; + if (!lpnResult.empty()) { + try { + pt.clear(); + std::stringstream ss; + ss << lpnResult; + boost::property_tree::read_json(ss, pt); + auto resultsChild = pt.get_child_optional("results"); + if (resultsChild) { + for (const auto& child : *resultsChild) { + const auto& r = child.second; + const auto class_id = GetData(r, "class_id"); + const auto class_name = GetData(r, "class_name"); + const auto x = GetData(r, "x"); + const auto y = GetData(r, "y"); + const auto width = GetData(r, "width"); + const auto height = GetData(r, "height"); + std::cout << " det[" << numDetections << "]" + << " id=" << class_id + << " name=" << class_name + << " bbox=(" << x << "," << y << "," + << width << "," << height << ")" + << std::endl; + ++numDetections; + } + } + } catch (const std::exception& e) { + std::cout << " JSON parse error: " << e.what() << std::endl; + } + } + std::cout << " => " << numDetections << " detection(s)" << std::endl; + totalDetections += numDetections; + delete image; + } + + std::cout << "\n=== ANSLPR_OD_FolderInferences_Test summary ===" << std::endl; + std::cout << "Images processed : " << imageFiles.size() << std::endl; + std::cout << "Total detections : " << totalDetections << std::endl; + if (!imageFiles.empty()) { + std::cout << "Avg inference : " + << (totalInferMs / static_cast(imageFiles.size())) << " ms" + << std::endl; + } + + ReleaseANSALPRHandle(&infHandle); + return 0; +} + + int ANSLPR_OD_INDOInferences_FileTest() { // Get the current working directory std::filesystem::path currentPath = std::filesystem::current_path(); @@ -3955,7 +4079,8 @@ int main() SetConsoleCP(CP_UTF8); #endif // ANSLPR_OD_INDOInferences_FileTest(); - ANSLPR_OD_Inferences_FileTest(); + //ANSLPR_OD_Inferences_FileTest(); + ANSLPR_OD_FolderInferences_Test(); //ANSLPR_OD_VideoTest(); //ANSLPR_BigSize_VideoTest(); //ANSLPR_CPU_VideoTest();