Skip to content

2.2.0

Choose a tag to compare

@jacotay7 jacotay7 released this 01 Oct 05:26

[2.2.0] - 2026-08-24

Added

  • CameraConfig.read_noise_correlated_fraction. The part of read noise that
    is common to every read of a nondestructive ramp, and so cancels when two
    reads are differenced. Without it every read was an independent draw and
    correlated double sampling could only ever cost a factor of sqrt(2) — but a
    real CDS frame is often quieter than a single read, because reference-level
    drift, bias settling, and 1/f noise persist between the two reads and
    differencing removes them. Defaults to 0, which is the previous behaviour.
    Only a differenced measurement can constrain it; single-read data cannot.

  • Vega-system J, H, and Ks bands. Bandpass.johnson previously stopped at
    I, so any near-infrared work had to leave the Vega system for AB — a
    different magnitude for the same star, by +0.89 (J), +1.37 (H), and +1.84
    (Ks). The zero points are derived from the 2MASS absolute calibration (Cohen,
    Wheaton & Megeath 2003) through the same integral the AB path uses, so the
    two systems now differ only in the reference spectrum and not in the
    convention. SpectralBandpass.johnson gained the matching tophats, which are
    the same shapes the AB survey table already used: a filter's shape is a
    property of the filter, not of the magnitude system.

  • Correlated double sampling as a first-class readout mode.
    Camera.correlated_double_sample resets, reads the pedestal, integrates, reads
    again, and returns the signed int32 ADU difference — the frame a camera in
    CDS mode actually delivers. It shares one reset-correlated readout core with
    nondestructive_series, so kTC noise and fixed bias structure cancel, read
    noise grows by sqrt(2), common mode is partly removed through its AR(1)
    correlation, and reset settling leaves its pedestal-to-signal residual. The
    interval-proportional bias rate survives differencing by construction and is
    documented as a dark-subtractable pedestal. pedestal_interval_s models a
    finite reset-to-read delay. correlated_double_sample_spectral is the
    wavelength-resolved twin, standing to it as expose_spectral does to
    expose; both spectral entry points now share one QE-folding step, so a cube
    cannot have QE applied twice or applied differently per readout mode.

  • Global-reset nondestructive read series for eAPD arrays.
    Camera.nondestructive_series and dark_nondestructive_series accumulate
    Poisson charge between resets, preserve a ramp's kTC realization, redraw read
    noise on every sample, and expose ramp/read timing in frame metadata.

  • A measured C-RED One preset and hybrid-array readout structure. The new
    first_light_imaging_cred_one preset separates the complete camera from the
    bare leonardo_saphira detector and models its 32 interleaved output channels,
    edge-dependent pedestal/noise, stable per-channel offsets and noise scales, and
    correlated frame-wide readout pedestal. Its spatial and temporal dark-frame
    parameters are constrained by the July 2026 CRED1 dark stack.

  • Reset-aware stack characterization. nondestructive_stack_statistics
    measures strong-reset cadence, ramp slopes, common mode, CDS/pixel noise,
    interleaved channels, and edge structure without treating correlated reads as
    independent exposures. ramp_photon_transfer fits conversion gain and
    split-ramp response nonuniformity from repeated NDR ramps.

  • examples/16_detector_showcase.py renders an animated WebP that exposes one
    incident photon field on a CCD, EMCCD, sCMOS, and eAPD side by side, each panel
    overlaid with the frame rate that camera sustained on the running machine. The
    clip is the README header image.

Changed

  • Refined the measured C-RED One response across gain and read rate. Fixed
    avalanche-gain nonuniformity, sublinear gain noise, NDR read-interval scaling,
    observed 42/3/5-read electronic cadences, reset settling, and separate broad
    side/narrow row-edge pedestal terms now reproduce the local operating-point
    stacks. The C-RED One and SAPHIRA QE curves were corrected to the manufacturer
    plot: 60% through 950 nm, linear to 80% at 1450 nm, then 80% to 2500 nm.

  • The README follows the documentation-first structure used across the sibling
    projects: docs link, showcase clip, install, quickstart, benchmarks, then the
    feature list. The preset table and the dark-frame model walkthrough now live
    only in the presets and noise-model guides, which they already duplicated.

  • Refreshed benchmarks/device-results.{json,md} on the RTX 5090 / Ryzen 9
    9950X3D reference machine against getframes 2.1.1, NumPy 2.2.6, and CuPy 14.1.1.

