Skip to main content

Module orient

Module orient 

Source
Expand description

Local lattice-orientation synthesis from point positions alone.

Both square strategies consume OrientedFeature<2> — each corner carries two local grid directions. When the caller has no per-corner orientation (Evidence::Positions: a dot grid, a circle grid, or a chessboard whose corners carry no axis estimate), this module recovers those two directions geometrically so the topological machinery runs unchanged.

§The perspective problem

The grid is viewed in perspective. The two grid directions are therefore not orthogonal in the image, and the angle between them varies across the image (the grid-line families converge toward two vanishing points). Any method that assumes a fixed 90° between the axes, or a single global orientation, is wrong. The estimate must be local and must not constrain the inter-axis angle.

§What is perspective-invariant

A projective map preserves straight lines, so three collinear grid points (i−1, j), (i, j), (i+1, j) stay collinear in the image. Hence a corner’s +u and −u neighbour chords are exactly antipodal (180° apart) even under arbitrary perspective. Folding chord orientation modulo π therefore collapses each axis neighbour-pair onto a single direction exactly — with no orthogonality assumption. The two grid directions show up as two distinct clusters in [0, π), separated by whatever angle the local perspective dictates.

A second fact makes the neighbour set reliable: for a grid cell the axis step is shorter than the diagonal step (√(a² + b²) > max(a, b)), and mild perspective scales both by roughly the same local factor, so a corner’s four nearest neighbours are its four axis neighbours (±u, ±v). The estimate uses those.

§Algorithm

  1. Per corner: fold the chord angles to its k nearest neighbours into [0, π). Generically these are two antipodal pairs collapsing to two directions.
  2. Pool every corner’s folded nearest-edge angles into a global distribution and pick its two dominant modes (g0, g1). This is a robust, image-wide prior — used only to seed the per-corner estimate and as a fallback, never as the answer.
  3. Per corner: run an undirected (mod-π) 2-means over the corner’s folded chords, seeded at (g0, g1). The two resulting centers are the corner’s two local grid directions — not constrained to be orthogonal, so they track the local perspective. An empty cluster falls back to its global seed.

§Precision contract

A corner whose synthesized axes are wrong is rejected downstream by the seed / attach geometry gates (axis-alignment, edge-length, residual) — it becomes a missing corner, never a mislabelled one. That is the correct trade for the workspace precision contract: a missing corner is acceptable, a wrong (i, j) label is not.

§Undirected circular statistics

Axis angles are undirected (period π): θ and θ + π are the same direction. Every mean here accumulates (cos 2θ, sin 2θ) and halves the atan2 result; raw (cos θ, sin θ) accumulation would break at the 0/π seam.

Functions§

synthesize_oriented2
Synthesize two local lattice axes for every point feature from neighbour geometry, returning oriented-2 features that carry the same source_index and position plus the recovered axes. The two axes are not constrained to be orthogonal — they track the local projected grid directions.
synthesize_oriented3
Synthesize three local lattice axes for every point feature from neighbour geometry — the hex analogue of synthesize_oriented2.
synthesize_oriented2_from_oriented1
Synthesize the second local lattice axis for every single-axis feature, keeping the caller-supplied axis as the first.