laser_line_center_centroid — OPTICS light_section op

• Data kinds: image2d → table

• Call: import fullseye as fs; fs.ledger.laser_line_center_centroid(image, min_peak_rel=0.1, half_window=None, background=0.0, clip_negative=True) (to call the implementation directly, import lightsection; lightsection.laser_line_center_centroid(image, min_peak_rel=0.1, half_window=None, background=0.0, clip_negative=True); from the registry, opsoptics.get("laser_line_center_centroid"))

• GPU: none (CPU reference implementation only)

Usage

Sub-pixel centre of a bright laser line in every column by the intensity centroid (table).

> The detailed description below is the original text — the summary and the headings are translated.

列ごとに最大の行 p を探し、[p − half_window, p + half_window](None = 列全体)の輝度から background を引いて負を 0 にした重みの重心を返す。最大値が画像全体の最大の min_peak_rel 倍以下の列は valid=False(row は NaN)。

For each column the peak row p is found, and the centroid of max(I − background, 0) over [p − half_window, p + half_window] (None = the whole column) is returned. Columns whose peak is at most min_peak_rel × the image maximum are marked valid=False (row NaN). clip_negative=False keeps negative weights (zero-mean noise stays zero-mean: the estimator is then linear in the noise, which is what light_section_uncertainty_budget's read-noise term assumes). Pixel-centre convention of camera: row r is the coordinate v = r exactly.

Returns {"kind": "laser_line_centers", "method": "centroid", "col", "row", "valid", "peak", "n_valid", "saturated", "truncated"}. Ground truth: a symmetric line profile has its centroid at the true centre; the centroid of a column-binned image equals that of the unbinned one (tests/test_lightsection.py). ★Limits: the centroid is biased toward the window's centre when the window cuts an off-centre profile, and a background left in the weights pulls it toward the column's middle.

★Saturation: the "peak" is the plateau of rows within 1e-9 of the column maximum that is connected to the first maximum; the window is [lo − half_window, hi + half_window] around that plateau (one row for an unclipped line: the same as before). A clipped, flat-topped line used to be windowed at the plateau's first row (−6.7 px at 20× over full scale, still valid). saturated marks the columns whose plateau has ≥ 3 rows, or ≥ 2 rows at the image's maximum (the ceiling); only those columns get the plateau window, every other column is windowed at its first maximum exactly as before (bit for bit). Two equal rows below the ceiling are common in quantised, unclipped lines (uint8, σ 3 px: 6 % of the columns at a peak of 200, 15 % at 60) and are not a plateau. The result in a saturated column is a centroid of clipped data, so treat it as degraded. ★Not detected: a plateau broken by a dip (dark-frame or flat-field correction after clipping leaves 1 LSB dips) — the plateau ends at the dip and the window shifts toward it again (measured 3.5 px, gauss 3.2 px, valid; 5.6 px before the plateau window); pass uncorrected data or clip it back to the ceiling first. truncated marks the columns whose weight at either end of the window (or of the image) is still ≥ max(min_peak_rel, 1e-3) × the column's peak weight — the profile was cut, so the centroid is pulled toward the window's middle (measured: σ 2 px, true 0.4 → 1.48). valid is not changed by either flag.

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_3d_sensor_modalities — py -3.11 examples/poc_3d_sensor_modalities.py

• poc_weld_bead_profile — py -3.11 examples/poc_weld_bead_profile.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 (light_section)

laser_line_center_gauss · laser_line_center_steger · light_section_rig · light_section_targets · light_section_render · light_plane_calibrate · light_section_profile · light_section_scan


*Provenance: lightsection.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.