bottle_fit_from_pattern — OPTICS watervision 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.bottle_fit_from_pattern(u_map, x_mm, pixel_pitch_um, camera_distance_mm, background_distance_mm, known=None, free_background=False, rows_per_section=None, restarts=3) -> 'dict' (to call the implementation directly, import watervision; watervision.bottle_fit_from_pattern(u_map, x_mm, pixel_pitch_um, camera_distance_mm, background_distance_mm, known=None, free_background=False, rows_per_section=None, restarts=3) -> 'dict'; from the registry, opsoptics.get("bottle_fit_from_pattern"))

• GPU: none (CPU reference implementation only)

Usage

Read the bottle back from the distortion of a background pattern -> dict (radius, wall, indices, fill line, and how well each is known).

u_map (rows, cols) is the background coordinate (mm) each pixel looks at (the u_mm of bottle_background_map, or a phase-shifting measurement of it; NaN where unusable); x_mm the column positions in the plane of the axis, pixel_pitch_um the pitch, the camera and the pattern are camera_distance_mm and background_distance_mm from the axis (known from the set-up). The fill line is found first, from the jump of the map between rows (the liquid and the air bend the pattern differently): rows above it are the empty section, rows below the filled one (level_mm as in fill_level_from_image: (rows below the boundary) * pitch). Each section is reduced to one profile (the median over its rows, rows_per_section of them if given, the two rows next to the boundary left out) and a closed-form model of concentric circles, written without the tracer (an angle and an impact parameter per ray), is fitted by Levenberg-Marquardt over radius_mm, wall_mm, n_glass and n_liquid (the filled section; a bottle with no fill line is one section whose core index is fitted). known is a dict of values held fixed ("radius_mm", "wall_mm", "n_glass", "n_liquid"); free_background=True also fits the distance of the pattern. The start is a scan of the radius from the silhouette (where the map leaves the straight line) with restarts (1 to 3) different index starts; the table reports how far the starts that reach the same cost disagree (start_spread).

The table has radius_mm, wall_mm, n_glass, n_liquid, background_distance_mm, free (the fitted names), sigma (1 sigma of each free name from the Gauss-Newton covariance with the residual scatter as the noise, None if not free), corr (correlation matrix of the free names) with corr_names, cond (condition number of the Jacobian with unit-length

columns: a large value means some combination of the parameters is hardly seen), singular_values, weak_direction (the combination with the smallest singular value, one number per free name) and weak_rel_sigma (the 1 sigma relative change along it), residual_rms_mm, n_columns and columns_rejected (resolved pixels the model calls blocked), converged, iterations, level_found / level_mm / level_row / level_jump_mm (False and None when the rows do not

jump: an empty or a full bottle has no fill line to read; level_mm is then None), fill_state ("partial" or "uniform"). poorly_determined lists the fitted names whose 1 sigma exceeds 1 % of the value or that sit on a bound, and separated is True only when the fit converged and that list is empty. This is a rule of thumb for the error of the noise, not a guarantee: the 1 % was chosen after measuring (at 25 % the flag called 15-20 % of the wrong wall estimates, and 73-76 % of the trials with n_glass off by more than 0.02, separated; at 1 % none of 535 wrong trials in two disjoint seed sets), the covariance assumes the model is exact, and a model error such as an off-centre inner wall changes the estimates without changing the residual, the sigma or this flag. n_liquid is None when no row of the map is left in the filled section. Quantities that barely move the pattern (a thin wall against the glass index when the edge pixels are lost, the pattern distance against the liquid index) come back there, with a large sigma and cond; they are not separated, and the table says so rather than giving a number without it. The model assumes concentric circles with a uniform wall, a horizontal 2-D section, an ideal pinhole, a flat background, exactly known distances and pitch, no dispersion and a flat liquid surface; the maps of bottle_background_map obey the same assumptions, so a test on them proves consistency, not that a real bottle is recovered. A map with fewer than 12 usable columns, a wrong shape, an unknown known name or a camera not beyond one diameter are refused.

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. 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.

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_bottle_pattern_inverse — py -3.11 examples/poc_bottle_pattern_inverse.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 (watervision)

caustic_irradiance · bottle_image · fill_level_from_image · bottle_background_map · wet_surface_appearance · wet_dry_inspection_pair · underwater_inspection_image · 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.