Configuration
TOML is the canonical configuration format. A complete example is shipped at
examples/configs/shack_hartmann_minimal.toml.
The root requires schema_version = 1 and has these tables:
| Table | Purpose |
|---|---|
input |
quantity, units, array shape, physical extent, static OPD |
telescope |
pupil diameter, obstruction, spiders, custom mask |
source |
NGS/LGS, photon rate or magnitude, field angle, throughput |
sensor |
sensor kind and sensing wavelength |
shack_hartmann |
lenslet and spot sampling |
pyramid |
pupil separation and modulation |
detector |
getframes preset, exposure, temperature, binning |
numerics |
device, dtype, FFT oversampling/workers, internal sampling |
Unknown keys and conflicting normalization choices are errors. File paths are
resolved relative to the TOML file. The normalized configuration has a short
SHA-256 digest used in frame provenance. All lengths are metres unless the
key explicitly says arcsec, deg, or pixels.
Complete key reference
The loader rejects omitted required keys, unknown keys, non-finite values, and values outside the ranges below. Defaults are shown in parentheses.
At the root, schema_version = 1 is required. An optional [metadata] table
accepts arbitrary designer notes and is preserved in the normalized
configuration; it does not affect optical propagation except through the
configuration digest.
input
| Key | Meaning and constraints |
|---|---|
quantity |
"opd" or "phase" (default "opd"). |
unit |
Must be "m" for OPD or "rad" for phase; units are never inferred. |
shape |
Required [height, width], positive integers. Every dynamic input has this shape. |
grid_extent_m |
Required physical width/height of the input grid, positive. |
reference_wavelength_m |
Required positive wavelength when quantity = "phase"; otherwise unused. |
static_opd_path |
Optional .npy, .npz, or FITS OPD map with exactly shape; resolved relative to the TOML file. |
telescope
| Key | Meaning and constraints |
|---|---|
pupil_diameter_m |
Required positive entrance-pupil diameter. |
central_obscuration_ratio |
Inner radius divided by outer radius, [0, 1), default 0. |
spiders |
Array of {angle_deg, width_fraction} tables (default []); angle is measured from +x and width is a fraction of a half-turn, [0, 1]. |
pupil_rotation_deg |
Rotation applied to spider angles, default 0. |
custom_mask_path |
Optional .npy, .npz, or FITS amplitude mask; values must be finite in [0, 1] and match the internal sensor grid. |
segments_across_pupil |
Optional square segment count (integer ≥2), used with analytic segment gaps. |
segment_gap_fraction |
Gap width as a fraction of segment pitch, [0, 0.99] (default 0); nonzero values require segments_across_pupil. |
source
| Key | Meaning and constraints |
|---|---|
kind |
"ngs" or "lgs" (default "ngs"). |
normalization |
"detector_photon_rate" or "magnitude" (default "detector_photon_rate"). |
detector_photon_rate_per_s |
Non-negative total photons/s at the detector surface before QE; required for direct-rate mode. |
magnitude |
Finite magnitude; required for magnitude mode. LGS must not use magnitude mode. |
magnitude_system |
"vega" or "ab" (default "vega"); used with band. |
band |
Required band name for magnitude mode, passed to getframes radiometry. |
throughput |
Scalar optical throughput in [0, 1] (default 1), used by magnitude radiometry. |
field_angle_arcsec |
Source centroid [x, y] in arcsec (default [0, 0]). |
angular_fwhm_arcsec |
Non-negative Gaussian source FWHM (default 0); nonzero values require quadrature order at least 2. |
angular_quadrature_order |
Positive deterministic source quadrature order (default 3). |
angular_kernel_path |
Optional three-column x_arcsec y_arcsec weight source kernel; mutually exclusive with nonzero FWHM and resolved relative to the TOML file. |
wavelengths_m |
Optional positive wavelength nodes. Empty means the sensor wavelength. |
wavelength_weights |
Optional non-negative weights matching wavelengths_m; normalized internally. |
lgs_ranges_m |
Optional positive sodium range nodes; only valid for LGS. Empty means a thin layer at the configured mean. |
lgs_range_weights |
Optional non-negative weights matching lgs_ranges_m; normalized internally. |
lgs_launch_position_m |
LGS launch [x, y] in the entrance-pupil plane (default [0, 0]); only valid for LGS. |
sed_path |
Optional two-column wavelength_nm value source spectrum, non-negative and increasing. |
transmission_path |
Optional two-column wavelength_nm value transmission curve in [0, 1]. |
SED and transmission curves use trapezoid quadrature on their common wavelength nodes. The resulting normalized wavelength, source-angle, and (for SH) range states are recorded in provenance.
An angular kernel is an arbitrary incoherent source morphology. Its offsets are
added to field_angle_arcsec, its non-negative weights are normalized, and each
sample is propagated independently. For example:
# x_arcsec y_arcsec relative_weight
-0.15 0.00 1
0.00 0.00 2
0.15 0.00 1
This is useful for measured binary-guide-star or resolved-source profiles. The kernel file hash and all normalized states are included in frame provenance.
sensor, detector, and numerics
| Table/key | Meaning and constraints |
|---|---|
sensor.kind |
Required "shack_hartmann" or "pyramid". |
sensor.wavelength_m |
Required positive reference sensing wavelength. |
detector.preset |
Existing getframes camera preset; mutually exclusive with detector.camera. |
detector.camera |
Inline getframes.CameraConfig dictionary; mutually exclusive with preset. |
detector.exposure_s |
Required non-negative exposure time. |
detector.temperature_c |
Optional detector temperature in °C. |
detector.binning |
Positive integer passed to getframes. |
detector.binning_mode |
"digital" or "on_chip" (default "digital"). |
detector.precision |
"float32" or "float64" (default "float64"). |
detector.include_truth |
Boolean (default true) controlling detector truth arrays. |
detector.readout_mode |
"integrate" (default) or "cds". "integrate" runs the ordinary getframes exposure and returns unsigned ADU. "cds" runs correlated double sampling — one global-reset ramp, pedestal read, integration, signal read — and returns the signed int32 difference. Requires detector.binning = 1, is incompatible with caller-owned out storage, and needs a getframes with Camera.correlated_double_sample. |
detector.cds_pedestal_interval_s |
Non-negative reset-to-pedestal-read delay in seconds (default 0.0). Only meaningful with readout_mode = "cds", and must be shorter than detector.exposure_s. It does not change the measured signal — exposure_s is the read-to-read integration — only the interval the pedestal read's own bias/noise terms are evaluated at. |
detector.qe_curve_path |
Optional configuration-relative two-column wavelength_nm qe curve passed to getframes; enables wavelength-resolved QE for broadband optical cubes. |
detector.roi |
Optional full-detector ROI table with non-negative left_px/top_px and positive width_px/height_px. Its shape must match the optical mosaic. |
numerics.dtype |
Optical real precision, "float32" or "float64" (default "float64"). |
numerics.device |
Execution device, "cpu" (default) or "gpu". GPU requires the makewfs[gpu] extra and a GPU-capable getframes; optical, truth, and ADU arrays stay device-resident. |
numerics.fft_oversampling |
Positive FFT integration oversampling (default 2). Also scales the pyramid propagation grid so diffraction beyond the detector crop is discarded instead of wrapping onto the pupil rims. |
numerics.fft_workers |
Positive scipy.fft worker count (default 1). |
numerics.pupil_samples_per_lenslet |
Optional integer ≥4 for SH internal pupil sampling; otherwise derived from input shape. |
numerics.pupil_supersampling |
Positive analytic pupil boundary sub-sampling factor (default 1). |
For a device-resident atmosphere → WFS → detector loop:
[numerics]
device = "gpu"
dtype = "float32"
fft_oversampling = 2
fft_workers = 1 # CPU-only FFT control; accepted but ignored by CuPy FFTs
[shack_hartmann] keys are:
| Key | Meaning and constraints |
|---|---|
lenslets_across_pupil |
Positive square lenslet count. |
pixels_per_subaperture |
Native detector pixels per lenslet, at least 2. |
spot_sampling_pixels_per_lambda_over_d |
Positive normalized sampling; mutually exclusive with physical relay fields. Values below 1 represent detector pixels wider than lambda/D. |
minimum_illuminated_fraction |
Validity threshold in [0, 1]. |
lenslet_fill_factor |
Square clear fill fraction in [0, 1] (default 1). |
lenslet_pitch_m |
Optional positive physical lenslet pitch. When omitted, the legacy telescope-pupil pitch is used. |
lenslet_focal_length_m |
Optional positive physical lenslet focal length. |
detector_pixel_pitch_m |
Optional positive detector pitch for physical sampling. |
relay_magnification |
Positive relay magnification (default 1). |
field_stop_radius_lambda_over_d |
Optional non-negative focal-plane field-stop radius. |
optical_blur_fwhm_pixels |
Gaussian blur FWHM in native pixels (default 0). |
optical_blur_kernel_path |
Optional measured odd-sized blur kernel path; mutually exclusive with Gaussian blur. |
detector_margin_pixels |
Non-negative zero-rate mosaic margin. |
lenslet_grid_rotation_deg |
Lenslet-frame rotation in degrees (default 0). |
lenslet_grid_offset_fraction |
[x, y] offset in lenslet-pitch fractions (default [0, 0]). |
[shack_hartmann] requires positive lenslets_across_pupil, at least two
pixels_per_subaperture, and minimum_illuminated_fraction in [0, 1].
Either positive spot_sampling_pixels_per_lambda_over_d or both
lenslet_focal_length_m and detector_pixel_pitch_m must be supplied, but
never both. The optional lenslet_fill_factor is in [0, 1],
relay_magnification is positive, field_stop_radius_lambda_over_d is
non-negative when supplied, optical_blur_fwhm_pixels is non-negative, and
optical_blur_kernel_path may reference a measured odd-sized, non-negative
.npy, .npz, or FITS PSF kernel; it is normalized to unit sum and is
mutually exclusive with optical_blur_fwhm_pixels. detector_margin_pixels
is a non-negative integer. The optional
lenslet_grid_rotation_deg rotates the lenslet coordinate frame, and
lenslet_grid_offset_fraction = [x, y] shifts its origin by fractions of one
subaperture pitch. These two controls use an explicit physical-coordinate
resampling path; the default zero values retain the fast axis-aligned path.
Integer-compatible focal grids use the batched FFT path. Arbitrary sampling,
including undersampled quadcell modes, uses an exact sampled DFT at detector
quadrature points so the configured plate scale is not rounded to an FFT bin.
[pyramid] requires pixels_across_pupil ≥ 8 and positive
pupil_separation_pixels. modulation_radius_lambda_over_d is non-negative;
zero radius requires exactly one sample, while nonzero modulation requires at
least four modulation_samples. detector_margin_pixels is non-negative.
numerics.pupil_supersampling sub-samples analytic circular, annular, spider,
and segment-gap boundaries before averaging each pupil pixel. Analytic features
are rotated by pupil_rotation_deg; custom masks should be supplied already
rotated because they are measured amplitude products. Custom masks may be
.npy, .npz, or FITS arrays in [0, 1].
Source normalization and morphology
For laboratory or already-calibrated flux, use:
[source]
kind = "ngs"
normalization = "detector_photon_rate"
detector_photon_rate_per_s = 2.0e9
The rate is photons/s at the detector surface before detector QE. For an NGS,
the alternative is normalization = "magnitude" with magnitude, band, and
magnitude_system = "vega" or "ab"; this uses getframes radiometry.
LGS return flux must be supplied directly because laser return prediction is out
of scope.
Shack–Hartmann sampling can be expressed either directly in normalized units or with physical lenslet optics. Do not provide both forms:
[shack_hartmann]
lenslets_across_pupil = 20
pixels_per_subaperture = 8
minimum_illuminated_fraction = 0.25
lenslet_pitch_m = 0.0002
lenslet_focal_length_m = 0.020
detector_pixel_pitch_m = 15e-6
relay_magnification = 1.0
In physical mode, makewfs derives pixels per lambda / D_subaperture as
f_lenslet * wavelength * relay_magnification /
(lenslet_pitch * detector_pixel_pitch). For backward compatibility,
lenslet_pitch_m defaults to the entrance-pupil diameter divided by the
lenslet count, but hardware models should provide the measured pitch explicitly.
field_stop_radius_lambda_over_d clips each subaperture's focal-plane field
stop, optical_blur_fwhm_pixels applies a flux-spreading optical Gaussian
before detector sampling, and detector_margin_pixels pads the assembled SH
mosaic with zero-rate margins. These are optical settings; detector binning and
noise remain in [detector]/getframes.
Wavelength and source-size quadrature are optional. Weights are normalized, and intensities—not complex fields—are summed:
[source]
kind = "ngs"
normalization = "detector_photon_rate"
detector_photon_rate_per_s = 2.0e9
wavelengths_m = [650e-9, 700e-9, 750e-9]
wavelength_weights = [0.2, 0.5, 0.3]
angular_fwhm_arcsec = 0.25
angular_quadrature_order = 3
# Optional two-column wavelength_nm / relative_value files:
# sed_path = "source_sed.txt"
# transmission_path = "filter_transmission.txt"
field_angle_arcsec = [x, y] is the source centroid. A finite FWHM uses a
deterministic Gaussian quadrature around that centroid. By default the detector
receives one summed photon-rate map and uses its scalar QE. For broadband
scenes, detector.qe_curve_path enables wavelength-resolved exposure through
the released getframes>=2.1.1 spectral cube API, preserving the incident cube
and wavelength nodes in detector truth.
For a sodium LGS, configure a detector-surface return rate and optional range profile. The range model is currently Shack–Hartmann-specific:
[source]
kind = "lgs"
normalization = "detector_photon_rate"
detector_photon_rate_per_s = 2.0e8
lgs_ranges_m = [89000.0, 90000.0, 91000.0]
lgs_range_weights = [0.25, 0.5, 0.25]
lgs_launch_position_m = [0.0, 0.0]
The supplied OPD is interpreted at the weighted mean range. Each range slice
gets the geometric perspective offset for each lenslet; this captures image
elongation but does not create range-resolved turbulent OPD. See the
guide-star guide for the approximation and pyturb handoff.
Detector choices
detector.preset names any installed getframes preset, including OCAM2K,
SAPHIRA, EMCCD, sCMOS, and generic teaching cameras. When the optical mosaic is
a hardware ROI rather than the full sensor, preserve its full-detector origin:
[detector.roi]
left_px = 4
top_px = 4
width_px = 228
height_px = 228
The ROI uses unbinned detector pixels, with left_px/top_px measured from the
full sensor's upper-left corner. Its (height_px, width_px) must equal the ideal
optical output shape. makewfs passes this geometry to getframes, which retains
the preset's native sensor size, evaluates detector effects in full-detector
coordinates, and returns the cropped image. Without detector.roi, a differing
optical shape retains the legacy behavior of replacing the camera resolution.
Detector binning and all noise physics remain in getframes.