Update for v1.0

This commit is contained in:
James Deng 2024-05-30 23:19:44 +08:00
parent 46cf2741d2
commit adf80d91df
76 changed files with 1487 additions and 716 deletions

48
python/README.md Normal file
View file

@ -0,0 +1,48 @@
## prepare env
```shell
# for ubuntu
sudo apt install python3-dev python3-pip cmake gcc g++ onnxruntime
# for centos
sudo yum install python3-devel python3-pip cmake gcc gcc-c++ onnxruntime
python3 -m pip install wheel setuptools
```
## quick build
* cmake project
```shell
git submodule update --init --recursive
mkdir build && cd build
# Note: static opencv libraries is required
cmake .. -DORT_HOME=${PATH_TO_ONNXRUNTIME} -DOpenCV_DIR=${PATH_TO_OPENCV_CMAKE_DIR} -DPYTHON=ON
make -j`nproc` bianbuai_pybind11_state VERBOSE=1
# Or
cmake --build . --config Release --verbose -j`nproc` --target bianbuai_pybind11_state
cmake --install . --config Release --verbose --component pybind11 # --strip
```
* python package
```shell
export ORT_HOME=${PATH_TO_ONNXRUNTIME}
export OPENCV_DIR=${PATH_TO_OPENCV_CMAKE_DIR}
python python/setup.py sdist bdist_wheel
```
## smoke unittest
```shell
# prepare env, e.g. with ubuntu22.04
python3 -m pip install opencv-python
# or just
sudo apt install python3-opencv
# run unittest under build diretctory
ln -sf ../rootfs/usr/share/ai-support data
cp ../tests/python/test_python_task.py .
python3 test_python_task.py
```

View file

@ -0,0 +1 @@
from bianbuai.capi._pybind_state import * # noqa

View file

@ -0,0 +1,3 @@
# -------------------------------------------------------------------------
# Copyright (c) SpacemiT Corporation. All rights reserved.
# --------------------------------------------------------------------------

View file

@ -0,0 +1,6 @@
# -------------------------------------------------------------------------
# Copyright (c) SpacemiT Corporation. All rights reserved.
# --------------------------------------------------------------------------
# This file can be modified by setup.py when building a manylinux2010 wheel
# When modified, it will preload some libraries needed for the python C extension

View file

@ -0,0 +1,10 @@
# -------------------------------------------------------------------------
# Copyright (c) SpacemiT Corporation. All rights reserved.
# --------------------------------------------------------------------------
"""
Ensure that dependencies are available and then load the extension module.
"""
from . import _ld_preload # noqa: F401
from .bianbuai_pybind11_state import * # noqa

View file

@ -0,0 +1,19 @@
if (NOT DEFINED OpenCV_SHARED OR OpenCV_SHARED STREQUAL "ON")
message(WARNING "Python binding suggests to link static OpenCV libraries")
endif()
file(GLOB_RECURSE BIANBU_PYBIND_SRCS "${CMAKE_SOURCE_DIR}/python/*.cc")
list(APPEND BIANBU_PYBIND_SRCS ${BIANBU_SRC_FILES})
pybind11_add_module(bianbuai_pybind11_state ${BIANBU_PYBIND_SRCS})
target_include_directories(bianbuai_pybind11_state PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR})
target_include_directories(bianbuai_pybind11_state SYSTEM PRIVATE ${OPENCV_INC})
target_include_directories(bianbuai_pybind11_state SYSTEM PRIVATE ${ORT_HOME}/include ${ORT_HOME}/include/onnxruntime)
target_link_libraries(bianbuai_pybind11_state PRIVATE ${HIDE_SYMBOLS_LINKER_FLAGS})
target_link_libraries(bianbuai_pybind11_state PRIVATE ${TARGET_SHARED_LINKER_FLAGS})
target_link_libraries(bianbuai_pybind11_state PRIVATE ${SPACEMITEP_LIB} onnxruntime ${OPENCV_LIBS})
install(TARGETS bianbuai_pybind11_state
RUNTIME COMPONENT pybind11 DESTINATION bin
LIBRARY COMPONENT pybind11 DESTINATION lib
ARCHIVE COMPONENT pybind11 DESTINATION lib)

