Skip to content

Register Point Clouds Using Point to Point ICP

SUMMARY

Register Point Clouds Using Point to Point ICP aligns two point clouds using the classic ICP (Iterative Closest Point) algorithm.

It repeatedly finds each source point's closest target point, computes the rigid transform that minimizes those point-to-point distances, applies it, and repeats until convergence or max_iterations. It requires the two clouds to already be roughly aligned, within max_correspondence_distance — use a coarse method first (the SDK's register_point_clouds_using_centroid_translation, register_point_clouds_using_cuboid_translation_sampler_icp, or register_point_clouds_using_fast_global_registration) if they aren't. Compare with register_point_clouds_using_point_to_plane_icp, which usually converges faster and more accurately when normals are available. The function returns the 4x4 transform found, not an already-moved point cloud — apply it with apply_transform_to_point_cloud.

Use this Skill when you want to refine an already roughly-aligned point cloud pair to a precise final alignment, without relying on surface normals.

The Skill

python
from telekinesis import vitreous
import numpy as np

transformation_matrix = vitreous.register_point_clouds_using_point_to_point_icp(
    source_point_cloud=source_point_cloud,
    target_point_cloud=target_point_cloud,
    initial_transformation_matrix=np.eye(4),
    max_iterations=500,
    max_correspondence_distance=10,
    estimate_scaling=False,
    min_fitness_score=0.0001,
)

aligned_point_cloud = vitreous.apply_transform_to_point_cloud(
    point_cloud=source_point_cloud,
    transformation_matrix=transformation_matrix,
    modify_inplace=False,
)
API Reference
Full parameter and return type documentation for register_point_clouds_using_point_to_point_icp.
View Reference →

Data Transfer Notice

There is no longer a fixed limit of 1 million points per request. However, very large datasets may result in slower data transfer and processing times. We are continuously optimizing performance as part of our beta program, with ongoing improvements to enhance speed and reliability.

Example

Source and Target Point Clouds

Raw sensor input i.e. target point cloud in green and object model in red

Registered Point Clouds

Registered source point cloud (red) aligned to target point cloud (green) using point-to-point ICP

The Code

python
"""
Demonstrates aligning point clouds using Point-to-Point Iterative Closest Point (ICP).
"""

import numpy as np
from loguru import logger
import rerun as rr

from telekinesis import vitreous, datatypes


def register_point_clouds_using_point_to_point_icp_example():
    """
    Aligns point clouds using Point-to-Point Iterative Closest Point (ICP).

    Iteratively refines alignment by minimizing point-to-point distances.
    Requires good initial alignment.
    """
    # ===================== Load Data ==========================================
    source_url = "https://assets.telekinesis.ai/examples/v1/point_clouds/gusset_0_icp_alignment.ply"
    target_url = "https://assets.telekinesis.ai/examples/v1/point_clouds/gusset_0_preprocessed.ply"
    source_point_cloud = datatypes.PointCloud.from_url(url=source_url, use_cache=True)
    target_point_cloud = datatypes.PointCloud.from_url(url=target_url, use_cache=True)

    # ===================== Run Skill ==========================================
    transformation_matrix = vitreous.register_point_clouds_using_point_to_point_icp(
        max_iterations=500,
        max_correspondence_distance=10,
        estimate_scaling=False,
        min_fitness_score=0.0001,
        source_point_cloud=source_point_cloud,
        target_point_cloud=target_point_cloud,
        initial_transformation_matrix=np.eye(4),
    )

    # ===================== Log ================================================
    logger.success(
        f"Registered {source_point_cloud} to {target_point_cloud} using point-to-point ICP"
    )
    logger.success(f"Results: {transformation_matrix}")
    logger.info(f"Transformation matrix data: {transformation_matrix.data}")
    logger.info(f"Transformation matrix shape: {transformation_matrix.shape}")
    logger.info(f"Transformation matrix ndim: {transformation_matrix.ndim}")
    logger.info(f"Transformation matrix dtype: {transformation_matrix.dtype}")

    # ===================== Visualization  (Optional) ===========================
    aligned_source_point_cloud = vitreous.apply_transform_to_point_cloud(
        point_cloud=source_point_cloud,
        transformation_matrix=transformation_matrix,
        modify_inplace=False,
    )

    rr.init("register_point_clouds_using_point_to_point_icp_example", spawn=True)
    datatypes.visualize(source_point_cloud, entity_path="/1-before_registration_source")
    datatypes.visualize(target_point_cloud, entity_path="/2-before_registration_target")
    datatypes.visualize(
        aligned_source_point_cloud, entity_path="/3-after_registration_source_aligned"
    )


if __name__ == "__main__":
    register_point_clouds_using_point_to_point_icp_example()

Runnable examples are available in the Telekinesis examples repository.

Follow the README in that repository to set up the environment, run this specific example with:

bash
cd telekinesis-examples
python examples/point_cloud/register_point_clouds_using_point_to_point_icp.py

Parameter Configuration

