normals_to_egi — REPRCONV direction op

• Data kinds: normals → image2d

• Call: import fullseye as fs; fs.ledger.normals_to_egi(normals, n_az=36, n_el=18) (to call the implementation directly, import reprconv; reprconv.normals_to_egi(normals, n_az=36, n_el=18); from the registry, opsreprconv.get("normals_to_egi"))

Usage

Normals (N,3) → the extended Gaussian image, an image2d of (n_el, n_az).

A 2-D histogram of directions (Horn, *Extended Gaussian Images*, Proc. IEEE 72(12) 1984). A map of "how much surface faces which way"; for objects dominated by planes, a peak stands in 1 bin. Irreversible -- it discards direction resolution to the bin width. The amount discarded can be measured: the angle between the centre direction of the most populated bin and the mean input direction is the quantisation error, measured 3.7 degrees at the default (36, 18) against a bin width of 10 degrees (selftest reports it).

Elevation bins are equal in sin(el) (equal solid angle). Equal bins in degrees make the poles excessively fine, raising a false peak that says "surfaces are concentrated at the north pole".

★ Passing a point cloud of positions raises no exception. backends_typed.TYPE_TO_SORT folds normals into points, so neither the evolver nor the ledger distinguishes the two. This op looks only at directions (it discards length), so passing (N,3) coordinates returns a histogram of "the direction of each point seen from the origin", plausibly. Measured (2026-09-08, a curve of 200 points vs normals all pointing straight up): non-zero bins only change 1 → 12, the largest bin 200 → 44, and the sum is 200 in both cases. "The sum matches the point count, so it is correct" cannot tell them apart. With fullseye.set_system("extra_checks", "on"), vectors that deviate from unit length are rejected (see Raises below).

Args:

normals: (N, 3).

n_az: number of azimuth bins (default 36 = 10-degree steps).

n_el: number of elevation bins (default 18).

Returns:

(n_el, n_az) float64 count map (rows = elevation, columns = azimuth).

Raises:

ValueError: bin count below 1 / above the limit / invalid input.

ValueError: with extra_checks='on', when it contains vectors whose length deviates from 1 by more than 1e-6

(catches the accident of passing a point cloud of positions as normals).

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.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• representation_conversion — py -3.11 examples/representation_conversion.py

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

keypoints_from_image2d

Same category (direction)

normals_to_angles · angles_to_normals


*Provenance: reprconv.py — REPRCONV 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.