Skip to main content

Module cluster

Module cluster 

Source
Expand description

Global grid-direction recovery from per-feature dual-axis estimates.

Given a set of features that each carry two undirected local lattice-axis estimates (e.g. chess-corner x-junctions whose two tanh-fit ridges approximate the local grid directions), this module recovers the two global grid-direction centres (Θ₀, Θ₁) and labels every feature by which of its two axes matches Θ₀ vs Θ₁.

It is the orientation-prior stage shared by every grid pipeline that ingests dual-oriented features. Target-specific glue (which features are eligible, how to map the canonical/swapped assignment onto a caller’s own label type, parity-coherence repair) stays caller-side; this module is the pure direction-clustering math.

Tier: stable facade — cluster_axes and its AxisClusterCenters / AxisAssignment types are re-exported at the crate root and follow normal semver intent.

§Inputs / outputs

  • Input: a slice of AxisFeature, each carrying its two AxisObservations (angle, sigma) and a detector strength. Axes whose sigma is the no-info sentinel (≥ π) or non-finite are skipped. Callers pre-filter to the features they want to vote (e.g. chessboard passes only its Strong corners).
  • Output:

§Algorithm

  1. Build a smoothed circular histogram on [0, π) with num_bins bins. For every feature and every axis k ∈ {0, 1}, add a vote at wrap_pi(axes[k].angle) with weight strength / (1 + axes[k].sigma).
  2. Smooth with a [1, 4, 6, 4, 1] / 16 circular kernel.
  3. Find local maxima. Keep peaks with total weight ≥ min_peak_weight_fraction × total. Pick the two strongest peaks separated by at least peak_min_separation_rad.
  4. Refine centres via double-angle 2-means over per-axis votes. Each axis vote θ is mapped to before averaging; the average is halved back — this is the correct undirected- angle (mod π) circular mean. Iterate up to max_iters_2means.
  5. Per-feature label: for each feature compute the two possible axis assignments (canonical vs swapped) and pick the cheaper. Require the LARGER distance in the winning assignment to be within the per-feature tolerance; otherwise the feature is unassigned.

Structs§

AngleVote
A single weighted vote over the mod-π circle.
AxisClusterCenters
The two recovered global grid-direction centres in [0, π) with theta0 ≤ theta1.
AxisClusterDebug
Stage introspection captured during a single cluster_axes run.
AxisFeature
A feature with two undirected local lattice-axis estimates plus a detector strength used to weight its histogram votes.
AxisObservation
One undirected local lattice-axis estimate feeding the clustering stage: an angle in radians and its 1σ angular uncertainty.
ClusterParams
Tuning for cluster_axes.
PeakPickOptions
Options for pick_two_peaks.

Enums§

AxisAssignment
Per-feature assignment produced by cluster_axes.

Functions§

angle_to_bin
Map an angle in [0, π) to the bin index in a histogram of n equal-width bins over that range. Idempotent under prior wrap_pi; inputs outside [0, π) are wrapped first.
angular_dist_pi
Smallest angular distance on the circle with period π. Result in [0, π/2].
assign_axes
Assign one feature’s two axes to a slot ordering given known centres.
bin_to_angle
Inverse of angle_to_bin: bin center angle in [0, π).
cluster_axes
Recover the two global grid-direction centres from a slice of dual-axis features and assign each feature to a slot ordering.
effective_tol_rad
Per-feature cluster admission threshold in radians.
pick_two_peaks
Pick the two strongest plateau-aware peaks from a smoothed circular histogram, subject to a minimum-weight floor and minimum angular separation.
refine_2means_double_angle
Refine two cluster centers via weighted 2-means on mod-π-circular vote angles using the double-angle trick: accumulate (w·cos 2θ, w·sin 2θ) per cluster and halve the resulting atan2. This is the correct circular mean for undirected angles (mod π); accumulating raw (cos θ, sin θ) silently returns garbage near the 0°/180° seam.
refit_centers
Refit cluster centres from a labelled subset’s axes only.
smooth_circular_5
Smooth a circular histogram with a one-pass [1, 4, 6, 4, 1] / 16 kernel. Handles the wrap boundary with rem_euclid. Empty input returns empty output.
wrap_pi
Wrap an angle to [0, π). Works for any finite input.