Note
Go to the end to download the full example code.
Register a Surface with ICP#
Recover the rigid transform between two surfaces with the iterative closest
point implementation behind pyvista.DataSetFilters.align().
import numpy as np
import pyvista as pv
from pyvista import examples
Load a reference surface#
The download_action_figure() scan is an
asymmetric reference mesh.
A camera position that frames the figure clearly.
cpos = pv.CameraPosition(
position=(150, -250, 150),
focal_point=(0, 0, 35),
viewup=(0, 0, 1),
)
Transform a copy away from the reference#
The transformed copy stands in for an incoming scan that needs to be registered back onto the reference.
offset = np.array(target.length) * 0.4
transform = pv.Transform().rotate_x(25).rotate_z(-35).translate((offset, -offset, offset))
source = target.transform(transform, inplace=False)
pl = pv.Plotter()
pl.add_mesh(target, color='lightgray', opacity=0.6)
pl.add_mesh(source, color='tomato', opacity=0.8)
pl.camera_position = cpos
pl.show()

Recover the rigid transform#
pyvista.DataSetFilters.align() runs ICP and returns both the aligned
mesh and the recovered transform matrix.
aligned, matrix = source.align(target, return_matrix=True)
_, closest_points = target.find_closest_cell(aligned.points, return_closest_point=True)
aligned['distance_to_target'] = np.linalg.norm(aligned.points - closest_points, axis=1)
pl = pv.Plotter(shape=(1, 2))
pl.subplot(0, 0)
pl.add_mesh(target, color='lightgray', opacity=0.6)
pl.add_mesh(source, color='tomato', opacity=0.8)
pl.camera_position = cpos
pl.subplot(0, 1)
pl.add_mesh(target, color='lightgray', opacity=0.4)
pl.add_mesh(aligned, scalars='distance_to_target', cmap='viridis')
pl.camera_position = cpos
pl.link_views()
pl.show()

Inspect the recovered transform#
The returned matrix maps the transformed copy back onto the reference.
array([[ 0.819, -0.574, -0. , -93.122],
[ 0.52 , 0.742, 0.423, -13.376],
[ -0.242, -0.346, 0.906, -67.538],
[ 0. , 0. , 0. , 1. ]])
Measure the residual distances#
A successful registration leaves a small point-to-surface residual.
(np.float64(1.5467277336856347e-05), np.float64(4.892452988130443e-05))
Total running time of the script: (0 minutes 1.663 seconds)