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 10 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
10 changes: 9 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)

Expand Down Expand Up @@ -446,6 +446,14 @@ if (BUILD_TESTS)
add_subdirectory(utest)
endif()

# ========================= Python module =============================
option(BUILD_PYTHON_MODULE "Build the python module for libpointmatcher" OFF)
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
189 changes: 189 additions & 0 deletions doc/CompilationPython.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
| [Tutorials Home](index.md) | [Previous](ImportExport.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 obviously 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** | |
| catch | 1.10.0 |
| python3-dev | 3.6.7 |
| pytest | 5.4.3 |
aguenette marked this conversation as resolved.
Show resolved Hide resolved

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

```bash
sudo apt install catch python3-dev
```

and `pytest` with `pip` :

```bash
pip3 install pytest
```
aguenette marked this conversation as resolved.
Show resolved Hide resolved

> ***Note :*** pybind11 is also compatible with Python2.7, which means that it is also possible to generate a module for Python2.7. But, this hasn't been tested. You can refer to the pybind11 [official documentation](https://pybind11.readthedocs.io/en/latest/index.html) for more informations on what you can do with pybind11 and how to do it.
aguenette marked this conversation as resolved.
Show resolved Hide resolved

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

## pybind11

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 [repository](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 the pybind11 tests. Next, you can install pybind11 by running this command :
aguenette marked this conversation as resolved.
Show resolved Hide resolved

```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
```

Before going any further, the `CMakeLists.txt` file from the `/python` directory needs to be modified as follows :
aguenette marked this conversation as resolved.
Show resolved Hide resolved

```cmake
#find_package(pybind11 2.5.0 REQUIRED)
add_subdirectory(pybind11) # add this line

# Everything in between remains unchanged

#if (pybind11_FOUND)
# message(STATUS "pybind11 v${pybind11_VERSION}")

pybind11_add_module(pypointmatcher ${PYBIND11_SOURCES})

target_link_libraries(pypointmatcher
PRIVATE
pointmatcher
${EXTERNAL_LIBS})

add_dependencies(pypointmatcher pointmatcher)

install(TARGETS pypointmatcher LIBRARY DESTINATION ${PYTHON_INSTALL_TARGET})
#else ()
# message(FATAL_ERROR "pybind11 is required! Please follow the \"Compiling \
#libpointmatcher's with python\" instructions from the official libpointmatcher's documentation.")
#endif ()
```

> ***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 python environment before proceeding with the next steps.*
aguenette marked this conversation as resolved.
Show resolved Hide resolved

#### 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's 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 :

```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's path 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 looks like this :

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

##### 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 correspond to a valid location or 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
```

***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
```
4 changes: 2 additions & 2 deletions examples/align_sequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ int main(int argc, char *argv[])
{
validateArgs(argc, argv);

typedef PointMatcher<float> PM;
typedef PointMatcherIO<float> PMIO;
typedef PointMatcher<double> PM;
typedef PointMatcherIO<double> PMIO;
aguenette marked this conversation as resolved.
Show resolved Hide resolved
typedef PM::TransformationParameters TP;
typedef PM::DataPoints DP;

Expand Down
4 changes: 2 additions & 2 deletions examples/build_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ int main(int argc, char *argv[])
{
validateArgs(argc, argv);

typedef PointMatcher<float> PM;
typedef PointMatcherIO<float> PMIO;
typedef PointMatcher<double> PM;
typedef PointMatcherIO<double> PMIO;
typedef PM::TransformationParameters TP;
typedef PM::DataPoints DP;

Expand Down
4 changes: 2 additions & 2 deletions examples/compute_overlap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ int main(int argc, char *argv[])
{
validateArgs(argc, argv);

typedef PointMatcher<float> PM;
typedef PointMatcherIO<float> PMIO;
typedef PointMatcher<double> PM;
typedef PointMatcherIO<double> PMIO;
typedef PM::Matrix Matrix;
typedef PM::TransformationParameters TP;
typedef PM::DataPoints DP;
Expand Down
2 changes: 1 addition & 1 deletion examples/filterProfiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ using namespace PointMatcherSupport;
using namespace std;
using namespace boost;

typedef PointMatcher<float> PM;
typedef PointMatcher<double> PM;
typedef PM::DataPoints DP;
typedef PM::Parameters Parameters;

Expand Down
2 changes: 1 addition & 1 deletion examples/icp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

using namespace std;

typedef PointMatcher<float> PM;
typedef PointMatcher<double> PM;
typedef PM::DataPoints DP;
typedef PM::Parameters Parameters;
typedef PointMatcherSupport::CurrentBibliography CurrentBibliography;
Expand Down
2 changes: 1 addition & 1 deletion examples/icp_advance_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using namespace std;
using namespace PointMatcherSupport;

typedef PointMatcher<float> PM;
typedef PointMatcher<double> PM;
typedef PM::DataPoints DP;
typedef PM::Parameters Parameters;

Expand Down
2 changes: 1 addition & 1 deletion examples/icp_customized.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ int main(int argc, char *argv[])
bool isCSV = true;
validateArgs(argc, argv, isCSV);

typedef PointMatcher<float> PM;
typedef PointMatcher<double> PM;
typedef PM::DataPoints DP;

// Load point clouds
Expand Down
2 changes: 1 addition & 1 deletion examples/icp_simple.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ int main(int argc, char *argv[])
bool isCSV = true;
validateArgs(argc, argv, isCSV);

typedef PointMatcher<float> PM;
typedef PointMatcher<double> PM;
typedef PM::DataPoints DP;

// Load point clouds
Expand Down
2 changes: 1 addition & 1 deletion examples/list_modules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ using namespace std;
using namespace PointMatcherSupport;

typedef PointMatcherSupport::Parametrizable::ParametersDoc ParametersDoc;
typedef PointMatcher<float> PM;
typedef PointMatcher<double> PM;

void printBibliographyHeader(const CurrentBibliography::Mode mode)
{
Expand Down
56 changes: 56 additions & 0 deletions examples/python/icp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import numpy as np

from pypointmatcher.pointmatcher import PointMatcher
from examples.python.utils import parse_translation, parse_rotation

PM = PointMatcher
DP = PM.DataPoints

config_file = "../data/default.yaml"
output_base_file = "tests_output/icp/"
is_3D = True
init_translation = "1,1,1" if is_3D else "1,1"
init_rotation = "1,0,0;0,1,0;0,0,1" if is_3D else "1,0;0,1"

if is_3D:
# 3D point clouds
ref = DP.load('../data/car_cloud400.csv')
data = DP.load('../data/car_cloud401.csv')
test_base = "3D"
else:
# 2D point clouds
ref = DP.load('../data/2D_twoBoxes.csv')
data = DP.load('../data/2D_oneBox.csv')
test_base = "2D"

icp = PM.ICP()

icp.loadFromYaml(config_file)
# icp.setDefault()

cloud_dimension = ref.getEuclideanDim()

assert cloud_dimension == 2 or cloud_dimension == 3, "Invalid input point clouds dimension"

translation = parse_translation(init_translation, cloud_dimension)
rotation = parse_rotation(init_rotation, cloud_dimension)
init_transfo = np.matmul(translation, rotation)

rigid_trans = PM.get().TransformationRegistrar.create("RigidTransformation")

if not rigid_trans.checkParameters(init_transfo):
print("Initial transformations is not rigid, identiy will be used")
init_transfo = np.identity(cloud_dimension + 1)

initialized_data = rigid_trans.compute(data, init_transfo)

T = icp(initialized_data, ref)

data_out = DP(initialized_data)
icp.transformations.apply(data_out, T)

ref.save(output_base_file + f"{test_base}_test_ref.vtk")
data.save(output_base_file + f"{test_base}_test_data_in.vtk")
data_out.save(output_base_file + f"{test_base}_test_data_out.vtk")

print(f"{test_base} ICP transformation:\n{T}".replace("[", " ").replace("]", " "))
Loading