Skip to content

Register Point Clouds Using Rotation Sampler ICP

SUMMARY

Register Point Clouds Using Rotation Sampler ICP finds the best alignment between two point clouds by trying many candidate rotations on a grid of Euler angles, refining each one with point-to-point ICP.

It samples rotations on a regular grid over Euler angles from x/y/z_min_deg to x/y/z_max_deg (relative to initial_transformation_matrix), runs ICP starting from each sampled rotation, and keeps whichever run converged to the best fitness score. This is useful when the two point clouds are positioned close to each other but the relative rotation between them isn't known precisely enough for plain ICP to converge on its own. Compare with register_point_clouds_using_cuboid_translation_sampler_icp, which instead searches over translations. The function returns the 4x4 transform that produced the best result, not an already-moved point cloud — apply it with apply_transform_to_point_cloud.

Use this Skill when you want to register two point clouds whose relative rotation is uncertain but whose relative position is already roughly known.

The Skill

python
from telekinesis import vitreous
import numpy as np

transformation_matrix = vitreous.register_point_clouds_using_rotation_sampler_icp(
    source_point_cloud=source_point_cloud,
    target_point_cloud=target_point_cloud,
    initial_transformation_matrix=np.eye(4),
    x_step_size_deg=30,
    y_step_size_deg=10,
    z_step_size_deg=30,
    x_min_deg=0,
    x_max_deg=180,
    y_min_deg=0,
    y_max_deg=180,
    z_min_deg=0,
    z_max_deg=180,
    early_stop_fitness_score=0.9,
    min_fitness_score=0.2,
    max_iterations=100,
    max_correspondence_distance=2,
    estimate_scaling=False,
)

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_rotation_sampler_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 rotation sampler ICP with multiple orientation initializations

The Code

python
"""
Demonstrates finding the best alignment by trying multiple rotations with ICP refinement.
"""

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

from telekinesis import vitreous, datatypes


def register_point_clouds_using_rotation_sampler_icp_example():
    """
    Finds best alignment by trying multiple rotations with ICP refinement.

    Samples rotations in Euler angle space, runs ICP for each, and keeps the best.
    """
    # ===================== Load Data ==========================================
    source_url = "https://assets.telekinesis.ai/examples/v1/point_clouds/zivid_bottle_cylinder_centered.ply"
    target_url = "https://assets.telekinesis.ai/examples/v1/point_clouds/zivid_bottle_segmented.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_rotation_sampler_icp(
        x_step_size_deg=30,
        y_step_size_deg=10,
        z_step_size_deg=30,
        x_min_deg=0,
        x_max_deg=180,
        y_min_deg=0,
        y_max_deg=180,
        z_min_deg=0,
        z_max_deg=180,
        early_stop_fitness_score=0.9,
        min_fitness_score=0.2,
        max_iterations=100,
        max_correspondence_distance=2,
        estimate_scaling=False,
        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 rotation sampler 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_rotation_sampler_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_rotation_sampler_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_rotation_sampler_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 the search — the rotation search happens relative to this initial alignment
x_step_size_degdatatypes.Int | int20Spacing, in degrees, between sampled rotations around the x-axis
y_step_size_degdatatypes.Int | int20Spacing, in degrees, between sampled rotations around the y-axis
z_step_size_degdatatypes.Int | int20Spacing, in degrees, between sampled rotations around the z-axis
x_min_degdatatypes.Int | int0Lower bound, in degrees, of the rotation search around the x-axis
x_max_degdatatypes.Int | int180Upper bound, in degrees, of the rotation search around the x-axis
y_min_degdatatypes.Int | int0Lower bound, in degrees, of the rotation search around the y-axis
y_max_degdatatypes.Int | int180Upper bound, in degrees, of the rotation search around the y-axis
z_min_degdatatypes.Int | int0Lower bound, in degrees, of the rotation search around the z-axis
z_max_degdatatypes.Int | int180Upper bound, in degrees, of the rotation search around the z-axis
early_stop_fitness_scoredatatypes.Float | float | int0.5A fitness score in [0, 1] at which the grid search stops early instead of trying every remaining sample
min_fitness_scoredatatypes.Float | float | int0.9Minimum fitness score in [0, 1] the best result must reach to be accepted at all
max_iterationsdatatypes.Int | int50Maximum number of ICP iterations run for each sampled rotation
max_correspondence_distancedatatypes.Float | float | int0.02Maximum distance, in meters, at which two points are considered a match during ICP
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

Returns

TypeDescription
datatypes.Mat4x4The best-scoring 4x4 transform found by the grid search — 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_rotation_sampler_icp Skill exposes fourteen tunable parameters that define the rotation search grid and control the ICP refinement run at each grid point.

