ocean_height_field — OPTICS watermedia op

• Data kinds: none → table (an op determined by its arguments alone — it takes no image or data input)

• Call: import fullseye as fs; fs.ledger.ocean_height_field(size, extent_m, wind_speed_mps, wind_dir_deg, t=0.0, seed=0, kind='pierson_moskowitz', directional='cos2', choppiness=0.0, depth_m=None, spreading_s=10.0, fetch_m=None, single_mode=None) -> 'dict' (to call the implementation directly, import watermedia; watermedia.ocean_height_field(size, extent_m, wind_speed_mps, wind_dir_deg, t=0.0, seed=0, kind='pierson_moskowitz', directional='cos2', choppiness=0.0, depth_m=None, spreading_s=10.0, fetch_m=None, single_mode=None) -> 'dict'; from the registry, opsoptics.get("ocean_height_field"))

• GPU: none (CPU reference implementation only)

Usage

A Tessendorf (2001) FFT sea surface at time t -> dict (height, slopes, normal map, displacement).

The wave-number spectrum is the omnidirectional spectrum of ocean_spectrum carried to the plane, psi(k, theta) = S(omega) D(theta) (d omega / d k) / k, with the dispersion omega^2 = g k (deep water) or omega^2 = g k tanh(k h) when depth_m is given. D is cos2 (2/pi cos^2 within 90 degrees of the wind) or cos2s (cos^(2s)((theta - theta_w) / 2), s = spreading_s), both normalised to 1 over the circle. The random amplitudes are h0(k) = (xi_r + j xi_i)/sqrt(2) sqrt(psi / 2) dk and h(k, t) = h0(k) e^(-j omega t) + conj(h0(-k)) e^(+j omega t), which is Hermitian, so the height is real (imag_residual reports the largest imaginary part left by the transform). The mean, the Nyquist row and column are zeroed. seed fixes the amplitudes:

the same seed gives the same sea at every t. choppiness moves each point horizontally by choppiness times the Tessendorf displacement (0 gives exactly zero displacement). size is the number of points per side (the grid is size x size over extent_m metres, wrap-around); wind_dir_deg is the direction the waves travel, from +x towards +y. single_mode=(mx, my, amplitude_m) replaces the random sea with one travelling cosine wave. The table has height, slope_x, slope_y (dh/dx, dh/dy), normal (H, W, 3), displacement_x / displacement_y, x_m / y_m, extent_m, variance_expected_m2 and slope_variance_expected (the sums of the spectrum over the modes in use), cox_munk_slope_variance (0.003 + 0.00512 U, Cox & Munk 1954), slope_variance_with_tail and omega_mode (single mode only). A sea of this kind has no capillary waves, so its resolved slope variance is far below the Cox & Munk value; slope_variance_with_tail adds the saturation range alpha / 2 ln(k_c / k_nyquist) of the spectrum up to the gravity-capillary wave number k_c = 370 rad/m. That agrees with Cox & Munk at 5 m/s and falls 40 % short at 10 m/s (the constant alpha of the Pierson-Moskowitz range does not grow with the wind); both are shown in the PoC.

Family-wide input contract (fail-closed)

Every optics op validates its input before computing (nothing slips through silently):

• Units are baked into the argument name — _mm / _um / _deg / _mrad. Confusing mm with µm does not crash; it yields a plausible-looking wrong answer, so the name prevents it. Nothing here guesses the unit from the magnitude.

• Strings raise ValueError — float('50') succeeds, so an unparsed configuration value would slip through as a length (measured: thin_lens('50', '200') returned a plausible 66.667 mm). bool is refused too, as the implicit promotion True == 1.

• complex / masked arrays raise ValueError (real-valued slots only; silently dropping the imaginary part or peeling off the mask is refused). NaN/Inf raises ValueError on every input.

• Division by zero and its relatives are refused by name: focal length 0, radius of curvature 0, refractive index <= 0, a fully opaque aperture (all zeros, so the normalisation is 0/0), a PSF whose sum is <= 0, a Stokes vector with S0 = 0, and an object sitting at the front focal point (the image is at infinity).

• Only two ops return a non-finite value, and both state it as a contract: depth_of_field returns far_mm = inf beyond the hyperfocal distance (that is what the hyperfocal distance means), and gaussian_beam returns wavefront_radius_mm = inf at the waist (the radius of curvature of a plane wavefront). Both also return a finite companion (far_is_infinite / curvature_per_mm). Any other silent NaN/Inf is detected internally and raises ValueError — "float64 overflowed" and "the answer is infinite" are different claims, so the first is never returned wearing the face of the second.

• Size caps: generated grids are capped by optics.MAX_GRID (4096); supplied fields/PSFs/apertures by optics.MAX_FIELD_ELEMENTS (2^24); ABCD element chains by optics.MAX_SYSTEM_ELEMENTS (1024); Zernike by MAX_ZERNIKE_TERMS (512) / MAX_ZERNIKE_ORDER (40) / MAX_ZERNIKE_BASIS (2^25). This closes, fail-closed, the paths where a small argument triggers a huge internal allocation (measured: n_max=40 × 4096² needs 108 GB).

• Physically impossible states are refused too: a Stokes vector with degree of polarisation > 1, negative transmittance, negative intensity, and invalid Zernike indices such as n-|m| odd.

Detailed usage guide

• optics_imaging family guide

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)

• poc_water_inspection_images — py -3.11 examples/poc_water_inspection_images.py

• poc_water_transparent_media — py -3.11 examples/poc_water_transparent_media.py

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

abcd_matrix · wavefront_stats · sfr_from_edge · paraxial_trace · seidel_coefficients · spot_stats · tolerance_analysis · wavefront_from_opd

Same category (watermedia)

ocean_spectrum · fresnel_water_air · snell_window · refracted_view · water_attenuation · underwater_image · phase_function_hg · scattering_phase_petzold


*Provenance: watermedia.py — OPTICS 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.