Skip to content

1.1.0

Choose a tag to compare

@jacotay7 jacotay7 released this 01 Oct 05:57
a188a82

[1.1.0] - 2026-08-24

  • Declared the license as a PEP 639 SPDX expression (license = "MIT" plus
    license-files) instead of the deprecated license = { text = "MIT" } table,
    and dropped the now-redundant License :: classifier. The built distribution
    carries License-Expression: MIT and License-File: LICENSE. No change to the
    license itself.

  • Requires getframes>=2.2.0. The detector.readout_mode = "cds" path calls
    getframes.Camera.correlated_double_sample[_spectral], which is released in
    getframes 2.2.0; the previous >=2.1.1 floor would have installed a getframes
    without it and failed at first CDS readout rather than at resolve time.

  • detector.background_photon_rate_per_s adds a uniform incident sky or
    thermal background in photons/s/pixel, passed to the getframes background
    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. makewfs does 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_budget gained
    band_min_nm, band_max_nm, quadrature_order, and extinction_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 signed int32 difference 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: makewfs still supplies only a
    photon-rate map and every noise term stays in getframes.
    detector.cds_pedestal_interval_s models a finite reset-to-pedestal-read
    delay. Note that exposure_s is 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 rejects binning > 1 and caller-owned out storage rather than silently
    ignoring them. See examples/cds_readout.py.

  • examples/showcase.py renders an animated WebP of four sensor
    configurations -- 20x20 SH, 60x60 SH, a modulated pyramid, and a
    range-elongated sodium LGS SH -- watching one wind-blown pyturb atmosphere, 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.toml to
    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.py now 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.py remains the genuine broadband
    demonstration, on a natural guide star.

  • docs/performance.md carries the refreshed CPU/GPU table and its matching
    environment, which had drifted from benchmarks/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 reported cupy: null on exactly the machines that had
    produced the GPU column.

  • mkdocs.yml declares site_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() and expose_integrated() now accept an optional
    caller-owned detector out array. 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 explicit out calls 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_integrated builds the fields for every
    (temporal sample, source state) pair as a single batch, and
    expose_integrated uses 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_pixels and 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, so getframes now owns both the value
    (CameraConfig.charge_diffusion_fwhm_px, declared by the andor_ocam2k
    preset) and the kernel model; the Shack--Hartmann engine asks getframes for
    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 required fft_oversampling instead 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_px and makewfs.resolve_camera_config are 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 to pixels_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 configured custom_mask_path is
    never resampled: it must already match the requested shape.

  • shack_hartmann.optical_blur_fwhm_pixels now means genuine focal-plane optical
    blur only, and is applied on the oversampled grid so sub-pixel widths stay
    physical. A measured optical_blur_kernel_path is supplied on the native pixel
    pitch and still applies after pixel integration.

  • The HAKA example raises fft_oversampling from 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 explicit lenslet_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 measured left_px=4, top_px=4, 228x228 ROI, placing amplifier
    boundaries at y=(56, 116, 176) and x=(116) in RTC image coordinates.
    Stability note: detector now serializes a roi key, so every configuration
    digest changes even when no ROI is configured. schema_version stays 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.