initial_transformation_matrix

  • Controls: The seed transform the rotation grid is centered on — every sampled rotation is relative to this.
  • Default: np.eye(4) (identity — no pre-alignment)
  • Provide a rough translation-only alignment here if source_point_cloud and target_point_cloud aren't already close in position; this Skill only searches over rotation, not translation.

x_step_size_deg / y_step_size_deg / z_step_size_deg

  • Controls: The spacing, in degrees, between sampled rotations around each axis.
  • Units: Degrees
  • Default: 20 on all three axes
  • Increase → coarser search — fewer samples, faster, but may miss the optimal rotation
  • Decrease → finer search — more samples, slower
  • Typical range: 5-45 degrees — use 5-10 for a fine search, 10-20 for balanced, 20-45 for coarse

x_min_deg / x_max_deg, y_min_deg / y_max_deg, z_min_deg / z_max_deg

  • Controls: The bounds, in degrees, of the rotation search along each Euler axis.
  • Units: Degrees
  • Default: 0 / 180 on all three axes
  • The _max_deg bound should exceed the _min_deg bound
  • Typical range: 0-360 degrees — use 0-180 to search a half-sphere of orientations, 0-360 for the full range

early_stop_fitness_score

  • Controls: A fitness score in [0, 1] at which the grid search stops early instead of trying every remaining sample.
  • Units: Dimensionless (fitness score)
  • Default: 0.5
  • Increase → accepts stopping sooner (faster), but may settle for a suboptimal alignment
  • Decrease → requires a better fit before stopping early
  • Typical range: 0.3-0.7

min_fitness_score

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

max_iterations

  • Controls: The maximum number of ICP iterations run for each sampled rotation.
  • Units: Iterations (integer)
  • Default: 50
  • Typical range: 10-200

max_correspondence_distance

  • Controls: The maximum distance, in meters, at which two points are considered a match during ICP.
  • Units: Meters
  • Default: 0.02
  • Set to roughly 2-5x the point spacing
  • Typical range: 0.01-0.1 meters

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.

TIP

Search a half-sphere (0-180 degrees) rather than the full 0-360 range on each axis whenever your object has no meaningful symmetry that would make the other half distinct — it halves the number of ICP runs for the same step_size_deg. If translation is also uncertain, resolve that separately (e.g. with register_point_clouds_using_cuboid_translation_sampler_icp or a known fixed translation) — this Skill only searches over rotation.

Where to Use the Skill

Common pipelines include:

  • 6D pose estimation with unknown orientation – aligning parts that land on a conveyor or fixture in an arbitrary orientation
  • Object alignment with unknown initial rotation – registering a reference model to a scan when the object's rotation, but not its position, is unpredictable
  • Multi-view point cloud registration – merging scans whose relative rotation wasn't tracked precisely enough for a direct ICP start
  • Refining a coarse rotation guess – running a finer search once a first-pass alignment narrows down the plausible orientation range

Alternative Skills

Skillvs. Register Point Clouds Using Rotation Sampler ICP
register_point_clouds_using_cuboid_translation_sampler_icpSearches over translations instead of rotations. Use it when the clouds are already close in orientation but the relative translation is unknown; use this Skill when it's the other way around.
register_point_clouds_using_point_to_point_icpPlain point-to-point ICP from a single initial guess, with no sampling. Use it once you already have a rotation estimate good enough for direct convergence; use this Skill first if you don't.
register_point_clouds_using_fast_global_registrationFeature-based global registration that doesn't require any rough pre-alignment at all. A good alternative starting point when neither the translation nor the rotation between the clouds is known.

The SDK's register_point_clouds_using_centroid_translation is a fast, translation-only coarse-alignment step commonly run alongside this Skill (it has no doc page yet, but its docstring — and this Skill's — reference each other directly) when translation also needs establishing before or after the rotation search.

When Not to Use the Skill

Do not use Register Point Clouds Using Rotation Sampler ICP when:

  • The relative rotation is already known – use register_point_clouds_using_point_to_point_icp (or register_point_clouds_using_point_to_plane_icp) directly instead
  • The translation between the clouds is unknown, not the rotation – use register_point_clouds_using_cuboid_translation_sampler_icp instead, which searches translations
  • You have no rough alignment at all, in rotation or translation – use register_point_clouds_using_fast_global_registration first, since it tolerates large initial misalignment
  • You need a fast result – this Skill runs a full ICP pass for every sampled rotation, so it is slower than a single ICP call
  • The rotation uncertainty spans the full range at a fine step size – the grid may need too many samples to search effectively
  • The point clouds may be at different scales – set estimate_scaling=True, or pre-scale the clouds, otherwise the search assumes matching scale

TIP

Keep the rotation search range (x_min_deg/x_max_deg/y_min_deg/y_max_deg/z_min_deg/z_max_deg) as tight as your actual orientation uncertainty allows — a smaller range at a given step size means fewer ICP runs and a faster search.