208
python/setup.py Normal file
View file

@ -0,0 +1,208 @@
import os, sys
import subprocess
from collections import namedtuple
from pathlib import Path
import shutil
from setuptools import Extension, setup, find_packages
from setuptools.command.build_ext import build_ext
#from setuptools.command.install import install as InstallCommandBase
#from setuptools.command.install_lib import install_lib as InstallLibCommandBase
package_name = "bianbuai"
SCRIPT_DIR = os.path.dirname(__file__)
TOP_DIR = os.path.realpath(os.path.join(SCRIPT_DIR, ".."))
# Convert distutils Windows platform specifiers to CMake -A arguments
PLAT_TO_CMAKE = {
"win32": "Win32",
"win-amd64": "x64",
"win-arm32": "ARM",
"win-arm64": "ARM64",
}
class CMakeExtension(Extension):
def __init__(self, name: str, sourcedir: str = "") -> None:
super().__init__(name, sources=[])
self.sourcedir = TOP_DIR # os.fspath(Path(sourcedir).resolve())
class CMakeBuild(build_ext):
def build_extension(self, ext: CMakeExtension) -> None:
# Must be in this form due to bug in .resolve() only fixed in Python 3.10+
ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name)
extdir = ext_fullpath.parent.resolve() / package_name / "capi"
# Using this requires trailing slash for auto-detection & inclusion of
# auxiliary "native" libs
debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug
cfg = "Debug" if debug else "Release"
# CMake lets you override the generator - we need to check this.
# Can be set with Conda-Build, for example.
cmake_generator = os.environ.get("CMAKE_GENERATOR", "")
# Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON
# EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code
# from Python.
cmake_args = [
#f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}",
f"-DPYTHON_EXECUTABLE={sys.executable}",
f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm
]
build_args = ["--target", "bianbuai_pybind11_state"]
install_args = ["--component", "pybind11", "--prefix", "."]
# Adding CMake arguments set as environment variable
# (needed e.g. to build for ARM OSx on conda-forge)
if "CMAKE_ARGS" in os.environ:
cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item]
# Pass other necessary args for pybind11 cmake project
onnxruntime_install_dir = os.environ.get("ORT_HOME", "/usr")
opencv4_cmake_dir = os.environ.get("OPENCV_DIR", "")
cmake_args += [
f"-DOpenCV_DIR={opencv4_cmake_dir}",
f"-DORT_HOME={onnxruntime_install_dir}",
f"-DPYTHON=ON",
]
if self.compiler.compiler_type != "msvc":
# Using Ninja-build since it a) is available as a wheel and b)
# multithreads automatically. MSVC would require all variables be
# exported for Ninja to pick it up, which is a little tricky to do.
# Users can override the generator with CMAKE_GENERATOR in CMake
# 3.15+.
if not cmake_generator or cmake_generator == "Ninja":
try:
import ninja
ninja_executable_path = Path(ninja.BIN_DIR) / "ninja"
cmake_args += [
"-GNinja",
f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}",
]
except ImportError:
pass
else:
# Single config generators are handled "normally"
single_config = any(x in cmake_generator for x in {"NMake", "Ninja"})
# CMake allows an arch-in-generator style for backward compatibility
contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"})
# Specify the arch if using MSVC generator, but only if it doesn't
# contain a backward-compatibility arch spec already in the
# generator name.
if not single_config and not contains_arch:
cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]]
# Multi-config generators have a different way to specify configs
if not single_config:
cmake_args += [
f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}"
]
build_args += ["--config", cfg]
# Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level
# across all generators.
if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ:
# self.parallel is a Python 3 only way to set parallel jobs by hand
# using -j in the build_ext call, not supported by pip or PyPA-build.
if hasattr(self, "parallel") and self.parallel:
# CMake 3.12+ only.
build_args += [f"-j{self.parallel}"]
build_temp = Path(self.build_temp) / ext.name
if not build_temp.exists():
build_temp.mkdir(parents=True)
subprocess.run(
["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True
)
#print("cmake args:", cmake_args)
subprocess.run(
["cmake", "--build", ".", *build_args], cwd=build_temp, check=True
)
#print("build args:", build_args)
subprocess.run(
["cmake", "--install", ".", *install_args], cwd=build_temp, check=True
)
#print("install args:", install_args)
# Copy installed libraries files to Python modules directory
shutil.copytree(os.path.join(build_temp, "lib"), f"{extdir}", dirs_exist_ok=True,)
# Get Git Version
try:
git_version = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=TOP_DIR).decode('ascii').strip()
except (OSError, subprocess.CalledProcessError):
git_version = None
print("GIT VERSION:", git_version)
# Get Release Version Number
with open(os.path.join(TOP_DIR, 'VERSION_NUMBER')) as version_file:
VersionInfo = namedtuple('VersionInfo', ['version', 'git_version'])(
version=version_file.read().strip(),
git_version=git_version
)
# Description
README = os.path.join(TOP_DIR, "README.md")
with open(README, encoding="utf-8") as fdesc:
long_description = fdesc.read()
classifiers = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'Intended Audience :: Education',
'Intended Audience :: Science/Research',
'Topic :: Scientific/Engineering',
'Topic :: Scientific/Engineering :: Mathematics',
'Topic :: Scientific/Engineering :: Artificial Intelligence',
'Topic :: Software Development',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Python Modules',
'Programming Language :: Python',
'Programming Language :: Python :: 3 :: Only'
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
]
if __name__ == "__main__":
# The information here can also be placed in setup.cfg - better separation of
# logic and declaration, and simpler if you include description/version in a file.
setup(
name=package_name,
version=VersionInfo.version,
description="Bianbu AI Support Python Package",
long_description=long_description,
long_description_content_type="text/markdown",
setup_requires=[],
tests_require=[],
cmdclass={"build_ext": CMakeBuild},
packages=find_packages("python", exclude=[]),
package_dir={"":"python"}, # tell distutils packages are under "python/"
ext_modules=[CMakeExtension(package_name)],
#package_data={},
include_package_data=True,
license='Apache License v2.0',
author="bianbu-ai-support",
author_email="bianbu-ai-support@spacemit.com",
url='https://gitlab.dc.com:8443/bianbu/ai/support',
install_requires=[],
entry_points={},
scripts=[],
python_requires=">=3.6",
classifiers=classifiers,
zip_safe=False,
)

