Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0b51da2
Add initial version of python bindings
sovrasov Dec 27, 2024
01b5770
Update python deps
sovrasov Jan 2, 2025
12c4824
Update linkage flags for GCC
sovrasov Jan 3, 2025
8bb9a5d
Classification bindings without config
sovrasov Jan 3, 2025
ae9dc94
Wrap batch inference
sovrasov Jan 3, 2025
b215fec
Wrap load() to enable setting correct number of infer requests
sovrasov Jan 3, 2025
67fd082
Add a simple any map converter
sovrasov Jan 3, 2025
a39ef11
Add license
sovrasov Jan 3, 2025
2c515cc
Wrap tensor results
sovrasov Jan 8, 2025
f9019e0
Update bindings directory structure
sovrasov Jan 10, 2025
ad44de2
Add cmake option to anable/disable bindings
sovrasov Jan 10, 2025
0016a07
Fix linter
sovrasov Jan 10, 2025
d5b33c6
Update copyrights
sovrasov Jan 10, 2025
0425d59
Update CI scripts
sovrasov Jan 10, 2025
bcaf3d7
Fix more issues in CI scripts
sovrasov Jan 10, 2025
24b865f
Add missing requirements
sovrasov Jan 10, 2025
9ae15b6
Dismiss pre-defined gha-specific ython paath
sovrasov Jan 10, 2025
aa8ada6
Force cmake to use python from venv
sovrasov Jan 10, 2025
35001f8
Workaround missing nanobind with global package installation
sovrasov Jan 10, 2025
a4472ac
Try python 3.10
sovrasov Jan 10, 2025
c7aab3f
Try to reduce the amount of warning in tests
sovrasov Jan 13, 2025
3a04f39
Limit ubuntu version in cpp build
sovrasov Jan 13, 2025
b617c3a
Ignore warnings on cpp pre-commit
sovrasov Jan 13, 2025
e108ee4
Update installation script
sovrasov Jan 13, 2025
e7f31e5
Update cpp accuracy build settings
sovrasov Jan 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions model_api/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

cmake_minimum_required(VERSION 3.26)

option(ENABLE_PY_BINDINGS "Enables building python bindings package" ON)

