Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python bindings, continued #82

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
include/libsgm_config.h
build/
build/
.cache
dist
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ option(ENABLE_ZED_DEMO "Build a Demo using ZED Camera" OFF)
option(ENABLE_SAMPLES "Build samples" OFF)
option(ENABLE_TESTS "Test library" OFF)
option(LIBSGM_SHARED "Build a shared library" OFF)
option(BUILD_PYTHON_WRAPPER "Build pybind11 wrappers" OFF)
option(BUILD_OPENCV_WRAPPER "Make library compatible with cv::Mat and cv::cuda::GpuMat of OpenCV" OFF)
set(CUDA_ARCHS "52;61;72;75;86" CACHE STRING "List of architectures to generate device code for")

Expand All @@ -16,6 +17,14 @@ ${PROJECT_SOURCE_DIR}/include/libsgm_config.h

add_subdirectory(src)

if(BUILD_PYTHON_WRAPPER)
if (LIBSGM_SHARED)
add_subdirectory(pysgm)
else()
message(WARNING "Python wrappers requires LIBSGM_SHARED=ON")
endif()
endif()

if(ENABLE_SAMPLES)
add_subdirectory(sample)
endif()
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The libSGM performance obtained from benchmark sample
|OpenCV|version >= 3.4.8|for samples|
|OpenCV CUDA module|version >= 3.4.8|for OpenCV wrapper|
|ZED SDK|version >= 3.0|for ZED sample|
|Pybind11|latest|for python bindings|

