Skip to content

Commit

Permalink
Add initial version of python bindings (#252)
Browse files Browse the repository at this point in the history
* Add initial version of python bindings

* Update python deps

* Update linkage flags for GCC

* Classification bindings without config

* Wrap batch inference

* Wrap load() to enable setting correct number of infer requests

* Add a simple any map converter

* Add license

* Wrap tensor results

* Update bindings directory structure

* Add cmake option to anable/disable bindings

* Fix linter

* Update copyrights

* Update CI scripts

* Fix more issues in CI scripts

* Add missing requirements

* Dismiss pre-defined gha-specific ython paath

* Force cmake to use python from venv

* Workaround missing nanobind with global package installation

* Try python 3.10

* Try to reduce the amount of warning in tests

* Limit ubuntu version in cpp build

* Ignore warnings on cpp pre-commit

* Update installation script

* Update cpp accuracy build settings
  • Loading branch information
sovrasov authored Jan 15, 2025
1 parent ee40e7a commit e97b12c
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 14 deletions.
12 changes: 7 additions & 5 deletions .github/workflows/test_accuracy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ concurrency:
cancel-in-progress: true
jobs:
test_accuracy:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: "3.10"
cache: pip
- name: Create and start a virtual environment
run: |
Expand All @@ -25,7 +25,7 @@ jobs:
run: |
source venv/bin/activate
python -m pip install --upgrade pip
pip install model_api/python/[tests] --extra-index-url https://download.pytorch.org/whl/cpu
pip install model_api/python/[tests,build] --extra-index-url https://download.pytorch.org/whl/cpu
- name: Prepare test data
run: |
source venv/bin/activate
Expand All @@ -35,13 +35,15 @@ jobs:
source venv/bin/activate
pytest --data=./data tests/python/accuracy/test_accuracy.py
DATA=data pytest --data=./data tests/python/accuracy/test_YOLOv8.py
- name: Install CPP ependencies
- name: Install CPP dependencies
run: |
sudo bash model_api/cpp/install_dependencies.sh
- name: Build CPP Test
run: |
pip install nanobind==2.4.0
pip install typing_extensions==4.12.2
mkdir build && cd build
cmake ../tests/cpp/accuracy/ -DCMAKE_CXX_FLAGS=-Werror
cmake ../tests/cpp/accuracy/
make -j
- name: Run CPP Test
run: |
Expand Down
14 changes: 8 additions & 6 deletions .github/workflows/test_precommit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ jobs:
# missingInclude: cppcheck can't find stl, openvino, opencv
other_options: --suppress=missingInclude -Imodel_api/cpp/models/include -Imodel_api/cpp/utils/include -Imodel_api/cpp/pipelines/include --check-config
CPP-Precommit:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: "3.10"
cache: pip
- name: Create and start a virtual environment
run: |
Expand All @@ -63,7 +63,7 @@ jobs:
run: |
source venv/bin/activate
python -m pip install --upgrade pip
pip install model_api/python/[tests] --extra-index-url https://download.pytorch.org/whl/cpu
pip install model_api/python/[tests,build] --extra-index-url https://download.pytorch.org/whl/cpu
sudo bash model_api/cpp/install_dependencies.sh
- name: Prepare test data
Expand All @@ -73,7 +73,9 @@ jobs:
- name: Build
run: |
mkdir build && cd build
cmake ../tests/cpp/precommit/ -DCMAKE_CXX_FLAGS=-Werror
pip install nanobind==2.4.0
pip install typing_extensions==4.12.2
cmake ../tests/cpp/precommit/
cmake --build . -j $((`nproc`*2+2))
- name: Run test
run: |
Expand All @@ -96,7 +98,7 @@ jobs:
run: |
source venv/Scripts/activate
python -m pip install --upgrade pip
pip install model_api/python/[tests] --extra-index-url https://download.pytorch.org/whl/cpu
pip install model_api/python/[tests,build] --extra-index-url https://download.pytorch.org/whl/cpu
curl https://storage.openvinotoolkit.org/repositories/openvino/packages/2024.6/windows/w_openvino_toolkit_windows_2024.6.0.17404.4c0f47d2335_x86_64.zip --output w_openvino_toolkit_windows.zip
unzip w_openvino_toolkit_windows.zip
rm w_openvino_toolkit_windows.zip
Expand All @@ -112,7 +114,7 @@ jobs:
shell: bash
run: |
mkdir build && cd build
MSYS_NO_PATHCONV=1 cmake ../examples/cpp/ -DOpenVINO_DIR=$GITHUB_WORKSPACE/w_openvino_toolkit_windows_2024.6.0.17404.4c0f47d2335_x86_64/runtime/cmake -DOpenCV_DIR=$GITHUB_WORKSPACE/opencv/opencv/build -DCMAKE_CXX_FLAGS=/WX
MSYS_NO_PATHCONV=1 cmake ../examples/cpp/ -DOpenVINO_DIR=$GITHUB_WORKSPACE/w_openvino_toolkit_windows_2024.6.0.17404.4c0f47d2335_x86_64/runtime/cmake -DOpenCV_DIR=$GITHUB_WORKSPACE/opencv/opencv/build -DCMAKE_CXX_FLAGS=/WX -DENABLE_PY_BINDINGS=OFF
cmake --build . --config Release -j $((`nproc`*2+2))
- name: Run sync sample
shell: cmd
Expand Down
9 changes: 8 additions & 1 deletion model_api/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Copyright (C) 2018-2024 Intel Corporation
# Copyright (C) 2018-2025 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
#

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) 2025 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
2 changes: 1 addition & 1 deletion tests/cpp/accuracy/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ if(MSVC)
endif()

if(CMAKE_CXX_COMPILER_ID MATCHES "^GNU|(Apple)?Clang$")
add_compile_options(-Wall -Wextra -Wpedantic)
add_compile_options(-Wall -Wextra)
endif()

if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64.*|aarch64.*|AARCH64.*)")
Expand Down

0 comments on commit e97b12c

Please sign in to comment.