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

[WIP] feature/python_module : Adding libpointmatcher's Python bindings (#222) #397

Merged
merged 32 commits into from
Nov 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3e6227b
Adding libpointmatcher's Python bindings
aguenette Aug 6, 2020
b80b5ac
Merge branch 'master' into feature/python_module
aguenette Aug 12, 2020
c519c15
Improved CMakeLists.txt
aguenette Aug 14, 2020
9f4d0c9
Adding the compilation tutorial to the documentation.
aguenette Aug 14, 2020
6b909ea
Fixed error in CMakeLists.txt.
simonpierredeschenes Aug 15, 2020
8170e5d
Added the IO module
aguenette Aug 18, 2020
35926a8
Fixed icp_advance_api.py and icp_customized.py examples
aguenette Aug 18, 2020
ee50bb4
Change C++ examples base type from float to double
aguenette Aug 18, 2020
6ccdc71
Change the way that vectors are bonded
aguenette Aug 18, 2020
5c3a25c
Fixed overloaded methods ambiguities based on constness
aguenette Aug 18, 2020
1c0bcaa
Improved the CMakeLists.txt
aguenette Aug 21, 2020
5c011a9
Forgot to name the members of PLYElement.
aguenette Aug 21, 2020
2969fa6
Improved compilation's tutorial
aguenette Aug 21, 2020
a03e81a
Added 3 new examples
aguenette Aug 21, 2020
d550d13
Removed a pure virtual method that didn't have to be mapped
aguenette Aug 21, 2020
61517b3
Fixed PointMatcher get() method
aguenette Aug 21, 2020
073f2a4
Replaced double type declaration to ScalarType type declaration
aguenette Aug 25, 2020
ed2a295
Moved header's #include directive to source
aguenette Aug 25, 2020
c724bd2
Renamed #include directive
aguenette Aug 25, 2020
4ba0b15
Removed unnecessary namespace aliases
aguenette Aug 25, 2020
dbf15ed
Moved header's aliases declaration to source
aguenette Aug 25, 2020
da515b9
Named members and parameters that were forgotten
aguenette Aug 25, 2020
c900da2
Renamed files to conform to the Python naming convention
aguenette Aug 25, 2020
d463b11
Made std::vector and std::map opaque
aguenette Aug 25, 2020
9936878
Improved the Python examples
aguenette Aug 26, 2020
dcde397
Added the usage tutorial
aguenette Aug 26, 2020
3fa79e3
Minor corrections in Python documentation.
simonpierredeschenes Aug 30, 2020
d662059
Merge branch 'master' of github.com:AlexandreG87/libpointmatcher into…
aguenette Oct 20, 2020
d937274
Merge branch 'master' into feature/python_module
pomerlef Oct 27, 2020
e9dc721
Revert change C++ examples base type from float to double.
aguenette Oct 29, 2020
956aadb
Reorganization of the namespace structure
aguenette Nov 1, 2020
0c1c68f
Merge branch 'master' into feature/python_module
aguenette Nov 2, 2020
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
11 changes: 10 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 2.8.11)
cmake_minimum_required(VERSION 2.8.12)
aguenette marked this conversation as resolved.
Show resolved Hide resolved

include(CheckSymbolExists)
include(GNUInstallDirs) # populate CMAKE_INSTALL_{LIB,BIN}DIR
Expand Down Expand Up @@ -467,6 +467,15 @@ if (BUILD_TESTS)
add_subdirectory(utest)
endif()

# ========================= Python module =============================
option(BUILD_PYTHON_MODULE "Build the python module for libpointmatcher" OFF)
option(USE_SYSTEM_PYBIND11 "Use the system installed pybind11 rather than using a git submodule" ON)
set(PYTHON_INSTALL_TARGET "" CACHE PATH "Target where to install the python module")

if (BUILD_PYTHON_MODULE)
add_subdirectory(python)
endif()

#=================== allow find_package() =========================
#
# the following case be used in an external project requiring libpointmatcher:
Expand Down
179 changes: 179 additions & 0 deletions doc/CompilationPython.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
| [Tutorials Home](index.md) | [Previous](UnitTestDev.md) | [Next](PythonModule.md) |
| :--- | :---: | ---: |

# Compiling libpointmatcher with Python

This tutorial presents the different steps of compiling *pypointmatcher*, the libpointmatcher's Python module, on Ubuntu and Mac OS X.

## Prerequisites

To get started, you will need the same prerequisites as libpointmatcher, but also some additional dependencies as listed here:

