robokudo.annotators.shape_estimator

Shape estimation for object hypotheses.

This module provides a minimal, pure-Python annotator that fits primitive shape models to segmented object point clouds.

Classes

CylinderAxisCandidate

Candidate axis direction together with a refitted cylinder.

ShapeEstimatorAnnotator

Estimate one primitive shape per object hypothesis point cloud.

Module Contents

class robokudo.annotators.shape_estimator.CylinderAxisCandidate

Candidate axis direction together with a refitted cylinder.

source: str
axis_direction: numpy.ndarray
fit: robokudo.utils.shape_fitting.CylinderFit
class robokudo.annotators.shape_estimator.ShapeEstimatorAnnotator(name: str = 'ShapeEstimatorAnnotator', descriptor: ShapeEstimatorAnnotator = Descriptor())

Bases: robokudo.annotators.core.ThreadedAnnotator

Estimate one primitive shape per object hypothesis point cloud.

Overview: 1. Validate input cloud (minimum_point_count) and optionally run

statistical outlier removal.

  1. Fit enabled primitives (sphere/cylinder/cuboid) on the same filtered points.

  2. Apply post-fit stabilization: - Cylinder: optional axis stabilization from principal/preferred axes. - Cuboid: optional in-plane orientation stabilization for near-square faces.

  3. Rank accepted candidates with select_best_shape(…).

  4. Convert the winning fit to a RoboKudo annotation (Sphere, Cylinder, or Cuboid), keep inlier indices mapped to the original object cloud, and publish visualization geometries.

Parameter groups: - Global data quality: minimum_point_count, outlier removal settings. - Shared fit behavior: distance_threshold, robust_loss. - Shape gates:

sphere/cylinder/cuboid enable flags plus per-shape size and ratio limits.

  • Candidate selection: selection_score_tolerance and cuboid preference settings for close scores.

  • Stabilization controls: cylinder axis and cuboid orientation stabilization.

Practical tuning order: 1. Set hard physical limits (max radii/heights/extents) for your scene. 2. Tune inlier thresholds (distance_threshold, per-shape min inlier ratio). 3. Tune selection preference parameters. 4. Only then tune stabilization triggers/tolerances.

This annotator intentionally favors robust, bounded fits for household-scale objects.

class Descriptor

Bases: robokudo.annotators.core.BaseAnnotator.Descriptor

Configuration descriptor for shape estimation.

class Parameters

Parameter container for shape estimation.

minimum_point_count: int = 80

Minimum number of points required for fitting.

distance_threshold: float = 0.01

Inlier threshold used for all shape residuals.

robust_loss: str = 'soft_l1'

Robust loss used for non-linear least squares.

minimum_inlier_ratio: float = 0.5

Minimum inlier ratio required for publishing a shape.

remove_statistical_outliers: bool = False

Whether statistical outlier removal is applied before fitting.

outlier_neighbor_count: int = 30

Neighbor count for statistical outlier removal.

outlier_standard_deviation_ratio: float = 2.0

Standard deviation ratio for statistical outlier removal.

fit_sphere: bool = False

Whether sphere fitting is enabled.

max_sphere_radius_meters: float = 0.4

Maximum allowed sphere radius.

max_sphere_radius_to_bbox_diagonal_ratio: float = 1.0

Maximum allowed ratio between sphere radius and point cloud diagonal.

max_sphere_radius_to_observed_extent_ratio: float = 0.8

Maximum allowed ratio between sphere radius and observed cloud extent.

max_sphere_center_distance_to_bbox_diagonal_ratio: float = 0.9

Maximum allowed ratio between center offset and point cloud diagonal.

fit_cylinder: bool = True

Whether cylinder fitting is enabled.

cylinder_minimum_inlier_ratio: float = 0.87

Minimum inlier ratio required for cylinder candidates.

max_cylinder_radius_meters: float = 0.4

Maximum allowed cylinder radius.

max_cylinder_height_meters: float = 1.0

Maximum allowed cylinder height.

max_cylinder_radius_to_bbox_diagonal_ratio: float = 1.0

Maximum allowed ratio between cylinder radius and cloud diagonal.

max_cylinder_radius_to_cross_section_extent_ratio: float = 0.95

Maximum allowed ratio between cylinder radius and observed cross-section extent.

max_cylinder_center_distance_to_bbox_diagonal_ratio: float = 0.8

Maximum allowed ratio between axis center offset and cloud diagonal.

cylinder_max_initializations: int = 12

Maximum number of cylinder initialization hypotheses for multi-start optimization.

cylinder_consensus_trials: int = 30

