watervision op• Data kinds: rgbimage × image2d → table
• Call: import fullseye as fs; fs.ledger.underwater_inspection_image(scene_albedo, distance_map_m, water_type, light='camera', lamp_power=1.0, camera_distance_m=None, depth_m=2.0, exposure_photons=10000.0, fov_deg=60.0, beam_exponent=6.0) -> 'dict' (to call the implementation directly, import watervision; watervision.underwater_inspection_image(scene_albedo, distance_map_m, water_type, light='camera', lamp_power=1.0, camera_distance_m=None, depth_m=2.0, exposure_photons=10000.0, fov_deg=60.0, beam_exponent=6.0) -> 'dict'; from the registry, opsoptics.get("underwater_inspection_image"))
• GPU: none (CPU reference implementation only)
A hull or pipe inspection image: a lamp at the camera lights a wall through water -> dict (image, per-pixel SNR).
A thin layer over watermedia.underwater_image (the Akkaynak and Treibitz form: direct signal plus backscatter veil). scene_albedo is (H, W) or (H, W, 3) and distance_map_m (H, W) the camera-to-surface distance (> 0). light="camera": the only light is a lamp at the camera of lamp_power (relative power at 1 m); the wall is lit by lamp_power exp(-c r) / r^2 (beam attenuation c of the water at 0.61 / 0.54 / 0.465 um), reflects its albedo and the return path attenuates with Kd. The lamp also lights the water in front of the lens: the veil of the water type's single-scattering backscatter grows as 1 - exp(-Kd r) and is weighted by the lamp's beam pattern, cos(theta)^beam_exponent with theta the angle of the pixel from the axis (fov_deg across the width): strongest in the centre, where the lamp and the camera look at the same water. A baseline between lamp and camera, camera_distance_m (default
None: the lamp is at the camera), reduces the overlap of the lit and the seen water by 1 / (1 + (baseline / r)^2). light="ambient": daylight at depth_m and a veil of the daylight, uniform over the image (lamp_power is not used). light="both": the lamp and the daylight together (the signals and the veils add).
snr = sqrt(N) direct / sqrt(direct + backscatter + 1e-12) is the shot-noise limited signal to noise of the direct signal against its own shot noise and the shot noise of the veil (N = exposure_photons per unit radiance): it falls with distance because the direct term falls and the veil grows. snr_clear is the same expression for water that does not attenuate or scatter (equal to snr as the distance tends to 0). The table has image (H, W, 3), direct, backscatter, snr, snr_clear, snr_mean (over the three bands), veil_profile (H, W), transmission, beam_attenuation_c, kd, water_type and light. A zero or negative distance, an albedo outside [0, 1], an unknown water type and a bad light are refused.
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. Exception, declared: the u_map of bottle_fit_from_pattern takes pixels that could not be measured as NaN (infinity is still refused).
• 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. Exception, declared: bottle_background_map returns NaN in u_mm for pixels whose ray is unusable (total internal reflection and the like), together with a valid mask and the counts.
• 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.
• 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.
• poc_water_inspection_images — py -3.11 examples/poc_water_inspection_images.py
table as input)abcd_matrix · wavefront_stats · sfr_from_edge · paraxial_trace · seidel_coefficients · spot_stats · tolerance_analysis · wavefront_from_opd
watervision)caustic_irradiance · bottle_image · fill_level_from_image · bottle_background_map · bottle_fit_from_pattern · wet_surface_appearance · wet_dry_inspection_pair · caustic_floor_segmentation
*Provenance: watervision.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.