diff --git a/modules/python/bindings/include/core/image_conversions.hpp b/modules/python/bindings/include/core/image_conversions.hpp index 1aad963d82..5a8d3b9deb 100644 --- a/modules/python/bindings/include/core/image_conversions.hpp +++ b/modules/python/bindings/include/core/image_conversions.hpp @@ -40,6 +40,7 @@ #include #include + namespace { using ConversionFunction1D = void(*)(unsigned char *, unsigned char *, unsigned int); @@ -300,9 +301,41 @@ void add_rgb_or_rgba_to_hsv_binding(py::class_ &pyImageConvert, }, "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")); } -} +/* Demosaicing implem */ +template +void add_demosaic_to_rgba_fn(py::class_ &pyImageConvert, void (*fn)(const DataType *, DataType *, unsigned int, unsigned int, unsigned int), const char *name) +{ + pyImageConvert.def_static(name, [fn](py::array_t &src, + py::array_t &dest, + unsigned int num_threads) { + py::buffer_info bufsrc = src.request(), bufdest = dest.request(); + const unsigned destBytes = 4; + + if (bufsrc.ndim != 2 || bufdest.ndim != 3) { + throw std::runtime_error("Expected to have source array with two dimensions and destination RGBA array with 3."); + } + if (bufdest.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[0]; + const unsigned width = bufdest.shape[1]; + 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 source with dimensions (" << height << ", " << width << ")"; + ss << "and RGB array with dimensions (" << bufdest.shape[0] << ", " << bufdest.shape[1] << ")"; + throw std::runtime_error(ss.str()); + } + + const DataType *bayer = static_cast(bufsrc.ptr); + DataType *rgba = static_cast(bufdest.ptr); + fn(bayer, rgba, height, width, num_threads); + }, "Demosaic function implementation, see C++ documentation.", py::arg("bayer_data"), py::arg("rgba"), py::arg("num_threads") = 0); +} +} void bindings_vpImageConvert(py::class_ &pyImageConvert) { @@ -338,7 +371,7 @@ void bindings_vpImageConvert(py::class_ &pyImageConvert) } } - //YUV conversions + // YUV conversions { using Conv = ConversionFromYUVLike; std::vector conversions = { @@ -378,6 +411,7 @@ void bindings_vpImageConvert(py::class_ &pyImageConvert) } } + // HSV <-> RGB/a add_hsv_to_rgb_or_rgba_binding(pyImageConvert, vpImageConvert::HSVToRGB, "HSVToRGB", 3); add_hsv_to_rgb_or_rgba_binding(pyImageConvert, vpImageConvert::HSVToRGB, "HSVToRGB", 3); add_hsv_to_rgb_or_rgba_binding(pyImageConvert, vpImageConvert::HSVToRGBa, "HSVToRGBa", 4); @@ -387,6 +421,43 @@ void bindings_vpImageConvert(py::class_ &pyImageConvert) add_rgb_or_rgba_to_hsv_binding(pyImageConvert, vpImageConvert::RGBToHSV, "RGBToHSV", 3); add_rgb_or_rgba_to_hsv_binding(pyImageConvert, vpImageConvert::RGBaToHSV, "RGBaToHSV", 4); add_rgb_or_rgba_to_hsv_binding(pyImageConvert, vpImageConvert::RGBaToHSV, "RGBaToHSV", 4); + + + // uint8_t implems + { + using DemosaicFn = void (*)(const uint8_t *, uint8_t *, unsigned int, unsigned int, unsigned int); + std::vector> functions = { + {static_cast(&vpImageConvert::demosaicRGGBToRGBaMalvar), "demosaicRGGBToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicGRBGToRGBaMalvar), "demosaicGRBGToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicGBRGToRGBaMalvar), "demosaicGBRGToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicBGGRToRGBaMalvar), "demosaicBGGRToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicRGGBToRGBaBilinear), "demosaicRGGBToRGBaBilinear"}, + {static_cast(&vpImageConvert::demosaicGRBGToRGBaBilinear), "demosaicGRBGToRGBaBilinear"}, + {static_cast(&vpImageConvert::demosaicGBRGToRGBaBilinear), "demosaicGBRGToRGBaBilinear"}, + {static_cast(&vpImageConvert::demosaicBGGRToRGBaBilinear), "demosaicBGGRToRGBaBilinear"} + }; + for (const auto &pair: functions) { + add_demosaic_to_rgba_fn(pyImageConvert, pair.first, pair.second); + } + } + //UInt16_t implems + { + using DemosaicFn = void (*)(const uint16_t *, uint16_t *, unsigned int, unsigned int, unsigned int); + std::vector> functions = { + {static_cast(&vpImageConvert::demosaicRGGBToRGBaMalvar), "demosaicRGGBToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicGRBGToRGBaMalvar), "demosaicGRBGToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicGBRGToRGBaMalvar), "demosaicGBRGToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicBGGRToRGBaMalvar), "demosaicBGGRToRGBaMalvar"}, + {static_cast(&vpImageConvert::demosaicRGGBToRGBaBilinear), "demosaicRGGBToRGBaBilinear"}, + {static_cast(&vpImageConvert::demosaicGRBGToRGBaBilinear), "demosaicGRBGToRGBaBilinear"}, + {static_cast(&vpImageConvert::demosaicGBRGToRGBaBilinear), "demosaicGBRGToRGBaBilinear"}, + {static_cast(&vpImageConvert::demosaicBGGRToRGBaBilinear), "demosaicBGGRToRGBaBilinear"} + }; + for (const auto &pair: functions) { + add_demosaic_to_rgba_fn(pyImageConvert, pair.first, pair.second); + } + } + } #endif diff --git a/modules/python/config/core.json b/modules/python/config/core.json index 32b5811e9f..f27121c4c9 100644 --- a/modules/python/config/core.json +++ b/modules/python/config/core.json @@ -571,6 +571,89 @@ "static": true, "signature": "void BGRToRGBa(unsigned char*, unsigned char*, unsigned int, unsigned int, bool)", "ignore": true + }, + { + "static": true, + "signature": "void demosaicBGGRToRGBaBilinear(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicBGGRToRGBaBilinear(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicGBRGToRGBaBilinear(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicGBRGToRGBaBilinear(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicGRBGToRGBaBilinear(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + + }, + { + "static": true, + "signature": "void demosaicGRBGToRGBaBilinear(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicRGGBToRGBaBilinear(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicRGGBToRGBaBilinear(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicBGGRToRGBaMalvar(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicBGGRToRGBaMalvar(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicGBRGToRGBaMalvar(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + + }, + { + "static": true, + "signature": "void demosaicGBRGToRGBaMalvar(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + + }, + { + "static": true, + "signature": "void demosaicGRBGToRGBaMalvar(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicGRBGToRGBaMalvar(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicRGGBToRGBaMalvar(const uint8_t*, uint8_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true + }, + { + "static": true, + "signature": "void demosaicRGGBToRGBaMalvar(const uint16_t*, uint16_t*, unsigned int, unsigned int, unsigned int)", + "ignore": true } ] }, diff --git a/modules/python/doc/_templates/custom-class-template.rst b/modules/python/doc/_templates/custom-class-template.rst index 96f82162f4..8f72e62d15 100644 --- a/modules/python/doc/_templates/custom-class-template.rst +++ b/modules/python/doc/_templates/custom-class-template.rst @@ -6,7 +6,7 @@ :members: :show-inheritance: :member-order: groupwise - :inherited-members: pybind11_builtins.pybind11_object + :inherited-members: pybind11_builtins.pybind11_object, pybind11_object :special-members: {% block methods %} diff --git a/modules/python/test/test_conversions.py b/modules/python/test/test_conversions.py index 161ada5611..922b3ffdca 100644 --- a/modules/python/test/test_conversions.py +++ b/modules/python/test/test_conversions.py @@ -62,3 +62,23 @@ def test_rgb_rgba_to_hsv(): hsv_old = hsv.copy() case['fn'](rgb, hsv) assert not np.allclose(hsv, hsv_old) + +def test_demosaic(): + h, w = 32, 32 + fns = [ + ImageConvert.demosaicRGGBToRGBaMalvar, + ImageConvert.demosaicGRBGToRGBaMalvar, + ImageConvert.demosaicGBRGToRGBaMalvar, + ImageConvert.demosaicBGGRToRGBaMalvar, + ImageConvert.demosaicRGGBToRGBaBilinear, + ImageConvert.demosaicGRBGToRGBaBilinear, + ImageConvert.demosaicGBRGToRGBaBilinear, + ImageConvert.demosaicBGGRToRGBaBilinear, + ] + for fn in fns: + for dtype in [np.uint8, np.uint16]: + bayer_data = np.ones((h, w), dtype=dtype) * 128 + rgba = np.empty((h, w, 4), dtype=dtype) + old_rgba = rgba.copy() + fn(bayer_data, rgba) + assert not np.allclose(rgba, old_rgba), f'Error when testing {fn}, with dtype {dtype}'