Skip to main content

rustcpd

Coherent Point Drift point-set registration — rigid, affine, deformable, constrained deformable, and statistical-shape-model/atlas — powered by a fast, deterministic, pure-Rust core (no BLAS, no compiled dependencies at install time: wheels ship for Linux, macOS, and Windows, CPython ≥ 3.9).

pip install rustcpd
import numpy as np
import rustcpd as cpd

# Rigid: recover the similarity transform mapping source onto target.
result = cpd.register_rigid(target, source)          # (N,3)/(M,3) arrays
aligned = result.points                              # (M, 3)
R, t, s = result.rotation, result.translation, result.scale

# Deformable with landmark constraints and an exact full-rank solve.
result = cpd.register_deformable(
    target, source,
    alpha=2.0, beta=2.0, low_rank=None,
    constraints=[(0, 0), (25, 25)],
)

# Large clouds: rank-300 kernel via pivoted Cholesky (much cheaper to
# build than the default "eigen" method as the source grows).
result = cpd.register_deformable(
    target, source, low_rank=300, low_rank_method="pivoted_cholesky",
)

# Statistical shape model (atlas): modes is (M*3, rank), point-major.
result = cpd.register_atlas(target, mean, modes, eigenvalues)
b = result.coefficients
posed = result.scale * (mean @ result.rotation) + result.translation

# Global pose search for atlas initialization (3-D).
init = cpd.pose_initialize(source, target, modes, eigenvalues)

# If source and modes were pre-scaled from a physical-size estimate (for
# example, a target-completeness prior), keep residual scale fixed while
# Pose-EM continues to optimize rotation and translation.
fragment_init = cpd.pose_initialize(
    prescaled_source, fragment, prescaled_modes, eigenvalues, with_scale=False,
)

# A few corresponding keypoints (same locations on the model and the fragment)
# steer the global pose search toward the keypoint-consistent basin, then keep
# those vertices anchored while register_atlas optimizes shape + pose. Helpful
# for fragments whose shape diverges from the mean. Both accept
# landmark_indices (source-vertex indices) + landmark_targets (their observed
# coordinates); off by default. Set the strength with landmark_sigma (a
# physical localization std, preferred) or the heuristic landmark_weight.
guided = cpd.pose_initialize(
    source, fragment, modes, eigenvalues, with_scale=False,
    # Fixed keypoint std τ (here the localization noise ~ 0.02·radius; squared
    # to a variance τ² internally) for both the basin scoring and the refinement
    # anchoring — the principled form, matching register_atlas(landmark_sigma=...)
    # below, so the whole pipeline uses one physical τ. (landmark_weight /
    # refine_landmark_weight remain as heuristic fallbacks.)
    landmark_indices=kp_idx, landmark_targets=kp_xyz,
    landmark_sigma=0.02 * radius, refine_landmark_sigma=0.02 * radius,
)
fit = cpd.register_atlas(
    fragment, source, modes, eigenvalues, with_scale=False,
    initial_rotation=guided.rotation, initial_translation=guided.translation,
    # Prefer landmark_sigma (an explicit localization std τ, here the keypoint
    # noise ~ 0.02·radius, squared internally) over the heuristic landmark_weight:
    # it gives a fixed constraint strength and keeps fit.sigma2 a clean surface
    # residual. fit.landmark_rms reports the landmark fit separately.
    landmark_indices=kp_idx, landmark_targets=kp_xyz, landmark_sigma=0.02 * radius,
)

# The fit's residual variance is a strong failure signal: a wrong pose basin
# cannot fit the fragment. Calibrate sigma2 -> P(correct) on a few labelled
# fits (recalibrate per dataset), then flag low-confidence fragments.
from rustcpd import calibration
cal = calibration.PoseConfidenceCalibrator.fit(sigma2_array, correct_array)
if not cal.trust(fit.sigma2):
    ...  # low confidence: review or collect more keypoints

After a deformable fit, apply the learned continuous warp to points it was never trained on — drive a dense mesh from a coarse registration, or move landmarks:

fit = cpd.register_deformable(target_subsample, source_subsample, beta=2.0)
warped_full = fit.transform(full_resolution_points)   # any (P, D) array

Read off soft correspondences from any registration — the best target match per source point and its confidence, plus the full posterior:

match = cpd.correspondences(target, result.points, result.sigma2)
match.matches        # (M,) best target index per source point
match.probability    # (M,) confidence in [0, 1]
match.posterior      # (M, N) full soft assignment matrix

Complete a partial shape and get per-point uncertainty. The atlas is a linear-Gaussian shape model, so a partial observation yields a closed-form posterior over its coefficients:

fit  = cpd.register_atlas(partial_target, mean, modes, eigenvalues)
post = fit.posterior(partial_target, mean, modes, eigenvalues, completeness=0.55)
completed  = post.predict()               # (M, D) filled-in shape
confidence = post.predictive_variance()   # (M,) per-point uncertainty
ensemble   = post.sample_shapes(200, seed=0)   # plausible completions

Visibility is inferred from the fitted correspondence; the optional completeness ∈ (0, 1] anchors it (roughly what fraction of the object was observed). Calibrate the uncertainty to nominal coverage with the rustcpd.calibration submodule (split-conformal). None of this touches the complete-data registration paths.

Shared keyword arguments on every registration: sigma2 (initial variance; estimated when omitted), max_iterations (default 100), tolerance, outlier_weight (uniform-outlier mixture weight in [0, 1)), k (k-nearest-neighbor sparse E-step; None = exact), parallel (default True; results are bitwise-identical to serial execution), and single_precision (opt-in f32 E-step, ~1e-7 accuracy, faster on large 2-D/3-D clouds).

Two more on rigid, affine, and deformable (atlas already had the first): normalize=True conditions the fit in an internal unit-scale frame and maps the result back to your coordinates — recommended for clouds in large physical units, where the absolute beta/sigma2 defaults would otherwise be mis-scaled. callback= is a per-iteration hook receiving {"iteration", "sigma2", "difference", "points"}; return False to stop early, True or None to continue:

result = cpd.register_deformable(
    target, source, normalize=True,
    callback=lambda s: s["sigma2"] > 1e-8,   # custom stopping rule
)

The heavy lifting happens in Rust with the GIL released, so other Python threads keep running. Invalid inputs raise ValueError. Bundled type stubs give editors and type checkers full signatures.

Parameter-selection guidance: see docs/TUNING.md in the repository.

Source, benchmarks, and the Rust API: see the repository.

License: BSD 2-Clause.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rustcpd-3.1.0.tar.gz (137.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rustcpd-3.1.0-cp39-abi3-win_amd64.whl (1.1 MB view details)

Uploaded CPython 3.9+Windows x86-64

rustcpd-3.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

rustcpd-3.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (923.9 kB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

rustcpd-3.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl (2.0 MB view details)

Uploaded CPython 3.9+macOS 10.12+ universal2 (ARM64, x86-64)macOS 10.12+ x86-64macOS 11.0+ ARM64

File details

Details for the file rustcpd-3.1.0.tar.gz.

File metadata

  • Download URL: rustcpd-3.1.0.tar.gz
  • Upload date:
  • Size: 137.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for rustcpd-3.1.0.tar.gz
Algorithm Hash digest
SHA256 353b555f69e28dd471946ad503b3a129487643eac5a185163a857de082647f06
MD5 060909a4a61b41f8592f79f083629974
BLAKE2b-256 32d076fa753023d01faf419a59e6b28c5c9b14cd9b5c646fbf429f5b1395bd7e

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustcpd-3.1.0.tar.gz:

Publisher: wheels.yml on agporto/rustcpd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustcpd-3.1.0-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: rustcpd-3.1.0-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for rustcpd-3.1.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 9208f8cade1d91f8b8ce4a60b8bee3d28c0208d4817f29c0dc07fe26c7e6a70f
MD5 e3a7af016e7b9cf5352c50303ced40cc
BLAKE2b-256 c6c7adc8e280b77cfc1d6f08f0939fcb0da4aac68abe24620b30dec879923cd6

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustcpd-3.1.0-cp39-abi3-win_amd64.whl:

Publisher: wheels.yml on agporto/rustcpd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustcpd-3.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rustcpd-3.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 8b1b87f64fd682727b07bd3ef3a45a7ec57d3a955562e80ee642a676dcb8e124
MD5 9fd4e0904409f29fc465cb18afcf7d8b
BLAKE2b-256 6122e7ab83b1bde1040bd9406f986122bf6b3d3ac68e458d555fba7ac5bf5073

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustcpd-3.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: wheels.yml on agporto/rustcpd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustcpd-3.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rustcpd-3.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 dfb32dbd0dad4f495665a65bef31653ef6f27b8f6a21f1a69bff558ecfc96619
MD5 a7b95fd076f87c901acfff5bba1e48c5
BLAKE2b-256 a1cefd5b489b7d8d96853ed657bd3693d8e74cde79eac46ef90b8440cbf58e5e

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustcpd-3.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: wheels.yml on agporto/rustcpd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustcpd-3.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.

File metadata

File hashes

Hashes for rustcpd-3.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm Hash digest
SHA256 2e91fcb57f74f4b47723cb4d010aa8379c7a6d9644d1d19d86c720bbc97aba96
MD5 5d901096a612de20fca5547336f3b2e5
BLAKE2b-256 d9f58b2c15fbab78d2575cebc2e7f4592a085ab8e7fc1efe5fcd6154dbec35bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustcpd-3.1.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:

Publisher: wheels.yml on agporto/rustcpd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

4.0.0

5 files

This release

3.1.0 This release

5 files

3.0.0

5 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page