diff --git a/modules/python/.gitignore b/modules/python/.gitignore index b8307c6f89..f015fe73c8 100644 --- a/modules/python/.gitignore +++ b/modules/python/.gitignore @@ -5,6 +5,6 @@ stubs/visp stubs/build *.eggs doc/_build -doc/_autosummary +doc/_autosummary/* doc/generated doc/api.rst diff --git a/modules/python/bindings/include/core/pixel_meter.hpp b/modules/python/bindings/include/core/pixel_meter.hpp index 51b26078a1..c55b272e2d 100644 --- a/modules/python/bindings/include/core/pixel_meter.hpp +++ b/modules/python/bindings/include/core/pixel_meter.hpp @@ -69,13 +69,39 @@ void bindings_vpPixelMeterConversion(py::class_ &pyPM) }, R"doc( Convert a set of 2D pixel coordinates to normalized coordinates. + :param cam: The camera intrinsics with which to convert pixels to normalized coordinates. + :param us: The pixel coordinates along the horizontal axis. + :param vs: The pixel coordinates along the vertical axis. :raises RuntimeError: If us and vs do not have the same dimensions and shape. :return: A tuple containing the x and y normalized coordinates of the input pixels. +Both arrays have the same shape as xs and ys. + +Example usage: + +.. testcode:: + + from visp.core import PixelMeterConversion, CameraParameters + import numpy as np + + h, w = 240, 320 + cam = CameraParameters(px=600, py=600, u0=320, v0=240) + + vs, us = np.meshgrid(range(h), range(w), indexing='ij') # vs and us are 2D arrays + vs.shape == (h, w) and us.shape == (h, w) + + xs, ys = PixelMeterConversion.convertPoints(cam, us, vs) + # xs and ys have the same shape as us and vs + assert xs.shape == (h, w) and ys.shape == (h, w) + + # Converting a numpy array to normalized coords has the same effect as calling on a single image point + u, v = 120, 120 + x, y = PixelMeterConversion.convertPoint(cam, u, v) + assert x == xs[v, u] and y == ys[v, u] )doc", py::arg("cam"), py::arg("us"), py::arg("vs")); } @@ -106,13 +132,39 @@ void bindings_vpMeterPixelConversion(py::class_ &pyMP) }, R"doc( Convert a set of 2D normalized coordinates to pixel coordinates. + :param cam: The camera intrinsics with which to convert normalized coordinates to pixels. + :param xs: The normalized coordinates along the horizontal axis. + :param ys: The normalized coordinates along the vertical axis. :raises RuntimeError: If xs and ys do not have the same dimensions and shape. -:return: A tuple containing the u,v pixel coordinates of the input normalized coordinates. +:return: A tuple containing the u,v pixel coordinate arrays of the input normalized coordinates. +Both arrays have the same shape as xs and ys. + +Example usage: + +.. testcode:: + + from visp.core import MeterPixelConversion, CameraParameters + import numpy as np + + cam = CameraParameters(px=600, py=600, u0=320, v0=240) + n = 20 + xs, ys = np.random.rand(n), np.random.rand(n) + + + us, vs = MeterPixelConversion.convertPoints(cam, xs, ys) + + # xs and ys have the same shape as us and vs + assert us.shape == (n,) and vs.shape == (n,) + + # Converting a numpy array to pixel coords has the same effect as calling on a single image point + x, y = xs[0], ys[0] + u, v = MeterPixelConversion.convertPoint(cam, x, y) + assert u == us[0] and v == vs[0] )doc", py::arg("cam"), py::arg("xs"), py::arg("ys")); } diff --git a/modules/python/config/core.json b/modules/python/config/core.json index 6d86ecdd7f..ff9937e676 100644 --- a/modules/python/config/core.json +++ b/modules/python/config/core.json @@ -651,10 +651,111 @@ ] }, "vpPixelMeterConversion": { - "additional_bindings": "bindings_vpPixelMeterConversion" + "additional_bindings": "bindings_vpPixelMeterConversion", + "methods": [ + { + "static": true, + "signature": "void convertEllipse(const vpCameraParameters&, const vpImagePoint&, double, double, double, double&, double&, double&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true, true, true, true, true, false, false, false, false, false], + "param_is_output": [false, false, false, false, false, true, true, true, true, true] + }, + { + "static": true, + "signature": "void convertLine(const vpCameraParameters&, const double&, const double&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true,true,true,false,false], + "param_is_output": [false,false,false,true,true] + }, + { + "static": true, + "signature": "void convertPoint(const vpCameraParameters&, const double&, const double&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true,true,true,false,false], + "param_is_output": [false,false,false,true,true] + }, + { + "static": true, + "signature": "void convertPoint(const vpCameraParameters&, const vpImagePoint&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true,true,false,false], + "param_is_output": [false,false,true,true] + }, + { + "static": true, + "signature": "void convertEllipse(const cv::Mat&, const cv::Mat&, const vpImagePoint&, double, double, double, double&, double&, double&, double&, double&)", + "ignore": true + }, + { + "static": true, + "signature": "void convertLine(const cv::Mat&, const double&, const double&, double&, double&)", + "ignore": true + }, + { + "static": true, + "signature": "void convertPoint(const cv::Mat&, const cv::Mat&, const double&, const double&, double&, double&)", + "ignore": true + }, + { + "static": true, + "signature": "void convertPoint(const cv::Mat&, const cv::Mat&, const vpImagePoint&, double&, double&)", + "ignore": true + } + ] + }, "vpMeterPixelConversion": { - "additional_bindings": "bindings_vpMeterPixelConversion" + "additional_bindings": "bindings_vpMeterPixelConversion", + "methods": [ + { + "static": true, + "signature": "void convertEllipse(const vpCameraParameters&, const vpImagePoint&, double, double, double, double&, double&, double&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true, true, true, true, true, false, false, false, false, false], + "param_is_output": [false, false, false, false, false, true, true, true, true, true] + }, + { + "static": true, + "signature": "void convertLine(const vpCameraParameters&, const double&, const double&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true,true,true,false,false], + "param_is_output": [false,false,false,true,true] + }, + { + "static": true, + "signature": "void convertPoint(const vpCameraParameters&, const double&, const double&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true,true,true,false,false], + "param_is_output": [false,false,false,true,true] + }, + { + "static": true, + "signature": "void convertPoint(const vpCameraParameters&, const vpImagePoint&, double&, double&)", + "use_default_param_policy": false, + "param_is_input": [true,true,false,false], + "param_is_output": [false,false,true,true] + }, + { + "static": true, + "signature": "void convertEllipse(const cv::Mat&, const cv::Mat&, const vpImagePoint&, double, double, double, double&, double&, double&, double&, double&)", + "ignore": true + }, + { + "static": true, + "signature": "void convertLine(const cv::Mat&, const double&, const double&, double&, double&)", + "ignore": true + }, + { + "static": true, + "signature": "void convertPoint(const cv::Mat&, const cv::Mat&, const double&, const double&, double&, double&)", + "ignore": true + }, + { + "static": true, + "signature": "void convertPoint(const cv::Mat&, const cv::Mat&, const vpImagePoint&, double&, double&)", + "ignore": true + } + ] }, "vpCircle": { "methods": [ diff --git a/modules/python/doc/CMakeLists.txt b/modules/python/doc/CMakeLists.txt index 617632448a..0a5a3e3430 100644 --- a/modules/python/doc/CMakeLists.txt +++ b/modules/python/doc/CMakeLists.txt @@ -68,11 +68,12 @@ configure_file( add_custom_target(visp_python_bindings_doc COMMAND ${PYTHON3_EXECUTABLE} -m pip install -r "${CMAKE_CURRENT_SOURCE_DIR}/requirements.txt" COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/_static" "${BINARY_BUILD_DIR}/_static" - COMMAND sphinx-build + COMMAND ${PYTHON3_EXECUTABLE} -m sphinx -b html -c "${BINARY_BUILD_DIR}" -d "${SPHINX_CACHE_DIR}" -j 8 + -E "${CMAKE_CURRENT_SOURCE_DIR}" "${SPHINX_HTML_DIR}" COMMENT "Building Sphinx HTML documentation for ViSP's Python bindings" diff --git a/modules/python/doc/conf.py.in b/modules/python/doc/conf.py.in index 89a67434ee..46cfa401e6 100644 --- a/modules/python/doc/conf.py.in +++ b/modules/python/doc/conf.py.in @@ -66,7 +66,6 @@ nbsphinx_allow_errors = True # Continue through Jupyter errors autodoc_typehints = "both" # Sphinx-native method. Not as good as sphinx_autodoc_typehints add_module_names = False # Remove namespaces from class/method signatures -import visp # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -153,7 +152,6 @@ html_theme = 'sphinx_immaterial' # html_theme_options = {} html_theme_options = { "toc_title_is_page_title": True, - "navigation_depth": 2, "repo_url": "https://github.com/lagadic/visp", "repo_name": "visp", "features": [ @@ -193,7 +191,7 @@ html_theme_options = { # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None +# html_short_title = 'ViSP documentation' # The name of an image file (relative to this directory) to place at the top # of the sidebar. diff --git a/modules/python/doc/conversions.rst b/modules/python/doc/conversions.rst index 33e393707a..43e386f1e0 100644 --- a/modules/python/doc/conversions.rst +++ b/modules/python/doc/conversions.rst @@ -22,8 +22,15 @@ they are a foreign concept to all the other Python libraries. For most scientific computing libraries, the standard data representation is based on `NumPy `_. Since most libraries will accept and manipulate these arrays, ViSP provides conversion functions. -To reinterpret a supported ViSP object as a Numpy array, use either: +From ViSP to NumPy +----------------------------------------- + + +Obtaining a view of a ViSP object +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To reinterpret a supported ViSP object as a Numpy array, use either: .. doctest:: @@ -37,6 +44,65 @@ To reinterpret a supported ViSP object as a Numpy array, use either: >>> np.all(np_vec == list_representation) True + >>> vec *= 2.0 + >>> np.all(np_vec == list_representation) + False + +or + +.. doctest:: + + >>> from visp.core import ColVector + >>> import numpy as np + + >>> list_representation = [i for i in range(3)] + >>> vec = ColVector(list_representation) # Initialize a 3 vector from a list + >>> np_vec = np.array(vec, copy=False) # A 1D numpy array of size 3 + + >>> np.all(np_vec == list_representation) + True + + >>> vec *= 2.0 # Modifying the ViSP vector modifies the NumPy view + >>> np.all(np_vec == list_representation) + False + +To obtain a copy of the ViSP representation you can simply use: + +.. doctest:: + + >>> from visp.core import ColVector + >>> import numpy as np + + >>> vec = ColVector(3, 0) + >>> np_vec = vec.numpy().copy() # or np.array(vec, copy=True) + >>> np_vec[0] = 1 + + >>> np_vec[0] == vec[0] + False + +Note that with these methods, some ViSP objects cannot be modified. +That is the case for :py:class:`visp.core.HomogeneousMatrix` and :py:class:`visp.core.RotationMatrix`, where an undesired modification +may lead to an invalid representation (Such as a rotation matrix not conserving its properties) + +Thus, this code will not work: + +.. doctest:: + :options: +IGNORE_EXCEPTION_DETAIL + + >>> from visp.core import RotationMatrix, HomogeneousMatrix + >>> import numpy as np + + >>> R = RotationMatrix() + >>> R.numpy()[0, 1] = 1.0 + Traceback (most recent call last): + File "", line 1, in + ValueError: assignment destination is read-only + + >>> T = HomogeneousMatrix() + >>> R.numpy()[0, 1] = 1.0 + Traceback (most recent call last): + File "", line 1, in + ValueError: assignment destination is read-only diff --git a/modules/python/doc/index.rst b/modules/python/doc/index.rst index a7f1e717d9..d429468c8a 100644 --- a/modules/python/doc/index.rst +++ b/modules/python/doc/index.rst @@ -13,4 +13,3 @@ Welcome to the ViSP Python binding documentation! todos.rst conversions.rst - api.rst diff --git a/modules/python/doc/todos.rst b/modules/python/doc/todos.rst index d1c00348ce..cd33877295 100644 --- a/modules/python/doc/todos.rst +++ b/modules/python/doc/todos.rst @@ -36,12 +36,7 @@ Documentation ---------------- * Generate documentation for: - * Enums * Functions in namespaces etc. - * In classes - - * Constructors - * Operators * Reference python types in Documentation * Prefer Python examples instead of C++ ones ? diff --git a/modules/python/test/test_core.py b/modules/python/test/test_core.py new file mode 100644 index 0000000000..01ffdd0f37 --- /dev/null +++ b/modules/python/test/test_core.py @@ -0,0 +1,39 @@ +def test_pixel_meter_convert_points(): + from visp.core import PixelMeterConversion, CameraParameters + import numpy as np + + h, w = 240, 320 + cam = CameraParameters(px=600, py=600, u0=320, v0=240) + + vs, us = np.meshgrid(range(h), range(w), indexing='ij') # vs and us are 2D arrays + + xs, ys = PixelMeterConversion.convertPoints(cam, us, vs) + # xs and ys have the same shape as us and vs + assert xs.shape == (h, w) and ys.shape == (h, w) + + # Converting a numpy array to normalized coords has the same effect as calling on a single image point + for v in range(h): + for u in range(w): + x, y = PixelMeterConversion.convertPoint(cam, u, v) + + assert x == xs[v, u] and y == ys[v, u] + +def test_meter_pixel_convert_points(): + from visp.core import MeterPixelConversion, CameraParameters + import numpy as np + + h, w = 240, 320 + cam = CameraParameters(px=600, py=600, u0=320, v0=240) + + # We use xs and ys as pixel coordinates here, but it's not really true (it's just more convenient) + ys, xs = np.meshgrid(range(h), range(w), indexing='ij') # vs and us are 2D arrays + + us, vs = MeterPixelConversion.convertPoints(cam, xs, ys) + # xs and ys have the same shape as us and vs + assert us.shape == (h, w) and vs.shape == (h, w) + + # Converting a numpy array to normalized coords has the same effect as calling on a single image point + for y in range(h): + for x in range(w): + u, v = MeterPixelConversion.convertPoint(cam, x, y) + assert u == us[y, x] and v == vs[y, x]