norlab-ulaval / libpointmatcher

An Iterative Closest Point (ICP) library for 2D and 3D mapping in Robotics
BSD 3-Clause "New" or "Revised" License
1.61k stars 544 forks source link

Convert numpy to DataPoints in Python #460

Closed brunoeducsantos closed 3 years ago

brunoeducsantos commented 3 years ago

Hi, Thanks for the awesome work.

Based on icp_simple example,

import numpy as np

from pypointmatcher import pointmatcher as pm
from utils import parse_translation, parse_rotation

PM = pm.PointMatcher
DP = PM.DataPoints

def icp_simple(src,dst,cloud_dimension, is_verbose):
    # Add an initial 2D translation before applying ICP (default: 0,0)
    init_translation = "0,0"
    # Add an initial 2D rotation before applying ICP (default: 1,0;0,1)
    init_rotation =  "1,0;0,1"
    src= DP(src)
    dst=DP(dst)
    # Parse the translation and rotation to be used to compute the initial transformation
    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")
    icp = PM.ICP()
    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(src, init_transfo)

    # Compute the transformation to express data in ref
    T = icp(initialized_data, dst)

    if is_verbose:
        print(f"match ratio: {icp.errorMinimizer.getWeightedPointUsedRatio():.6}")

    # Transform data to express it in ref
    data_out = DP(initialized_data)
    icp.transformations.apply(data_out, T)
    return T

Although I am providing as input a numpy array shape (N_points,2).

Getting the following issue:


TypeError: __init__(): incompatible constructor arguments. The following argument types are supported:
    1. pypointmatcher.pointmatcher.PointMatcher.DataPoints()
    2. pypointmatcher.pointmatcher.PointMatcher.DataPoints(featureLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, descriptorLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, pointCount: int)
    3. pypointmatcher.pointmatcher.PointMatcher.DataPoints(featureLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, descriptorLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, timeLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, pointCount: int)
    4. pypointmatcher.pointmatcher.PointMatcher.DataPoints(arg0: pypointmatcher.pointmatcher.PointMatcher.DataPoints)
    5. pypointmatcher.pointmatcher.PointMatcher.DataPoints(features: numpy.ndarray[float32[m, n]], featureLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels)
    6. pypointmatcher.pointmatcher.PointMatcher.DataPoints(features: numpy.ndarray[float32[m, n]], featureLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, descriptors: numpy.ndarray[float32[m, n]], descriptorLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels)
    7. pypointmatcher.pointmatcher.PointMatcher.DataPoints(features: numpy.ndarray[float32[m, n]], featureLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, descriptors: numpy.ndarray[float32[m, n]], descriptorLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels, times: numpy.ndarray[int64[m, n]], timeLabels: pypointmatcher.pointmatcher.PointMatcher.DataPoints.Labels)

Invoked with: array([[  1.        ,  51.33557047],
       [ 81.4       ,  93.96375839],
       [161.8       , 136.59194631],
       [242.2       , 179.22013423],
       [322.6       , 221.84832215],
       [  1.        ,  49.29707113],
       [ 81.4       ,  95.72050209],
       [161.8       , 142.14393305],
       [242.2       , 188.56736402],
       [322.6       , 234.99079498],
       [  1.        ,  49.29707113],
       [ 81.4       ,  95.72050209],
       [161.8       , 142.14393305],
       [242.2       , 188.56736402],
       [322.6       , 234.99079498],
       [  1.        ,  49.29707113],
       [ 81.4       ,  95.72050209],
       [161.8       , 142.14393305],
       [242.2       , 188.56736402],
       [322.6       , 234.99079498],
       [  1.        ,  49.29707113],
       [ 81.4       ,  95.72050209],
       [161.8       , 142.14393305],
       [242.2       , 188.56736402],
       [322.6       , 234.99079498],
       [  1.        ,  49.29707113],
       [ 81.4       ,  95.72050209],
       [161.8       , 142.14393305],
       [242.2       , 188.56736402],
       [322.6       , 234.99079498],
       [  1.        , 145.56      ],
       [ 81.4       , 184.152     ],
       [161.8       , 222.744     ],
       [242.2       , 261.336     ],
       [322.6       , 299.928     ],
       [  1.        , 145.56      ],
       [ 81.4       , 184.152     ],
       [161.8       , 222.744     ],
       [242.2       , 261.336     ],
       [322.6       , 299.928     ],
       [  1.        , 145.56      ],
       [ 81.4       , 184.152     ],
       [161.8       , 222.744     ],
       [242.2       , 261.336     ],
       [322.6       , 299.928     ],
       [  1.        , 145.56      ],
       [ 81.4       , 184.152     ],
       [161.8       , 222.744     ],
       [242.2       , 261.336     ],
       [322.6       , 299.928     ]])

What am I missing ?

Using Python 3.9 and Ubuntu 20.04

Thanks, Bruno

aguenette commented 3 years ago

Hi @brunoeducsantos!

When you are using a 2D or a 3D array to initialize a DataPoints, you must also provide Labels for the features of your array. So, for your 2D array, you need to do something like the following code snippet before creating your DataPoints:

label_x = DP.Label('x', 1) # first argument is the name of the label and second argument is the dimension spanning of the label
label_y = DP.Label('y', 1)
labels = DP.Labels([label_x, label_y])
# or 
labels = DP.Labels([DP.Label('x', 1), DP.Label('y', 1)])

src = DP(src, labels)
dst = DP(dst, labels)

So, the first coordinate of each point will be labelled 'x' and the second coordinate 'y'.

Also, I need to warn you about the fact that it's not recommended to use Python 3.9.0 with pybind11 < 2.6.0, which should be the case, since the pybind11's version that is recommended in the tutorials is 2.5.0. But, if your Python version is newer than 3.9.0, you should be fine, if not, you should downgrade to Python 3.8. For more detailed information about that issue, here's the link to pybind11 github page (it's the first two paragraphs of the README).

brunoeducsantos commented 3 years ago

Hi @aguenette , Your suggestion sorts out my issue.

I have just one following up question. My current code is:

import numpy as np

from pypointmatcher import pointmatcher as pm
from utils import parse_translation, parse_rotation

PM = pm.PointMatcher
DP = PM.DataPoints

def icp_simple(src,dst,config_file, is_verbose):
    #convert numpy to DM
    label_x = DP.Label('x', 1) # first argument is the name of the label and second argument is the dimension spanning of the label
    label_y = DP.Label('y', 1)
    labels = DP.Labels([label_x, label_y])
    src = DP(src, labels)
    dst = DP(dst, labels)

    # Parse the translation and rotation to be used to compute the initial transformation
    icp = PM.ICP()
     # load YAML config
    icp.loadFromYaml(config_file)

    # Compute the transformation to express data in ref
    T = icp(src, dst)

    if is_verbose:
        print(f"match ratio: {icp.errorMinimizer.getWeightedPointUsedRatio():.6}")

    return T

From T , I am getting an NXN matrix instead of homogenous matrix. My input data is shape : (N_points,2) (2D data).

What am I missing out?

Thanks, Bruno

pomerlef commented 3 years ago

I think you will need to add label_pad = DP.Label('pad', 1) when you are building your point cloud. And of course labels = DP.Labels([label_x, label_y, label_pad]).

Then, you will need to provide your point cloud as homogeneous coordinates using a matrix that is (3 x N), the last row being only ones. It looks tedious at the construction of the point cloud object, but it save time during the computation of ICP.

So, I guess you will need to first use np.transpose(src) and then append ones.

brunoeducsantos commented 3 years ago

Thanks @pomerlef ! That sorts it out. Thanks a lot for your help!