Skip to content

Commit

Permalink
More work on doc and testing
Browse files Browse the repository at this point in the history
  • Loading branch information
SamFlt committed Dec 6, 2023
1 parent d9692b2 commit 65a98c7
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 15 deletions.
2 changes: 1 addition & 1 deletion modules/python/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ stubs/visp
stubs/build
*.eggs
doc/_build
doc/_autosummary
doc/_autosummary/*
doc/generated
doc/api.rst
54 changes: 53 additions & 1 deletion modules/python/bindings/include/core/pixel_meter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,39 @@ void bindings_vpPixelMeterConversion(py::class_<vpPixelMeterConversion> &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"));
}
Expand Down Expand Up @@ -106,13 +132,39 @@ void bindings_vpMeterPixelConversion(py::class_<vpMeterPixelConversion> &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"));
}
Expand Down
105 changes: 103 additions & 2 deletions modules/python/config/core.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
3 changes: 2 additions & 1 deletion modules/python/doc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 1 addition & 3 deletions modules/python/doc/conf.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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.
Expand Down
68 changes: 67 additions & 1 deletion modules/python/doc/conversions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://numpy.org/>`_.
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::

Expand All @@ -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 "<stdin>", line 1, in <module>
ValueError: assignment destination is read-only

>>> T = HomogeneousMatrix()
>>> R.numpy()[0, 1] = 1.0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: assignment destination is read-only



Expand Down
1 change: 0 additions & 1 deletion modules/python/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,3 @@ Welcome to the ViSP Python binding documentation!

todos.rst
conversions.rst
api.rst
5 changes: 0 additions & 5 deletions modules/python/doc/todos.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?
Expand Down
39 changes: 39 additions & 0 deletions modules/python/test/test_core.py
Original file line number Diff line number Diff line change
@@ -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]

0 comments on commit 65a98c7

Please sign in to comment.