Supported primitive shapes in pyRANSAC-3D
masterpyRANSAC-3D provides RANSAC implementations for fitting the following primitive shapes to 3D point clouds:
- Plane
- Cylinder
- Cuboid
- Sphere
- Line
- Circle
- Point
repository·master·Indexed 20 days ago
https://github.com/leomariga/pyransac-3dA Python tool for fitting primitive 3D shapes to point clouds using the Random Sample Consensus (RANSAC) algorithm. Version 0.6.1 supports fitting planes, cylinders, cuboids, spheres, lines, circles, and points. It provides specialized classes for each shape, including methods to define distance thresholds and maximum iterations, as well as callback functionality for monitoring progress or implementing early stopping.
pyRANSAC-3D provides RANSAC implementations for fitting the following primitive shapes to 3D point clouds:
Install the pyransac3d package using pip. This package requires numpy as a dependency.
pip3 install pyransac3dThe fit method in the Point class accepts an optional callback function. This is useful for monitoring progress, visualizing intermediate results, or implementing custom early-stopping logic.
The callback is called with a dict containing the following read-only keys:
iteration: current iteration index (0-based)sample_indices: indices of the points sampled this iterationsample_points: the sampled points, np.array (1, 3)model: dict with this iteration's candidate centerinliers: inlier indices found for this iteration's candidatebest_model: dict with the best center found so farbest_inliers: best inlier indices found so faris_best: True if this iteration became the new best candidateTo stop the fitting process early, ensure the callback returns a truthy value.
def my_callback(state):
print(f"Iteration {state['iteration']}: Best inliers found: {len(state['best_inliers'])}")
# Stop if we found more than 50 inliers
if len(state['best_inliers']) > 50:
return True
return False
# Usage
ransac.fit(pts, callback=my_callback)To fit a sphere to a point cloud, use the Sphere class from pyransac3d.
(N, 3).pyrsc.Sphere()..fit(points, thresh=threshold).The method returns the center coordinates, the radius, and the indices of the inlier points.
import pyransac3d as pyrsc
# points must be a numpy array (N, 3)
points = load_points(.)
sph = pyrsc.Sphere()
# center: [x, y, z], radius: float, inliers: array of inlier indices
center, radius, inliers = sph.fit(points, thresh=0.4)To fit a plane to a point cloud, use the Plane class from pyransac3d.
(N, 3).pyrsc.Plane()..fit(points, threshold) where threshold is the distance tolerance.The method returns the plane equation in the form Ax + By + Cz + D and the indices of the inlier points.
import pyransac3d as pyrsc
# points must be a numpy array (N, 3)
points = load_points(.)
plane1 = pyrsc.Plane()
# best_eq is [A, B, C, D], best_inliers is the array of inlier indices
best_eq, best_inliers = plane1.fit(points, 0.01)You can provide a callback function to Cylinder.fit to inspect the progress of the RANSAC algorithm. This is useful for plotting progress or implementing custom early-stopping criteria.
The callback receives a dict containing the following state keys (all arrays are read-only):
iteration: Current iteration index (0-based).sample_indices: Indices of the points sampled in the current iteration.sample_points: The sampled points (np.array of shape (3, 3)).model: A dict with the current candidate: {'center': ..., 'axis': ..., 'radius': ...}.inliers: Inlier indices found for the current candidate.best_model: A dict with the best candidate found so far: {'center': ..., 'axis': ..., 'radius': ...}.best_inliers: The best inlier indices found so far.is_best: True if the current iteration produced a new best candidate.If the callback returns a truthy value, the fitting process terminates immediately.
def my_callback(state):
print(f"Iteration {state['iteration']}: Best inliers so far = {len(state['best_inliers'])}")
# Example early stopping: stop if we find more than 50 inliers
if len(state['best_inliers']) > 50:
return True
return False
# Pass the callback to the fit method
center, axis, radius, inliers = cylinder.fit(pts, callback=my_callback)The Line class implements the RANSAC (Random Sample Consensus) method to find the best equation for a line in 3D space. It selects two random points from a point cloud to define a candidate line and iteratively improves the model by maximizing the number of inliers within a specified distance threshold.
To use it, instantiate Line() and call the .fit() method with your point cloud.
Mathematical Representation: The resulting line is defined by the equation $y = Ax + B$, where:
A is the 3D slope (direction vector) as a np.array (1, 3).B is the axis interception as a np.array (1, 3).Parameters:
pts: A 3D point cloud as a np.array (N, 3).thresh: The distance threshold. Points within this distance from the line are considered inliers (default: 0.2).maxIteration: The maximum number of RANSAC iterations (default: 1000).callback: An optional callable for monitoring progress or implementing early stopping.import numpy as np
from pyransac3d.line import Line
# Create a sample point cloud
pts = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2], [5, 0, 0]], dtype=float)
# Initialize and fit the line
line_fitter = Line()
A, B, inliers = line_fitter.fit(pts, thresh=0.1, maxIteration=500)
print("Slope A:", A)
print("Intercept B:", B)
print("Inlier indices:", inliers)The pyransac3d package provides several geometric primitive classes used for RANSAC-based shape fitting. You can import these directly from the top-level package to represent or fit shapes such as Planes, Spheres, Cylinders, Cuboids, Circles, Lines, and Points.
from pyransac3d import Plane, Sphere, Cylinder, Cuboid, Circle, Line, PointThe Cylinder class implements a RANSAC algorithm to find an infinite height cylinder within a 3D point cloud. It returns the cylinder's axis, center, radius, and the indices of the inlier points.
Warning: The current implementation of the cylinder RANSAC does not produce good results on real-world data. The developers are working on an improved version using normals.
To use it, instantiate the class and call the fit method with your point cloud and desired parameters.
from pyransac3d import Cylinder
import numpy as np
# Initialize the cylinder model
cylinder = Cylinder()
# Prepare your 3D point cloud (N, 3) numpy array
pts = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 1]])
# Fit the cylinder
center, axis, radius, inliers = cylinder.fit(pts, thresh=0.2, maxIteration=1000)
print(f"Center: {center}")
print(f"Axis: {axis}")
print(f"Radius: {radius}")
print(f"Inlier indices: {inliers}")The fit method of the Line class accepts an optional callback function. This function is invoked after every iteration and receives a dict containing the current state. If the callback returns a truthy value, the fitting process stops immediately and returns the current best result.
State dictionary keys available in the callback:
iteration: current iteration index (0-based)sample_indices: indices of the points sampled this iterationsample_points: the sampled points, np.array (2, 3)model: dict with this iteration's candidate A and Binliers: inlier indices found for this iteration's candidatebest_model: dict with the best A and B found so farbest_inliers: best inlier indices found so faris_best: True if this iteration became the new best candidateNote: The arrays in the state dictionary should be treated as read-only.
This is useful for plotting progress, inspecting intermediate results, or implementing custom early-stopping criteria (e.g., stopping if a certain number of inliers is reached).
def my_callback(state):
print(f"Iteration: {state['iteration']} | Inliers: {len(state['inliers'])}")
# Stop if we find more than 100 inliers
if len(state['best_inliers']) > 100:
return True
return False
line_fitter.fit(pts, callback=my_callback)The Sphere class implements the RANSAC algorithm to find the center and radius of a sphere within a 3D point cloud.
To use it, instantiate the class and call the fit method with your point cloud data. The method requires at least 4 points in the input array.
Parameters:
pts: A numpy array of shape (N, 3) representing the 3D point cloud.thresh: (Optional) The distance threshold from the sphere's surface. Points within this distance of the hull are considered inliers. Default is 0.2.maxIteration: (Optional) The maximum number of RANSAC iterations. Default is 1000.callback: (Optional) A callable invoked after every iteration. If the callback returns a truthy value, the fitting process stops early.Returns:
center: The center of the sphere as a numpy.array of shape (3,).radius: The radius of the sphere.inliers: An array of indices corresponding to the inlier points from the original point cloud.from pyransac3d import Sphere
import numpy as np
# Create a dummy point cloud
pts = np.random.rand(100, 3)
# Initialize and fit
sphere = Sphere()
center, radius, inliers = sphere.fit(pts, thresh=0.1, maxIteration=500)
print(f"Center: {center}")
print(f"Radius: {radius}")
print(f"Inlier count: {len(inliers)}")The Sphere.fit method accepts an optional callback function. This is useful for real-time visualization, inspecting intermediate results, or implementing custom early-stopping logic.
The callback receives a dict containing the current state. Note that the arrays in this dictionary should be treated as read-only.
State Dictionary Keys:
iteration: Current iteration index (0-based).sample_indices: Indices of the 4 points sampled this iteration.sample_points: The 4 sampled points (np.array of shape (4, 3)).model: Dictionary containing the current candidate center and radius.inliers: Indices of inliers found for the current candidate.best_model: Dictionary containing the best center and radius found so far.best_inliers: Indices of the best inliers found so far.is_best: Boolean, True if the current iteration produced a new best model.To stop the fitting process early, ensure your callback returns a truthy value.
def my_callback(state):
print(f"Iteration: {state['iteration']} | Best Inliers: {len(state['best_inliers'])}")
# Stop if we find more than 50 inliers
if len(state['best_inliers']) > 50:
return True
return False
sphere = Sphere()
center, radius, inliers = sphere.fit(pts, callback=my_callback)