# Multi config generators such as Visual Studio ignore CMAKE_BUILD_TYPE. Multi config generators are configured with
# CMAKE_CONFIGURATION_TYPES, but limiting options in it completely removes such build options
get_property(GENERATOR_IS_MULTI_CONFIG_VAR GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
Expand Down Expand Up @@ -81,6 +83,11 @@ if(MSVC)
/EHsc) # Enable standard C++ stack unwinding, assume functions with extern "C" never throw
elseif(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|(Apple)?Clang$")
target_compile_options(model_api PRIVATE -Wall -Wextra -Wpedantic)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
endif()

if (ENABLE_PY_BINDINGS)
add_subdirectory(py_bindings)
endif()

include(GenerateExportHeader)
Expand Down
2 changes: 2 additions & 0 deletions model_api/cpp/cmake/model_apiConfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ find_dependency(OpenCV COMPONENTS core imgproc)
find_dependency(OpenVINO COMPONENTS Runtime)

include("${CMAKE_CURRENT_LIST_DIR}/model_apiTargets.cmake")

check_required_components()
28 changes: 28 additions & 0 deletions model_api/cpp/py_bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (C) 2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
#

set(DEV_MODULE Development.Module)

find_package(Python COMPONENTS Interpreter ${DEV_MODULE} REQUIRED)

execute_process(
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
find_package(nanobind CONFIG REQUIRED)


file(GLOB BINDINGS_SOURCES ./*.cpp)
file(GLOB BINDINGS_HEADERS ./*.hpp)

nanobind_add_module(py_model_api NB_STATIC STABLE_ABI LTO ${BINDINGS_SOURCES} ${BINDINGS_HEADERS})

target_link_libraries(py_model_api PRIVATE model_api)

nanobind_add_stub(
py_model_api_stub
MODULE py_model_api
OUTPUT py_model_api.pyi
PYTHON_PATH $<TARGET_FILE_DIR:py_model_api>
DEPENDS py_model_api
)
26 changes: 26 additions & 0 deletions model_api/cpp/py_bindings/py_base.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>

#include <openvino/openvino.hpp>

#include "models/image_model.h"
#include "models/results.h"

namespace nb = nanobind;

void init_base_modules(nb::module_& m) {
nb::class_<ResultBase>(m, "ResultBase").def(nb::init<>());

nb::class_<ModelBase>(m, "ModelBase")
.def("load", [](ModelBase& self, const std::string& device, size_t num_infer_requests) {
auto core = ov::Core();
self.load(core, device, num_infer_requests);
});

nb::class_<ImageModel, ModelBase>(m, "ImageModel");
}
88 changes: 88 additions & 0 deletions model_api/cpp/py_bindings/py_classificaiton.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

#include <nanobind/ndarray.h>
#include <nanobind/operators.h>
#include <nanobind/stl/map.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/unique_ptr.h>
#include <nanobind/stl/vector.h>

#include "models/classification_model.h"
#include "models/results.h"
#include "py_utils.hpp"

namespace pyutils = vision::nanobind::utils;

void init_classification(nb::module_& m) {
nb::class_<ClassificationResult::Classification>(m, "Classification")
.def(nb::init<unsigned int, const std::string, float>())
.def_rw("id", &ClassificationResult::Classification::id)
.def_rw("label", &ClassificationResult::Classification::label)
.def_rw("score", &ClassificationResult::Classification::score);

nb::class_<ClassificationResult, ResultBase>(m, "ClassificationResult")
.def(nb::init<>())
.def_ro("topLabels", &ClassificationResult::topLabels)
.def("__repr__", &ClassificationResult::operator std::string)
.def_prop_ro(
"feature_vector",
[](ClassificationResult& r) {
if (!r.feature_vector) {
return nb::ndarray<float, nb::numpy, nb::c_contig>();
}

return nb::ndarray<float, nb::numpy, nb::c_contig>(r.feature_vector.data(),
r.feature_vector.get_shape().size(),
r.feature_vector.get_shape().data());
},
nb::rv_policy::reference_internal)
.def_prop_ro(
"saliency_map",
[](ClassificationResult& r) {
if (!r.saliency_map) {
return nb::ndarray<float, nb::numpy, nb::c_contig>();
}

return nb::ndarray<float, nb::numpy, nb::c_contig>(r.saliency_map.data(),
r.saliency_map.get_shape().size(),
r.saliency_map.get_shape().data());
},
nb::rv_policy::reference_internal);

nb::class_<ClassificationModel, ImageModel>(m, "ClassificationModel")
.def_static(
"create_model",
[](const std::string& model_path,
const std::map<std::string, nb::object>& configuration,
bool preload,
const std::string& device) {
auto ov_any_config = ov::AnyMap();
for (const auto& item : configuration) {
ov_any_config[item.first] = pyutils::py_object_to_any(item.second, item.first);
}

return ClassificationModel::create_model(model_path, ov_any_config, preload, device);
},
nb::arg("model_path"),
nb::arg("configuration") = ov::AnyMap({}),
nb::arg("preload") = true,
nb::arg("device") = "AUTO")

.def("__call__",
[](ClassificationModel& self, const nb::ndarray<>& input) {
return self.infer(pyutils::wrap_np_mat(input));
})
.def("infer_batch", [](ClassificationModel& self, const std::vector<nb::ndarray<>> inputs) {
std::vector<ImageInputData> input_mats;
input_mats.reserve(inputs.size());

for (const auto& input : inputs) {
input_mats.push_back(pyutils::wrap_np_mat(input));
}

return self.inferBatch(input_mats);
});
}
33 changes: 33 additions & 0 deletions model_api/cpp/py_bindings/py_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

#include "py_utils.hpp"

namespace vision::nanobind::utils {

cv::Mat wrap_np_mat(const nb::ndarray<>& input) {
if (input.ndim() != 3 || input.shape(2) != 3 || input.dtype() != nb::dtype<uint8_t>()) {
throw std::runtime_error("Input image should have HWC_8U layout");
}

int height = input.shape(0);
int width = input.shape(1);

return cv::Mat(height, width, CV_8UC3, input.data());
}

ov::Any py_object_to_any(const nb::object& py_obj, const std::string& property_name) {
if (nb::isinstance<nb::str>(py_obj)) {
return ov::Any(std::string(static_cast<nb::str>(py_obj).c_str()));
} else if (nb::isinstance<nb::float_>(py_obj)) {
return ov::Any(static_cast<double>(static_cast<nb::float_>(py_obj)));
} else if (nb::isinstance<nb::int_>(py_obj)) {
return ov::Any(static_cast<int>(static_cast<nb::int_>(py_obj)));
} else {
OPENVINO_THROW("Property \"" + property_name + "\" has unsupported type.");
}
}

} // namespace vision::nanobind::utils
17 changes: 17 additions & 0 deletions model_api/cpp/py_bindings/py_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once
#include <nanobind/ndarray.h>

#include <opencv2/core/core.hpp>
#include <openvino/openvino.hpp>

namespace nb = nanobind;

namespace vision::nanobind::utils {
cv::Mat wrap_np_mat(const nb::ndarray<>& input);
ov::Any py_object_to_any(const nb::object& py_obj, const std::string& property_name);
} // namespace vision::nanobind::utils
17 changes: 17 additions & 0 deletions model_api/cpp/py_bindings/py_vision_api.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

#include <nanobind/nanobind.h>

namespace nb = nanobind;

void init_classification(nb::module_& m);
void init_base_modules(nb::module_& m);

NB_MODULE(py_model_api, m) {
m.doc() = "Nanobind binding for OpenVINO Vision API library";
init_base_modules(m);
init_classification(m);
}
5 changes: 4 additions & 1 deletion model_api/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ docs = [
"breathe",
"graphviz",
]
full = ["openvino_model_api[dependencies, ovms, tests, docs]"]
build = [
"nanobind==2.4.0",
]
full = ["openvino_model_api[dependencies, ovms, tests, docs, build]"]

[project.urls]
Homepage = "https://github.com/openvinotoolkit/model_api"
Expand Down
Loading