Fixed

  • 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.

  • Persistent cameras now cache the dark-signal expectation. Repeated exposures
    at the same exposure time, temperature, precision, and detector configuration
    reuse the full-detector DSNU/hot-pixel/glow map; changing either physical key
    rebuilds it. Seeded stochastic frames and detector truth remain unchanged.

  • sCMOS per-pixel read noise is now a fixed property of the sensor. The
    per-pixel read-noise RMS map implied by read_noise_nonuniformity was drawn from
    the per-frame generator, so it was re-randomised in every frame. Single-frame
    spatial statistics were unaffected, but every pixel ended up with the same
    expected noise through time, which is not how an sCMOS behaves: each pixel has
    its own source-follower and column ADC. The map is now built once from
    fixed_pattern_seed and cached in FixedPatternMaps, alongside PRNU and DSNU.
    Verified against dark stacks from three real back-illuminated sCMOS cameras
    (KURO 1200B, Prime 95B, Marana 4.2B-11): splitting a stack in half and
    correlating the two per-pixel temporal-variance maps gives r = 0.89–0.94 on the
    real detectors and r = 0.004 with the old model, now r ≈ 0.96.
    This changes generated pixel values for any configuration with
    read_noise_nonuniformity > 0; read_noise_e and the spatial statistics of a
    single frame are unchanged.

Added

  • DetectorWorkspace plus optional workspace= / caller-owned out= execution
    on scalar and spectral camera exposures. Full-detector ROI inputs, private
    photo/total scratch, and digitised destinations can now be reused without
    allowing returned truth or default frames to alias mutable scratch. The
    workspace is device/shape/precision-bound and rejects concurrent use.
  • CameraConfig.charge_diffusion_fwhm_px, charge_diffusion_kernel(), and
    apply_charge_diffusion() make lateral detector charge spreading available to
    focal-plane simulators at their own oversampling. The OCAM2K preset declares
    its measured 0.37-pixel FWHM; under-resolved kernels fail explicitly, while
    native-resolution camera frames state in metadata that diffusion was not
    applied rather than silently becoming a numerical no-op.
  • Full-detector region-of-interest simulation through
    CameraConfig.roi=(left, top, width, height). Cameras accept and return
    ROI-shaped arrays while evaluating detector physics and fixed patterns on the
    native sensor before cropping. Camera.sensor_resolution,
    CameraConfig.output_resolution, and active amplifier-boundary properties make
    the full-versus-ROI geometry explicit. Exact full-detector split pixels remain
    available when an ROI is active.
  • getframes.analysis.characterize: detector characterisation from frame
    stacks.
    Where photon_transfer_curve drives a simulated camera, this works
    on stacks that already exist -- raw data off a real detector, or simulated
    frames. stack_statistics reduces any iterable of frames (arrays, Frames, a
    dark_series generator, your own file reader) to per-pixel temporal mean and
    variance in one streaming pass, so stacks larger than memory are fine.
    characterize_dark then measures conversion gain, read noise (with its
    per-pixel map, log-normal width and RTS tail), dark current, bias and DSNU from
    darks alone -- no flat field needed, because dark charge is Poisson and so
    serves as the PTC charge source. characterize_flat adds full well, PRNU and
    linearity. DarkCharacterization.to_config() returns a CameraConfig, closing
    the loop: measure a real camera, then simulate it. StackStats.split=True
    additionally gives temporal_repeatability, the split-half test that separates
    genuine per-pixel noise structure from chi-squared sampling scatter.
    New guide (docs/guides/characterization.md) and example
    (examples/15_detector_characterization.py).
  • read_noise_rts_fraction / read_noise_rts_factor: an optional second,
    noisier read-noise population modelling the random-telegraph-signal (RTS) pixels
    of a real sCMOS array. Measured on three real sensors, ~0.5% of pixels sit above
    3x the median read noise where a single log-normal predicts ~0.01%; these are the
    pixels that limit faint-source detection. Defaults to off.
  • detector_glow_edge_scale_px: makes detector_glow_e_per_s edge-concentrated
    with an exponential falloff, instead of uniform, modelling amplifier glow emitted
    at the array periphery. Renormalised so the array mean is unchanged; still fixed
    and exposure-scaling, so an exposure-matched master dark removes it. Defaults to
    0 (uniform, the previous behaviour).

