Library / SDK
neka-nat/probreg avatar
neka-nat/probreg

probreg: point cloud registration with probabilistic models instead of ICP

Python package for point cloud registration using probabilistic model (Coherent Point Drift, GMMReg, SVR, GMMTree, FilterReg, Bayesian CPD)

996 stars152 forksPythonMIT

At a glance

What is it?
probreg is a Python package that implements several point cloud registration algorithms, CPD, GMMReg, SVR, GMMTree, FilterReg and Bayesian CPD, behind an Open3D interface, with a C++ core built through pybind11. The repository publishes its own timing comparison, in which ICP is the fastest option.
Who is it for?
Adopt probreg when you need a probabilistic registration method, chiefly Coherent Point Drift, for point clouds that are noisy, partially observed or need a non-rigid fit, and you are already working in Python with Open3D.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 94 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Registration as a probabilistic model, not a nearest neighbour search

The problem probreg addresses is aligning two point clouds that describe the same object or scene, when the second one has been moved, rotated, scaled, partially observed or deformed. Iterative Closest Point, or ICP, is the usual answer, and it works by repeatedly finding the closest point in the target for each source point and solving a small rigid fit.

The project's position is that the stochastic registration algorithms are more robust than ICP. The argument is about correspondences. ICP's correspondence is a hard geometric nearest neighbour decision that is sensitive to outliers, partial overlap and initial misalignment, while the probabilistic formulations treat the correspondence as a distribution that is estimated rather than assumed. The README demonstrates the difference with animation files rather than charts, including a noise test that runs ICP, CPD and FilterReg side by side on the same data.

The audience is 3D perception and geometry processing engineers who have hit the failure cases of ICP: scans that do not fully overlap, sensor noise that pulls correspondences to the wrong surface, or objects that genuinely deform and so need a non-rigid model.

The package also states two core features: an Open3D interface, and both rigid and non-rigid transformation. Those two lines are the contract. You hand it Open3D point cloud objects and get back transform parameters, and the surrounding Open3D toolchain for reading files, downsampling, colouring and visualising stays yours.

Seven algorithms, and a table of which transform each can express

The algorithm list is grouped by the probabilistic idea behind it, and the grouping is the most useful part of the documentation.

Under maximum likelihood when the target or source point cloud is observation data you get Coherent Point Drift, its Extended variant that adds correspondence priors, Color Coherent Point Drift, and FilterReg. Under variational Bayesian inference there is Bayesian Coherent Point Drift. Under distance minimisation of two probabilistic distributions there is GMMReg and Support Vector Registration. Under hierarchical stochastic model there is GMMTree.

The transformations table then tells you which of those can express which kind of motion, and the gaps in it are as informative as the entries. CPD handles rigid registration with scale and a 6D pose, and offers affine and MCT for non-rigid. SVR and GMMReg give a 6D pose and TPS for non-rigid. GMMTree is rigid only. FilterReg offers 6D pose rigid registration in point to point, point to plane and FPFH based flavours, plus a deformable kinematic mode. Bayesian CPD, marked experimental, is listed with a dash for rigid and a combined rigid, scale and non-rigid model for non-rigid.

So the library is not a uniform set of interchangeable estimators. Choosing an algorithm means choosing both a probabilistic model and a motion model, and the constraints are not symmetrical. A rigid registration task on a noisy scan points at CPD or FilterReg. A deformable fit points at CPD with MCT, SVR with TPS, or the experimental FilterReg deformable mode. GMMTree is a rigid option in a hierarchical formulation, and Bayesian CPD is the one to look at only if you are willing to accept an experimental path with no rigid entry.

The example directory matches that table, with separate scripts for cpd_rigid, cpd_affine2d, cpd_nonrigid2d, gmmtree_rigid, filterreg_rigid, filterreg_deformable, filterreg_feature, bcpd_nonrigid and the color variant.

A C++ core reached through pybind11, with OpenMP detected at build time

probreg is not pure Python, and that is visible in the packaging. The build system lists pybind11 among its requirements, and setup.py imports Extension and the build_ext command from setuptools to build native code. The Python layer is a wrapper over compiled kernels, which is what makes the runtime figures in the README plausible in the first place.

Two build details are worth knowing before you install. First, setup.py contains a routine that checks whether the default compiler supports OpenMP by compiling a small test program with the -fopenmp flag, and if the compile succeeds the build continues with OpenMP enabled. If it fails, the build proceeds without it, so a compiler without OpenMP support is a degraded path rather than a hard error. Second, there is a find_eigen function that looks for the Eigen 3 include directory in a list of standard locations, with /usr/local/include/eigen3 among them, and a user supplied hint is accepted.

The native dependencies are not vendored as plain files. The repository has a .gitmodules file and a third_party/ directory, and the installation instructions clone the repository with the recursive flag, which is what pulls those submodules in. Skip the recursive flag and you get a tree that cannot build, and the failure will look like a missing header rather than a missing submodule.

The Python side of the dependency list is wider than a registration library strictly needs: scipy, scikit-learn, matplotlib, open3d, pyyaml, addict, pandas and transforms3d. open3d is the one that shapes the API, since it is the point cloud type you pass in and out. The rest are the machinery behind the models.

Three installation routes, and only one of them gets the submodules

The documented installation is pip, one line, and it is the route most users should take:

bash
pip install probreg

The source route exists for people who need the C++ extension built locally, and the recursive clone is the part that matters:

bash
git clone https://github.com/neka-nat/probreg.git --recursive
cd probreg
pip install -e .

A third route is the Makefile, which drives Poetry for the development setup:

bash
poetry self update
poetry install --no-interaction
poetry run pip install -e .

And the test target is a single command, which is also how you check that a source install actually works:

bash
poetry run python -m unittest discover

Two optional dependency groups are declared. The experimental group pulls in dq3d, the dual quaternion library that the dual quaternion skinning topics point at, and the gpu group pulls in cupy. For CUDA support the README asks you to install cupy directly:

bash
pip install cupy

The GPU examples in the tree include cpd_rigid_cuda.py, cpd_affine3d_cuda.py and cpd_nonrigid3d_cuda.py, and the topics list dual quaternion and dual quaternion skinning alongside the expectation maximisation and variational inference ideas, so the experimental group is tied to specific algorithm paths rather than to the whole package.

The project metadata is worth reading once. It requires Python 3.9 or newer and below 4.0, declares version 0.3.8, and takes its build backend from setuptools with poetry-core and pybind11 as build requirements.

A first registration: load, align, transform, draw

The getting started sample reads a PCD file and calls CPD registration, and it is short enough to read as an API description. The imports are the interface surface, with open3d imported as o3 and the algorithm module pulled in by name:

python
import copy
import numpy as np
import open3d as o3
from probreg import cpd

The setup creates a target by copying the source and rotating it, so the ground truth transformation is known:

python
# load source and target point cloud
source = o3.io.read_point_cloud('bunny.pcd')
source.remove_non_finite_points()
target = copy.deepcopy(source)

Then the registration itself, where the call returns three values and the first is the transform parameters:

python
# compute cpd registration
tf_param, _, _ = cpd.registration_cpd(source, target)
result = copy.deepcopy(source)
result.points = tf_param.transform(result.points)

And the visualisation, where the three clouds are coloured differently before being drawn:

python
# draw result
source.paint_uniform_color([1, 0, 0])
target.paint_uniform_color([0, 1, 0])
result.paint_uniform_color([0, 0, 1])
o3.visualization.draw_geometries([source, target, result])

Two things stand out for a new user. The result is a transform parameter object, not a raw matrix, and it is applied by calling transform on the point array, so the same object could carry a scale or a non-rigid field depending on the algorithm and mode you chose. And the function returns three values, of which the documentation only makes sense of the first, which suggests the other two carry information the sample does not need, most likely the correspondence and the probability arrays, though the README does not spell that out.

The example data is in the repository, including bunny.pcd, cloud_0.pcd, frag_115.ply, frag_116.ply and the text coordinate files used by the two dimensional examples, so the sample runs without hunting for a dataset.

The project's own timings show ICP winning on speed

The README publishes a time measurement example, and it is worth reading closely because it is not the comparison a marketing page would print. The command is:

bash
OMP_NUM_THREADS=1 python time_measurement.py

And the results the project records are:

bash
# Results [s]
# ICP(Open3D):  0.0014092829951550812
# CPD:  0.038112225010991096
# SVR:  0.036476270004641265
# GMMTree:  0.10535842599347234
# FilterReg:  0.005098833993542939

ICP in Open3D is the fastest by roughly an order of magnitude over CPD and SVR, GMMTree is the slowest, and FilterReg sits close to ICP. This is the project's own measurement, on its own test data, with a single OpenMP thread pinned, so it should be read as one data point rather than a general ranking. Even so, it sets the expectation correctly: the probabilistic methods buy robustness, and they pay for it in time.

The right comparison for a practitioner is therefore between two approaches to the same problem. Open3D's own ICP, which is already a dependency of probreg, solves alignment by nearest neighbour correspondences and is very fast when the initial alignment is close and the overlap is high. probreg's CPD treats the correspondence as a latent random variable estimated in an expectation maximisation loop, which is why it tolerates noise and partial overlap that break ICP.

If your scans are already roughly aligned and clean, Open3D's ICP is the better tool and you do not need this package. If the initial pose is unknown, the data is noisy, or the object deforms, the extra cost buys the accuracy, and the FPFH based FilterReg variant and the deformable modes are where the package's newest contributions sit.

Version 0.3.8, no GitHub releases, and a citation that still says 0.1.6

The package is MIT licensed, with the LICENSE file in the repository root and the license field in the project metadata agreeing. The dependency versions are pinned with upper bounds, so a fresh install resolves to a known combination, and the build requires Python 3.9 or newer.

The versioning picture has three numbers and they do not match. The project metadata says 0.3.8. The repository has no GitHub releases at all, so there is no tag list to check and no release notes to read for upgrade notes. And the citation block in the README still declares version 0.1.6 with a date of 2019-9-29, so if you cite the software from the README you are citing a version from several years back, and if you cite it from the package metadata you are citing a version that was never tagged on GitHub.

The last push to the repository was on 2026-07-21 and the project is not archived, so the working tree is being maintained. The absence of releases is a real gap for a package in the 0.3 line, because a pip upgrade moves you to whatever the index holds, and there is no changelog in the repository to tell you what changed between two installed versions.

The documentation is a readthedocs project, with a .readthedocs.yml in the repository root, and the build configuration is in the docs/ directory. The Sphinx dependency and the sphinx-rtd-theme in the development group confirm that is how the docs are produced.

A reasonable stance for an engineer is to pin the version you evaluate, read the API reference for the algorithms you plan to use, and treat the missing release history as a reason to hold that pin rather than to track a moving head.

Editorial conclusion

Adopt probreg when you need a probabilistic registration method, chiefly Coherent Point Drift, for point clouds that are noisy, partially observed or need a non-rigid fit, and you are already working in Python with Open3D. Do not adopt it as a drop-in replacement for ICP, because the timing example the project ships shows ICP completing a test in 0.0014 seconds against 0.038 for CPD, and the rigid Bayesian CPD path is marked as not supported while the deformable FilterReg mode is experimental. Before you depend on it, install from source with the recursive clone so the third_party submodules are present, confirm your Python is 3.9 or newer, and run the test suite with poetry run python -m unittest discover.

Frequently asked questions

How do I install probreg?

The package is on PyPI and installs with pip install probreg. For a source build, clone with the recursive flag so the third_party submodules are fetched, then run pip install -e . inside the cloned directory.

Which probreg algorithms support non-rigid registration?

CPD offers affine and MCT modes, SVR offers TPS, FilterReg offers a deformable kinematic mode that is marked experimental, and Bayesian CPD offers a combined rigid, scale and non-rigid model in an experimental state. GMMTree is listed as rigid only.

Does probreg support GPU acceleration?

Yes, through CuPy. The README asks you to install cupy, the gpu optional dependency group pulls in cupy, and the example directory contains cpd_rigid_cuda.py and cpd_affine3d_cuda.py.

How do I run the probreg test suite?

The Makefile has a test target that runs poetry run python -m unittest discover, and the repository also contains a tests/ directory. The same Makefile provides a setup target that installs dependencies with Poetry.

Which Python versions does probreg support?

The project metadata requires Python 3.9 or newer and below 4.0. The build is a setuptools backend with pybind11, and the native extension is compiled during installation.

Official sources

  1. Issues
  2. License: MIT
  3. neka-nat/probreg on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/neka-nat-probreg.svg)](https://hysenlabs.com/projects/neka-nat-probreg)

Was this useful?