The radius norm: circular and square shells

The warped-Cartesian sensor lays a uniform lattice in a native plane and pushes each sample outward along its own ray by a radial cortical magnification law. radius_norm selects which norm measures radius in that native plane, and so what shape the iso-eccentricity shells take:

radius_norm

native radius

shells

pairs with

2.0 (default)

‖p‖₂

concentric circles

fov_type='circular'

math.inf

max(|pₓ|, |pᵧ|)

concentric squares

fov_type='square'

Both settings are the same sensor family running the same magnification law. Along the four axes they are the same one-dimensional map; they can only differ off-axis. What the infinity norm buys is full square coverage: the native square maps exactly onto the visual square, so every cell is valid including the corners, and nothing has to be masked away. Under the Euclidean norm the native corners overshoot the square FoV and get masked.

import math

from fovi.sensing.coords import SamplingCoords

coords = SamplingCoords(
    fov=16.0, cmf_a=0.5, res=64,
    style="warped_cartesian_as_grid", fov_type="square",
    radius_norm=math.inf,
)
image_coordinates = coords.as_grid(coords.cartesian, sample_dim=0)

radius_norm is accepted anywhere fov_type is, including RetinalTransform and the saccades config block (radius_norm: .inf in YAML). The _as_grid style returns the same samples as an upright image, shaped (batch, channels, resolution, resolution); the vector style is (batch, channels, resolution * resolution).

fov_type='wang' normalizes the Euclidean radius at the native square’s side centers, so it has no infinity-norm counterpart and is rejected with radius_norm=inf.

Mapping

Let p be a native coordinate, R = fov/2, a = cmf_a, b = max_val, and let s be the native radius measured in radius_norm. The visual radius is

g(s) = (a/R) × expm1((s/b) × log1p(bR/a)),

and the visual coordinate is p × g(s)/s, with its analytic limit at the origin. The inverse is b × log1p(Rt/a) / log1p(bR/a) for visual radius t. native_to_visual and visual_to_native expose both directions for tensors shaped (..., 2), including coordinates outside the footprint used for padding.

Pixel centers lie inside the boundary. Under radius_norm=inf the continuous square boundary maps exactly to itself.

max_val is the maximum radius in units of fov/2, measured in the norm that fov_type selects, and it is also the half-extent of the native lattice — the normalization that makes those two coincide. One asymmetry is worth knowing: under radius_norm=inf, max_val enters the normalizer, so changing it rescales the CMF; under radius_norm=2.0 it does not. The two norms therefore agree along the axes exactly when max_val = 1, which is the value used throughout.

The native manifold and receptive-field distance coordinates are two-dimensional. Polar coordinates always contain ordinary Euclidean eccentricity and angle, whichever norm drives the warp.

What the square shells cost

The map preserves direction but is not locally isotropic. Under radius_norm=inf the CMF is exact only along the axes: differentiating along a diagonal ray gives an effective foveal constant of √2·a rather than a, so the fovea is about 1.41× coarser on the diagonals, converging to parity in the far periphery. Magnification is therefore not constant over circles of equal Euclidean eccentricity — it is constant over squares.

max is also not differentiable where |pₓ| = |pᵧ|, so the Jacobian has seams along the diagonals, and lattice points land on those seams at every resolution. The forward/inverse pair stays exact there; only the derivative is one-sided.

Field geometry

planar and legacy use the existing planar visual-chart convention. spherical interprets visual-chart Euclidean radius as angular eccentricity. Under radius_norm=inf the footprint is square in that chart and its entire boundary must stay below the antipode — the corners reach √2 · max_val · fov/2, which is what the geometry check enforces. A square chart is not necessarily a square footprint in another camera projection. The native sensor remains a two-dimensional lattice.

Comparison images

Run scripts/render_sensor_fov_examples.py from the repository root. It renders both radius norms alongside the other sensor examples, plus square_shell_comparison.png showing mapped shells and sample locations against the Wang footprint. Use --output-dir to choose an artifact directory.