## Build Instructions
```
Expand Down
26 changes: 26 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[build-system]
requires = ["scikit-build-core", "pybind11"]
build-backend = "scikit_build_core.build"

[project]
name = "pysgm"
authors = [
{name = "Oleksandr Slovak"},
{name = "Christoph Liebender", email = "[email protected]"}
]
license = {file = "LICENSE"}
readme = "README.md"
version = "3.1.0"

[project.urls]
Repository = "https://github.com/fixstars/libSGM.git"

[tool.scikit-build]
cmake.args = [
"-DLIBSGM_SHARED=ON",
"-DBUILD_PYTHON_WRAPPER=ON"
]
wheel.exclude = [
"**/*.h",
"**/*.cmake"
]
15 changes: 15 additions & 0 deletions pysgm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
find_package(Python COMPONENTS Development Interpreter)
find_package(pybind11 CONFIG)

pybind11_add_module(pysgm MODULE "pysgm.cpp")
target_include_directories(pysgm PRIVATE "${PROJECT_SOURCE_DIR}/include")
target_link_libraries(pysgm PRIVATE sgm)

if(BUILD_OPENCV_WRAPPER)
target_link_libraries(pysgm PRIVATE ${OpenCV_LIBS})
endif()

install(
TARGETS pysgm
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages
)
192 changes: 192 additions & 0 deletions pysgm/pysgm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#include <pybind11/pybind11.h>

#include <libsgm.h>
#include <libsgm_wrapper.h>

#ifdef BUILD_OPENCV_WRAPPER

#include <pybind11/numpy.h>

namespace pybind11 {
namespace detail {
template<>
struct type_caster<cv::Mat> {
public:
PYBIND11_TYPE_CASTER(cv::Mat, _("numpy.ndarray"));

//! 1. cast numpy.ndarray to cv::Mat
bool load(handle obj, bool) {
array b = reinterpret_borrow<array>(obj);
buffer_info info = b.request();

//const int ndims = (int)info.ndim;
int nh = 1;
int nw = 1;
int nc = 1;
int ndims = info.ndim;
if (ndims == 2) {
nh = info.shape[0];
nw = info.shape[1];
} else if (ndims == 3) {
nh = info.shape[0];
nw = info.shape[1];
nc = info.shape[2];
} else {
char msg[64];
std::sprintf(msg, "Unsupported dim %d, only support 2d, or 3-d", ndims);
throw std::logic_error(msg);
return false;
}

int dtype;
if (info.format == format_descriptor<unsigned char>::format()) {
dtype = CV_8UC(nc);
} else if (info.format == format_descriptor<unsigned short>::format()) {
dtype = CV_16UC(nc);
} else if (info.format == format_descriptor<int>::format()) {
dtype = CV_32SC(nc);
} else if (info.format == format_descriptor<float>::format()) {
dtype = CV_32FC(nc);
} else {
throw std::logic_error("Unsupported type, only support uchar, int32, float");
return false;
}

value = cv::Mat(nh, nw, dtype, info.ptr);
return true;
}

//! 2. cast cv::Mat to numpy.ndarray
static handle cast(const cv::Mat &mat, return_value_policy, handle defval) {
// UNUSED(defval);


std::string format = format_descriptor<unsigned char>::format();
size_t elemsize = sizeof(unsigned char);
int nw = mat.cols;
int nh = mat.rows;
int nc = mat.channels();
int depth = mat.depth();
int type = mat.type();
int dim = (depth == type) ? 2 : 3;

if (depth == CV_8U) {
format = format_descriptor<unsigned char>::format();
elemsize = sizeof(unsigned char);
} else if (depth == CV_16U) {
format = format_descriptor<unsigned short>::format();
elemsize = sizeof(unsigned short);
} else if (depth == CV_16S) {
format = format_descriptor<short>::format();
elemsize = sizeof(short);
} else if (depth == CV_32S) {
format = format_descriptor<int>::format();
elemsize = sizeof(int);
} else if (depth == CV_32F) {
format = format_descriptor<float>::format();
elemsize = sizeof(float);
} else {
throw std::logic_error("Unsupport type!");
}

std::vector<size_t> bufferdim;
std::vector<size_t> strides;
if (dim == 2) {
bufferdim = {(size_t) nh, (size_t) nw};
strides = {elemsize * (size_t) nw, elemsize};
} else if (dim == 3) {
bufferdim = {(size_t) nh, (size_t) nw, (size_t) nc};
strides = {(size_t) elemsize * nw * nc, (size_t) elemsize * nc, (size_t) elemsize};
}
return array(buffer_info(mat.data, elemsize, format, dim, bufferdim, strides)).release();
}
};
}
}//! end namespace pybind11::detail

#endif // BUILD_OPENCV_WRAPPER


#define RW(type_name, field_name) .def_readwrite(#field_name, &type_name::field_name)
namespace py = pybind11;

PYBIND11_MODULE(pysgm, m) {

m.doc() = "libSGM python binding";

py::enum_<sgm::ExecuteInOut>(m, "EXECUTE_INOUT")
.value("EXECUTE_INOUT_HOST2HOST", sgm::ExecuteInOut::EXECUTE_INOUT_HOST2HOST)
.value("EXECUTE_INOUT_HOST2CUDA", sgm::ExecuteInOut::EXECUTE_INOUT_HOST2CUDA)
.value("EXECUTE_INOUT_CUDA2HOST", sgm::ExecuteInOut::EXECUTE_INOUT_CUDA2HOST)
.value("EXECUTE_INOUT_CUDA2CUDA", sgm::ExecuteInOut::EXECUTE_INOUT_CUDA2CUDA)
;

py::enum_<sgm::PathType>(m, "PathType")
.value("SCAN_4PATH", sgm::PathType::SCAN_4PATH)
.value("SCAN_8PATH", sgm::PathType::SCAN_8PATH)
;

py::class_<sgm::StereoSGM> StereoSGM(m, "StereoSGM");

py::class_<sgm::StereoSGM::Parameters>(StereoSGM, "Parameters")
.def(py::init<int, int, float, bool, sgm::PathType, int, int>(),
py::arg("P1") = 10,
py::arg("P2") = 120,
py::arg("uniqueness") = 0.95f,
py::arg("subpixel") = false,
py::arg("PathType") = sgm::PathType::SCAN_8PATH,
py::arg("min_disp") = 0,
py::arg("LR_max_diff") = 1
)
RW(sgm::StereoSGM::Parameters, P1)
RW(sgm::StereoSGM::Parameters, P2)
RW(sgm::StereoSGM::Parameters, uniqueness)
RW(sgm::StereoSGM::Parameters, subpixel)
RW(sgm::StereoSGM::Parameters, path_type)
RW(sgm::StereoSGM::Parameters, min_disp)
RW(sgm::StereoSGM::Parameters, LR_max_diff);

StereoSGM
.def(py::init<int, int, int, int, int, sgm::ExecuteInOut, const sgm::StereoSGM::Parameters &>(),
py::arg("width") = 612,
py::arg("height") = 514,
py::arg("disparity_size") = 128,
py::arg("input_depth_bits") = 8U,
py::arg("output_depth_bits") = 8U,
py::arg("inout_type") = sgm::ExecuteInOut::EXECUTE_INOUT_HOST2HOST,
py::arg("param") = sgm::StereoSGM::Parameters()
)
.def(py::init<int, int, int, int, int, int, int, sgm::ExecuteInOut, const sgm::StereoSGM::Parameters &>())
.def("execute", [](sgm::StereoSGM &w, uintptr_t left_pixels, uintptr_t right_pixels, uintptr_t dst) {
w.execute((void *)left_pixels, (void *)right_pixels, (void *)dst);
})
.def("get_invalid_disparity", &sgm::StereoSGM::get_invalid_disparity)
;

#ifdef BUILD_OPENCV_WRAPPER

py::class_<sgm::LibSGMWrapper> LibSGMWrapper(m, "LibSGMWrapper");

LibSGMWrapper
.def(py::init<int, int, int, float, bool, sgm::PathType, int, int>(),
py::arg("numDisparity") = 128,
py::arg("P1") = 10,
py::arg("P2") = 120,
py::arg("uniquenessRatio") = 0.95f,
py::arg("subpixel") = false,
py::arg("pathType") = sgm::PathType::SCAN_8PATH,
py::arg("minDisparity") = 0,
py::arg("lrMaxDiff") = 1)
.def("getInvalidDisparity", &sgm::LibSGMWrapper::getInvalidDisparity)
.def("hasSubpixel", &sgm::LibSGMWrapper::hasSubpixel)
.def("execute", [](sgm::LibSGMWrapper &w, cv::Mat &left_pixels, const cv::Mat &right_pixels) {
cv::Mat disp;
w.execute(left_pixels, right_pixels, disp);
return disp;
});

#endif // BUILD_OPENCV_WRAPPER

m.def("SUBPIXEL_SCALE", []() { return sgm::StereoSGM::SUBPIXEL_SCALE; });
m.def("SUBPIXEL_SHIFT", []() { return sgm::StereoSGM::SUBPIXEL_SHIFT; });
}
36 changes: 36 additions & 0 deletions sample/pysgm/pysgm_test_opencv_wrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env python3

import sys

import cv2
import numpy as np

import pysgm

disp_size = 128

sgm_opencv_wrapper = pysgm.LibSGMWrapper(
numDisparity=int(disp_size), P1=int(10), P2=int(120), uniquenessRatio=np.float32(0.95),
subpixel=False, pathType=pysgm.PathType.SCAN_8PATH, minDisparity=int(0),
lrMaxDiff=int(1))


I1 = cv2.imread(sys.argv[1], cv2.IMREAD_GRAYSCALE)
I2 = cv2.imread(sys.argv[2], cv2.IMREAD_GRAYSCALE)

disp = sgm_opencv_wrapper.execute(I1, I2)

if sgm_opencv_wrapper.hasSubpixel():
disp = disp.astype('float') / pysgm.SUBPIXEL_SCALE()

if sgm_opencv_wrapper.hasSubpixel():
mask = disp == (sgm_opencv_wrapper.getInvalidDisparity() / pysgm.SUBPIXEL_SCALE())
else:
mask = disp == sgm_opencv_wrapper.getInvalidDisparity()

disp = (255. * disp / disp_size)
disp_color = cv2.applyColorMap(disp.astype("uint8"), cv2.COLORMAP_JET)
disp_color[mask, :] = 0

cv2.imshow("disp_color", disp_color)
cv2.waitKey(0)
39 changes: 39 additions & 0 deletions sample/pysgm/pysgm_test_raw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3

import sys

import cv2
import numpy as np

import pysgm

disp_size = 128

I1 = cv2.imread(sys.argv[1], cv2.IMREAD_GRAYSCALE)
I2 = cv2.imread(sys.argv[2], cv2.IMREAD_GRAYSCALE)

disp = np.zeros_like(I2)

I1_ptr, _ = I1.__array_interface__['data']
I2_ptr, _ = I2.__array_interface__['data']
disp_ptr, _ = disp.__array_interface__['data']

height, width = I1.shape

params = pysgm.StereoSGM.Parameters(P1=int(10), P2=int(120), uniqueness=np.float32(0.95), subpixel=False,
PathType=pysgm.PathType.SCAN_8PATH, min_disp=int(0), LR_max_diff=int(1))

sgm = pysgm.StereoSGM(width=width, height=height, disparity_size=int(disp_size), input_depth_bits=int(8),
output_depth_bits=int(8), inout_type=pysgm.EXECUTE_INOUT.EXECUTE_INOUT_HOST2HOST,
param=params)

sgm.execute(I1_ptr, I2_ptr, disp_ptr)

mask = disp == np.int16(sgm.get_invalid_disparity())
disp = (255. * disp / disp_size)

disp_color = cv2.applyColorMap(disp.astype("uint8"), cv2.COLORMAP_JET)
disp_color[mask] = 0

cv2.imshow("disp_color", disp_color)
cv2.waitKey(0)