| Name | Version <br> (Tested August 2020 on Ubuntu 18.04) |
| :--- | :---: |
| pybind11 | 2.5.0 |
| Python3 | 3.6.9 |
|||
| **Dependencies** | |
| python3-dev | 3.6.7 |
| catch | 1.10.0 |
| pytest | 5.4.3 |
aguenette marked this conversation as resolved.
Show resolved Hide resolved

`pytest` needs to be installed with `pip`:

```bash
pip3 install pytest
```

But `catch` and `python3-dev` need to be installed with a package manager:

*Ubuntu users:*

```bash
sudo apt install catch python3-dev
```
aguenette marked this conversation as resolved.
Show resolved Hide resolved

*Mac OS users*:

```bash
brew install catch2
```

The rest of this tutorial will guide you through the necessary steps to compile pypointmatcher.

## pybind11

pybind11 is a library used to create Python bindings of existing C++ code and vice versa. So, in order to be able to compile pypointmatcher, you must either install pybind11 on your system or add it as a git submodule in the libpointmatcher's `contrib/` directory. You must then create a symbolic link to this git submodule in the `python/` directory. Go [here](#installing-pybind11) for the installation steps or [here](#adding-pybind11) for the git sudmodule steps.

### Installing pybind11 (recommended) <a name="installing-pybind11"></a>

