gicp — 3D 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.

Usage

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.

References (sample data, literature)

• 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.

Runnable examples (verified samples that actually call this op)

• gicp_register — py -3.11 examples_3d/gicp_register.py

Ops the type connects to (they accept pose as input)

fuse_to_voxel · pose_error · bundle_adjust · mean_reprojection_error · optimize_pose_graph · relative_pose · mean_edge_error · rotation_translation_error

Same category (gicp)

estimate_covariances


*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.