KeyTypeDefaultDescription
source_point_clouddatatypes.PointCloudrequiredThe point cloud to align
target_point_clouddatatypes.PointCloudrequiredThe point cloud to align to
initial_transformation_matrixdatatypes.Mat4x4 | np.ndarray | list[list[float]]np.eye(4)A 4x4 transform applied to source_point_cloud before ICP starts — should already bring it within max_correspondence_distance of target_point_cloud (use the identity if they're already roughly aligned)
max_iterationsdatatypes.Int | int50Maximum number of ICP iterations to run
max_correspondence_distancedatatypes.Float | float | int0.05Maximum distance, in meters, at which two points are considered a match
estimate_scalingdatatypes.Bool | boolFalseWhether to also estimate and apply a uniform scale factor between the two point clouds, instead of assuming they're at the same scale
min_fitness_scoredatatypes.Float | float | int0.9The minimum fitness score, in [0, 1], the final result must reach to be accepted

Returns

TypeDescription
datatypes.Mat4x4The 4x4 transform found by ICP — this is the transform itself, not an already-transformed point cloud. Pass it to apply_transform_to_point_cloud (as transformation_matrix) to actually move source_point_cloud's points. Use .data for the raw (4, 4) array.

Raises

ExceptionCondition
TypeErrorA parameter's value does not match its expected type (see the Parameter Configuration table above)
ValueErrorinitial_transformation_matrix is not shape (4, 4) (or, for a list input, doesn't contain only numeric elements)
ConfigurationErrorThe TELEKINESIS_API_KEY environment variable is not set
SerializationErrorThe request input failed to serialize, or the response failed to deserialize
RequestTimeoutErrorThe request to the Vitreous service timed out
TransportErrorA network failure occurred before a response was received
ClientErrorThe Vitreous service rejected the request due to invalid input, invalid data, or another unexpected 4xx response
AuthenticationErrorThe API key was rejected as invalid or expired
AuthenticationServiceErrorThe authentication service was unavailable
ServerErrorThe Vitreous service returned a 5xx or otherwise unexpected error response

How to Tune the Parameters

The register_point_clouds_using_point_to_point_icp Skill exposes four tunable parameters that control the initial guess, the ICP iteration budget, and how the final result is accepted.

initial_transformation_matrix

  • Controls: The starting transform applied to source_point_cloud before ICP begins.
  • Default: np.eye(4) (identity — use this if the clouds are already roughly aligned)
  • Must already bring source_point_cloud within max_correspondence_distance of target_point_cloud — this Skill does not search for a rough alignment itself.

max_iterations

  • Controls: The maximum number of ICP iterations to run.
  • Units: Iterations (integer)
  • Default: 50
  • Increase → more refinement, but slower
  • Decrease → faster, but may stop before fully converging
  • Typical range: 10-200 — use 10-30 for fast, 30-50 for balanced, 50-200 for high precision

max_correspondence_distance

  • Controls: The maximum distance, in meters, at which two points are considered a match.
  • Units: Meters
  • Default: 0.05
  • Increase → allows matching more distant points, risking incorrect matches
  • Decrease → requires closer matches
  • Set to roughly 2-5x the point spacing
  • Typical range: 0.01-0.1 meters — use 0.01-0.02 for dense clouds, 0.02-0.05 for medium, 0.05-0.1 for sparse

estimate_scaling

  • Controls: Whether to also estimate and apply a uniform scale factor between the two point clouds, instead of assuming they're at the same scale.
  • Default: False
  • Set to True only if the clouds might genuinely be at different scales.

min_fitness_score

  • Controls: The minimum fitness score, in [0, 1], the final result must reach to be accepted.
  • Units: Dimensionless (fitness score)
  • Default: 0.9
  • Increase → requires higher-quality alignment; results below the threshold are rejected
  • Decrease → accepts lower-quality alignment
  • Typical range: 0.7-0.99

TIP

If ICP fails to converge or returns a poor fitness score, the problem is usually the starting point, not these parameters — verify initial_transformation_matrix actually brings the clouds within max_correspondence_distance before loosening min_fitness_score or increasing max_iterations.

Where to Use the Skill

Common pipelines include:

  • Fine alignment refinement – tightening a coarse registration (from register_point_clouds_using_fast_global_registration or a sampler-based ICP Skill) into a precise final transform
  • Precise 6D pose estimation – computing an accurate object pose for robotic manipulation once a rough alignment is available
  • Frame-to-frame registration – aligning consecutive scans in a mobile robot or multi-view capture pipeline where consecutive poses are already close
  • Quality inspection – measuring how closely a scanned part matches a reference model after alignment

Alternative Skills

Skillvs. Register Point Clouds Using Point to Point ICP
register_point_clouds_using_point_to_plane_icpMatches by distance to the target's tangent plane using normals instead of raw point-to-point distance; typically converges faster and more accurately when normals are available. Use point-to-point ICP when normals are unavailable or unreliable.
register_point_clouds_using_fast_global_registrationFeature-based registration that doesn't require rough pre-alignment. Run it first if the clouds aren't already roughly positioned within max_correspondence_distance.
register_point_clouds_using_cuboid_translation_sampler_icpGrid-searches translations with repeated ICP runs to establish a rough alignment. An alternative coarse pre-alignment step when only the translation is uncertain.

The SDK's register_point_clouds_using_centroid_translation is the fastest coarse pre-alignment option among the ones this Skill's docstring recommends, useful when only a translation offset (not a rotation) separates the clouds.

When Not to Use the Skill

Do not use Register Point Clouds Using Point to Point ICP when:

  • The point clouds aren't already roughly aligned within max_correspondence_distance – run a coarse method first, such as the SDK's register_point_clouds_using_centroid_translation, register_point_clouds_using_cuboid_translation_sampler_icp, register_point_clouds_using_rotation_sampler_icp, or register_point_clouds_using_fast_global_registration
  • The point clouds have well-defined normals – use register_point_clouds_using_point_to_plane_icp instead, which typically converges faster and more accurately in that case
  • The point clouds may be at different scales – set estimate_scaling=True, or pre-scale the clouds, otherwise the algorithm assumes matching scale
  • The point clouds are very sparse – ICP correspondences become unreliable with too few nearby points to match against

TIP

min_fitness_score is a rejection gate, not a tuning knob for accuracy — if you're getting rejections, first check whether max_correspondence_distance and the initial alignment are appropriate for your data before lowering the threshold.