The very first step is to clone [pybind11](https://github.com/pybind/pybind11) into a directory of your choice.

At the moment, pypointmatcher can only be compiled with **version 2.5.0** of pybind11. To install the right version, run the following commands:

```bash
cd pybind11
git checkout v2.5.0
```

Once this is done, run the following commands:

```bash
mkdir build && cd build
cmake ..
make check -j 4
```

This will both compile and run pybind11 tests. Next, you can install pybind11 by running this command:

```bash
sudo make install
aguenette marked this conversation as resolved.
Show resolved Hide resolved
```

Once this is done, return to libpointmatcher's `build/` directory.

You're now ready to proceed to the [configuration step](#configuration).

### Adding pybind11 as a `git` submodule <a name="adding-pybind11"></a>

An alternative to installing pybind11 on your system is to add its repository as a git submodule and create a symbolic link into the `python/` directory. To do this, you will first need to clone the repository as a git submodule by running the following commands in your terminal from the `contrib/` directory.

```bash
cd contrib
git submodule add https://github.com/pybind/pybind11.git
```

This will add pybind11 as a git submodule of libpointmatcher into the `contrib/` directory. Then, still from the `contrib/` directory, run this command to create a symbolic link to pybind11 in the `python/` directory:

```bash
ln -sr pybind11 ../python/pybind11
```

At the moment, pypointmatcher can only be compiled with **version 2.5.0** of pybind11. To install the right version, run the following commands:

```bash
cd pybind11
git checkout v2.5.0
```

Finally, tell CMake that you want to use pybind11 as a git submodule by setting the `USE_SYSTEM_PYBIND11` variable to `OFF`:

```bash
cmake -D USE_SYSTEM_PYBIND11=OFF ..
```

> ***IMPORTANT:*** When this method is used, it is very important to checkout the version **2.5.0** of pybind11 or it will be impossible to generate the build files.

Once this is done, return to libpointmatcher's `build/` directory.

You're now ready to proceed to the [configuration step](#configuration).

## Configuring the variables <a name="configuration"></a>

> ***Note:*** *It is recommended to create a virtual environment before proceeding with the next steps. For this, you can use the [virtualenv tool](https://virtualenv.pypa.io/en/stable/). If you are not familiar with Python virtual environments, you can [read this tutorial](https://realpython.com/python-virtual-environments-a-primer/), which explains very well the reasons for using a virtual environment, or [watch this video tutorial](https://youtu.be/nnhjvHYRsmM)*

#### Specifying the path

First, you need to specify where you want the module to be installed. To do so, you must provide the path by setting the CMake variable `PYTHON_INSTALL_TARGET` with an absolute path to your Python environment `site-packages` location. This can be achieve manually or automatically.

##### The manual way:

Launch the Python interpreter and run the following commands to find the path to the `site-packages/` directory of your current Python environment:

```bash
>>> import site
>>> site.getsitepackages()
```

> ***Note:*** If you are using the system's Python environment, replace the `getsitepackages()` function call by `getusersitepackages()`.

This will output a list of installation paths for your current Python environment. Now, choose the one that is located in the `python_env_path/lib/python3.x/` directory. The command to run should look like this:

```bash
cmake -D PYTHON_INSTALL_TARGET=python_env_path/lib/python3.x/site-packages ..
```

> ***NOTE:*** Replace the `x` with your Python minor version number.

##### The automatic way:

If you don't want to set the path manually, here's a command that should automatically pick the right one for you:

```bash
cmake -D PYTHON_INSTALL_TARGET=$(python3 -c "import site; print(site.getsitepackages()[0])") ..
```

> ***Note:*** If you are using the system's Python environment, replace the `site.getsitepackages()[0]` by `site.getusersitepackages()`.

> ***IMPORTANT:*** *This last example is the default behavior if no path has been set before compiling the module.* ***Please, make sure that this corresponds to a valid location or the module will be installed in a wrong location and this will lead to an import error.***

#### Enabling the compilation

By default, pypointmatcher compilation is disabled. In order to compile it, you must set the CMake variable `BUILD_PYTHON_MODULE` to `ON`:

```bash
cmake -D BUILD_PYTHON_MODULE=ON ..
aguenette marked this conversation as resolved.
Show resolved Hide resolved
```

Everything is now set up to proceed to the compilation and the installation.

## Compilation

Now, to compile pypointmatcher into the `build/` directory, run the following command:

```bash
make pypointmatcher -j N
```

where `N` is the number of jobs (or threads) you allow at once on your computer for the compilation. If no argument is passed after `-j`, there will be no limit to the number of jobs.

> ***Note:*** *Depending on your system, the compilation can take quite some time, so consider leaving the `-j` command with no argument in order to speed up this step.*

## Installation

And finally, to install the module on your system, run the following command:

```bash
sudo make install
```

67 changes: 67 additions & 0 deletions doc/PythonModule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
| [Tutorials Home](index.md) | [Previous](CompilationPython.md) |
| :--- | ---: |

# Using libpointmatcher with Python

This tutorial presents the different things to know before using *pypointmatcher*, the libpointmatcher's Python module.

## Differences between the C++ and Python APIs

Despite the fact that pypointmatcher and libpointmatcher have very similar APIs, the fact remains that they differ in some ways. So, why not start by listing these differences.

#### STL containers vs Python data structures

pybind11 provides automatic conversion between C++'s STL containers and their Python equivalent data structures. That is, `std::vector`/`std::deque`/`std::list`/`std::array` are converted into a Python `list`, `std::set`/`std::unordered_set` are converted into a Python `set` and finally `std::map`/`std::unordered_map` are converted into a Python `dict`.

Although this can be very useful, it comes with some major downsides, [which you can see here](https://pybind11.readthedocs.io/en/latest/advanced/cast/stl.html#automatic-conversion), hence the need to make the classes inheriting from STL containers and some `typedef` *"opaque"*. For the moment, only the `std::vector` and the `std::map` containers are made *"opaque"*, i.e. they keep the same name as the one used in libpointmatcher, but they adopt the behavior of a `list` and a `dict` respectively.

> ***Note:*** This also means that these opaque STL containers must be used in constructors and methods that accept one of these types as parameter.

For more information about pybind11 STL containers conversion, visit [this section](https://pybind11.readthedocs.io/en/latest/advanced/cast/stl.html) of the official documentation.

#### Eigen vs Numpy

pybind11 also provides transparent conversion between *Eigen*'s `Matrix`, `Map` and `SparseMatrix` and *numpy*'s `array` and `ndarray` data types. That is, you can seamlessly use *numpy*'s `ndarray` instead of *Eigen*'s `Vector` or `Matrix`.

For more information about pybind11 *Eigen* to *numpy* data type conversion, visit [this section](https://pybind11.readthedocs.io/en/latest/advanced/cast/eigen.html) of the official documentation.

#### Overloaded methods based on constness

In libpointmatcher, more precisely in the `DataPoints` class, some methods are overloaded based on constness, i.e. they will be called with a constant `DataPoints`. So, to avoid ambiguous calls, the suffix `_const` has been appended to the method names. E.g. in the `compute_overlap.py` example, the `getDescriptorViewByName("inliers")` method was calling the `const` version before this fix. For more information on pybind11 overloaded method mechanisms, visit [this section](https://pybind11.readthedocs.io/en/latest/classes.html#overloaded-methods) of the official documentation.

#### Contructors/methods with std::istream or std::ostream as paramater

Some constructors and methods of libpointmatcher have as parameter either an `std::istream` to build an object from a YAML configuration file or an `std::ostream` to dump information. pybind11 doesn't allow to call these constructors/methods with their Python equivalent, i.e. `sys.stdin` and `sys.stdout`. So, to get around this problem, the constructors/methods having a `std::istream` as parameter must be used with a `std::string` instead and those having a `std::ostream` must be used without parameter.

## Structure of pypointmatcher

Before going further, here is the general structure of pypointmatcher to give you a better idea of how to use it.

```textmate
pypointmatcher # The main module.
|
|_ pointmatcher # The submodule containing functions and classes that are dependent on scalar types.
|
|_ pointmatchersupport # The submodule containing everything defined in the
| # pointmatchersupport namespace, i.e. functions and classes which are not dependent on scalar types.
|
|_ datapointsfilters # The submodule containing the differents DataPointsFilters.
|
|_ errorminimizers # The submodule containing the differents ErrorMinimizers.
```

## General use of pypointmatcher

To help you get familiar with pypointmatcher, some C++ examples have been translated to show you how it is possible to use the Python version of libpointmatcher.

Now that you know the major differences between the C++ and Python API, we suggest newcomers and those who are less familiar with the library to follow, or re-follow, the tutorials in the beginner and advanced sections using the Python examples instead, in order to have a better understanding of how libpointmatcher can be used with Python.

Experienced users of libpointmatcher can, if they wish, take a look at the different Python examples located in the `examples/python/` directory and compare their uses with their C++ counterparts, keeping in mind what makes them different from each other.

The major difference is that they don't require command line arguments like the C++ version. All you have to do is open a terminal and go to the `examples/python/` directory and run one of the examples as a Python module script:

```bash
python3 icp_simple.py
```

> ***Note:*** All the information to run a default ICP algorithm is already in the examples, it is up to the user to change these values in order to run a custom ICP algorithm. So take the time to understand what the code does in the examples before making changes.
4 changes: 2 additions & 2 deletions doc/UnitTestDev.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
| [Tutorials Home](index.md) | [Previous](TransformationDev.md) |
| :--- | ---: |
| [Tutorials Home](index.md) | [Previous](TransformationDev.md) | [Next](CompilationPython.md) |
| :--- | :---: | ---: |

# Testing libpointmatcher Modules

Expand Down
15 changes: 10 additions & 5 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

# Tutorials

This page lists the available tutorials for libpointmatcher. The [Beginner Section](#beginner) is aimed at the more casual user and contains high-level information on the various steps of point cloud registration. The [Advanced Section](#advanced) is targeted at those with existing experience with point cloud registration and proficiency in C++ development. Those who wish to contribute to libpointmatcher can follow the guidelines in the [Developer](#developer) section.
This page lists the available tutorials for libpointmatcher. The [Beginner](#beginner) section is aimed at the more casual user and contains high-level information on the various steps of point cloud registration. The [Advanced](#advanced) section is targeted at those with existing experience with point cloud registration and proficiency in C++ development. Those who wish to contribute to libpointmatcher can follow the guidelines in the [Developer](#developer) section. Finally, the [Python](#python) section is intended for those who are interested in using libpointmatcher with *Python*.

## Compilation<a name="compilation"></a>
## Compilation <a name="compilation"></a>

- [Ubuntu: How to compile libpointmatcher](CompilationUbuntu.md)
- [Mac OS X: How to compile libpointmatcher](CompilationMac.md)
- [Windows: How to compile libpointmatcher](CompilationWindows.md)

## Beginner<a name="beginner"></a>
## Beginner <a name="beginner"></a>

- [What is libpointmatcher about?](Introduction.md)
- [What can I do with libpointmatcher?](ApplicationsAndPub.md)
Expand All @@ -24,7 +24,7 @@ This page lists the available tutorials for libpointmatcher. The [Beginner Secti
- [Configuring libpointmatcher using YAML](Configuration.md)
- [Supported file types and importing/exporting point clouds](ImportExport.md)

## Advanced<a name="advanced"></a>
## Advanced <a name="advanced"></a>

- [How to link a project to libpointmatcher?](LinkingProjects.md)
- [How to use libpointmatcher in ROS?](UsingInRos.md)
Expand All @@ -39,12 +39,17 @@ This page lists the available tutorials for libpointmatcher. The [Beginner Secti
- How to do a nearest neighbor search between two point clouds without an ICP object? [See the comments here.](https://github.com/ethz-asl/libpointmatcher/issues/193#issuecomment-276093785)
- How to construct a `DataPoints` from my own point cloud? [See the unit test on `Datapoints` here.](https://github.com/ethz-asl/libpointmatcher/blob/master/utest/ui/DataFilters.cpp#L52)

## Developer<a name="developer"></a>
## Developer <a name="developer"></a>

- [Creating a DataPointsFilter](DataPointsFilterDev.md)
- [Creating a Transformation](TransformationDev.md)
- [Creating unit tests](UnitTestDev.md)

## Python <a name="python"></a>

- [Compiling libpointmatcher with Python](CompilationPython.md)
- [Using libpointmatcher with Python](PythonModule.md)

**Note**: if you don't find what you need, don't hesitate to propose or participate to new tutorials.

---
Expand Down
Loading