Skip to content

Commit

Permalink
Merge pull request lagadic#1377 from fspindle/fix_python_bindings
Browse files Browse the repository at this point in the history
Fix python bindings build
  • Loading branch information
fspindle authored Apr 16, 2024
2 parents 9a0b1f0 + 97a4a94 commit d6f5010
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 65 deletions.
123 changes: 73 additions & 50 deletions modules/python/bindings/include/core/image_conversions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ void call_conversion_fn(ConversionFunction1D fn, unsigned char *src, unsigned ch
fn(src, dest, h * w);
}




template <typename ConversionFn>
struct SimpleConversionStruct
{
Expand Down Expand Up @@ -229,72 +226,98 @@ unsigned size411(unsigned h, unsigned w)
return h * w + ((h / 4) * (w / 4)) * 2;
}

template<typename T>
void add_hsv_to_rgb_or_rgba_binding(py::class_<vpImageConvert> &pyImageConvert,
void (*fn)(const T *, const T *, const T *, unsigned char *, unsigned int), const char *name, const unsigned destBytes)
void rgb_or_rgba_to_hsv_verification(const py::buffer_info &bufsrc, const py::buffer_info &bufdest, const unsigned destBytes, const unsigned height, const unsigned width)
{
pyImageConvert.def_static(name, [fn, destBytes](py::array_t<T, py::array::c_style> &src,
py::array_t<unsigned char, py::array::c_style> &dest) {
py::buffer_info bufsrc = src.request(), bufdest = dest.request();
if (bufsrc.ndim != 3 || bufdest.ndim != 3) {
throw std::runtime_error("Expected to have src and dest arrays with at least two dimensions.");
}
if (bufsrc.shape[0] != 3) {
if (bufdest.shape[0] != 3) {
throw std::runtime_error("Source array should be a 3D array of shape (3, H, W) ");
}
if (bufdest.shape[2] != destBytes) {
if (bufsrc.shape[2] != destBytes) {
std::stringstream ss;
ss << "Target array should be a 3D array of shape (H, W, " << destBytes << ")";
throw std::runtime_error(ss.str());
}
const unsigned height = bufsrc.shape[1];
const unsigned width = bufsrc.shape[2];
if (bufdest.shape[0] != height || bufdest.shape[1] != width) {
if (bufsrc.shape[0] != height || bufsrc.shape[1] != width) {
std::stringstream ss;
ss << "src and dest must have the same number of pixels, but got HSV planes with dimensions (" << height << ", " << width << ")";
ss << "and RGB array with dimensions (" << bufdest.shape[0] << ", " << bufdest.shape[1] << ")";
throw std::runtime_error(ss.str());
}
}

void add_hsv_double_to_rgb_or_rgba_binding(py::class_<vpImageConvert> &pyImageConvert,
void (*fn)(const double *, const double *, const double *, unsigned char *, unsigned int), const char *name, const unsigned destBytes)
{
pyImageConvert.def_static(name, [fn, destBytes](py::array_t<double, py::array::c_style> &src,
py::array_t<unsigned char, py::array::c_style> &dest) {
py::buffer_info bufsrc = src.request(), bufdest = dest.request();
const unsigned height = bufsrc.shape[1];
const unsigned width = bufsrc.shape[2];
rgb_or_rgba_to_hsv_verification(bufdest, bufsrc, destBytes, height, width);

const T *h = static_cast<T *>(bufsrc.ptr);
const T *s = h + (height * width);
const T *v = s + (height * width);
const double *h = static_cast<double *>(bufsrc.ptr);
const double *s = h + (height * width);
const double *v = s + (height * width);
unsigned char *dest_ptr = static_cast<unsigned char *>(bufdest.ptr);
fn(h, s, v, dest_ptr, height * width);

}, "Convert from HSV Planes (as a 3 x H x W array) to a an RGB/RGBA array (as an H x W x 3 or H x W x 4 array)", py::arg("hsv"), py::arg("rgb"));
}

template<typename T>
void add_rgb_or_rgba_to_hsv_binding(py::class_<vpImageConvert> &pyImageConvert,
void (*fn)(const unsigned char *, T *, T *, T *, unsigned int), const char *name, const unsigned destBytes)
void add_hsv_uchar_to_rgb_or_rgba_binding(py::class_<vpImageConvert> &pyImageConvert,
void (*fn)(const unsigned char *, const unsigned char *, const unsigned char *, unsigned char *, unsigned int, bool), const char *name, const unsigned destBytes)
{
pyImageConvert.def_static(name, [fn, destBytes](py::array_t<unsigned char, py::array::c_style> &src,
py::array_t<T, py::array::c_style> &dest) {
py::array_t<unsigned char, py::array::c_style> &dest, bool h_full) {
py::buffer_info bufsrc = src.request(), bufdest = dest.request();
const unsigned height = bufsrc.shape[1];
const unsigned width = bufsrc.shape[2];
rgb_or_rgba_to_hsv_verification(bufdest, bufsrc, destBytes, height, width);

const unsigned char *h = static_cast<unsigned char *>(bufsrc.ptr);
const unsigned char *s = h + (height * width);
const unsigned char *v = s + (height * width);
unsigned char *dest_ptr = static_cast<unsigned char *>(bufdest.ptr);
fn(h, s, v, dest_ptr, height * width, h_full);

}, "Convert from HSV Planes (as a 3 x H x W array) to a an RGB/RGBA array (as an H x W x 3 or H x W x 4 array)", py::arg("hsv"), py::arg("rgb"), py::arg("h_full")=true);
}

void add_rgb_or_rgba_uchar_to_hsv_binding(py::class_<vpImageConvert> &pyImageConvert,
void (*fn)(const unsigned char *, unsigned char *, unsigned char *, unsigned char *, unsigned int, bool), const char *name, const unsigned destBytes)
{
pyImageConvert.def_static(name, [fn, destBytes](py::array_t<unsigned char, py::array::c_style> &src,
py::array_t<unsigned char, py::array::c_style> &dest,
bool h_full) {
py::buffer_info bufsrc = src.request(), bufdest = dest.request();
if (bufsrc.ndim != 3 || bufdest.ndim != 3) {
throw std::runtime_error("Expected to have src and dest arrays with at least two dimensions.");
}
if (bufdest.shape[0] != 3) {
throw std::runtime_error("Source array should be a 3D array of shape (3, H, W) ");
}
if (bufsrc.shape[2] != destBytes) {
std::stringstream ss;
ss << "Target array should be a 3D array of shape (H, W, " << destBytes << ")";
throw std::runtime_error(ss.str());
}
const unsigned height = bufdest.shape[1];
const unsigned width = bufdest.shape[2];
if (bufsrc.shape[0] != height || bufsrc.shape[1] != width) {
std::stringstream ss;
ss << "src and dest must have the same number of pixels, but got HSV planes with dimensions (" << height << ", " << width << ")";
ss << "and RGB array with dimensions (" << bufdest.shape[0] << ", " << bufdest.shape[1] << ")";
throw std::runtime_error(ss.str());
}
rgb_or_rgba_to_hsv_verification(bufsrc, bufdest, destBytes, height, width);

unsigned char *h = static_cast<unsigned char *>(bufdest.ptr);
unsigned char *s = h + (height * width);
unsigned char *v = s + (height * width);
const unsigned char *rgb = static_cast<unsigned char *>(bufsrc.ptr);
fn(rgb, h, s, v, height * width, h_full);

}, "Convert from HSV Planes (as a 3 x H x W array) to a an RGB/RGBA array (as an H x W x 3 or H x W x 4 array)", py::arg("rgb"), py::arg("hsv"), py::arg("h_full")=true);
}

void add_rgb_or_rgba_double_to_hsv_binding(py::class_<vpImageConvert> &pyImageConvert,
void (*fn)(const unsigned char *, double *, double *, double *, unsigned int), const char *name, const unsigned destBytes)
{
pyImageConvert.def_static(name, [fn, destBytes](py::array_t<unsigned char, py::array::c_style> &src,
py::array_t<double, py::array::c_style> &dest) {
py::buffer_info bufsrc = src.request(), bufdest = dest.request();
const unsigned height = bufdest.shape[1];
const unsigned width = bufdest.shape[2];
rgb_or_rgba_to_hsv_verification(bufsrc, bufdest, destBytes, height, width);

T *h = static_cast<T *>(bufdest.ptr);
T *s = h + (height * width);
T *v = s + (height * width);
double *h = static_cast<double *>(bufdest.ptr);
double *s = h + (height * width);
double *v = s + (height * width);
const unsigned char *rgb = static_cast<unsigned char *>(bufsrc.ptr);
fn(rgb, h, s, v, height * width);

Expand Down Expand Up @@ -412,15 +435,15 @@ void bindings_vpImageConvert(py::class_<vpImageConvert> &pyImageConvert)
}

// HSV <-> RGB/a
add_hsv_to_rgb_or_rgba_binding<unsigned char>(pyImageConvert, vpImageConvert::HSVToRGB, "HSVToRGB", 3);
add_hsv_to_rgb_or_rgba_binding<double>(pyImageConvert, vpImageConvert::HSVToRGB, "HSVToRGB", 3);
add_hsv_to_rgb_or_rgba_binding<unsigned char>(pyImageConvert, vpImageConvert::HSVToRGBa, "HSVToRGBa", 4);
add_hsv_to_rgb_or_rgba_binding<double>(pyImageConvert, vpImageConvert::HSVToRGBa, "HSVToRGBa", 4);

add_rgb_or_rgba_to_hsv_binding<unsigned char>(pyImageConvert, vpImageConvert::RGBToHSV, "RGBToHSV", 3);
add_rgb_or_rgba_to_hsv_binding<double>(pyImageConvert, vpImageConvert::RGBToHSV, "RGBToHSV", 3);
add_rgb_or_rgba_to_hsv_binding<unsigned char>(pyImageConvert, vpImageConvert::RGBaToHSV, "RGBaToHSV", 4);
add_rgb_or_rgba_to_hsv_binding<double>(pyImageConvert, vpImageConvert::RGBaToHSV, "RGBaToHSV", 4);
add_hsv_uchar_to_rgb_or_rgba_binding(pyImageConvert, vpImageConvert::HSVToRGB, "HSVToRGB", 3);
add_hsv_double_to_rgb_or_rgba_binding(pyImageConvert, vpImageConvert::HSVToRGB, "HSVToRGB", 3);
add_hsv_uchar_to_rgb_or_rgba_binding(pyImageConvert, vpImageConvert::HSVToRGBa, "HSVToRGBa", 4);
add_hsv_double_to_rgb_or_rgba_binding(pyImageConvert, vpImageConvert::HSVToRGBa, "HSVToRGBa", 4);

add_rgb_or_rgba_uchar_to_hsv_binding(pyImageConvert, vpImageConvert::RGBToHSV, "RGBToHSV", 3);
add_rgb_or_rgba_double_to_hsv_binding(pyImageConvert, vpImageConvert::RGBToHSV, "RGBToHSV", 3);
add_rgb_or_rgba_uchar_to_hsv_binding(pyImageConvert, vpImageConvert::RGBaToHSV, "RGBaToHSV", 4);
add_rgb_or_rgba_double_to_hsv_binding(pyImageConvert, vpImageConvert::RGBaToHSV, "RGBaToHSV", 4);


// uint8_t implems
Expand Down
8 changes: 4 additions & 4 deletions modules/python/config/core_image.json
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@
},
{
"static": true,
"signature": "void RGBaToHSV(const unsigned char*, unsigned char*, unsigned char*, unsigned char*, unsigned int)",
"signature": "void RGBaToHSV(const unsigned char*, unsigned char*, unsigned char*, unsigned char*, unsigned int, bool)",
"ignore": true,
"custom_implem": true
},
Expand All @@ -360,13 +360,13 @@
},
{
"static": true,
"signature": "void HSVToRGB(const unsigned char*, const unsigned char*, const unsigned char*, unsigned char*, unsigned int)",
"signature": "void HSVToRGB(const unsigned char*, const unsigned char*, const unsigned char*, unsigned char*, unsigned int, bool)",
"ignore": true,
"custom_implem": true
},
{
"static": true,
"signature": "void HSVToRGBa(const unsigned char*, const unsigned char*, const unsigned char*, unsigned char*, unsigned int)",
"signature": "void HSVToRGBa(const unsigned char*, const unsigned char*, const unsigned char*, unsigned char*, unsigned int, bool)",
"ignore": true,
"custom_implem": true
},
Expand All @@ -384,7 +384,7 @@
},
{
"static": true,
"signature": "void RGBToHSV(const unsigned char*, unsigned char*, unsigned char*, unsigned char*, unsigned int)",
"signature": "void RGBToHSV(const unsigned char*, unsigned char*, unsigned char*, unsigned char*, unsigned int, bool)",
"ignore": true,
"custom_implem": true
},
Expand Down
1 change: 1 addition & 0 deletions modules/python/doc/conf.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ html_theme = 'sphinx_immaterial'
# html_theme_options = {}
html_theme_options = {
"toc_title_is_page_title": True,
"font": False,
"repo_url": "https://github.com/lagadic/visp",
"repo_name": "visp",
"features": [
Expand Down
7 changes: 4 additions & 3 deletions modules/python/doc/rst/dev/dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ If you encounter a compilation error, make sure to first try rebuilding after cl
Pybind did generate problems (an error at the pybind include line) that were solved like this.

Static and member methods have the same name
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
----------------------------------------------

If, when importing visp in python, you encounter this message:

Expand All @@ -86,7 +86,7 @@ If, when importing visp in python, you encounter this message:
Then it means that a class has both a static method and a member method with the same name. You should :ref:`rename either one through the config files <Function options>`.

Abstract class not detected
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
----------------------------------------------

If you have this error:

Expand All @@ -103,7 +103,8 @@ This error occurs because some methods are defined as pure virtual in a parent c


Template errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
----------------------------------------------


If you have an issue that looks like:

Expand Down
4 changes: 2 additions & 2 deletions modules/python/doc/rst/python_api/python_specific_fns.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ Core module
----------------------

* :py:class:`~visp.core.PixelMeterConversion` and :py:class:`~visp.core.MeterPixelConversion` both
have a vectorised implementation of :code:`convertPoint`, called :code:`convertPoints`, accepting NumPy arrays
have a vectorised implementation of :code:`convertPoint`, called :code:`convertPoints`, accepting NumPy arrays


MBT module
-----------------------

* :py:class:`~visp.mbt.MbGenericTracker` as a reworked version of :py:meth:`visp.mbt.MbGenericTracker.track`, taking as inputs
maps of color images and of numpy representations (of shape H x W x 3) of the point clouds.
maps of color images and of numpy representations (of shape H x W x 3) of the point clouds.
6 changes: 2 additions & 4 deletions modules/python/generator/visp_python_bindgen/doc_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,8 @@ def to_cstring(s: str) -> str:
current_char = 0
result = ''
while current_char < len(s):
result += f'''R"doc(
{s[current_char: min((current_char + per_string_limit), len(s))]})doc"
'''
result += f'''R"doc({s[current_char: min((current_char + per_string_limit), len(s))]})doc"
'''
current_char += per_string_limit
return result

Expand Down
10 changes: 8 additions & 2 deletions modules/python/generator/visp_python_bindgen/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,14 @@ def get_py_args(parameters: List[types.Parameter], specs, env_mapping) -> List[s
def make_arg(name: str) -> str:
return f'py::arg("{name}")'
py_args = []
arg_index = 0
for parameter in parameters:
parameter_name = parameter.name
if parameter_name is None:
parameter_name = f'arg{arg_index}'

if parameter.default is None or not parameter_can_have_default_value(parameter, specs, env_mapping):
py_args.append(make_arg(parameter.name))
py_args.append(make_arg(parameter_name))
else:
t = parameter.type
gt = lambda typename: get_typename(typename, specs, env_mapping)
Expand Down Expand Up @@ -260,7 +265,8 @@ def make_arg(name: str) -> str:
default_value_rep = default_value_rep.replace('"', '\"') # Escape inner quotes in std::string args like std::string("hello"). This would break parsing at compile time
default_value = env_mapping.get(default_value) or default_value

py_args.append(f'py::arg_v("{parameter.name}", {default_value}, "{default_value_rep}")')
py_args.append(f'py::arg_v("{parameter_name}", {default_value}, "{default_value_rep}")')
arg_index += 1

return py_args

Expand Down

0 comments on commit d6f5010

Please sign in to comment.