.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/3_registration/plot_rigid_1_3d.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_3_registration_plot_rigid_1_3d.py: Rigid alignment in 3D with landmarks ==================================== In this example, we demonstrate how to perform rigid registration (also known alignment) of two 3D meshes. We also show how to improve the registration by adding landmarks. .. GENERATED FROM PYTHON SOURCE LINES 9-24 .. code-block:: Python import sys import pykeops import pyvista as pv import torch from pyvista import examples sys.path.append(pykeops.get_build_folder()) import skshapes as sks color_1 = 'tan' color_2 = 'brown' .. GENERATED FROM PYTHON SOURCE LINES 25-30 Load data --------- We load two meshes from the `pyvista` example datasets. We then rescale them to lie in the unit cube to avoid dealing with scale issues. .. GENERATED FROM PYTHON SOURCE LINES 30-50 .. code-block:: Python # shape1 = sks.PolyData(examples.download_human()) shape1 = sks.PolyData(examples.download_woman().rotate_y(90)) shape2 = sks.PolyData(examples.download_doorman()) shape1.point_data.clear() shape2.point_data.clear() def bounds(shape): return torch.max(shape.points, dim=0).values - torch.min(shape.points, dim=0).values lims1 = bounds(shape1) lims2 = bounds(shape2) rescale1 = torch.max(lims1) shape1.points -= torch.min(shape1.points, dim=0).values shape1.points /= rescale1 rescale2 = torch.max(lims2) shape2.points -= torch.min(shape2.points, dim=0).values shape2.points /= rescale2 .. rst-class:: sphx-glr-script-out .. code-block:: none /opt/hostedtoolcache/Python/3.11.9/x64/lib/python3.11/site-packages/skshapes/input_validation/converters.py:102: UserWarning: Mesh has been cleaned and points were removed. point_data is ignored. return func(*new_args, **kwargs) .. GENERATED FROM PYTHON SOURCE LINES 51-55 Plot the data ------------- Let us have a look at the two shapes we want to align. Clearly, they are not aligned and a rigid transformation is needed. .. GENERATED FROM PYTHON SOURCE LINES 55-62 .. code-block:: Python plotter = pv.Plotter() plotter.add_mesh(shape1.to_pyvista(), color=color_1) plotter.add_mesh(shape2.to_pyvista(), color=color_2) plotter.show() .. tab-set:: .. tab-item:: Static Scene .. image-sg:: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_001.png :alt: plot rigid 1 3d :srcset: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_001.png :class: sphx-glr-single-img .. tab-item:: Interactive Scene .. offlineviewer:: /home/runner/work/scikit-shapes/scikit-shapes/doc/source/auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_001.vtksz .. GENERATED FROM PYTHON SOURCE LINES 63-70 Apply the registration ---------------------- We now apply the registration. The meshes points are not in correspondence and we need to use a loss function that can handle this. We use the nearest neighbors loss. Using this loss leads to converge to a local minimum, where the shapesa are aligned upside down. .. GENERATED FROM PYTHON SOURCE LINES 70-89 .. code-block:: Python loss = sks.NearestNeighborsLoss() model = sks.RigidMotion() registration = sks.Registration( model=model, loss=loss, n_iter=2, verbose=True, ) # default optimizer is torch.optim.LBFGS registration.fit(source=shape2, target=shape1) morph = registration.transform(source=shape2) plotter = pv.Plotter() plotter.add_mesh(shape1.to_pyvista(), color=color_1) plotter.add_mesh(morph.to_pyvista(), color=color_2) plotter.show() .. tab-set:: .. tab-item:: Static Scene .. image-sg:: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_002.png :alt: plot rigid 1 3d :srcset: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_002.png :class: sphx-glr-single-img .. tab-item:: Interactive Scene .. offlineviewer:: /home/runner/work/scikit-shapes/scikit-shapes/doc/source/auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_002.vtksz .. rst-class:: sphx-glr-script-out .. code-block:: none [KeOps] Generating code for ArgKMin_Reduction reduction (with parameters 0) of formula Sum((a-b)**2) with a=Var(0,3,0), b=Var(1,3,1) ... OK [pyKeOps] Compiling pykeops cpp 346a12ed2c module ... OK Initial loss : 4.28e-01 = 4.28e-01 + 1 * 0.00e+00 (fidelity + regularization_weight * regularization) Loss after 1 iteration(s) : 1.45e-01 = 1.45e-01 + 1 * 0.00e+00 (fidelity + regularization_weight * regularization) Loss after 2 iteration(s) : 4.74e-02 = 4.74e-02 + 1 * 0.00e+00 (fidelity + regularization_weight * regularization) .. GENERATED FROM PYTHON SOURCE LINES 90-100 Add landmarks ------------- We now add landmarks to the two shapes. Three landmarks (head, left hand, right hand) are enough to greatly improve the registration. If you are running this script locally, you can use the landmark setter application to select the landmarks interactively. If you are seeing this in the gallery, here is a recording of the landmark setter application being used to select the landmarks: .. image:: ../../images/demolandmarks.gif .. GENERATED FROM PYTHON SOURCE LINES 100-135 .. code-block:: Python if not pv.BUILDING_GALLERY: # If not in the gallery, we can use vedo to open the landmark setter # Setting the default backend to vtk is necessary when running in a notebook import vedo vedo.settings.default_backend= 'vtk' sks.LandmarkSetter([shape1, shape2]).start() else: # Set the landmarks manually landmarks1 = [4808, 147742, 1774] landmarks2 = [325, 2116, 1927] shape1.landmark_indices = landmarks1 shape2.landmark_indices = landmarks2 colors = ["red", "green", "blue"] plotter = pv.Plotter() plotter.add_mesh(shape1.to_pyvista(), color=color_1) for i in range(len(shape1.landmark_indices)): plotter.add_points( shape1.landmark_points[i].numpy(), color=colors[i % 3], render_points_as_spheres=True, point_size=25, ) plotter.add_mesh(shape2.to_pyvista(), color=color_2) for i in range(len(shape2.landmark_indices)): plotter.add_points( shape2.landmark_points[i].numpy(), color=colors[i % 3], render_points_as_spheres=True, point_size=25, ) plotter.show() .. tab-set:: .. tab-item:: Static Scene .. image-sg:: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_003.png :alt: plot rigid 1 3d :srcset: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_003.png :class: sphx-glr-single-img .. tab-item:: Interactive Scene .. offlineviewer:: /home/runner/work/scikit-shapes/scikit-shapes/doc/source/auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_003.vtksz .. GENERATED FROM PYTHON SOURCE LINES 136-142 Register again with a loss that includes landmarks -------------------------------------------------- Now the loss is the sum of `NearestNeighborsLoss` and `LandmarkLoss`, the mean L2 distance between the landmarks in the two shapes. The registration now converges to a better solution. .. GENERATED FROM PYTHON SOURCE LINES 142-159 .. code-block:: Python loss_landmarks = sks.NearestNeighborsLoss() + sks.LandmarkLoss() registration = sks.Registration( model=model, loss=loss_landmarks, n_iter=2, verbose=True, ) registration.fit(source=shape2, target=shape1) morph = registration.transform(source=shape2) plotter = pv.Plotter() plotter.add_mesh(shape1.to_pyvista(), color=color_1) plotter.add_mesh(morph.to_pyvista(), color=color_2) plotter.show() .. tab-set:: .. tab-item:: Static Scene .. image-sg:: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_004.png :alt: plot rigid 1 3d :srcset: /auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_004.png :class: sphx-glr-single-img .. tab-item:: Interactive Scene .. offlineviewer:: /home/runner/work/scikit-shapes/scikit-shapes/doc/source/auto_examples/3_registration/images/sphx_glr_plot_rigid_1_3d_004.vtksz .. rst-class:: sphx-glr-script-out .. code-block:: none Initial loss : 1.58e+00 = 1.58e+00 + 1 * 0.00e+00 (fidelity + regularization_weight * regularization) Loss after 1 iteration(s) : 7.36e-02 = 7.36e-02 + 1 * 0.00e+00 (fidelity + regularization_weight * regularization) Loss after 2 iteration(s) : 7.13e-02 = 7.13e-02 + 1 * 0.00e+00 (fidelity + regularization_weight * regularization) .. rst-class:: sphx-glr-timing **Total running time of the script:** (1 minutes 12.836 seconds) .. _sphx_glr_download_auto_examples_3_registration_plot_rigid_1_3d.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_rigid_1_3d.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_rigid_1_3d.py ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_