Update docs/api.md for the CameraModel geometry redesign

Documents CameraModel/CameraModelTolerance, the rewritten
GeometryCalibration, z_tolerance, the two pointing angles, and every
downstream signature change (ModalFitter, SyntheticBeamGenerator,
PhaseRetriever, BeamReconstructor) introduced by the redesign.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Martino Ferrari
2026-07-03 12:33:00 +02:00
parent 57dc7d743e
commit c6b824660d
+132 -48
View File
@@ -15,11 +15,33 @@ Every class/function below is exported from the top-level `he11lib` package
## Quick start ## Quick start
```python ```python
from he11lib import BeamReconstructor, MeasurementPlane from he11lib import (
BeamReconstructor,
CameraModel,
CameraModelTolerance,
MeasurementPlane,
)
# planes: a list of >=3 MeasurementPlane objects built from your own # planes: a list of >=3 MeasurementPlane objects built from your own
# flux arrays (see MeasurementPlane below). # flux arrays (see MeasurementPlane below).
reconstructor = BeamReconstructor(w0=5e-3, z0=0.5, wavelength=1.76e-3)
# Nominal camera pose/intrinsics from calibration; every field here is
# refined jointly with the mode fit because its tolerance is nonzero.
camera = CameraModel(
focal_length_px=2000.0,
position=(0.0, 0.0, -2.0),
orientation_deg=(0.0, 0.0, 0.0),
)
camera_tolerance = CameraModelTolerance(
focal_length_px=20.0,
position=(0.01, 0.01, 0.05),
orientation_deg=(2.0, 2.0, 2.0),
)
reconstructor = BeamReconstructor(
w0=5e-3, z0=0.5, wavelength=1.76e-3,
camera=camera, camera_tolerance=camera_tolerance,
)
result = reconstructor.reconstruct(planes) result = reconstructor.reconstruct(planes)
for mode, (power_fraction, phase_rad) in result.purity.items(): for mode, (power_fraction, phase_rad) in result.purity.items():
@@ -28,19 +50,22 @@ for mode, (power_fraction, phase_rad) in result.purity.items():
## `data` — `MeasurementPlane`, `ReconstructionResult` ## `data` — `MeasurementPlane`, `ReconstructionResult`
### `MeasurementPlane(flux, z, pixel_scale=None, viewing_angle_deg=None, label=None)` ### `MeasurementPlane(flux, z, z_tolerance=0.0, label=None)`
One measurement: a 2D flux array plus its acquisition metadata. One measurement: a 2D flux array plus its acquisition metadata.
- `flux` — 2D `np.ndarray` of flux values. Dead-pixel correction, background - `flux` — 2D `np.ndarray` of flux values. Dead-pixel correction, background
subtraction, and saturation clipping are assumed already handled upstream. subtraction, and saturation clipping are assumed already handled upstream.
- `z` — nominal distance from the output window, in meters. Must be `> 0`. - `z` — nominal distance from the output window, in meters. Must be `> 0`.
- `pixel_scale` — known meters/pixel, or `None` if unknown (then jointly - `z_tolerance``+/-` bound, in meters, around the nominal `z` within
fit by `ModalFitter`/`BeamReconstructor`). which the true distance is jointly refined by `ModalFitter`. Must be
- `viewing_angle_deg` — known camera viewing angle relative to the beam `>= 0`; `0` (the default) means `z` is trusted exactly and held fixed.
axis, in degrees, or `None` if unknown (also jointly fit).
- `label` — optional human-readable identifier. - `label` — optional human-readable identifier.
Per-plane camera geometry (`pixel_scale`/`viewing_angle_deg`) no longer
lives on `MeasurementPlane` — camera pose/intrinsics are a single shared
`CameraModel` for the whole reconstruction (see `geometry` below).
### `validate_planes(planes)` ### `validate_planes(planes)`
Raises `ValueError` if there are fewer than 3 planes, planes have Raises `ValueError` if there are fewer than 3 planes, planes have
@@ -58,9 +83,14 @@ and `BeamReconstructor.reconstruct`):
- `purity: dict[(p, l), (power_fraction, phase_rad)]` - `purity: dict[(p, l), (power_fraction, phase_rad)]`
- `reconstructed_field: np.ndarray` — reconstructed complex field. - `reconstructed_field: np.ndarray` — reconstructed complex field.
- `centers: list[(x, y)]` — fitted beam transverse center per plane, meters. - `centers: list[(x, y)]` — fitted beam transverse center per plane, meters.
- `pointing_angle_deg: float` — fitted shared beam pointing angle (tilt). - `pointing_angle_horizontal_deg`, `pointing_angle_vertical_deg: float`
- `geometry: dict[str, float]` — geometry parameters used or fitted (keys fitted shared beam pointing (tilt) angles, independent horizontal and
`pixel_scale_{i}`, `viewing_angle_deg_{i}` per plane index `i`). vertical.
- `geometry: dict[str, float]` — geometry parameters used or fitted: the 9
`CameraModel` field names from `he11lib.geometry.CAMERA_FIELD_NAMES`
(`focal_length_px`, `position_x`, `position_y`, `position_z`, `yaw_deg`,
`pitch_deg`, `roll_deg`, `principal_point_x`, `principal_point_y`), plus
`z_{i}` per plane index `i` (that plane's fitted/held distance).
- `residuals: list[np.ndarray]` — per-plane (measured modeled) flux maps. - `residuals: list[np.ndarray]` — per-plane (measured modeled) flux maps.
Empty when `used_phase_retrieval` is `True`. Empty when `used_phase_retrieval` is `True`.
- `coefficient_uncertainty: dict[(p, l), float]` — 1-sigma uncertainty on - `coefficient_uncertainty: dict[(p, l), float]` — 1-sigma uncertainty on
@@ -87,17 +117,55 @@ waist radius `w0` (m), waist location `z0` (m), and radiation `wavelength`
onto each `(p, l)` in `modes`, returning `dict[(p, l), complex]` onto each `(p, l)` in `modes`, returning `dict[(p, l), complex]`
coefficients (Riemann-sum inner product; `dx` is the grid spacing). coefficients (Riemann-sum inner product; `dx` is the grid spacing).
## `geometry` — `GeometryCalibration` ## `geometry` — `CameraModel`, `CameraModelTolerance`, `GeometryCalibration`
`GeometryCalibration(plane)` wraps a single `MeasurementPlane` and resolves ### `CameraModel(focal_length_px, position, orientation_deg, principal_point=(0.0, 0.0))`
its pixel-to-physical-coordinate mapping.
- `pixel_scale_known` / `viewing_angle_known``bool` properties. A nominal pinhole camera pose/intrinsics shared across every plane in one
- `physical_coordinates(pixel_scale=None, viewing_angle_deg=None)` reconstruction. Always a point estimate — never trusted as exact by
returns `(x, y)` physical coordinate grids matching the plane's flux itself; trust is expressed via the paired `CameraModelTolerance`.
shape. Values known on the `MeasurementPlane` take precedence over the
`override` arguments; raises `ValueError` if a value is neither known nor - `focal_length_px` — focal length in pixel units.
overridden. - `position``(x, y, z)` camera position in the beam-axis world frame,
meters; `z=0` is the output window.
- `orientation_deg``(yaw, pitch, roll)`, degrees. All-zero means the
boresight is normal to every `z=const` target plane with no in-plane
rotation.
- `principal_point``(px, px)` offset from the frame center.
### `CameraModelTolerance(focal_length_px, position, orientation_deg, principal_point=(0.0, 0.0))`
Per-field `+/-` refinement bound, same shape as `CameraModel`. Every field
must be `>= 0` (raises `ValueError` otherwise). A field's tolerance of `0`
holds that `CameraModel` field fixed at its nominal value during fitting;
`> 0` lets `ModalFitter` refine it within `[nominal - tolerance, nominal +
tolerance]`.
### `GeometryCalibration(camera)`
Wraps a `CameraModel` and resolves pixel <-> physical coordinate mappings
via true pinhole projection (not a uniform affine/cosine approximation).
- `pixel_coordinates(x, y, z) -> (row, col)` — forward-projects physical
`(x, y)` at depth `z` to pixel coordinates. Raises `ValueError` if the
point is behind the camera (`Z_cam <= 0`).
- `physical_coordinates(image_shape, z) -> (x, y)` — inverse-projects every
pixel in a frame of `image_shape` to physical `(x, y)` on the `z=const`
plane, via ray-plane intersection (this is what produces genuine
keystoning — non-uniform spacing across the frame — for tilted/off-axis
poses). Raises `ValueError` if the plane is edge-on to or behind the
camera.
- `effective_pixel_scale(image_shape, z) -> float` — a single isotropic
meters/pixel figure (finite-difference approximation at the frame
center), for callers like `DiffusionDeconvolver` that assume one
isotropic pixel-space kernel.
### `CAMERA_FIELD_NAMES`, `camera_to_values`, `tolerance_to_values`, `camera_from_values`
Module-level helpers used internally by `ModalFitter` to flatten/unflatten
`CameraModel`/`CameraModelTolerance` into the optimizer's parameter vector.
Not usually needed by application code, but exported for advanced use
(e.g. inspecting `CAMERA_FIELD_NAMES` to interpret `ReconstructionResult.geometry` keys).
## `noise` — `NoiseEstimator` ## `noise` — `NoiseEstimator`
@@ -121,25 +189,30 @@ pass a `deconvolver` to `BeamReconstructor`.
- `deconvolve(image, pixel_scale, noise_to_signal_ratio=1e-3)` — regularized - `deconvolve(image, pixel_scale, noise_to_signal_ratio=1e-3)` — regularized
(Wiener) removal of the blur. (Wiener) removal of the blur.
Note: the blur/deconvolution kernel is isotropic in pixel space. If a Note: the blur/deconvolution kernel is isotropic in pixel space. A tilted
plane has a nonzero `viewing_angle_deg`, its `x` and `y` pixel axes have or off-axis `CameraModel` produces a pixel scale that varies across the
different physical scales (see `SyntheticBeamGenerator` below), so frame and between `x`/`y` (keystoning), so `deconvolve` uses
deconvolution is only exact for `viewing_angle_deg == 0`; at oblique `GeometryCalibration.effective_pixel_scale` — a single isotropic
angles it is an approximation. approximation evaluated at the frame center. This is exact only for an
on-axis, untilted camera; at oblique poses it is an accepted
approximation (see `CLAUDE.md`).
## `synthetic` — `SyntheticBeamGenerator` ## `synthetic` — `SyntheticBeamGenerator`
`SyntheticBeamGenerator(basis, image_shape, pixel_scale)` — forward model `SyntheticBeamGenerator(basis, camera)` — forward model used to validate
used to validate the pipeline against known ground truth, and to evaluate the pipeline against known ground truth, and to evaluate experimental
experimental design (e.g. "would these distances separate my modes?"). design. `camera` is the ground-truth `CameraModel` (position/orientation/
`pixel_scale` is the physical pixel size, in meters, along the non-tilted intrinsics) used to render each plane via true perspective projection.
`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)` - `generate(coefficients, z_list, image_shape, *, center=(0.0, 0.0), pointing_angle_horizontal_deg=0.0, pointing_angle_vertical_deg=0.0, z_tolerance=0.0, nominal_z_offsets=None, noise_std=0.0, seed=None) -> list[MeasurementPlane]`
— returns one `MeasurementPlane` per `z` in `z_list`. The beam's — returns one `MeasurementPlane` per (true) `z` in `z_list`. The beam's
transverse center drifts linearly with `z` according to transverse center drifts linearly with `z` according to the two
`pointing_angle_deg`, starting from `center` at `z0`. independent pointing angles, starting from `center` at the basis's
`z0`. `nominal_z_offsets`, if given, maps a true `z` to an offset
applied to that plane's *nominal* `z` — letting a reconstruction be
tested against a deliberately-offset nominal input while the plane's
flux is still rendered at the true `z`. Every resulting plane shares
`z_tolerance`.
## `fitting` — `ModalFitter`, `generate_mode_shells` ## `fitting` — `ModalFitter`, `generate_mode_shells`
@@ -152,16 +225,22 @@ Groups candidate `LG_{p,l}` modes into shells of increasing order
### `ModalFitter(basis, noise_estimator=None)` ### `ModalFitter(basis, noise_estimator=None)`
Core reconstruction path: a joint nonlinear least-squares fit of complex LG Core reconstruction path: a joint nonlinear least-squares fit of complex LG
coefficients, beam center/pointing, and (if unknown) geometry. coefficients, beam center/pointing, and any nonzero-tolerance camera/`z`
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` - `fit(planes, modes, camera, camera_tolerance, initial_coefficients=None, initial_center=(0.0, 0.0), initial_pointing_deg=(0.0, 0.0)) -> ReconstructionResult`
— fits exactly the given candidate `modes`. — fits exactly the given candidate `modes`. Every `CameraModel` field
- `fit_auto(planes, max_order=4, bic_improvement_threshold=10.0) -> ReconstructionResult` with a nonzero `camera_tolerance` entry, and every plane whose
`z_tolerance` is nonzero, is refined within `[nominal - tolerance,
nominal + tolerance]`; zero-tolerance fields are held fixed at their
nominal value.
- `fit_auto(planes, camera, camera_tolerance, max_order=4, bic_improvement_threshold=10.0) -> ReconstructionResult`
— starts from `LG_00` and grows the candidate mode set shell-by-shell — starts from `LG_00` and grows the candidate mode set shell-by-shell
(via `generate_mode_shells`), stopping once BIC no longer improves by (via `generate_mode_shells`), stopping once BIC no longer improves by
more than `bic_improvement_threshold`, capped at `max_order`. Emits a more than `bic_improvement_threshold`, capped at `max_order`. Emits a
`UserWarning` (does not raise) if the cap is reached while the fit is `UserWarning` (does not raise) if the cap is reached while the fit is
still improving. still improving, or if the number of free camera+`z` parameters is large
relative to the number of planes (see `CLAUDE.md`'s degeneracy pitfall).
## `phase_retrieval` — `PhaseRetriever`, `propagate_angular_spectrum` ## `phase_retrieval` — `PhaseRetriever`, `propagate_angular_spectrum`
@@ -176,11 +255,12 @@ model implicitly assumed by `LGBasis`'s closed-form paraxial modes.
### `PhaseRetriever(wavelength)` ### `PhaseRetriever(wavelength)`
- `retrieve(planes, pixel_scale=None, viewing_angle_deg=None, max_iterations=200) -> PhaseRetrievalResult` - `retrieve(planes, camera, max_iterations=200) -> PhaseRetrievalResult`
— multi-plane Gerchberg-Saxton phase retrieval: propagates a trial — multi-plane Gerchberg-Saxton phase retrieval: propagates a trial
complex field back and forth between planes, enforcing the measured complex field back and forth between planes, enforcing the measured
amplitude (`sqrt(flux)`) at each plane, without assuming a finite mode amplitude (`sqrt(flux)`) at each plane, without assuming a finite mode
basis. basis. All planes are propagated on one common physical grid, derived
from `camera` at the smallest-`z` plane's depth.
### `PhaseRetrievalResult` ### `PhaseRetrievalResult`
@@ -192,23 +272,27 @@ table, as `BeamReconstructor` does internally for its fallback path.
## `reconstruct` — `BeamReconstructor` ## `reconstruct` — `BeamReconstructor`
`BeamReconstructor(w0, z0, wavelength, max_order=4, noise_estimator=None, deconvolver=None, force_phase_retrieval=False, phase_retrieval_residual_threshold=None)` `BeamReconstructor(w0, z0, wavelength, camera, camera_tolerance, 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 High-level orchestrator wiring together the full pipeline: optional
diffusion deblurring → `ModalFitter.fit_auto` → optional diffusion deblurring → `ModalFitter.fit_auto` → optional
`PhaseRetriever` fallback. `PhaseRetriever` fallback. `camera`/`camera_tolerance` are the nominal
shared `CameraModel` and its per-field refinement bounds for this
reconstruction.
- `reconstruct(planes) -> ReconstructionResult` - `reconstruct(planes) -> ReconstructionResult`
1. Validates `planes` (see `validate_planes`). 1. Validates `planes` (see `validate_planes`).
2. If `deconvolver` is set, deblurs each plane (raises `ValueError` if a 2. If `deconvolver` is set, deblurs each plane using
plane's `pixel_scale` isn't known). `GeometryCalibration(camera).effective_pixel_scale(plane.flux.shape, plane.z)`.
3. Runs `ModalFitter(basis, noise_estimator).fit_auto(planes, max_order)`. 3. Runs `ModalFitter(basis, noise_estimator).fit_auto(planes, camera, camera_tolerance, max_order)`.
4. Runs the `PhaseRetriever` fallback instead, projecting its recovered 4. Runs the `PhaseRetriever` fallback instead, projecting its recovered
field onto all modes up to `max_order`, if `force_phase_retrieval` is field onto all modes up to `max_order`, if `force_phase_retrieval` is
`True`, or if `phase_retrieval_residual_threshold` is set and the `True`, or if `phase_retrieval_residual_threshold` is set and the
modal fit's noise-weighted RMS residual exceeds it. In that case modal fit's noise-weighted RMS residual exceeds it. In that case
`result.residuals` is empty and `coefficient_uncertainty` is `NaN` `result.residuals` is empty, `coefficient_uncertainty` is `NaN` per
per mode (phase retrieval doesn't produce a fit covariance). mode, `geometry` is empty, and both pointing-angle fields are `NaN`
(phase retrieval doesn't fit geometry/pointing or produce a fit
covariance).
## `plotting` — diagnostic visualizations ## `plotting` — diagnostic visualizations