Changed

  • Float32 cameras now cache amplifier gain/offset and structured-bias maps in
    float32 instead of retaining detector-sized float64 coefficients. The float64
    reference path is unchanged. A matched 2048x2048 structured-digitization
    benchmark improved by 1.328x on the local CPU and 1.382x on a Quadro P620 while
    persistent coefficient storage fell from 96 MiB to 48 MiB.

  • The princeton_instruments_kuro_1200b, photometrics_prime_95b, and
    andor_marana_4_2b_11 presets now carry measured conversion gain, read noise,
    dark current, bias offset, and non-uniformity terms, fitted from a per-pixel dark
    photon-transfer analysis of real frames rather than taken from datasheets. The
    largest corrections: conversion gain (1.25-1.3 -> 0.77-0.87 e-/ADU, the low-signal
    leg of these dual-gain modes) and dark_current_nonuniformity (0.03 -> 0.11-0.33,
    which had been roughly an order of magnitude too low). Each preset documents the
    operating mode and temperature the values apply to.

  • dark_current_nonuniformity raised to 0.23 on the remaining sCMOS presets
    (generic_scmos, hamamatsu_orca_fusion, hamamatsu_orca_quest_2,
    tucsen_aries_6504_pro, andor_cb1_0_5mp), which previously carried 0.02-0.03 or
    omitted the field entirely. 0.23 is the median of the three cameras measured
    against real dark stacks (0.11, 0.23, 0.33); each preset documents that it is a
    realistic default carried over from characterised hardware rather than a figure
    from that camera's datasheet. The same four conventional sCMOS presets also gain
    the measured RTS population (read_noise_rts_fraction = 0.016, factor 2.65), and
    andor_cb1_0_5mp / hamamatsu_orca_quest_2 gain a read_noise_nonuniformity of
    0.2 where they previously had none at all. hamamatsu_orca_quest_2 deliberately
    keeps no RTS population --- photon-number resolution depends on a tightly screened
    read-noise distribution, and importing a tail measured on conventional 11 um sCMOS
    would misrepresent it.

  • andor_marana_4_2b_11 gains its measured hot-pixel population
    (hot_pixel_fraction = 1e-4 above 10x the median dark rate).

  • docs/guides/validation.md documents how to validate a preset against a real dark
    stack: measuring conversion gain from darks alone (no flats needed), and the
    split-half test for repeatable per-pixel read noise.

  • examples/16_detector_showcase.py now shows each detector in the regime it is
    built for
    rather than putting all four on one shared V-band field at a shared
    200 ms exposure. The old framing was a tidy controlled comparison but an unfair
    showcase: once leonardo_saphira carried a realistic near-infrared
    dark-plus-background ceiling, its panel was dark-noise dominated in a visible
    200 ms exposure (star/noise 0.93 against 7.4-22.6 for the other three), and no
    exposure time fixes that, because amplified dark noise grows as sqrt(t) while
    the star signal grows as t. The panels are now a 5 s deep-sky CCD field, a
    500 Hz EMCCD wavefront sensor, 100 ms wide-field sCMOS, and the C-RED One eAPD
    doing H-band AO in correlated double sampling at its 1750 Hz maximum frame
    rate — which the clip measures at ~1,789 frames/s on an RTX 5090. The clip is
    the README header image and exercises this release's CDS path, C-RED One preset,
    and Vega H band.

Fixed

  • The detector showcase no longer scales a panel to its own hot pixels. The
    clip took its per-panel display maximum at a hardcoded 99.95th percentile, which
    is exactly the hot_pixel_fraction that leonardo_saphira declares, so the
    display range was pinned to a defect population sitting near 20,000 ADU behind
    the avalanche gain stage and every physical feature crushed to black. The
    percentile is now taken from below the preset's declared defect fraction.
  • The detector showcase subtracts a measured dark pedestal rather than the bias
    offset alone.
    Bias is not the zero point once a detector carries real dark
    current through a gain stage — 80 e-/s at 50x avalanche gain is ~400 ADU per
    200 ms frame — and a CDS frame sits on its own exposure-dependent bias-rate
    pedestal rather than on the bias offset at all.
  • 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.