Initial commit: he11lib mode-purity reconstruction library

Full implementation of Laguerre-Gauss modal reconstruction for gyrotron
beam diagnostics, per the approved design spec, plus tests, docs, and
a runnable end-to-end example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-07-02 21:47:40 +02:00
commit 03b63ba03a
31 changed files with 3341 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
# he11lib API Reference
`he11lib` reconstructs the Laguerre-Gauss (LG) modal content ("mode purity")
of a free-space-propagating gyrotron RF beam from a set of thermal (flux)
images taken at different distances from the output window.
See `examples/full_pipeline_example.py` for a runnable end-to-end
demonstration, and
`docs/superpowers/specs/2026-07-02-gyrotron-mode-purity-design.md` for the
full design rationale.
Every class/function below is exported from the top-level `he11lib` package
(e.g. `from he11lib import BeamReconstructor`), except where noted.
## Quick start
```python
from he11lib import BeamReconstructor, MeasurementPlane
# planes: a list of >=3 MeasurementPlane objects built from your own
# flux arrays (see MeasurementPlane below).
reconstructor = BeamReconstructor(w0=5e-3, z0=0.5, wavelength=1.76e-3)
result = reconstructor.reconstruct(planes)
for mode, (power_fraction, phase_rad) in result.purity.items():
print(mode, power_fraction, phase_rad)
```
## `data` — `MeasurementPlane`, `ReconstructionResult`
### `MeasurementPlane(flux, z, pixel_scale=None, viewing_angle_deg=None, label=None)`
One measurement: a 2D flux array plus its acquisition metadata.
- `flux` — 2D `np.ndarray` of flux values. Dead-pixel correction, background
subtraction, and saturation clipping are assumed already handled upstream.
- `z` — nominal distance from the output window, in meters. Must be `> 0`.
- `pixel_scale` — known meters/pixel, or `None` if unknown (then jointly
fit by `ModalFitter`/`BeamReconstructor`).
- `viewing_angle_deg` — known camera viewing angle relative to the beam
axis, in degrees, or `None` if unknown (also jointly fit).
- `label` — optional human-readable identifier.
### `validate_planes(planes)`
Raises `ValueError` if there are fewer than 3 planes, planes have
mismatched flux shapes, or `z` values are not all distinct. Called
internally by `ModalFitter.fit`/`fit_auto`, `PhaseRetriever.retrieve`, and
`BeamReconstructor.reconstruct` — you generally don't need to call it
yourself. Not exported from the top-level package; import via
`from he11lib.data import validate_planes` if needed.
### `ReconstructionResult`
Output of a full reconstruction (returned by `ModalFitter.fit`/`fit_auto`
and `BeamReconstructor.reconstruct`):
- `purity: dict[(p, l), (power_fraction, phase_rad)]`
- `reconstructed_field: np.ndarray` — reconstructed complex field.
- `centers: list[(x, y)]` — fitted beam transverse center per plane, meters.
- `pointing_angle_deg: float` — fitted shared beam pointing angle (tilt).
- `geometry: dict[str, float]` — geometry parameters used or fitted (keys
`pixel_scale_{i}`, `viewing_angle_deg_{i}` per plane index `i`).
- `residuals: list[np.ndarray]` — per-plane (measured modeled) flux maps.
Empty when `used_phase_retrieval` is `True`.
- `coefficient_uncertainty: dict[(p, l), float]` — 1-sigma uncertainty on
each mode's fitted power fraction. `NaN` per mode when
`used_phase_retrieval` is `True`.
- `used_phase_retrieval: bool` — whether the phase-retrieval fallback (not
the modal fit) produced this result.
## `modes` — `LGBasis`
`LGBasis(w0, z0, wavelength)` — the LG mode basis referenced to a known
waist radius `w0` (m), waist location `z0` (m), and radiation `wavelength`
(m).
- `beam_radius(z)``w(z)`.
- `inverse_radius_of_curvature(z)``1/R(z)` (well-defined, `0`, at the
waist).
- `gouy_phase(z, p, l)` — Gouy phase of mode `(p, l)` at `z`.
- `field(x, y, z, p, l)` — complex `LG_{p,l}` field sampled on the `(x, y)`
grid at distance `z`.
- `field_superposition(x, y, z, coefficients)` — complex field for
`coefficients: dict[(p, l), complex]`.
- `project(complex_field, x, y, dx, z, modes)` — projects `complex_field`
onto each `(p, l)` in `modes`, returning `dict[(p, l), complex]`
coefficients (Riemann-sum inner product; `dx` is the grid spacing).
## `geometry` — `GeometryCalibration`
`GeometryCalibration(plane)` wraps a single `MeasurementPlane` and resolves
its pixel-to-physical-coordinate mapping.
- `pixel_scale_known` / `viewing_angle_known``bool` properties.
- `physical_coordinates(pixel_scale=None, viewing_angle_deg=None)`
returns `(x, y)` physical coordinate grids matching the plane's flux
shape. Values known on the `MeasurementPlane` take precedence over the
`override` arguments; raises `ValueError` if a value is neither known nor
overridden.
## `noise` — `NoiseEstimator`
`NoiseEstimator()` — automatic per-image noise estimation (no
user-supplied noise parameter needed).
- `estimate_std(image)` — fast Laplacian-based (Immerkær 1996) noise
standard-deviation estimate.
- `weights(image)` — per-pixel weights (`1/sigma**2`) for noise-weighted
least squares.
## `deconvolution` — `DiffusionDeconvolver`
`DiffusionDeconvolver(thermal_diffusivity, dwell_time)` — optional
correction for lateral thermal-diffusion blur in the absorbing target
(`thermal_diffusivity` in m²/s, `dwell_time` in s). Disabled unless you
pass a `deconvolver` to `BeamReconstructor`.
- `blur_sigma_m()` — Gaussian blur standard deviation, in meters.
- `blur(image, pixel_scale)` — forward blur (for synthetic testing).
- `deconvolve(image, pixel_scale, noise_to_signal_ratio=1e-3)` — regularized
(Wiener) removal of the blur.
Note: the blur/deconvolution kernel is isotropic in pixel space. If a
plane has a nonzero `viewing_angle_deg`, its `x` and `y` pixel axes have
different physical scales (see `SyntheticBeamGenerator` below), so
deconvolution is only exact for `viewing_angle_deg == 0`; at oblique
angles it is an approximation.
## `synthetic` — `SyntheticBeamGenerator`
`SyntheticBeamGenerator(basis, image_shape, pixel_scale)` — forward model
used to validate the pipeline against known ground truth, and to evaluate
experimental design (e.g. "would these distances separate my modes?").
`pixel_scale` is the physical pixel size, in meters, along the non-tilted
`y` axis; the `x` axis is compressed by `1/cos(viewing_angle_deg)` to model
an oblique camera view.
- `generate(coefficients, z_list, *, center=(0, 0), pointing_angle_deg=0.0, viewing_angle_deg=0.0, noise_std=0.0, seed=None)`
— returns one `MeasurementPlane` per `z` in `z_list`. The beam's
transverse center drifts linearly with `z` according to
`pointing_angle_deg`, starting from `center` at `z0`.
## `fitting` — `ModalFitter`, `generate_mode_shells`
### `generate_mode_shells(max_order)`
Groups candidate `LG_{p,l}` modes into shells of increasing order
`2p + |l|`, up to and including `max_order`. Returns
`list[list[(p, l)]]`, one list of modes per order.
### `ModalFitter(basis, noise_estimator=None)`
Core reconstruction path: a joint nonlinear least-squares fit of complex LG
coefficients, beam center/pointing, and (if unknown) geometry.
- `fit(planes, modes, initial_coefficients=None, initial_center=(0.0, 0.0), initial_tilt_deg=(0.0, 0.0), initial_pixel_scale=None, initial_viewing_angle_deg=0.0) -> ReconstructionResult`
— fits exactly the given candidate `modes`.
- `fit_auto(planes, max_order=4, bic_improvement_threshold=10.0) -> ReconstructionResult`
— starts from `LG_00` and grows the candidate mode set shell-by-shell
(via `generate_mode_shells`), stopping once BIC no longer improves by
more than `bic_improvement_threshold`, capped at `max_order`. Emits a
`UserWarning` (does not raise) if the cap is reached while the fit is
still improving.
## `phase_retrieval` — `PhaseRetriever`, `propagate_angular_spectrum`
Fallback reconstruction path for when the modal fit's residual stays high,
or when the mode content isn't well described by a small finite mode set.
### `propagate_angular_spectrum(field, dx, dz, wavelength)`
Free-space-propagates a complex `field` (pixel spacing `dx`) by distance
`dz` via the (paraxial) angular-spectrum method — the same propagation
model implicitly assumed by `LGBasis`'s closed-form paraxial modes.
### `PhaseRetriever(wavelength)`
- `retrieve(planes, pixel_scale=None, viewing_angle_deg=None, max_iterations=200) -> PhaseRetrievalResult`
— multi-plane Gerchberg-Saxton phase retrieval: propagates a trial
complex field back and forth between planes, enforcing the measured
amplitude (`sqrt(flux)`) at each plane, without assuming a finite mode
basis.
### `PhaseRetrievalResult`
`field, x, y, z, center, residual` — the recovered complex field (at the
smallest-`z` plane) on its `(x, y)` grid, the estimated beam center
(intensity centroid), and the final RMS amplitude-mismatch residual.
Project `field` onto `LGBasis` (via `LGBasis.project`) to get a purity
table, as `BeamReconstructor` does internally for its fallback path.
## `reconstruct` — `BeamReconstructor`
`BeamReconstructor(w0, z0, wavelength, max_order=4, noise_estimator=None, deconvolver=None, force_phase_retrieval=False, phase_retrieval_residual_threshold=None)`
High-level orchestrator wiring together the full pipeline: optional
diffusion deblurring → `ModalFitter.fit_auto` → optional
`PhaseRetriever` fallback.
- `reconstruct(planes) -> ReconstructionResult`
1. Validates `planes` (see `validate_planes`).
2. If `deconvolver` is set, deblurs each plane (raises `ValueError` if a
plane's `pixel_scale` isn't known).
3. Runs `ModalFitter(basis, noise_estimator).fit_auto(planes, max_order)`.
4. Runs the `PhaseRetriever` fallback instead, projecting its recovered
field onto all modes up to `max_order`, if `force_phase_retrieval` is
`True`, or if `phase_retrieval_residual_threshold` is set and the
modal fit's noise-weighted RMS residual exceeds it. In that case
`result.residuals` is empty and `coefficient_uncertainty` is `NaN`
per mode (phase retrieval doesn't produce a fit covariance).
## `plotting` — diagnostic visualizations
Each function returns a `matplotlib.figure.Figure` for the caller to
display (`fig.show()`) or save (`fig.savefig(...)`); none of them call
`plt.show()` themselves.
- `plot_mode_purity(result)` — bar chart of power fraction per mode.
- `plot_center_trace(planes, result)` — fitted beam center `(x, y)` vs. `z`.
- `plot_residuals(planes, result)` — per-plane residual maps. Raises
`ValueError` if `result.residuals` is empty (e.g. after the
phase-retrieval fallback).
@@ -0,0 +1,212 @@
# he11lib — Gyrotron Beam Mode Purity Reconstruction
**Date:** 2026-07-02
**Status:** Approved for planning
## Purpose
A general-purpose, reusable Python library for reconstructing the Laguerre-Gauss
(LG) modal content ("mode purity") of a free-space-propagating gyrotron RF beam,
from a set of thermal (flux) images captured at different distances from the
gyrotron output window. The library must account for real-world measurement
issues: unknown/partial camera geometry, sensor noise, target thermal-diffusion
blur, and unknown beam pointing/centering — not just an idealized intensity fit.
This is a from-scratch library (empty project directory), intended to be used by
others beyond the initial author.
## Scope
- Beam regime: **free-space quasi-optical propagation** (post mode-converter /
output window), not waveguide-confined hybrid modes.
- Mode basis: **Laguerre-Gauss modes** `LG_{p,l}`, referenced to a **known**
waist size `w0` and waist location `z0` supplied by the user (design values
from the gyrotron/mode-converter specs) — these are inputs, not fit
parameters.
- Input data: **pre-processed NumPy flux arrays** (already extracted from raw
NVF radiometric camera files by the user's own pipeline). Dead-pixel
correction, background/ambient subtraction, and saturation-clipping detection
are already handled upstream and out of scope for this library. Because the
data is flux (not temperature), there is no temperature-to-power nonlinearity
to correct.
- Number of measurement planes: **3 to 10**, at distances spanning roughly
0100 cm from the output window (e.g. 30/40/50/60 cm), known to a nominal
precision (set by a translation stage or tape measure).
- Camera geometry (pixel-to-length scale, viewing angle relative to beam axis):
may be **known from a prior calibration** (supplied as input) **or unknown**
(estimated jointly with everything else).
- Beam **transverse center and pointing angle** are always unknown and must be
estimated from the images.
- Residual sensor noise (NETD-type, after upstream preprocessing) is
**auto-estimated per image**; no user-supplied noise parameter required for
the default path.
- Target thermal-diffusion blur correction (deconvolution) is **optional**,
parametrized by target thermal diffusivity + exposure/dwell time.
- Deliverables include full API docs and an end-to-end example
script/notebook demonstrating the complete pipeline.
## Architecture
Modular/composable design: each concern is an independent, testable component
with a clear interface, wired together by a high-level orchestrator. This
supports both simple end-to-end use and power-user access to individual
stages (e.g., using `LGBasis` standalone, or swapping in a custom noise
model).
### Components
- **`data.py``MeasurementPlane`, `ReconstructionResult`**
`MeasurementPlane`: container for one measurement — flux array, nominal `z`
distance, optional known pixel scale (mm/px) and viewing angle (deg),
optional metadata (timestamp, label). `ReconstructionResult`: container for
all pipeline outputs (see below).
- **`geometry.py``GeometryCalibration`**
Applies projective/perspective correction to compensate for oblique camera
viewing angle, and converts pixel coordinates to physical length units. If
pixel scale and/or viewing angle are supplied on a `MeasurementPlane`, uses
them directly; if not supplied, exposes them as free parameters to be
solved jointly by the `ModalFitter`.
- **`noise.py``NoiseEstimator`**
Estimates residual per-image noise standard deviation automatically (e.g.
from low-signal/background regions or high-frequency residual content).
Produces per-pixel or per-image weights used by the `ModalFitter`'s
noise-weighted least squares.
- **`deconvolution.py``DiffusionDeconvolver`**
Optional step. Given target material thermal diffusivity and
exposure/dwell time, builds a blur kernel modeling lateral heat spreading
in the absorbing target and deconvolves it from each plane before fitting.
Disabled by default.
- **`modes.py``LGBasis`**
Given reference `w0`, `z0`, generates complex LG mode fields `LG_{p,l}(x,
y, z)` at arbitrary transverse coordinates and axial distance `z`,
including correct Gouy phase and beam-radius evolution. Supports
evaluating a finite candidate set of `(p, l)` indices and projecting an
arbitrary complex field onto the basis (used both by the modal fit and by
the phase-retrieval fallback).
- **`fitting.py``ModalFitter`**
Core reconstruction path. Parameters: complex LG coefficients
(amplitude + phase) for each candidate mode, beam transverse center `(x,
y)` per plane, a shared beam pointing angle (tilt), and any uncalibrated
geometry parameters (pixel scale / viewing angle) not supplied on the
`MeasurementPlane`s. Objective: noise-weighted nonlinear least-squares
residual between modeled `|Σ c_j · LG_j(x, y, z)|²` and measured flux,
summed over all planes.
**Automatic mode-set growth**: starts from `LG_00`, incrementally adds
candidate modes in shells of increasing order, and stops growing once an
information-criterion (e.g. BIC) / residual-reduction test shows no
meaningful improvement, subject to a configurable maximum order cap (for
tractability and as a safety bound). Warns (does not error) if the cap is
hit while still improving, or if final residuals remain large.
- **`phase_retrieval.py``PhaseRetriever`**
Optional/fallback path. Gerchberg-Saxton-style iterative multi-plane phase
retrieval: propagates a trial complex field back and forth between
measurement planes (via free-space propagation, e.g. angular spectrum),
enforcing the measured amplitude (sqrt of flux) at each plane, without
assuming a finite mode basis. Used when explicitly requested, or
automatically as a fallback when `ModalFitter`'s residual stays high after
mode-set growth completes. The recovered field is then projected onto
`LGBasis` to produce a purity table.
- **`synthetic.py``SyntheticBeamGenerator`**
Forward model: given known mode coefficients, `w0`/`z0`, geometry
(center, tilt, viewing angle, pixel scale), noise level, and optional
diffusion blur, generates synthetic multi-plane flux images. Used for
library validation (recover known ground truth) and for users to test
experimental design (e.g., "would these 4 distances separate my modes?").
- **`reconstruct.py``BeamReconstructor`**
High-level orchestrator: given a list of `MeasurementPlane`s and
configuration (known `w0`/`z0`, optional deconvolution params, optional
mode-set cap, optional forced phase-retrieval mode), runs geometry
correction → noise estimation → optional deconvolution → modal fit (with
automatic growth) → optional phase-retrieval fallback → produces a
`ReconstructionResult`. Each stage remains independently accessible for
power users.
- **`plotting.py`**
Diagnostic visualizations: measured vs. reconstructed intensity per plane,
residual maps, mode purity bar chart, beam center/pointing trace across
planes.
### Data flow
1. Build a list of `MeasurementPlane` objects (flux array + nominal z +
optional known geometry per plane).
2. `GeometryCalibration` applies projective correction using supplied
pixel-scale/viewing-angle, or flags them as unknowns.
3. `NoiseEstimator` computes per-plane noise weights.
4. `DiffusionDeconvolver` optionally deblurs each plane.
5. `ModalFitter` runs the joint noise-weighted nonlinear least-squares fit
over LG coefficients + center + pointing + any uncalibrated geometry
params, growing the mode set automatically.
6. If requested, or if fit residual remains high, `PhaseRetriever` runs as a
fallback and its result is projected onto `LGBasis`.
7. `BeamReconstructor` assembles a `ReconstructionResult` containing: mode
purity table (power fraction + phase per mode), reconstructed complex
field, fitted beam center/pointing per plane, geometry parameters used or
fitted, per-plane residual maps, and coefficient uncertainties (from the
fit's covariance).
## Testing strategy
`SyntheticBeamGenerator` is the backbone of validation: generate synthetic
multi-plane data from known ground-truth mode content, geometry, and noise,
run it through the full pipeline (and through individual components in
isolation), and assert recovered parameters match ground truth within
tolerance. Individual components also get targeted unit tests against known
analytic cases (e.g. `LGBasis` orthogonality and known Gouy phase values,
single-pure-mode recovery, geometry correction on synthetic projective
distortions).
## Error handling
Validate only at boundaries: reject malformed `MeasurementPlane` inputs
(mismatched array shapes, non-positive `z`, fewer than 3 planes). Warn
(rather than raise) when automatic mode-set growth hits its configured cap
while still improving, or when final fit residuals remain large — the caller
gets the result plus a diagnostic flag, not a crash.
## Dependencies
NumPy, SciPy (`optimize` for the nonlinear least-squares fit, `ndimage` for
projective transforms and deconvolution), Matplotlib for diagnostic
plotting. No GPU requirement.
## Package layout
```
he11lib/
data.py # MeasurementPlane, ReconstructionResult
geometry.py # GeometryCalibration
noise.py # NoiseEstimator
deconvolution.py # DiffusionDeconvolver
modes.py # LGBasis
fitting.py # ModalFitter (+ automatic mode-set growth)
phase_retrieval.py # PhaseRetriever
synthetic.py # SyntheticBeamGenerator
reconstruct.py # BeamReconstructor (orchestrator)
plotting.py # diagnostic visualizations
docs/
... # API docs
examples/
full_pipeline_example.py # end-to-end demo of the whole pipeline
tests/
...
```
## Deliverables
- Full implementation of all components above.
- API documentation for the public interface of every module.
- An end-to-end example (script or notebook) exercising the complete
pipeline on synthetic data, from `SyntheticBeamGenerator` through
`BeamReconstructor` to `plotting.py` diagnostics.
- Test suite covering synthetic ground-truth recovery and per-component unit
tests.