Skip to content

Releases: jacotay7/getframes

getframes 2.6.1

Choose a tag to compare

@jacotay7 jacotay7 released this 18 Oct 21:57
5c78ec1

Fixed

  • Allow aocore 0.2 (aocore>=0.1.2,<0.3). aocore 0.2.0 is additive and the
    test suite passes against it; the old <0.2 cap made getframes impossible to
    install next to packages that need aocore 0.2 (e.g. shmpipeline-ao 0.2).

getframes 2.6.0

Choose a tag to compare

@jacotay7 jacotay7 released this 11 Oct 07:41
01e6c2b

Added

  • Calibration: ADU to input-referred electrons and incident photons
    (gf.Calibration, Camera.calibration). Calibration.from_camera(cam, exposure, n_darks=64, seed=...) averages exposure-matched dark frames into a
    mean master dark on the camera's device and precision; to_electrons applies
    L^-1[(ADU - dark) * gain_e_per_adu / em_gain] / flat, inverting the
    configured nonlinearity (closed form or Newton) by default; to_photons
    divides by the scalar QE expose applies, a quantum_efficiency, the
    qe_curve at wavelength_nm, or its effective value over a bandpass;
    saturated flags pixels whose charge reached the input well, the output
    register or the ADC ceiling. It works on NumPy and CuPy frames, takes an
    optional unit-mean flat, and handles binned frames. Tests check that the mean
    calibrated flux matches the incident photons to 0.5% for EMCCD, qCMOS, sCMOS,
    eAPD, CCD and CMOS presets (issue #10).
  • Camera.effective_read_noise_e(binning=..., binning_mode=...): the
    per-frame read noise referred to the input (electrons collected in the
    pixel): output read noise (the quadrature mean of a per-pixel map), kTC/reset
    noise and avalanche-scaled noise, divided by the EM/avalanche gain. The
    docstring and the new guide give the excess-noise-factor SNR.
  • Guide: A WFS camera in a closed loop:
    exposure vs. frame period, ADU calibration and its accuracy, input-referred
    noise and the excess noise factor, recommended EMCCD/qCMOS/sCMOS/eAPD/CCD
    presets, and keeping small frames cheap.
  • benchmarks/bench_small_frames.py: seeded 256x256 WFS frames for one preset
    per detector class, with before/after comparison and a SHA-256 digest of
    seeded output for bit-identity checks; results in
    benchmarks/small-frame-results.md.

Performance

  • Photon-starved EMCCD and eAPD frames are faster on the CPU, with
    bit-identical seeded output.
    NumPy returns Gamma(shape=0) as exactly zero
    without consuming a draw, but still visits every such pixel; the gain stage
    now samples only the pixels that hold charge when they are a minority (a
    wavefront sensor's dark background), and skips the * 1 shape scaling at
    F = sqrt(2). Same draws, same order, same generator state afterwards;
    seeded frames of every preset, in both precisions, are bit-identical to 2.5.0.
    On one Neoverse-N1 core (256x256 Shack-Hartmann spots, 1 ms, in-process A/B):
    the Gamma draw 1.43 → 0.78 ms; whole frames andor_ocam2k 6.57 → 5.95 ms
    (1.10x), leonardo_saphira 4.79 → 4.29 ms (1.12x), first_light_imaging_cred_one
    7.16 → 6.63 ms (1.08x); presets without a gain stage are unchanged. The CPU
    chain is otherwise at its sampler floor (about 85% of a frame is NumPy's
    Poisson, Gamma and Gaussian draws). An exact vectorised replica of NumPy's
    small-rate Poisson for the CIC draw was tried and measured no faster, so it
    was not kept. See benchmarks/small-frame-results.md.

Documented

  • The 1--2% calibrated-flux deficit seen in the dithered Shack-Hartmann
    prototype is not a deterministic offset of the signal chain: averaged over
    300 frames the prototype recovers 1.002 (OCAM2K), 0.999 (ORCA-Quest 2) and
    1.001 (generic sCMOS) of the incident flux, while a single 288x288 frame
    scatters by 1.2%, 1.0% and 3.3%. The guide lists the deterministic offsets a
    pipeline can introduce (median master dark, scalar vs. spectral QE,
    nonlinearity, saturation, unflattened fixed patterns) and their remedies.
  • The Nüvü HNü 240 / 128 presets cap the amplified charge at their 500 / 800
    e- full_well_e (no output_full_well_e), so at EM gain 1000 a pixel holding
    one electron saturates; the guide warns about it (#11).

getframes 2.5.0

Choose a tag to compare

@jacotay7 jacotay7 released this 07 Oct 18:37
1128258

Added

  • Arm benchmark data point (benchmarks/device-results-neoverse-n1.*): the
    device table on an Ampere Neoverse-N1 host (16 pinned cores) with an RTX
    4060 and an RTX A400.

Performance

  • Small GPU frames run up to 2.6x faster, with bit-identical seeded output.
    WFS-sized frames were launch-bound: the host spent longer issuing about twenty
    small CuPy kernels per frame than the GPU spent running them. The GPU path now
    computes the photo and total expectations in one fused kernel and the whole
    readout (full-well clip, defects, reset/avalanche/read noise, gain, bias
    pedestal and structure, common mode, rounding, ADC saturation, uint32
    conversion) in another; keeps scalar inputs (background, extra_electrons,
    the EM-gain scale, the CIC rate) on the host instead of uploading them every
    frame; samples Poisson counts directly in the working precision; and reuses
    the chain's photo + dark + extra sum as FrameTruth.mean_electrons. The
    noise is drawn by the same calls in the same order and the fused kernels
    repeat the same floating-point operations (FMA contraction off), so every
    seeded GPU frame and truth array is unchanged; a new GPU test compares them
    bit for bit against the separate operations. bench_devices.py on an Ampere
    Neoverse-N1 host (12 pinned cores), frames/s, 2.4.0 → now, median of three
    interleaved runs: RTX 4060 — Pyramid 80x80 2,422 → 6,418 (2.65x),
    Shack-Hartmann 160x160 2,424 → 6,340 (2.62x), OCAM2K 240x240 1,650 → 2,733
    (1.66x), SAPHIRA 256x320 1,809 → 2,072 (1.15x), 1024x1024 240 → 247 (1.03x);
    RTX A400 — Pyramid 2,403 → 6,315 (2.63x), the larger frames 1.02–1.06x.
    Larger frames are bound by CuPy's double-precision Poisson and Gamma
    samplers. See the GPU guide.
  • The NumPy path skips whole-frame identity arithmetic (adding a zero offset,
    dividing by a unit gain, multiplying by a unit avalanche-gain map) and reuses
    the expectation sum as the truth: 1–5% faster (1024x1024: 13.4 → 14.1 frames/s
    on the same host), with identical seeded output. It remains bound by NumPy's
    Poisson sampler (about 80% of a 1024x1024 frame), whose single sequential
    stream cannot be threaded without changing seeded frames.

Fixed

  • benchmarks/bench_devices.py reported the CPU of Arm hosts as aarch64;
    it now reads the model from lscpu (e.g. Neoverse-N1) when
    /proc/cpuinfo has no model name.

getframes 2.4.0

Choose a tag to compare

@jacotay7 jacotay7 released this 07 Oct 05:17
7b01daf

Added

  • device="gpu:N" and device="auto". Every device argument (Camera,
    get_backend, and the CLI's [camera] table) now speaks the AO stack's
    vocabulary (aocore CONVENTIONS 8.1): "cpu", "gpu", "gpu:N" for CUDA
    device N, and "auto", which picks the GPU when CuPy is installed and sees
    a device and the CPU otherwise. A "gpu:N" beyond the devices CuPy sees
    raises a ValueError naming the count. The old spellings ("numpy",
    "cuda", "cupy") still work, now case-insensitively. A GPU camera is
    pinned to its card: the fixed-pattern maps and the cuRAND streams are created
    on it and every camera method runs with it current, so a "gpu:1" camera
    works whichever device is current at the call, and with_config keeps it.
    New: Camera.device_id, ArrayBackend.device_id, ArrayBackend.spec
    ("cpu" or "gpu:N") and ArrayBackend.activate() (the device context,
    for calling the low-level noise functions on another card).
  • precision="single" / "double". The working precision takes the
    shared names (aocore CONVENTIONS 8.2), with "float32"/"float64" kept as
    aliases: on Camera, as a new precision keyword on
    Scene.photon_rate_map/photoelectron_rate_map (beside dtype), and on the
    noise functions that take a float_dtype (simulate_frame,
    fixed_pattern_maps, dark_signal_map, photo_signal_map). A dtype and a
    precision that disagree raise ValueError. getframes.resolve_precision
    maps any of these names to the NumPy dtype. Camera.precision still reports
    the dtype name ("float32"/"float64") whichever spelling was passed.
    dataset.pairs(dtype=...) is unchanged: it is the host storage type of the
    finished arrays, not a working precision.
  • The CLI's [camera] table takes a device key.
  • Conformance tests for the device and precision vocabulary, including that
    each precision name selects the same dtype as in aocore.
  • Edge-flux conformance for every PSF model. tests/test_conformance.py
    now uses aocore 0.1.3's image-builder checks (check_point_source_centring,
    check_point_source_flux) instead of feeding analytic PSFs through the
    OPD-driven checks with a dummy OPD, and adds check_edge_flux_loss for
    Gaussian, Moffat, elliptical Gaussian, Airy and array PSFs, guarding the
    2.3.0 fix. The dev extra pins aocore>=0.1.3,<0.2; the runtime
    requirement is unchanged.

Changed

  • An unknown device string now raises ValueError listing the accepted words
    ('cpu', 'gpu', 'gpu:N' ... or 'auto'), and a non-string device a
    TypeError. device="gpu" with CuPy installed but no CUDA device raises
    RuntimeError at construction rather than failing at the first frame.
  • Camera.__repr__ shows the GPU number (device='gpu:0').
  • The noise functions' float_dtype default is now None (still float64).

Fixed

  • dataset.pairs with a GPU camera failed on an implicit CuPy-to-NumPy
    conversion; it now copies each frame to the host through to_numpy, as do
    the CLI's .npy/.npz writers now that the CLI can select a GPU.

getframes 2.3.0

Choose a tag to compare

@jacotay7 jacotay7 released this 07 Oct 03:35
d7299cf

Fixed

  • MoffatPSF, EllipticalGaussianPSF and AiryPSF no longer put light that
    falls off the frame back onto it.
    They normalised their stamp after
    clipping it to the frame, so a source on the edge column deposited its full
    flux instead of the part that lands on the detector (a Moffat star centred on
    column 0 kept 100% of its light, where GaussianPSF correctly keeps about
    half). The stamp is now normalised before clipping, which follows the AO stack
    convention that light lost at a detector edge is lost, not renormalised
    (aocore CONVENTIONS 3.3). Sources wholly inside the frame are unchanged.

Changed

  • Generic primitives now come from aocore. getframes is part of an
    adaptive-optics stack whose shared conventions and primitives live in
    aocore, now a core dependency
    (aocore>=0.1.2,<0.2). noise.block_sum is aocore.block_sum, re-exported
    so existing imports keep working; it accepts everything it did before and now
    also takes a (fy, fx) factor and leading batch axes, and raises a clear
    ValueError for a factor below 1 or a shape that does not divide (previously
    a bare reshape error, or a ZeroDivisionError for a factor of 0).
    Frame.binned sums through it. AiryPSF and Thermal take their radians per
    arcsecond from aocore.ARCSEC_TO_RAD, and Vignetting its centred grid from
    aocore.coordinate_grid. Every output is bit-for-bit unchanged.

Added

  • Conformance tests (tests/test_conformance.py) run the
    aocore.conformance checks that apply to a detector package: every PSF model
    deposits unit flux, a source at (n - 1) / 2 and the vignetting pattern are
    centred on the optical axis for odd and even windows.

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

Read more

2.1.1

Choose a tag to compare

@jacotay7 jacotay7 released this 26 Jul 19:44

[2.1.1] - 2026-07-26

Added

  • Exact multi-amplifier ROI geometry and measured row-major gain/offset response
    fields. The OCAM2K preset now declares its eight-output 4x2 layout; cropped
    configurations can preserve their true split pixels.
  • Optional CuPy detector execution via Camera(..., device="gpu"). Photon-rate,
    electron, FrameTruth, and digitised ADU arrays remain device-resident through
    scalar and spectral exposure paths, including all detector artifact models and
    both binning modes. Frame.device, get_backend, get_array_module, and
    to_numpy make the execution and host-copy boundaries explicit.
  • CUDA parity tests cover seeded reproducibility, CPU/GPU statistics, spectral
    truth, CCD/CMOS/sCMOS/EMCCD/eAPD paths, fixed structure, gain, and detector
    artifacts. The benchmark runner accepts --device gpu|both.
  • Added a reproducible paired-device bulk-throughput suite, self-describing JSON
    artifact, rendered reference table, and README/GPU-guide CPU-versus-GPU results
    for representative WFS, EMCCD, eAPD, and large science-frame workflows.

Changed

  • Re-characterized the OCAM2K preset from the Keck unit report and supplied
    detector studies: approximately 28 output e-/ADU (21.43 ADU per input electron
    at EM gain 600), 0.360 e- input-referred read noise, 1.579 e-/pixel/s dark
    current, and a separately derived 0.004912 e-/pixel/frame CIC term. The output
    saturation charge and representative Keck bias now use the same conversion
    convention.
  • Fixed PRNU/DSNU/hot-pixel, amplifier, bias-structure, and defect maps are built
    once on persistent Camera construction rather than regenerated for every
    frame. This preserves the fixed-pattern contract and materially improves both
    CPU and GPU warm exposure throughput.
  • Optimized warm detector execution with in-place signal construction and
    digitization, direct full-array Gamma sampling, working-precision CPU/GPU
    Gaussian and Gamma draws, and a reusable per-call GPU seed stream. The RTX
    5090 reference improves GPU CMOS throughput by 1.36x–1.42x and GPU EMCCD/eAPD
    throughput by 2.35x–2.41x over the initial implementation without changing the
    detector distributions.

Fixed

  • Separated image-area and post-multiplication saturation with the optional,
    backwards-compatible CameraConfig.output_full_well_e. Gain-stage input charge
    now clips at full_well_e before multiplication. The OCAM2K preset uses Andor's
    published 270,000 e- image well and Keck's measured 10,000-count output ceiling,
    instead of incorrectly clipping multiplied signal at 250 e- (25 ADU).

getframes 2.1.0

Choose a tag to compare

@jacotay7 jacotay7 released this 20 Jul 00:56

[2.1.0] - 2026-07-19

Added

  • Five scientific-camera presets used by the Keck tip/tilt and low-bandwidth WFS
    detector trade example: KURO 1200B, Prime 95B, Marana 4.2B-11, QHY530 Pro II,
    and Aries 6504 Pro. All five now include a wavelength-resolved qe_curve:
    Marana and Aries from Andor's/Gpixel's published data (the Aries
    QE x fill factor trace is digitized from Gpixel's graph); KURO 1200B,
    Prime 95B, and QHY530 Pro II are digitized from published manufacturer QE
    plots.
  • A quantum_efficiency override on Camera.expose_series, matching
    Camera.expose and supporting pre-integrated spectral electron-rate series.
  • A Keck/SciMeasure Little Joe CCD39 preset with the manufacturer CCD39-01
    standard-AR/no-window spectral-response curve.
  • getframes.analysis.matched_filter_centroid, a background-insensitive
    cross-correlation centroid with sub-pixel peak refinement.
  • Frame.binned(factor, method=...), post-read digital binning of a frame into
    factor x factor super-pixels (sum or mean).
  • CameraConfig.supported_binnings and CameraConfig.binning_method — binning is
    now a first-class config parameter. The Keck-trade presets set these directly and
    drop the extra.detector_modes per-binning tables; cameras with more than one
    read-noise operating point keep a slim extra.read_modes list instead.
  • Native pixel binning in the signal chain: Camera.expose, Camera.expose_series,
    and noise.simulate_frame accept binning and binning_mode. "digital"
    (post-read, the default) reads each native pixel with its own read noise then sums,
    so binned read noise grows as binning; "on_chip" (pre-read charge-domain /
    hardware binning) sums the charge before the amplifier, so a single read noise is
    applied per super-pixel. Exposed noise.block_sum for the super-pixel summation.
  • getframes.analysis.centroid now accepts a per-pixel array background (e.g. a
    master sky+dark frame) and an optional threshold (scalar or per-pixel noise
    map), making it a calibrated thresholded centre-of-gravity estimator suitable
    for a real-time controller. Both additions are backwards compatible.
  • A Keck LGS tip/tilt and low-bandwidth wavefront-sensor detector trade-study
    example, including physically sampled AO/sub-aperture PSFs, complete detector
    Monte Carlo simulations, blackbody NGS weighting of each camera's QE curve
    (--ngs-teff), the 600–950 nm TTS/LBWFS arm bandpass folded into the zero-point
    bands, Monte Carlo standard errors on every point, a per-camera closed-loop
    cadence optimisation (a physical a·f + b·f² noise model fitted to the Monte
    Carlo data, saturation-aware, against a Tyler tilt-lag disturbance model), and
    a best/worst-performer frame gallery figure beside the trade curves.

Fixed

  • Type-checking compatibility with newer NumPy (2.2): tightened stub types for
    _stamp_bounds/_radial_grid shape arguments, pinned float64 on a few
    np.linspace constructions, and dropped now-unused type: ignore comments
    around the trapz/trapezoid shim. No behavioural change.

Full Changelog: 2.0.0...2.1.0

Public Release of getframes

Choose a tag to compare

@jacotay7 jacotay7 released this 29 Jun 20:44

getframes 2.0.0

getframes is a Python library for generating physically realistic synthetic
camera frames — dark, bias, flat, and rendered star fields — from CCD, CMOS,
EMCCD, eAPD, and sCMOS detectors. It models the complete
photon → electron → ADU signal path with auditable noise physics, so you can
build and test image-processing pipelines against known ground truth.

This is the first PyPI release. The full public API is frozen under Semantic
Versioning for the 2.x series.

Install

pip install getframes

Quick start

import getframes as gf

# Dark frame from a built-in preset
cam = gf.Camera.from_preset("andor_ikon_m934")
frame = cam.dark_frame(exposure=60.0, temperature=-60.0, seed=0)
frame.data       # 2-D NumPy array of ADU
frame.truth      # noise-free ground truth

# Render a star field
scene = gf.Scene(
    shape=(512, 512),
    optics=gf.Telescope(
        aperture_diameter_m=2.5,
        throughput=0.3,
        plate_scale_arcsec_per_pixel=0.4,
        band=gf.Bandpass.johnson("V"),
    ),
    psf=gf.MoffatPSF(fwhm_arcsec=1.1, beta=3.0),
    sources=[gf.PointSource(x=256, y=256, magnitude=18.0)],
    sky=gf.Sky(surface_brightness_mag_arcsec2=21.5),
)
frame = cam.with_config(resolution=(512, 512)).observe(scene, exposure=300.0, seed=1)

What's included

Detector physics

The full noise chain for five sensor types: read noise, dark current
(temperature-scaled with a doubling-temperature model), PRNU/DSNU fixed-pattern
noise, shot noise, a unified stochastic EM/avalanche gain stage (EMCCD and eAPD),
clock-induced charge, digitization, bias, and saturation.

Optional realism effects — all off by default — add CTI trailing, charge blooming,
inter-pixel capacitance, kTC/reset noise, multi-amplifier readout offsets,
cosmic-ray tracks, defect maps, and polynomial nonlinearity.

Scene rendering

Render astronomical sources through a PSF and telescope onto a detector with
Camera.observe. Sources include point sources, extended sources (Sérsic profiles
or array cutouts), spatially uniform illumination, and large catalogs. PSF models
cover Gaussian, Moffat, Airy (with central obstruction), elliptical Gaussian, and
user-supplied arrays. The scene layer supports vignetting, radial distortion, and
WCS sky-coordinate projection.

Calibration

combine stacks frames into masters (mean, median, or sigma-clip). calibrate
performs the standard bias/dark/flat reduction so a reduced frame can be compared
directly against Frame.truth — the noise-free ground truth attached to every
frame.

Time series and observations

Camera.observe_series returns an Observation — a structured sequence of frames
with per-frame timestamps, pointing offsets, and per-source truth light curves.
Sources can carry LightCurve brightness variations (box transit, sinusoidal, or
arbitrary function). Pointing models jitter, linear drift, and programmed dither.

Quantitative radiometry

AB-system and Vega-system photometry via Bandpass.ab (SDSS ugriz, Gaia, 2MASS)
and Bandpass.johnson (Johnson UBVRI). Spectral energy distributions with
SED.from_flux_density, real measured transmission curves via
SpectralBandpass.from_file, interstellar extinction (Cardelli–Clayton–Mathis
1989), and thermal background graybody emission for IR scenes. astropy.units
quantities are accepted wherever wavelength or flux density is expected.

Dataset generation and CLI

getframes.dataset.pairs() streams {"raw": ADU array, "truth": electrons array}
pairs reproducibly to disk for training ML pipelines. The getframes CLI
(presets / generate config.toml / dataset config.toml) turns a shareable
TOML file into frames or a dataset without writing Python.

Preset library

Ten built-in camera presets covering the full sensor-type range:

Preset Type Notes
andor_ikon_m934 CCD Deep-cooled back-illuminated scientific CCD
andor_ixon_ultra_888 EMCCD Single-photon-sensitive
leonardo_saphira eAPD HgCdTe IR avalanche array
zwo_asi2600mm CMOS Sony IMX571 cooled CMOS
hamamatsu_orca_fusion sCMOS Back-thinned, per-pixel read noise
generic_ccd / generic_cmos / generic_emccd / generic_eapd / generic_scmos — Idealised references for testing

Validation

tests/test_validation.py asserts the physics against analytic and published
references: AB/Vega zero points, the gain-stage excess noise factor,
CTI/IPC/blooming charge conservation, PSF flux conservation, PTC parameter
recovery, and reduced-frame truth recovery.

Requirements

Python 3.10–3.13. Core dependencies: numpy, scipy, astropy. matplotlib
is available as pip install getframes[examples] for the runnable examples.