Python diagnostics workflow¶
Reconstruction diagnostics are collected by a Rust callback and returned to
Python as a dictionary shaped like the Rust ReconstructionDiagnostics model.
Recording does not invoke Python or acquire the GIL inside the reconstruction
loop.
Reusable calculations are split by responsibility. Rust metrics contains
domain-agnostic reference/estimate comparisons; algorithms::objective
contains objective functions optimized by reconstruction algorithms; and
evaluation combines a reconstruction with optional reference model and
measurement stack.
Python exposes the direct equivalents under fpm.metrics and
fpm.evaluation. This module focuses only on run-scoped diagnostic requests,
Fourier coverage, convergence records, recorder cadence, and serialization.
See intensity metrics for standalone reference/estimate image
calculations such as NRMSE, PSNR, SSIM, and Poisson deviance.
Every reconstruction records result.trace independently of this optional
recorder. Its one-based tuples contain (iteration, objective,
elapsed_seconds), where seconds are measured from the start of the run and
continue across checkpoint resume. Recorder iteration diagnostics are optional
derived values at the configured cadence and are not the execution trace.
Reusable metrics, recorder dictionaries, diagnostic JSON, and tabular exports
all use reference_sum and estimate_sum.
End-to-end report¶
import fpm_rs as fpm
recorder = fpm.DiagnosticRecorder("basic")
result = algorithm.run(problem, callbacks=[recorder])
diagnostics = recorder.diagnostics()
fpm.diagnostics.make_diagnostic_report(
diagnostics,
output_dir="diagnostic_report",
)
result.scalar_diagnostics is the reconstruction result's small, dynamic
scalar summary.
The structured recorder output is retrieved with recorder.diagnostics().
Radial Fourier power spectrum¶
fpm.radial_fourier_spectrum(field) computes a forward two-dimensional FFT
of a complex field and averages its normalized power in integer-radius annuli.
It returns a dictionary containing radius_px, power, and sample_count.
The radii are Fourier-grid pixels; convert them to physical spatial frequency
only when the input sampling pitch is known.
Algorithms can additionally expose long-form result.algorithm_metrics tuples:
(iteration, namespace, metric, value). ADMM uses the admm namespace for
primal_residual_rms and dual_residual_rms. Global Gauss–Newton uses
global_gauss_newton for conjugate-gradient iterations and residual ratio,
line-search evaluations and accepted scale, and the pre-update gradient norm.
These values do not appear in universal iteration rows or scalar diagnostics.
Algorithms without stable algorithm-specific metrics return an empty list.
Joint physical illumination runs emit data_loss, regularization_loss,
total_loss, geometry-refresh count, intensity-only-update count, and rejected
step count in the physical_illumination namespace. Callback contexts use the
refreshed calibrated model. Rust callbacks can inspect absolute parameters and
conditioning warnings through
context.state.physical_illumination_calibration(); Python iteration callbacks
receive the scalar phase records in algorithm_metrics and the complete typed
state snapshot in physical_illumination_calibration.
The report writer creates plots only for sections present in the diagnostics.
It always writes summary.txt. No intermediate JSON file is required.
Recorder modes¶
| Mode | Iteration diagnostics | Fourier coverage | Frame summaries | Raw-stack statistics |
|---|---|---|---|---|
minimal |
yes | no | no | no |
basic |
yes | yes | no | no |
debug |
yes | yes | yes | yes |
simulation |
yes | yes | no | no |
basic is the default. Frame summaries require an additional calibrated
forward pass over the measurement stack at each recording interval, so they
are restricted to debug. Object and pupil snapshots are disabled in every
Python preset.
Use every to reduce the recording cadence:
recorder = fpm.DiagnosticRecorder("debug", every=5)
The recorder clears its previous values when a new reconstruction starts, so reusing an object produces diagnostics for the latest run rather than combining unrelated runs. Clones share that state: retain one clone for reading while one runner owns another, but do not install clones in simultaneous runs. The recorder deliberately recovers a poisoned internal lock as best-effort diagnostic state; the next run resets it before recording.
Individual plots¶
The dictionary returned by the recorder can be passed directly to every plot.
All plotting entry points now live under fpm.plot. Like
plot_reconstruction, each function returns its Matplotlib figure and a
dictionary of named axes:
diagnostics = recorder.diagnostics()
figure, axes = fpm.plot.plot_convergence(diagnostics)
axes["objective"].set_title("Experiment convergence")
Reconstruction plots live in the same namespace:
figure, axes = fpm.plot.plot_fourier_coverage(diagnostics)
figure, axes = fpm.plot.plot_reconstruction(truth, result)
The plotting helpers only build figures. Save them explicitly when needed:
figure, axes = fpm.plot.plot_frame_residuals(diagnostics)
figure.savefig("diagnostic_report/frame_residuals.png", dpi=180, bbox_inches="tight")
Unavailable sections are skipped without error. Matplotlib is imported only
when plotting is requested; install the plot optional dependency when using
these functions. Direct callers own returned figures and should close them when
finished. make_diagnostic_report saves and closes its figures automatically.
Optional JSON persistence¶
JSON is optional and contains the same structured diagnostic model:
recorder.to_json("diagnostics.json")
diagnostics = fpm.diagnostics.load_diagnostics("diagnostics.json")
fpm.diagnostics.make_diagnostic_report(
diagnostics,
output_dir="diagnostic_report_from_json",
)
The report function also accepts the JSON path directly. Object and pupil snapshots are not exported.
One maintained site tutorial and two focused repository notebooks provide the same workflow with inline plot display:
diagnostics_quickstart.ipynbis the published tutorial for the lightweightbasicrecorder and overview plots.python/examples/diagnostics_debug.ipynbfocuses on debug-only frame and raw-stack inspection.python/examples/diagnostics_plots.ipynbdemonstrates the individual plotting helpers and axes customization.
Each notebook displays figures inline and includes commented savefig(...)
calls that can be uncommented when file export is desired.
Launch the focused examples from the repository environment with
pixi run jupyter lab.