Repository navigation
1.1.0
[1.1.0] - 2026-08-24
-
Declared the license as a PEP 639 SPDX expression (
license = "MIT"plus
license-files) instead of the deprecatedlicense = { text = "MIT" }table,
and dropped the now-redundantLicense ::classifier. The built distribution
carriesLicense-Expression: MITandLicense-File: LICENSE. No change to the
license itself. -
Requires
getframes>=2.2.0. Thedetector.readout_mode = "cds"path calls
getframes.Camera.correlated_double_sample[_spectral], which is released in
getframes 2.2.0; the previous>=2.1.1floor would have installed a getframes
without it and failed at first CDS readout rather than at resolve time. -
detector.background_photon_rate_per_sadds a uniform incident sky or
thermal background in photons/s/pixel, passed to thegetframesbackground
term on every readout path including correlated double sampling and the
spectral variants. It is light, so it collects charge and carries shot noise;
detector dark current remains the camera preset's own.makewfsdoes not
compute the rate, because converting a sky surface brightness to a rate per
pixel needs the field stop and plate scale, which belong to the instrument. -
The Keck example's photon budget covers arbitrary passbands, and no longer
extrapolates its extinction curve silently.broadband_budgetgained
band_min_nm,band_max_nm,quadrature_order, andextinction_paths, so a
near-infrared sensing arm can use the same budget as the visible one. Multiple
extinction tables are concatenated in wavelength, keeping the Gemini optical
measurement and the near-infrared continuum separately attributed, and a band
reaching past the last tabulated point now raises instead of silently taking
numpy.interp's flat continuation. That check immediately caught the existing
HAKA configuration: its 400-950 nm band runs 50 nm past the optical curve, so
it had been clamping extinction at the 900 nm value.mauna_kea_extinction_nir.csv
supplies the measured J/H/K coefficients from Leggett et al. (2006, MNRAS
373, 781; UFTI on UKIRT over 21 photometric nights), held flat across each
MKO passband so integrating the table over that passband returns the
published number. The entries between the passbands sit in the 1.4 and 1.9 um
telluric water bands and are indicative only.KECK_ALUMINUM_MIRROR_REFLECTIVITY_NIR
records that
aluminium is about 0.97 per surface in the near infrared against 0.88 in the
visible -- a 30% flux difference over three reflections. -
Correlated double sampling as a selectable detector readout mode.
detector.readout_mode = "cds"routes the adapter to the released
getframes.Camera.correlated_double_sample[_spectral]instead of
expose[_spectral], returning the signedint32difference of the two reads
of one global-reset ramp. This is how nondestructive-readout IR arrays such as
the C-RED One are actually operated, and it is the natural readout for a
pyramid sensor on one. Ownership is unchanged:makewfsstill supplies only a
photon-rate map and every noise term stays ingetframes.
detector.cds_pedestal_interval_smodels a finite reset-to-pedestal-read
delay. Note thatexposure_sis the read-to-read integration, not the frame
period: a C-RED One at its 1750 Hz maximum CDS rate integrates for 1/3500 s,
because the other half of the frame period is the reset and pedestal read.
CDS rejectsbinning > 1and caller-ownedoutstorage rather than silently
ignoring them. Seeexamples/cds_readout.py. -
examples/showcase.pyrenders an animated WebP of four sensor
configurations -- 20x20 SH, 60x60 SH, a modulated pyramid, and a
range-elongated sodium LGS SH -- watching one wind-blownpyturbatmosphere, each panel
overlaid with the end-to-end throughput that configuration sustained on the
running machine. The clip is the README header image. The atmosphere uses
engine="extrude"so a long clip never replays turbulence a periodic spectral
screen would have wrapped. -
The README follows the documentation-first structure used across the
sibling projects: docs link, showcase clip, install, quickstart, benchmarks,
then the feature list. -
Renamed
benchmarks/configs/shack_hartmann_broadband_lgs.tomlto
shack_hartmann_quadrature_9sample.toml. "Broadband LGS" is a contradiction:
a sodium beacon returns the 589 nm D2 line broadened by roughly 0.003 nm,
while the config sweeps 585--595 nm, about three orders of magnitude wider.
Its spectral axis is a quadrature load for the polychromatic path, not
beacon physics, and the new name says so; the config's optical content and its
benchmark numbers are unchanged, so results remain comparable across the
rename. The README,docs/performance.md, and a new header comment in the
config itself all state the distinction. Benchmark artifacts recorded before
this change (benchmarks/reference-results.json,
benchmarks/reference-table.md) still refer to the old filename.
examples/showcase.pynow runs a monochromatic 589 nm beacon sampled at five
altitudes through the sodium layer, which is where spot elongation actually
comes from;examples/realistic_broadband.pyremains the genuine broadband
demonstration, on a natural guide star. -
docs/performance.mdcarries the refreshed CPU/GPU table and its matching
environment, which had drifted frombenchmarks/device-results.md. -
Refreshed
benchmarks/device-results.{json,md}on the RTX 5090 / Ryzen 9
9950X3D reference machine against makewfs 1.0.0, getframes 2.1.1, pyturb 1.0.0,
NumPy 2.2.6, and CuPy 14.1.1. -
Benchmark provenance now records the CuPy version when CuPy is installed
under a CUDA-specific wheel name (cupy-cuda12x/cupy-cuda11x); the metadata
block previously reportedcupy: nullon exactly the machines that had
produced the GPU column. -
mkdocs.ymldeclaressite_url, so the published documentation emits
canonical links and a sitemap. -
Compatible GPU Shack--Hartmann optics now use a first-use-JIT compiled
executor. CuPy specializes one CUDA kernel for the fixed lenslet, temporal,
spectral, precision, field-stop, and detector-sampling geometry, then reuses
its process and disk caches. It fuses sampled-DFT propagation through photon
mosaics and composes detector-owned focal charge diffusion with native-pixel
integration once at startup. The array implementation remains the exact
reference/fallback for CPU, FFT, continuous/native optical blur, and oversized
CUDA geometries. A cold isolated-cache, alternating 20-frame physical HAKA
benchmark on a Quadro P620 measured 24.85 ms versus 569.67 ms p50 (22.93x),
after a 3.176 s first-use compile, with photon/spectral/captured-rate relative
disagreements below 1e-7. -
WavefrontSensor.expose()andexpose_integrated()now accept an optional
caller-owned detectoroutarray. The detector adapter pairs it with
getframes.DetectorWorkspace, including wavelength-resolved exposures, so a
high-rate owner can keep one stable contiguous ADU destination. Ordinary calls
retain independent frame lifetimes; only explicitoutcalls alias the
caller's storage. -
Persistent Shack--Hartmann sensors now cache propagation geometry. Half-sample
FFT ramps, sampled-DFT kernels, field-stop masks, and backend blur kernels are
built once per compatible source geometry instead of once per frame. The ordinary
large FFT path is intentionally unchanged; matched local timings improved the
geometry-heavy field-stop/DFT paths by roughly 6--20% with optical parity tests. -
A temporally integrated exposure now renders its samples in one pass.
ShackHartmannEngine.render_integratedbuilds the fields for every
(temporal sample, source state) pair as a single batch, and
expose_integrateduses it when the engine provides it. The motivation is
not transform size: on a HAKA-scale configuration the transforms are about
four percent of a render, and the cost is dominated by the fixed dispatch
overhead of the many small elementwise operations around them, which is paid
per call however much data the call carries. Presenting the whole exposure at
once amortises that: the reference HAKA exposure drops from 13.7 ms to
11.2 ms.Averaging the spot intensities before the mosaic is legitimate because
everything downstream of them -- mosaic assembly, flux scaling, and the
captured-rate accounting -- is linear in the spots, and there is a test
asserting the batched and sequential paths agree rather than leaving that as
an argument. Agreement is to float round-off from the changed summation
order, about 1e-7 relative in single precision, not bit-for-bit. -
Detector charge diffusion now reaches the Shack--Hartmann spots. The
measured OCAM2K value was previously carried as
shack_hartmann.optical_blur_fwhm_pixelsand applied after pixel
integration, where a 0.37-pixel FWHM Gaussian is a numerical no-op, so a
measured detector property changed nothing. Charge diffusion is detector
physics, sogetframesnow owns both the value
(CameraConfig.charge_diffusion_fwhm_px, declared by theandor_ocam2k
preset) and the kernel model; the Shack--Hartmann engine asksgetframesfor
the operator at its own focal-plane oversampling and applies it to the
oversampled irradiance ahead of the pixel-area integration that collects the
diffused charge. Configuration that cannot represent the width now fails with
the requiredfft_oversamplinginstead of applying nothing.
This changes delivered spot profiles and slope gains for any detector
declaring a nonzero width, so recorded HAKA evidence must be regenerated.
makewfs.charge_diffusion_fwhm_pxandmakewfs.resolve_camera_configare new
public helpers for consumers needing a sensor property before a frame exists. -
Added
WavefrontSensor.subaperture_plate_scale_arcsec()and
subaperture_field_of_view_arcsec(), which report what one detector pixel and
one subaperture window subtend on sky. A Shack--Hartmann's pixel block is
already a hard square field stop: each spot is formed and integrated only over
its own block and the blocks tile without overlap, so light beyond the pixel
field neither reaches the detector nor contaminates a neighbour. That was
implicit in the pixel count, where a change topixels_per_subaperture, the
relay magnification, or the detector margin would move the implied stop
silently. Both are derived from the same spot-sampling geometry that forms the
spots rather than restating it. Light spilling between subapertures from a
physical stop larger than the pixel field remains unmodelled. -
Added
WavefrontSensor.pupil_illumination(shape=None), which evaluates the
configured telescope pupil on a requested grid (default the OPD input grid).
Consumers that own actuator or wavefront models need the illumination on their
own grid, and pupil formation belongs here. A configuredcustom_mask_pathis
never resampled: it must already match the requested shape. -
shack_hartmann.optical_blur_fwhm_pixelsnow means genuine focal-plane optical
blur only, and is applied on the oversampled grid so sub-pixel widths stay
physical. A measuredoptical_blur_kernel_pathis supplied on the native pixel
pitch and still applies after pixel integration. -
The HAKA example raises
fft_oversamplingfrom 2 to 4, the minimum that
represents the OCAM2K's measured 0.37-pixel charge diffusion. -
Fixed arbitrary Shack--Hartmann spot sampling so normalized plate scales no
longer snap to a nearby integer FFT grid. Integer-compatible geometries retain
the FFT path; arbitrary and undersampled quadcell modes use a sampled DFT at
detector-cell quadrature points, with CPU/GPU-compatible batching. Physical
lenslet models can now provide an explicitlenslet_pitch_m, separating
hardware focal-plane sampling from the telescope-pupil coordinate scale. -
Added detector-owned full-sensor ROI configuration through
[detector.roi]. makewfs now passes ROI origin and shape to getframes instead
of replacing a preset's native detector resolution. The HAKA OCAM2K simulation
uses its measuredleft_px=4,top_px=4, 228x228 ROI, placing amplifier
boundaries at y=(56, 116, 176) and x=(116) in RTC image coordinates.
Stability note:detectornow serializes aroikey, so every configuration
digest changes even when no ROI is configured.schema_versionstays 1 and
existing TOML files load unchanged; only recorded provenance digests must be
regenerated. -
Accelerated the common two-times Shack--Hartmann detector integration with
direct flux-preserving strided sums, and made temporal exposure integration
accumulate rates and OPD incrementally instead of stacking full frame cubes. -
Batch GPU Shack--Hartmann source states only when they share the same FFT
geometry. CPU and wavelength-dependent field-stop execution remain on the
sequential reference path. A matched 64-frame Quadro P620 HAKA-class
benchmark reduces median optics time by 2.16% with a maximum relative
photon-rate difference of 5.1e-8.