36
python/utils.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef SUPPORT_PYTHON_UTILS_H_
#define SUPPORT_PYTHON_UTILS_H_
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <string>
#include "opencv2/opencv.hpp"
namespace py = pybind11;
struct Box {
int x1;
int y1;
int x2;
int y2;
float score;
std::string label_text;
unsigned int label;
Box(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0, float score = 0.0,
std::string label_text = "", unsigned int label = 0)
: x1(x1),
y1(y1),
x2(x2),
y2(y2),
score(score),
label_text(label_text),
label(label) {}
};
cv::Mat numpy_uint8_3c_to_cv_mat(const py::array_t<unsigned char> &input) {
py::buffer_info buf = input.request();
cv::Mat mat(buf.shape[0], buf.shape[1], CV_8UC3, (unsigned char *)buf.ptr);
return mat;
}
#endif // SUPPORT_PYTHON_UTILS_H_

176
python/wrapper.cc Normal file
View file

@ -0,0 +1,176 @@
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include "task/vision/image_classification_task.h"
#include "task/vision/image_classification_types.h"
#include "task/vision/object_detection_task.h"
#include "task/vision/object_detection_types.h"
#include "task/vision/pose_estimation_task.h"
#include "task/vision/pose_estimation_types.h"
#include "utils.h"
namespace py = pybind11;
struct PYImageClassificationTask {
public:
PYImageClassificationTask(const ImageClassificationOption &option) {
task_ = std::make_shared<ImageClassificationTask>(option);
}
int getInitFlag() { return task_->getInitFlag(); };
ImageClassificationResult Classify(const py::array_t<unsigned char> &input) {
cv::Mat img = numpy_uint8_3c_to_cv_mat(input);
return task_->Classify(img);
}
private:
std::shared_ptr<ImageClassificationTask> task_;
};
struct PYObjectDetectionTask {
public:
PYObjectDetectionTask(const ObjectDetectionOption &option) {
task_ = std::make_shared<ObjectDetectionTask>(option);
}
int getInitFlag() { return task_->getInitFlag(); };
std::vector<Box> Detect(const py::array_t<unsigned char> &input) {
cv::Mat img = numpy_uint8_3c_to_cv_mat(input);
ObjectDetectionResult result = task_->Detect(img);
std::vector<Box> result_boxes;
for (size_t i = 0; i < result.result_bboxes.size(); i++) {
Box box;
box.x1 = result.result_bboxes[i].x1;
box.y1 = result.result_bboxes[i].y1;
box.x2 = result.result_bboxes[i].x2;
box.y2 = result.result_bboxes[i].y2;
box.label_text = result.result_bboxes[i].label_text;
box.label = result.result_bboxes[i].label;
box.score = result.result_bboxes[i].score;
result_boxes.push_back(box);
}
return result_boxes;
}
private:
std::shared_ptr<ObjectDetectionTask> task_;
};
struct PYPoseEstimationTask {
public:
PYPoseEstimationTask(const PoseEstimationOption &option) {
task_ = std::make_shared<PoseEstimationTask>(option);
}
int getInitFlag() { return task_->getInitFlag(); };
std::vector<PosePoint> Estimate(const py::array_t<unsigned char> &input,
const Box &box) {
cv::Mat img = numpy_uint8_3c_to_cv_mat(input);
Boxi boxi;
boxi.x1 = box.x1;
boxi.y1 = box.y1;
boxi.x2 = box.x2;
boxi.y2 = box.y2;
boxi.label_text = box.label_text.c_str();
boxi.label = box.label;
boxi.score = box.score;
PoseEstimationResult result = task_->Estimate(img, boxi);
return result.result_points;
}
private:
std::shared_ptr<PoseEstimationTask> task_;
};
#define PYARG_COMMON \
py::arg("model_path") = "", py::arg("label_path") = "", \
py::arg("intra_threads_num") = 2, py::arg("inter_threads_num") = 2
#define PYARG_DETECT \
py::arg("score_threshold") = -1.f, py::arg("nms_threshold") = -1.f, \
py::arg("class_name_whitelist"), py::arg("class_name_blacklist")
#define PYARG_HUMAN_POSE \
py::arg("model_path") = "", py::arg("intra_threads_num") = 2, \
py::arg("inter_threads_num") = 2
#define PYARG_COMMENT(x) "Option for " #x
PYBIND11_MODULE(bianbuai_pybind11_state, m) {
py::class_<ImageClassificationOption>(m, "ImageClassificationOption")
.def(py::init())
.def(py::init<const std::string &, const std::string &, const int &,
const int &>(),
PYARG_COMMENT(ImageClassificationTask), PYARG_COMMON)
.def_readwrite("model_path", &ImageClassificationOption::model_path)
.def_readwrite("label_path", &ImageClassificationOption::label_path)
.def_readwrite("intra_threads_num",
&ImageClassificationOption::intra_threads_num)
.def_readwrite("inter_threads_num",
&ImageClassificationOption::inter_threads_num);
py::class_<ImageClassificationResult>(m, "ImageClassificationResult")
.def_readwrite("label_text", &ImageClassificationResult::label_text)
.def_readwrite("label_index", &ImageClassificationResult::label)
.def_readwrite("score", &ImageClassificationResult::score);
py::class_<PYImageClassificationTask>(m, "ImageClassificationTask")
.def(py::init<const ImageClassificationOption &>())
.def("getInitFlag", &PYImageClassificationTask::getInitFlag)
.def("Classify", &PYImageClassificationTask::Classify);
py::class_<ObjectDetectionOption>(m, "ObjectDetectionOption")
.def(py::init())
.def(py::init<const std::string &, const std::string &, const int &,
const int &, const int, const int, const std::vector<int> &,
const std::vector<int> &>(),
PYARG_COMMENT(ObjectDetectionTask), PYARG_COMMON, PYARG_DETECT)
.def_readwrite("model_path", &ObjectDetectionOption::model_path)
.def_readwrite("label_path", &ObjectDetectionOption::label_path)
.def_readwrite("intra_threads_num",
&ObjectDetectionOption::intra_threads_num)
.def_readwrite("inter_threads_num",
&ObjectDetectionOption::inter_threads_num)
.def_readwrite("score_threshold", &ObjectDetectionOption::score_threshold)
.def_readwrite("nms_threshold", &ObjectDetectionOption::nms_threshold)
.def_readwrite("class_name_whitelist",
&ObjectDetectionOption::class_name_whitelist)
.def_readwrite("class_name_blacklist",
&ObjectDetectionOption::class_name_blacklist);
py::class_<Box>(m, "Box")
.def(py::init<int, int, int, int, float, std::string, unsigned int>(),
"Box Info for Object Detection Task", py::arg("x1") = 0,
py::arg("y1") = 0, py::arg("x2") = 0, py::arg("y2") = 0,
py::arg("score") = 0.f, py::arg("label_text") = "",
py::arg("label") = 0)
.def_readwrite("x1", &Box::x1)
.def_readwrite("y1", &Box::y1)
.def_readwrite("x2", &Box::x2)
.def_readwrite("y2", &Box::y2)
.def_readwrite("score", &Box::score)
.def_readwrite("label_text", &Box::label_text)
.def_readwrite("label", &Box::label);
py::class_<PYObjectDetectionTask>(m, "ObjectDetectionTask")
.def(py::init<const ObjectDetectionOption &>())
.def("getInitFlag", &PYObjectDetectionTask::getInitFlag)
.def("Detect", &PYObjectDetectionTask::Detect);
py::class_<PoseEstimationOption>(m, "PoseEstimationOption")
.def(py::init())
.def(py::init<const std::string &, const int &, const int &>(),
PYARG_COMMENT(PoseEstimationTask), PYARG_HUMAN_POSE)
.def_readwrite("model_path", &PoseEstimationOption::model_path)
.def_readwrite("intra_threads_num",
&PoseEstimationOption::intra_threads_num)
.def_readwrite("inter_threads_num",
&PoseEstimationOption::inter_threads_num);
py::class_<PYPoseEstimationTask>(m, "PoseEstimationTask")
.def(py::init<PoseEstimationOption &>())
.def("getInitFlag", &PYPoseEstimationTask::getInitFlag)
.def("Estimate", &PYPoseEstimationTask::Estimate);
py::class_<PosePoint>(m, "PosePoint")
.def(py::init())
.def(py::init<int, int, float>(),
"2D Point with score for Pose Estimation Task", py::arg("x") = 0,
py::arg("y") = 0, py::arg("score") = 0.f)
.def_readwrite("x", &PosePoint::x)
.def_readwrite("y", &PosePoint::y)
.def_readwrite("score", &PosePoint::score);
}