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
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,
)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
"""
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:
cd telekinesis-examples
python examples/point_cloud/register_point_clouds_using_point_to_point_icp.pyParameter Configuration
| Key | Type | Default | Description |
|---|---|---|---|
source_point_cloud | datatypes.PointCloud | required | The point cloud to align |
target_point_cloud | datatypes.PointCloud | required | The point cloud to align to |
initial_transformation_matrix | datatypes.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_iterations | datatypes.Int | int | 50 | Maximum number of ICP iterations to run |
max_correspondence_distance | datatypes.Float | float | int | 0.05 | Maximum distance, in meters, at which two points are considered a match |
estimate_scaling | datatypes.Bool | bool | False | Whether 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_score | datatypes.Float | float | int | 0.9 | The minimum fitness score, in [0, 1], the final result must reach to be accepted |
Returns
| Type | Description |
|---|---|
datatypes.Mat4x4 | The 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
| Exception | Condition |
|---|---|
TypeError | A parameter's value does not match its expected type (see the Parameter Configuration table above) |
ValueError | initial_transformation_matrix is not shape (4, 4) (or, for a list input, doesn't contain only numeric elements) |
ConfigurationError | The TELEKINESIS_API_KEY environment variable is not set |
SerializationError | The request input failed to serialize, or the response failed to deserialize |
RequestTimeoutError | The request to the Vitreous service timed out |
TransportError | A network failure occurred before a response was received |
ClientError | The Vitreous service rejected the request due to invalid input, invalid data, or another unexpected 4xx response |
AuthenticationError | The API key was rejected as invalid or expired |
AuthenticationServiceError | The authentication service was unavailable |
ServerError | The 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_cloudbefore ICP begins. - Default:
np.eye(4)(identity — use this if the clouds are already roughly aligned) - Must already bring
source_point_cloudwithinmax_correspondence_distanceoftarget_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
Trueonly 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_registrationor 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
| Skill | vs. Register Point Clouds Using Point to Point ICP |
|---|---|
| register_point_clouds_using_point_to_plane_icp | Matches 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_registration | Feature-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_icp | Grid-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'sregister_point_clouds_using_centroid_translation,register_point_clouds_using_cuboid_translation_sampler_icp,register_point_clouds_using_rotation_sampler_icp, orregister_point_clouds_using_fast_global_registration - The point clouds have well-defined normals – use
register_point_clouds_using_point_to_plane_icpinstead, 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.

