758 lines
29 KiB
C++
758 lines
29 KiB
C++
#include "ANSCUSTOMPY.h"
|
|
#include <filesystem>
|
|
#include <json.hpp>
|
|
using json = nlohmann::json;
|
|
namespace fs = std::filesystem;
|
|
namespace py = pybind11;
|
|
|
|
namespace {
|
|
using ThreadLocalModelMap = std::unordered_map<int, std::shared_ptr<ANSCENTER::ModelWrapper>>;
|
|
ThreadLocalModelMap& GetThreadLocalState() {
|
|
thread_local ThreadLocalModelMap threadState;
|
|
return threadState;
|
|
}
|
|
}
|
|
std::string CustomParametersToJson(const ANSCENTER::Params& params) {
|
|
json root;
|
|
// ROI_Config
|
|
for (const auto& cfg : params.ROI_Config) {
|
|
json j;
|
|
j["Rectangle"] = cfg.Rectangle;
|
|
j["Polygon"] = cfg.Polygon;
|
|
j["Line"] = cfg.Line;
|
|
j["MinItems"] = cfg.MinItems;
|
|
j["MaxItems"] = cfg.MaxItems;
|
|
j["Name"] = cfg.Name;
|
|
j["ROI-Match"] = cfg.ROIMatch;
|
|
root["ROI_Config"].push_back(j);
|
|
}
|
|
|
|
// ROI_Options
|
|
for (const auto& opt : params.ROI_Options) {
|
|
root["ROI_Options"].push_back(opt);
|
|
}
|
|
|
|
// Parameters
|
|
for (const auto& param : params.Parameters) {
|
|
json j;
|
|
j["Name"] = param.Name;
|
|
j["DataType"] = param.DataType;
|
|
j["NoOfdecimals"] = param.NoOfDecimals;
|
|
j["MaxValue"] = param.MaxValue;
|
|
j["MinValue"] = param.MinValue;
|
|
j["StartValue"] = param.StartValue;
|
|
j["ListItems"] = param.ListItems;
|
|
j["DefaultValue"] = param.DefaultValue;
|
|
j["Value"] = param.Value;
|
|
root["Parameters"].push_back(j);
|
|
}
|
|
|
|
// ROI_Values
|
|
for (const auto& rv : params.ROI_Values) {
|
|
json j;
|
|
j["ROI-Match"] = rv.ROIMatch;
|
|
|
|
for (const auto& pt : rv.ROIPoints) {
|
|
j["ROIPoints"].push_back({ {"x", pt.x}, {"y", pt.y} });
|
|
}
|
|
|
|
j["Option"] = rv.Option;
|
|
j["Name"] = rv.Name;
|
|
j["OriginalImageSize"] = rv.OriginalImageSize;
|
|
root["ROI_Values"].push_back(j);
|
|
}
|
|
|
|
// Convert to compact JSON string (no whitespace)
|
|
std::string jsonString = root.dump(); // dump() without indent = compact
|
|
|
|
return jsonString;
|
|
}
|
|
namespace ANSCENTER
|
|
{
|
|
std::once_flag PythonRuntime::initFlag;
|
|
std::unique_ptr<PythonRuntime> PythonRuntime::instance = nullptr;
|
|
std::atomic<bool> PythonRuntime::_isInitialized{ false };
|
|
std::atomic<bool> PythonRuntime::shuttingDown{ false };
|
|
std::mutex PythonRuntime::_instanceMutex;
|
|
|
|
std::mutex ANSCUSTOMPY::_globalInstancesMutex;
|
|
std::unordered_set<ANSCUSTOMPY*> ANSCUSTOMPY::_globalInstances;
|
|
std::atomic<int> ANSCUSTOMPY::globalInstanceCounter{ 1 };
|
|
|
|
// Declare GetPythonRuntime as a friend function of PythonRuntime
|
|
PythonRuntime& GetPythonRuntime(const std::wstring& pythonHome);
|
|
|
|
PythonRuntime& GetPythonRuntime(const std::wstring& pythonHome) {
|
|
std::call_once(PythonRuntime::initFlag, [&]() {
|
|
std::lock_guard<std::mutex> lock(PythonRuntime::_instanceMutex);
|
|
PythonRuntime::instance = std::unique_ptr<PythonRuntime>(new PythonRuntime(pythonHome));
|
|
});
|
|
return *PythonRuntime::instance;
|
|
}
|
|
|
|
PythonRuntime::PythonRuntime(const std::wstring& home) : pythonHome(home) {
|
|
InitializeInterpreter();
|
|
}
|
|
|
|
bool PythonRuntime::IsInitialized() {
|
|
return _isInitialized.load(std::memory_order_acquire) && Py_IsInitialized();
|
|
}
|
|
|
|
void PythonRuntime::InitializeInterpreter() {
|
|
if (!pythonHome.empty()) {
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 4996)
|
|
Py_SetPythonHome(pythonHome.c_str());
|
|
#pragma warning(pop)
|
|
}
|
|
py::initialize_interpreter();
|
|
_isInitialized.store(true, std::memory_order_release);
|
|
PatchPythonStreamsSafe();
|
|
}
|
|
|
|
void PythonRuntime::PatchPythonStreamsSafe() {
|
|
try {
|
|
py::exec(R"(
|
|
import sys, io
|
|
class DummyStream(io.StringIO):
|
|
def write(self, txt): pass
|
|
def flush(self): pass
|
|
if sys.stdout is None or not hasattr(sys.stdout, "write"):
|
|
sys.stdout = DummyStream()
|
|
if sys.stderr is None or not hasattr(sys.stderr, "write"):
|
|
sys.stderr = DummyStream()
|
|
)");
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
std::cerr << "[PatchPythonStreamsSafe] Python error: " << e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
void PythonRuntime::AddToSysPath(const std::string& path) {
|
|
if (!IsInitialized()) {
|
|
std::cerr << "[PythonRuntime::AddToSysPath] Interpreter not initialized!\n";
|
|
return;
|
|
}
|
|
py::gil_scoped_acquire gil;
|
|
py::module_ sys = py::module_::import("sys");
|
|
sys.attr("path").attr("insert")(0, path);
|
|
}
|
|
|
|
void PythonRuntime::Shutdown() {
|
|
std::lock_guard<std::mutex> lock(_instanceMutex);
|
|
if (shuttingDown.exchange(true)) return;
|
|
if (_isInitialized.load(std::memory_order_acquire)) {
|
|
try {
|
|
py::gil_scoped_acquire gil;
|
|
py::module::import("gc").attr("collect")();
|
|
}
|
|
catch (...) {
|
|
std::cerr << "[PythonRuntime::Shutdown] GC failed (ignored)." << std::endl;
|
|
}
|
|
try {
|
|
py::finalize_interpreter();
|
|
}
|
|
catch (...) {
|
|
std::cerr << "[PythonRuntime::Shutdown] finalize_interpreter failed." << std::endl;
|
|
}
|
|
_isInitialized.store(false, std::memory_order_release);
|
|
}
|
|
instance.reset();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// /// Initializes the ANSCUSTOMPY model with the given parameters.
|
|
/// </summary>
|
|
ANSCUSTOMPY::ANSCUSTOMPY() {
|
|
_instanceId = globalInstanceCounter.fetch_add(1, std::memory_order_relaxed);
|
|
_destroyed = false;
|
|
_isInitialized = false;
|
|
}
|
|
ANSCUSTOMPY::~ANSCUSTOMPY() {
|
|
try {
|
|
if (PythonRuntime::IsInitialized())
|
|
Destroy();
|
|
}
|
|
catch (...) {
|
|
std::cerr << "[~ANSCUSTOMPY] Exception during destruction." << std::endl;
|
|
}
|
|
}
|
|
void ANSCUSTOMPY::RegisterSelf() {
|
|
{
|
|
std::lock_guard<std::mutex> lock(_globalInstancesMutex);
|
|
_globalInstances.insert(this);
|
|
}
|
|
auto& threadState = GetThreadLocalState();
|
|
if (!_wrapper || !_wrapper->modelFactory) return;
|
|
if (threadState.find(_instanceId) == threadState.end()) {
|
|
if (_wrapper && _wrapper->modelFactory) {
|
|
py::gil_scoped_acquire gil;
|
|
auto newWrapper = std::make_shared<ModelWrapper>();
|
|
newWrapper->modelFactory = _wrapper->modelFactory;
|
|
newWrapper->map[0] = std::make_shared<py::object>(_wrapper->modelFactory());
|
|
threadState[_instanceId] = newWrapper;
|
|
}
|
|
}
|
|
}
|
|
void ANSCUSTOMPY::UnregisterSelf() {
|
|
std::lock_guard<std::mutex> lock(_globalInstancesMutex);
|
|
_globalInstances.erase(this);
|
|
}
|
|
void ANSCUSTOMPY::ClearThreadState() {
|
|
auto& threadState = GetThreadLocalState();
|
|
auto it = threadState.find(_instanceId);
|
|
if (it != threadState.end()) {
|
|
if (PythonRuntime::IsInitialized()) {
|
|
py::gil_scoped_acquire gil;
|
|
it->second->map.clear();
|
|
}
|
|
else {
|
|
it->second->map.clear();
|
|
}
|
|
threadState.erase(it);
|
|
}
|
|
}
|
|
bool ANSCUSTOMPY::EnsureModelReady(const std::string& methodName) {
|
|
if (!_isInitialized || _destroyed || !PythonRuntime::IsInitialized()) {
|
|
return false;
|
|
}
|
|
//auto& threadState = GetThreadLocalState();
|
|
//if (threadState.find(_instanceId) == threadState.end()) {
|
|
// if (_wrapper && _wrapper->modelFactory) {
|
|
// py::gil_scoped_acquire gil;
|
|
// auto newWrapper = std::make_shared<ModelWrapper>();
|
|
// newWrapper->modelFactory = _wrapper->modelFactory;
|
|
// newWrapper->map[0] = std::make_shared<py::object>(_wrapper->modelFactory());
|
|
// threadState[_instanceId] = newWrapper;
|
|
// }
|
|
// else {
|
|
// return false;
|
|
// }
|
|
//}
|
|
return true;
|
|
}
|
|
py::object ANSCUSTOMPY::GetThreadLocalModel() {
|
|
auto& threadState = GetThreadLocalState();
|
|
auto it = threadState.find(_instanceId);
|
|
if (_destroyed || it == threadState.end() || !it->second) {
|
|
return py::none();
|
|
}
|
|
auto& modelMap = it->second->map;
|
|
auto modelIt = modelMap.find(0);
|
|
if (modelIt == modelMap.end()) {
|
|
py::gil_scoped_acquire gil;
|
|
auto obj = it->second->modelFactory();
|
|
if (obj.is_none()) return py::none();
|
|
auto wrapped = std::make_shared<py::object>(obj);
|
|
modelMap[0] = wrapped;
|
|
return *wrapped;
|
|
}
|
|
return *modelIt->second;
|
|
}
|
|
bool ANSCUSTOMPY::Destroy() {
|
|
//std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
if (_destroyed) return true;
|
|
_destroyed = true;
|
|
ClearThreadState();
|
|
UnregisterSelf();
|
|
return true;
|
|
}
|
|
void ANSCUSTOMPY::SafeShutdownAll(bool waitForShutdown) {
|
|
std::unordered_set<ANSCUSTOMPY*> instancesCopy;
|
|
{
|
|
std::lock_guard<std::mutex> lock(_globalInstancesMutex);
|
|
instancesCopy = _globalInstances;
|
|
}
|
|
for (ANSCUSTOMPY* instance : instancesCopy) {
|
|
if (instance) {
|
|
try {
|
|
instance->ClearThreadState();
|
|
instance->Destroy();
|
|
}
|
|
catch (...) {
|
|
std::cerr << "[SafeShutdownAll] Destroy failed." << std::endl;
|
|
}
|
|
}
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
|
auto shutdownFunc = []() {
|
|
try {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
|
int retry = 0;
|
|
while (!ANSCUSTOMPY::_globalInstances.empty() && retry++ < 100) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
}
|
|
if (PythonRuntime::IsInitialized()) {
|
|
py::gil_scoped_acquire gil;
|
|
py::module::import("gc").attr("collect")();
|
|
PythonRuntime::Shutdown();
|
|
}
|
|
}
|
|
catch (...) {
|
|
std::cerr << "[SafeShutdownAll] Shutdown failed." << std::endl;
|
|
}
|
|
};
|
|
if (waitForShutdown) shutdownFunc();
|
|
else std::thread(shutdownFunc).detach();
|
|
}
|
|
|
|
|
|
bool ANSCUSTOMPY::InitializePythonModel(const std::string& moduleName, const std::string& fullPath) {
|
|
if (!PythonRuntime::IsInitialized()) {
|
|
_logger.LogError("[InitializePythonModel] Python error:\n", "Python interpreter is not initialized.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::gil_scoped_acquire gil;
|
|
try {
|
|
py::exec("import logging; logging.getLogger('ultralytics').setLevel(logging.CRITICAL)", py::globals());
|
|
py::dict locals;
|
|
std::ostringstream code;
|
|
code << "import importlib.util\n"
|
|
<< "spec = importlib.util.spec_from_file_location('" << moduleName << "', r'" << fullPath << "')\n"
|
|
<< "mod = importlib.util.module_from_spec(spec)\n"
|
|
<< "spec.loader.exec_module(mod)\n";
|
|
|
|
py::exec(code.str(), py::globals(), locals);
|
|
py::object mod = locals["mod"];
|
|
py::object modelClass = mod.attr("ANSModel");
|
|
|
|
auto& threadState = GetThreadLocalState();
|
|
auto& wrapper = threadState[_instanceId]; // ensure per-thread-per-instance
|
|
if (!wrapper) wrapper = std::make_shared<ModelWrapper>();
|
|
|
|
wrapper->modelFactory = [modelClass]() {
|
|
py::gil_scoped_acquire gil;
|
|
py::object instance = modelClass();
|
|
return instance;};
|
|
_wrapper = wrapper; // Store for main thread access too
|
|
return true;
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("InitializePythonModel", std::string("Python error: ") + e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ANSCUSTOMPY::Initialize(std::string licenseKey, ModelConfig modelConfig,
|
|
const std::string& modelZipFilePath, const std::string& modelZipPassword,
|
|
std::string& labelMap) {
|
|
//std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
try {
|
|
// Step 1: Base model init
|
|
bool result = ANSODBase::Initialize(licenseKey, modelConfig, modelZipFilePath, modelZipPassword, labelMap);
|
|
if (!result) return false;
|
|
|
|
_modelConfig = modelConfig;
|
|
_modelConfig.inpHeight = 640;
|
|
_modelConfig.inpWidth = 640;
|
|
if (_modelConfig.modelMNSThreshold < 0.2) _modelConfig.modelMNSThreshold = 0.5;
|
|
if (_modelConfig.modelConfThreshold < 0.2) _modelConfig.modelConfThreshold = 0.5;
|
|
|
|
// Step 2: Validate model folder
|
|
if (_modelFolder.empty()) {
|
|
_logger.LogError("ANSCUSTOMPY::Initialize", "_modelFolder is not set.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
// Step 3: Set Python Home
|
|
std::wstring pythonHome;
|
|
const fs::path sharedPython = "C:/ProgramData/ANSCENTER/Python311";
|
|
const fs::path sharedDll = sharedPython / "python311.dll";
|
|
|
|
if (fs::exists(sharedDll)) {
|
|
pythonHome = sharedPython.wstring();
|
|
}
|
|
else {
|
|
pythonHome = (fs::path(_modelFolder) / "python_env").wstring();
|
|
}
|
|
|
|
// Step 4: Initialize Python and sys.path
|
|
auto& runtime = GetPythonRuntime(pythonHome);
|
|
runtime.AddToSysPath(_modelFolder);
|
|
|
|
// Step 5: Load Python model class from unique module
|
|
//_scriptPath = _modelFolder + "/ansinfer.py";
|
|
_scriptPath = fs::path(_modelFolder).append("ansinfer.py").string();
|
|
_moduleName = "ansinfer_" + std::to_string(_instanceId);
|
|
|
|
if (!InitializePythonModel(_moduleName, _scriptPath)) {
|
|
_logger.LogError("ANSCUSTOMPY::Initialize", "Failed to load Python model.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
// Step 6: Load class names
|
|
_classFilePath = CreateFilePath(_modelFolder, "classes.names");
|
|
std::ifstream isValidFileName(_classFilePath);
|
|
if (!isValidFileName) {
|
|
_logger.LogDebug("ANSCUSTOMPY::LoadModel. Load classes from string", _classFilePath, __FILE__, __LINE__);
|
|
LoadClassesFromString();
|
|
}
|
|
else {
|
|
_logger.LogDebug("ANSCUSTOMPY::LoadModel. Load classes from file", _classFilePath, __FILE__, __LINE__);
|
|
LoadClassesFromFile();
|
|
}
|
|
|
|
labelMap.clear();
|
|
if (!_classes.empty())
|
|
labelMap = VectorToCommaSeparatedString(_classes);
|
|
|
|
_destroyed = false;
|
|
_isInitialized = true;
|
|
RegisterSelf();
|
|
return true;
|
|
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("ANSCUSTOMPY::Initialize", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ANSCUSTOMPY::LoadModel(const std::string& modelZipFilePath, const std::string& modelZipPassword) {
|
|
//std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
try {
|
|
// Step 0: Assign unique instance ID
|
|
if (!ANSODBase::LoadModel(modelZipFilePath, modelZipPassword))
|
|
return false;
|
|
|
|
if (_modelFolder.empty()) {
|
|
_logger.LogError("ANSCUSTOMPY::LoadModel", "_modelFolder is not set.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
// Step 3: Set Python home
|
|
std::wstring pythonHome;
|
|
const fs::path sharedPython = "C:/ProgramData/ANSCENTER/Python311";
|
|
const fs::path sharedDll = sharedPython / "python311.dll";
|
|
|
|
if (fs::exists(sharedDll)) {
|
|
pythonHome = sharedPython.wstring();
|
|
}
|
|
else {
|
|
pythonHome = (fs::path(_modelFolder) / "python_env").wstring();
|
|
}
|
|
|
|
// Step 4: Initialize Python and sys.path
|
|
auto& runtime = GetPythonRuntime(pythonHome);
|
|
runtime.AddToSysPath(_modelFolder);
|
|
|
|
// Step 5: Load Python model class from unique module
|
|
//_scriptPath = _modelFolder + "/ansinfer.py";
|
|
_scriptPath = fs::path(_modelFolder).append("ansinfer.py").string();
|
|
_moduleName = "ansinfer_" + std::to_string(_instanceId);
|
|
|
|
if (!InitializePythonModel(_moduleName, _scriptPath)) {
|
|
_logger.LogError("ANSCUSTOMPY::LoadModel", "Failed to load Python model.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
_destroyed = false;
|
|
_isInitialized = true;
|
|
RegisterSelf();
|
|
return true;
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("ANSCUSTOMPY::LoadModel", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ANSCUSTOMPY::LoadModelFromFolder(std::string licenseKey, ModelConfig modelConfig,
|
|
std::string modelName, std::string className,
|
|
const std::string& modelFolder,
|
|
std::string& labelMap)
|
|
{
|
|
//std::lock_guard<std::recursive_mutex> lock(_mutex);
|
|
try {
|
|
// Step 0: Assign unique instance ID early
|
|
if (!ANSODBase::LoadModelFromFolder(licenseKey, modelConfig, modelName, className, modelFolder, labelMap))
|
|
return false;
|
|
_modelConfig = modelConfig;
|
|
_modelConfig.inpHeight = 640;
|
|
_modelConfig.inpWidth = 640;
|
|
if (_modelConfig.modelMNSThreshold < 0.2) _modelConfig.modelMNSThreshold = 0.5;
|
|
if (_modelConfig.modelConfThreshold < 0.2) _modelConfig.modelConfThreshold = 0.5;
|
|
|
|
// Step 2: Validate model folder
|
|
if (_modelFolder.empty()) {
|
|
_logger.LogError("ANSCUSTOMPY::Initialize", "_modelFolder is not set.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
// Step 3: Set Python home
|
|
std::wstring pythonHome;
|
|
const fs::path sharedPython = "C:/ProgramData/ANSCENTER/Python311";
|
|
const fs::path sharedDll = sharedPython / "python311.dll";
|
|
|
|
if (fs::exists(sharedDll)) {
|
|
pythonHome = sharedPython.wstring();
|
|
}
|
|
else {
|
|
pythonHome = (fs::path(_modelFolder) / "python_env").wstring();
|
|
}
|
|
|
|
// Step 4: Initialize Python and sys.path
|
|
auto& runtime = GetPythonRuntime(pythonHome);
|
|
runtime.AddToSysPath(_modelFolder);
|
|
|
|
// Step 5: Load Python model class from unique module
|
|
//_scriptPath = _modelFolder + "/ansinfer.py";
|
|
_scriptPath = fs::path(_modelFolder).append("ansinfer.py").string();
|
|
_moduleName = "ansinfer_" + std::to_string(_instanceId);
|
|
|
|
if (!InitializePythonModel(_moduleName, _scriptPath)) {
|
|
_logger.LogError("ANSCUSTOMPY::Initialize", "Failed to load Python model.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
// Step 6: Load class labels
|
|
_classFilePath = CreateFilePath(_modelFolder, "classes.names");
|
|
std::ifstream isValidFileName(_classFilePath);
|
|
if (!isValidFileName) {
|
|
_logger.LogDebug("ANSCUSTOMPY::LoadModel", "Load classes from string", __FILE__, __LINE__);
|
|
LoadClassesFromString();
|
|
}
|
|
else {
|
|
_logger.LogDebug("ANSCUSTOMPY::LoadModel", "Load classes from file", __FILE__, __LINE__);
|
|
LoadClassesFromFile();
|
|
}
|
|
|
|
labelMap.clear();
|
|
if (!_classes.empty())
|
|
labelMap = VectorToCommaSeparatedString(_classes);
|
|
_destroyed = false;
|
|
_isInitialized = true;
|
|
RegisterSelf();
|
|
return true;
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("ANSCUSTOMPY::LoadModelFromFolder", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<Object> ANSCUSTOMPY::RunInference(const cv::Mat& input) {
|
|
return RunInference(input, "CustomPyCam");
|
|
}
|
|
std::vector<Object> ANSCUSTOMPY::RunInference(const cv::Mat& input, const std::string& camera_id) {
|
|
std::vector<Object> results;
|
|
if (!_isInitialized) {
|
|
_logger.LogError("ANSCUSTOMPY::RunInference", "Model is not initialized.", __FILE__, __LINE__);
|
|
return results;
|
|
}
|
|
if (!_licenseValid) {
|
|
_logger.LogError("ANSCUSTOMPY::RunInference", "Invalid License", __FILE__, __LINE__);
|
|
return results;
|
|
}
|
|
if (input.empty()) {
|
|
_logger.LogError("ANSCUSTOMPY::RunInference", "Input image is empty.", __FILE__, __LINE__);
|
|
return results;
|
|
}
|
|
try {
|
|
std::string jsonStr = RunPythonInferenceFromMat(input);
|
|
if (!jsonStr.empty()) {
|
|
results = ANSUtilityHelper::GetDetectionsFromString(jsonStr);
|
|
}
|
|
else {
|
|
_logger.LogWarn("ANSCUSTOMPY::RunInference", "Empty JSON result from Python inference.", __FILE__, __LINE__);
|
|
}
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("ANSCUSTOMPY::RunInference", e.what(), __FILE__, __LINE__);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
std::string ANSCUSTOMPY::RunPythonInferenceFromMat(const cv::Mat& image) {
|
|
if (!EnsureModelReady("RunPythonInferenceFromMat"))
|
|
return "";
|
|
if (image.empty())
|
|
return "";
|
|
|
|
try {
|
|
py::object model = GetThreadLocalModel();
|
|
if (model.is_none()) {
|
|
_logger.LogError("ANSCUSTOMPY::RunPythonInferenceFromMat", "Model is None.", __FILE__, __LINE__);
|
|
return "";
|
|
}
|
|
py::gil_scoped_acquire gil;
|
|
if (!py::hasattr(model, "run_inference")) {
|
|
_logger.LogError("ANSCUSTOMPY::RunPythonInferenceFromMat", "Model has no 'run_inference' method.", __FILE__, __LINE__);
|
|
return "";
|
|
}
|
|
py::bytes image_bytes(reinterpret_cast<const char*>(image.data), image.total() * image.elemSize());
|
|
py::object result = model.attr("run_inference")(image_bytes, image.cols, image.rows, image.channels());
|
|
return result.cast<std::string>();
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("RunPythonInferenceFromMat", e.what(), __FILE__, __LINE__);
|
|
return "";
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("RunPythonInferenceFromMat", e.what(), __FILE__, __LINE__);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
bool ANSCUSTOMPY::OptimizeModel(bool fp16, std::string& optimizedModelFolder) {
|
|
if (!EnsureModelReady("OptimizeModel")) return false;
|
|
try {
|
|
py::object model = GetThreadLocalModel();
|
|
if (model.is_none()) {
|
|
_logger.LogError("ANSCUSTOMPY::OptimizeModel", "Model is None.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::gil_scoped_acquire gil;
|
|
if (!py::hasattr(model, "model_optimize")) {
|
|
_logger.LogError("ANSCUSTOMPY::OptimizeModel", "Python model has no method 'model_optimize'.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::object result;
|
|
try {
|
|
result = model.attr("model_optimize")(fp16);
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("ANSCUSTOMPY::OptimizeModel", std::string("Exception in 'model_optimize': ") + e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
if (result.is_none()) {
|
|
_logger.LogError("ANSCUSTOMPY::OptimizeModel", "Python 'model_optimize' returned None.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
if (!py::isinstance<py::bool_>(result)) {
|
|
_logger.LogError("ANSCUSTOMPY::OptimizeModel", "Unexpected return type from 'model_optimize'. Expected bool.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
bool success = result.cast<bool>();
|
|
if (success) {
|
|
optimizedModelFolder = fs::path(_scriptPath).parent_path().string();
|
|
}
|
|
|
|
return success;
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("ANSCUSTOMPY::OptimizeModel", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("ANSCUSTOMPY::OptimizeModel", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
bool ANSCUSTOMPY::ConfigureParameters(Params& param) {
|
|
if (!EnsureModelReady("ConfigureParamaters")) return false;
|
|
|
|
try {
|
|
py::object model = GetThreadLocalModel();
|
|
if (model.is_none()) {
|
|
_logger.LogError("ANSCUSTOMPY::ConfigureParamaters", "Model is None.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::gil_scoped_acquire gil;
|
|
if (!py::hasattr(model, "configureParameters")) {
|
|
_logger.LogError("ANSCUSTOMPY::ConfigureParamaters", "Python model missing 'configureParameters' method.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::object result;
|
|
try {
|
|
result = model.attr("configureParameters")();
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("ANSCUSTOMPY::ConfigureParamaters", std::string("Exception in 'configureParameters': ") + e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
if (result.is_none()) {
|
|
_logger.LogError("ANSCUSTOMPY::ConfigureParamaters", "Returned None from Python method.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
if (!py::isinstance<py::str>(result)) {
|
|
_logger.LogError("ANSCUSTOMPY::ConfigureParamaters", "Expected Python string return from 'configureParameters'.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
std::string jsonStr = result.cast<std::string>();
|
|
if (jsonStr.empty()) {
|
|
_logger.LogError("ANSCUSTOMPY::ConfigureParamaters", "Empty JSON string returned from Python.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
param = ANSUtilityHelper::ParseCustomParameters(jsonStr);
|
|
return true;
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("ANSCUSTOMPY::ConfigureParamaters", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("ANSCUSTOMPY::ConfigureParamaters", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ANSCUSTOMPY::SetParameters(const Params& param) {
|
|
if (!EnsureModelReady("SetParamaters")) return false;
|
|
try {
|
|
std::string jsonStr = CustomParametersToJson(param);
|
|
if (jsonStr.empty()) {
|
|
_logger.LogError("ANSCUSTOMPY::SetParamaters", "Serialized parameter string is empty.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::object model = GetThreadLocalModel();
|
|
if (model.is_none()) {
|
|
_logger.LogError("ANSCUSTOMPY::SetParamaters", "Model is None.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::gil_scoped_acquire gil;
|
|
if (!py::hasattr(model, "setParameters")) {
|
|
_logger.LogError("ANSCUSTOMPY::SetParamaters", "Python model has no method 'setParameters'.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
py::object result;
|
|
try {
|
|
result = model.attr("setParameters")(jsonStr);
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("ANSCUSTOMPY::SetParamaters", std::string("Exception in 'setParameters': ") + e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
if (result.is_none()) {
|
|
_logger.LogError("ANSCUSTOMPY::SetParamaters", "Python method returned None.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
if (!py::isinstance<py::bool_>(result)) {
|
|
_logger.LogError("ANSCUSTOMPY::SetParamaters", "Python method returned non-boolean type.", __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
|
|
return result.cast<bool>();
|
|
}
|
|
catch (const py::error_already_set& e) {
|
|
PyErr_Print();
|
|
_logger.LogFatal("ANSCUSTOMPY::SetParamaters", std::string("Python error: ") + e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
catch (const std::exception& e) {
|
|
_logger.LogFatal("ANSCUSTOMPY::SetParamaters", e.what(), __FILE__, __LINE__);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|