Number of random axis hypotheses used to seed cylinder multi-start optimization.

cylinder_inlier_polishing_iterations: int = 0

Number of post-optimization inlier-only polishing iterations for cylinder fitting.

stabilize_cylinder_axis_with_principal_axis: bool = True

Whether cylinder axis should be stabilized using point principal axis.

cylinder_axis_stabilization_trigger_degrees: float = 8.0

Minimum axis deviation angle required to trigger cylinder stabilization.

cylinder_axis_stabilization_max_score_drop: float = 0.08

Maximum tolerated score drop for accepting stabilized cylinder.

prefer_upright_cylinder_axis: bool = True

Whether an upright preferred axis should be favored when scores are close.

preferred_cylinder_axis_direction: typing_extensions.Tuple[float, float, float] = (0.0, 0.0, 1.0)

Preferred global cylinder axis direction used as stabilization prior.

upright_cylinder_axis_score_tolerance: float = 0.05

Maximum score gap for preferring a more upright cylinder axis.

fit_cuboid: bool = True

Whether cuboid fitting is enabled.

max_cuboid_extent_meters: float = 0.6

Maximum allowed cuboid edge length for any axis.

cuboid_distance_threshold: float = 0.015

Inlier threshold used specifically for cuboid fitting.

cuboid_minimum_inlier_ratio: float = 0.35

Minimum inlier ratio required for cuboid candidates.

selection_score_tolerance: float = 0.05

Maximum score gap for considering two fits equivalent.

prefer_cuboid_when_score_close: bool = True

Whether cuboids should be preferred when scores are close.

cuboid_preference_score_margin: float = 0.06

Maximum score gap to still prefer a box-like cuboid candidate.

cuboid_preference_inlier_ratio_tolerance: float = 0.05

Maximum inlier-ratio gap to still prefer a cuboid.

cuboid_box_like_cross_section_asymmetry_threshold: float = 0.12

Minimum cross-section asymmetry to classify a cuboid as box-like.

cuboid_box_like_cube_axis_similarity_tolerance: float = 0.12

Maximum axis spread ratio to classify a cuboid as cube-like.

stabilize_ambiguous_cuboid_orientation: bool = True

Whether ambiguous cuboid in-plane orientation should be stabilized.

cuboid_ambiguous_in_plane_extent_relative_difference: float = 0.3

Maximum relative in-plane extent difference treated as orientation-ambiguous.

log_candidate_metrics: bool = True

Whether candidate metrics are logged on debug level.

log_rejection_reasons: bool = True

Whether shape rejection reasons are logged when metrics logging is enabled.

parameters
compute() py_trees.common.Status

Estimate shapes and publish annotations and 3D visualizations.

_estimate_shape_for_object_hypothesis(object_hypothesis: robokudo.types.scene.ObjectHypothesis) typing_extensions.Optional[typing_extensions.Tuple[robokudo.types.annotation.Shape, typing_extensions.List[typing_extensions.Dict[str, typing_extensions.Any]]]]

Estimate one shape candidate for a single object hypothesis.

_prepare_point_cloud(point_cloud: open3d.geometry.PointCloud) tuple[open3d.geometry.PointCloud, numpy.ndarray]

Return a filtered point cloud and retained index mapping.

_stabilize_cylinder_axis_if_tilted(cylinder_fit: robokudo.utils.shape_fitting.CylinderFit, points: numpy.ndarray) robokudo.utils.shape_fitting.CylinderFit

Stabilize cylinder axis using principal axes and optional upright prior.

_collect_cylinder_axis_candidates(points: numpy.ndarray) typing_extensions.List[typing_extensions.Tuple[str, numpy.ndarray]]

Return candidate cylinder axis directions for stabilization.

_contains_similar_axis_direction(existing_axis_candidates: typing_extensions.List[typing_extensions.Tuple[str, numpy.ndarray]], candidate_axis_direction: numpy.ndarray, similarity_threshold_degrees: float = 8.0) bool

Return whether candidate axis is similar to one axis already in the list.

_preferred_cylinder_axis_direction() typing_extensions.Optional[numpy.ndarray]

Return configured preferred cylinder axis direction if valid.

_select_cylinder_axis_candidate(candidate_fits: typing_extensions.List[CylinderAxisCandidate]) CylinderAxisCandidate

Select one cylinder axis candidate by score and optional upright preference.

_refit_cylinder_with_fixed_axis(points: numpy.ndarray, fixed_axis_direction: numpy.ndarray) typing_extensions.Optional[robokudo.utils.shape_fitting.CylinderFit]

Refit cylinder radius and finite height while keeping axis direction fixed.

