Skip to content

Repository files navigation

makewfs

CI PyPI Python Docs License: MIT

Documentation: jacotay7.github.io/makewfs

Configuration-driven adaptive-optics wavefront-sensor images.

Animated wavefront sensor showcase: Shack-Hartmann, high-order Shack-Hartmann, modulated pyramid and range-elongated sodium LGS sensors watching one wind-blown atmosphere, with live throughput.

makewfs turns a pupil-plane phase/OPD map into the image an adaptive-optics wavefront sensor would actually record. It supports Shack–Hartmann and four-face pyramid sensors, with modulation, broadband and finite-source quadrature, and sodium-layer LGS geometry — everything from the pupil to the ADU.

The package owns the wavefront-sensor optics and nothing else. It deliberately reuses pyturb for atmospheric OPD and getframes for detector response and noise; neither model is reimplemented here. It runs on NumPy by default and switches to CUDA (via CuPy) with a single configuration field.

Install

python -m pip install makewfs            # CPU
python -m pip install "makewfs[gpu]"     # + CuPy for CUDA 12.x

Quickstart

import makewfs

wfs = makewfs.WavefrontSensor.from_toml("wfs.toml")
frame = wfs.expose(opd_m, seed=0)  # getframes.Frame, data in ADU

rate = wfs.photon_rate(opd_m)  # ideal photons/s/native detector pixel
frame = wfs.expose_integrated(samples)  # several temporal OPD samples, one exposure

Construct the sensor once and call expose() with each new residual wavefront: the only per-frame inputs are the wavefront and an optional noise seed, because telescope, source, sensor, sampling and camera choices all live in versioned configuration. Set device = "gpu" under [numerics] and pass a CuPy OPD array for the fully device-resident path; frame.data is then a CuPy ADU array, and getframes.to_numpy(frame.data) marks an intentional host boundary.

Runnable starter configurations live at examples/configs/shack_hartmann_minimal.toml and examples/configs/pyramid_minimal.toml.

See Quickstart for the full walkthrough, Configuration for every field with its units and range, and Concepts plus Units and coordinates if you are new to wavefront sensing.

Benchmarks

Warm end-to-end frame throughput on an AMD Ryzen 9 9950X3D and an NVIDIA RTX 5090, including optics, getframes detector noise, truth and ADU, with device-resident input/output and no host transfers. The raw artifact and its invocation are versioned with the benchmarks:

Workflow Output Work samples CPU (frames/s) GPU (frames/s) Speedup
20×20 SH, float32 160×160 1 127 2,042 16.13×
60×60 SH, float64 360×360 1 18 899 50.11×
9-sample quadrature SH 64×64 9 186 780 4.20×
Pyramid, 8-point modulation, float32 80×80 8 659 1,646 2.50×
Pyramid, 32-point modulation, float64 108×108 32 24 924 37.94×

Higher is better. A tiny unmodulated 54×54 pyramid case remains CPU-faster (3,526 versus 1,605 frames/s) because GPU launch overhead dominates its small optical workload. The 9-sample row (shack_hartmann_quadrature_9sample.toml) is a deliberate quadrature load — three wavelengths crossed with three beacon ranges — not a physical beacon: a sodium LGS is monochromatic at the 589 nm D2 line, broadened by only ~0.003 nm, so its spectral axis exists there purely to exercise the polychromatic path. The showcase clip above instead runs a physically correct narrowband beacon sampled across the sodium layer's depth, which is where spot elongation actually comes from. Compatible sampled-DFT Shack–Hartmann geometries are compiled on first use, so warm each fixed sensor before measuring or entering a real-time loop. Reproduce the table with

python benchmarks/run.py --device both --frames 100 \
  $(printf -- '--config %s ' benchmarks/configs/*.toml)

See the full snapshot and the performance guide for the methodology.

Features

  • Shack–Hartmann sensors — lenslet geometry with physical sampling controls (spot_sampling_pixels_per_lambda_over_d, minimum_illuminated_fraction), flux-conserving pixel integration, and explicit crop accounting; see Shack-Hartmann.
  • Four-face pyramid sensors — fixed-mask pyramid with circular modulation (radius and sample count) and configurable pupil separation; see Pyramid.
  • Broadband and finite sources — deterministic spectral and angular quadrature, measured source curves, and user-supplied angular kernels, so extended and polychromatic beacons are integrated as intensities, not fields.
  • Laser guide stars — sodium-range sampling with launch-position geometry and the resulting spot elongation; see Guide stars. The mean-altitude LGS OPD approximation is documented and deliberately distinct from range-resolved turbulence.
  • Analytic pupils — central obscuration, segmented and rotated apertures, static OPD maps and user masks, hashed for provenance.
  • Configuration as the contract — immutable TOML with units in every key name, unknown-key and range rejection with path-specific messages, a versioned schema, and digests of every referenced file; see Configuration.
  • Precision you choose — matched float32/complex64 and float64/complex128 paths, tested on both, with no silent promotion in the hot path.
  • GPU-optional, end to endnumerics.device = "gpu" runs optics and the getframes detector device-resident, with CPU parity tests; compatible Shack–Hartmann geometries use a specialized CUDA execution plan and fall back automatically to the readable reference implementation for any unsupported optical feature.
  • Real-time friendly — construct once and reuse; static grids, masks and normalization are cached on the sensor, and expose()/expose_integrated() accept a caller-owned out= array so high-rate loops control storage lifetime.
  • Built to interoperatepyturb supplies the atmosphere and getframes the detector, including wavelength-resolved QE; see Interoperability.
  • Validated against theory — piston invariance, non-negative intensity, flux accounting, photon-rate linearity, stable sign/axis conventions and sampling convergence are asserted quantitatively; see Validation.
  • Stable surfaceload_config, WavefrontSensor and simulate are frozen under SemVer as of 1.0.

See the API reference for every public function and class, the documentation gallery and runnable examples for sensor comparisons, modulation trades, LGS elongation and closed-loop injection, and Troubleshooting when a frame does not look the way you expect.

Contributing

See CONTRIBUTING.md and AGENTS.md. Run the checks locally with:

ruff check . && ruff format --check . && python -m mypy && pytest

License

MIT — see LICENSE.

About

A repostiory that simulate hyper realistic wavefront sensor frames fast.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages