gicp op• Data kinds: points × points → pose
• Call: import fullseye as fs; fs.ledger.gicp(source, target, max_iter: 'int' = 30, k: 'int' = 20, epsilon: 'float' = 0.001, tol: 'float' = 1e-08, init=None, reproducible: 'bool' = False) -> 'dict' (to call the implementation directly, import gicp; gicp.gicp(source, target, max_iter: 'int' = 30, k: 'int' = 20, epsilon: 'float' = 0.001, tol: 'float' = 1e-08, init=None, reproducible: 'bool' = False) -> 'dict'; from the registry, ops3d.get("gicp"))
• Return value through the ledger: fullseye.ledger.gicp(...) returns only the declared out type pose (the underlying function also returns auxiliary values). When you need what was dropped, use fullseye.ledger.gicp.raw(...) or call gicp.gicp directly.
• GPU: none (CPU reference implementation only)
Estimate the rigid transform (R,t) by Generalized-ICP (covariance-weighted Mahalanobis ICP).
At each iteration, sets up source→target nearest-neighbour correspondences, and minimises the Mahalanobis cost Σ d_iᵀ W_i d_i, which measures the residual d_i = R·s_i + t - q_i with the weight W_i = (C_target[q_i] + R·C_source[s_i]·Rᵀ)⁻¹, by Gauss-Newton with the small-angle linearisation R ← (I+[ω]×)R. From each correspondence's Jacobian J_i = [-[R·s_i+t]× | I] (3×6), it solves the normal equations (Σ J_iᵀ W_i J_i) x = -(Σ J_iᵀ W_i d_i) (6×6) for the increment x=[ω|τ], converts it back to a rotation with Rodrigues, and accumulates it.
Covariances are plane-to-plane (estimate_covariances). Whereas point-to-plane projects the residual onto the target's normal direction (rank-1), GICP uses a weight combining the full 3×3 covariances of both source and target, so it is robust on planar, noisy point clouds.
ICP is a local optimisation, so it assumes a close initialisation (pass a coarse match or init).
Arguments:
source (N,3): point cloud to be moved.
target (M,3): reference (fixed) point cloud.
max_iter: maximum number of iterations.
k: number of neighbours for covariance estimation.
epsilon: small variance along the normal for plane-to-plane (0<ε<1).
tol: convergence threshold. Stops when the incremental translation ‖τ‖ is below tol×(RMS radius of target) and
the incremental rotation ‖ω‖ (rad) is below tol (scale-relative).
init: initial pose tuple (R0(3,3), t0(3,)), or None (identity).
reproducible: if True, the N-point sums Σ J_iᵀ W_i J_i and Σ J_iᵀ W_i d_i use ozakimm (Ozaki scheme) reductions,
applying the pose uses element-wise operations, and the RMSE uses math.fsum. Independent of source point order and the BLAS thread count,
R, t and rmse are bit-for-bit identical (if a covariance neighbourhood contains points at exactly tied distances, which neighbours get chosen may change).
The default False behaves as before.
Returns:
dict:
"R" (3,3) ndarray — rotation satisfying target ≈ R·source + t.
"t" (3,) ndarray — translation.
"rmse" float — Euclidean RMSE over the final correspondences (accepted points).
"iterations" int — actual number of iterations.
Exceptions:
ValueError: invalid input shape / too few points / numerical divergence (non-finite) = fail-closed.
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• gicp_register — py -3.11 examples_3d/gicp_register.py
pose as input)fuse_to_voxel · pose_error · bundle_adjust · mean_reprojection_error · optimize_pose_graph · relative_pose · mean_edge_error · rotation_translation_error
gicp)*Provenance: gicp.py — 3D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.