_point_cloud_principal_axes(points: numpy.ndarray) typing_extensions.List[numpy.ndarray]

Return principal point-cloud axes sorted by descending variance.

_axis_angle_degrees(first_axis: numpy.ndarray, second_axis: numpy.ndarray) float

Return unsigned angle in degrees between two axis directions.

_normalized_vector(vector: numpy.ndarray) numpy.ndarray

Return normalized vector with safe fallback for near-zero norm.

_cross_section_max_extent_for_axis(points: numpy.ndarray, axis_center: numpy.ndarray, axis_direction: numpy.ndarray) float

Return largest observed cross-section extent orthogonal to the cylinder axis.

_orthogonal_axes_for_plane_normal(plane_normal: numpy.ndarray) typing_extensions.Tuple[numpy.ndarray, numpy.ndarray]

Return two orthonormal basis axes orthogonal to the given plane normal.

_stabilize_cuboid_orientation_if_ambiguous(cuboid_fit: robokudo.utils.shape_fitting.CuboidFit, points: numpy.ndarray) robokudo.utils.shape_fitting.CuboidFit

Stabilize in-plane cuboid orientation for near-square visible faces.

_project_axis_onto_plane(axis: numpy.ndarray, plane_normal: numpy.ndarray) numpy.ndarray

Project axis onto the plane orthogonal to the given plane normal.

_refit_cuboid_with_fixed_orientation(points: numpy.ndarray, fixed_rotation_matrix: numpy.ndarray, extent_support_indices: numpy.ndarray) typing_extensions.Optional[robokudo.utils.shape_fitting.CuboidFit]

Refit cuboid center and extents while keeping rotation fixed.

_map_to_object_indices(object_hypothesis: robokudo.types.scene.ObjectHypothesis, local_indices: numpy.ndarray) typing_extensions.List[int]

Map local point indices back to object hypothesis index space.

_fit_to_annotation(fit_result: robokudo.utils.shape_fitting.FittedShape) robokudo.types.annotation.Shape

Convert a fit result into a RoboKudo shape annotation.

_sphere_fit_to_annotation(fit_result: robokudo.utils.shape_fitting.SphereFit) robokudo.types.annotation.Sphere

Create a sphere annotation from a sphere fit.

_cylinder_fit_to_annotation(fit_result: robokudo.utils.shape_fitting.CylinderFit) robokudo.types.annotation.Cylinder

Create a cylinder annotation from a cylinder fit.

_cuboid_fit_to_annotation(fit_result: robokudo.utils.shape_fitting.CuboidFit) robokudo.types.annotation.Cuboid

Create a cuboid annotation from a cuboid fit.

_rotation_matrix_from_axis(axis_direction: numpy.ndarray) numpy.ndarray

Create an orthonormal rotation matrix with z aligned to the axis.

_create_visualization_geometries(object_hypothesis: robokudo.types.scene.ObjectHypothesis, filtered_cloud: open3d.geometry.PointCloud, best_fit: robokudo.utils.shape_fitting.FittedShape) typing_extensions.List[typing_extensions.Dict[str, typing_extensions.Any]]

Create visualization geometries for one fitted object.

_create_inlier_colored_cloud(cloud: open3d.geometry.PointCloud, inlier_indices: numpy.ndarray) open3d.geometry.PointCloud

Return a copy of the cloud with inliers and outliers color coded.

_fit_to_o3d_geometry(fit_result: robokudo.utils.shape_fitting.FittedShape) open3d.geometry.Geometry

Convert a fitted primitive into an Open3D geometry.

_fit_to_coordinate_frame(fit_result: robokudo.utils.shape_fitting.FittedShape) open3d.geometry.TriangleMesh

Create a coordinate frame located at the fitted primitive center.

_minimum_inlier_ratio_for_fit(fit_result: robokudo.utils.shape_fitting.FittedShape) float

Return the configured minimum inlier ratio for one fitted shape type.

_log_rejected_candidate(object_hypothesis: robokudo.types.scene.ObjectHypothesis, shape_name: str) None

Log one rejected shape candidate.

_log_candidate_metrics(object_hypothesis: robokudo.types.scene.ObjectHypothesis, fit_result: robokudo.utils.shape_fitting.FittedShape) None

Log shape candidate metrics for debugging.

_log_selected_candidate(object_hypothesis: robokudo.types.scene.ObjectHypothesis, fit_result: robokudo.utils.shape_fitting.FittedShape) None

Log selected candidate metrics for debugging.

_fit_summary(fit_result: robokudo.utils.shape_fitting.FittedShape) str

Return a compact summary string for one fitted candidate.