fp_reconstruct — OPTICS fpm op

• Data kinds: table → table

• Call: import fullseye as fs; fs.ledger.fp_reconstruct(stack, upsample=None, n_iter=200, pupil_update=True, pupil_start=2, alpha=1.0, beta=1.0, led_correction=False, led_start=3, stall_tol=0.01, stall_window=10, agree_tol=0.08, starts=2, margin_px=None, intensity_correction=False, pupil_degree=4, on_fail='raise') (to call the implementation directly, import fourierptycho; fourierptycho.fp_reconstruct(stack, upsample=None, n_iter=200, pupil_update=True, pupil_start=2, alpha=1.0, beta=1.0, led_correction=False, led_start=3, stall_tol=0.01, stall_window=10, agree_tol=0.08, starts=2, margin_px=None, intensity_correction=False, pupil_degree=4, on_fail='raise'); from the registry, opsoptics.get("fp_reconstruct"))

• GPU: none (CPU reference implementation only)

Usage

Recover the high-resolution amplitude and phase from the set of low-resolution images (table, kind="fp_result"). Refuses if it does not converge.

Sequential phase retrieval (EPRY of Ou et al. 2014, the same form as ePIE): one image at a time in ascending illumination NA, replace the amplitude of the estimated low-resolution field with the measured amplitude, and use the difference to correct the object spectrum with step alpha and the pupil with step beta (the pupil from iteration pupil_start on; outside the circle it is kept at 0).

• pupil_degree (default 4): the pupil phase update is projected onto polynomials of order 2–4 orthogonalised over the circle (12 terms; low-order aberrations such as defocus, astigmatism, coma and spherical), and the amplitude is kept at 1. None gives per-pixel free EPRY (the form of the original paper) —— ★a free pupil absorbs noise, so the residual keeps falling and never becomes stationary (in a 32-pixel, 25-image synthetic run, still more than 1 % per 10 iterations after 200).

• upsample: magnification of the high-resolution grid. If None, the smallest integer that accommodates the illumination NA with pixels ≤ λ/(2 NA_syn).

• led_correction: from iteration led_start on, for each image the pupil centre is tried at the 9 positions within ±1 pixel and the minimum residual is taken (ties are compared on rounded values, the earlier one in the candidates' declaration order wins); then the LED array geometry (shift x, y, rotation, height) is fitted to the table's led_xyz_mm by least squares and the centres are reset to that prediction (Sun et al. 2016's simulated annealing replaced by a deterministic local search).

• intensity_correction: from iteration 3 on, the per-image brightness ratio is matched to the mean of the estimate (for real data where the LED brightness is unknown). The image with the smallest illumination NA is held fixed as the reference —— ★if all are moved, a brighter estimate makes the measured images brighter again too, a positive feedback; on real data the residual diverged 20,000-fold in 200 iterations (with the pupil projected onto polynomials).

• Convergence: a fixed point of the relative amplitude residual and agreement within agree_tol of 2 runs with different initial values (starts=2). If the residual does not fall, the iterations run out, or the 2 runs reach different answers, it is "not converged". on_fail="raise" (default) raises FPNotConvergedError (MetricContractError, recorded in record), "record" returns the record (converged=False).

• starts=1 does not pass the uniqueness gate (stationarity alone can give converged=True): uniqueness_checked becomes False and "uniqueness not checked" is added to reason.

• Upper bound of the residual: the convergence test only looks at the change of the residual. If the final residual exceeds RESIDUAL_WARN (0.1), residual_warning=True is set and a warning is added to reason (the verdict is unchanged). Measured on synthetic data: converged reconstructions give 0.004–0.03 (including an rms 0.12-wave aberration); images unrelated to the illumination k stall at 0.17 with a single start.

Returns: dict(kind="fp_result", converged, reason, n_iter, residual, residual_history, residual_history_starts, step_history (relative change of the object spectrum per sweep), start_disagreement, field_second_start, amplitude, phase, field, pupil, pupil_support, na_synthetic (= NA_obj + the largest illumination NA used, including diagonal LEDs), na_synthetic_axis (value taken from the largest x, y direction cosine = the value that determines the resolution of bars along the axes), pixel_um, upsample, margin_px, led_centres, led_geometry, intensity_scale, uniqueness_checked, residual_warning, residual_ceiling).

Raises: ValueError (the table is incomplete or a value is out of range, the images carry no energy (the sum over all images after dividing by the exposure, edges excluded), the LED geometry fitted by the LED correction put a pupil centre outside the high-resolution grid).

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_fourier_ptychography — py -3.11 examples/poc_fourier_ptychography.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 (fpm)

fp_design · fp_usaf_target · fp_capture · fp_stack_from_images · fp_element_contrast


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