Skip to content

GPU detector execution

Install the optional CUDA backend and select it when constructing a persistent camera:

python -m pip install 'getframes[gpu]'
import cupy as cp
import getframes as gf

camera = gf.Camera.from_preset(
    "andor_ocam2k",
    device="gpu",
    precision="float32",
)
photon_rate = cp.full(camera.resolution, 2.0e6, dtype=cp.float32)
frame = camera.expose(photon_rate, exposure=1.0e-3, seed=0)

assert frame.device == "gpu"
adu_gpu = frame.data
truth_gpu = frame.truth.mean_electrons
adu_cpu = gf.to_numpy(adu_gpu)

The complete detector chain stays on the selected device: fixed PRNU/DSNU and defect maps, photo and dark expectations, Poisson/read/reset noise, stochastic EM/eAPD gain, cosmic rays, blooming, CTI, IPC, nonlinearity, amplifier maps, binning, truth, and ADU digitisation. Wavelength-resolved Camera.expose_spectral likewise preserves its incident cube and integrated truth on device. Static fixed-pattern maps are constructed in the camera's working precision and cached once, so reuse the same Camera in a frame loop.

frame.data is the zero-copy device interface. np.asarray(frame), Frame.stats(), and Frame.to_fits() are explicit host-facing operations and copy GPU data. Use getframes.to_numpy() when a named host boundary is clearer.

Reproducibility and parity

A per-call seed repeats exactly on the same backend and dependency version. NumPy and CuPy use independent generators, as do most scientific CPU/GPU packages; the same integer seed is not expected to produce identical pixels across devices. Tests instead compare mean and variance against the same detector physics and verify deterministic repetition separately on each backend.

Reference throughput

The July 2026 development reference used an AMD Ryzen 9 9950X3D and NVIDIA RTX 5090 with Python 3.12, NumPy 2.2.6, SciPy 1.16.3, and CuPy 13.6.0. Each cell reused one persistent float32 camera, performed ten untimed warm-up frames, and then ran for at least two seconds. Photon-rate maps, detector truth, and ADU stayed on the selected device; camera construction and host transfers were not timed. The camera RNG was seeded once and advanced for every frame. CUDA was synchronized immediately before and after each timed region.

Workflow Detector Native shape CPU (frames/s) GPU (frames/s) Speedup
Pyramid WFS CMOS CMOS 80x80 5,264.9 10,565.4 2.01x
Shack-Hartmann WFS CMOS CMOS 160x160 1,372.0 10,538.1 7.68x
OCAM2K EMCCD EMCCD 240x240 347.7 8,057.1 23.17x
SAPHIRA eAPD eAPD 256x320 283.2 7,481.8 26.42x
Large science CMOS CMOS 1024x1024 31.5 1,431.9 45.39x

Higher frames/s is better. Relative to the original GPU implementation on the same machine, the optimized path is 1.36x–1.42x faster for CMOS and about 2.35x–2.41x faster for EMCCD/eAPD. The hot path now reuses a reseedable GPU RNG, samples directly in float32, applies Gamma gain without mask/gather/scatter or a host synchronization, and reuses the realized-electron buffer during in-place digitization. CPU float32 Gaussian sampling and buffer reuse improve the large CMOS case by about 11%.

The benchmark artifact records the exact command, revision, dirty-checkout flag, environment, methodology, elapsed time, frame count, frames/s, and megapixels/s for every cell. The checked-in snapshot was intentionally recorded from the GPU development checkout, so it is evidence rather than a release guarantee. See the rendered snapshot and raw JSON.

An additional owner-isolation benchmark covers structured-detector digitization. On this repository's Quadro P620, native float32 amplifier and bias maps reduced the 2048x2048 digitization median from 9.196 ms to 6.654 ms (1.382x); the local CPU median fell from 29.712 ms to 22.373 ms (1.328x). Persistent coefficient storage fell from 96 MiB to 48 MiB. These numbers exclude stochastic detector stages and are not full-exposure speedup claims; the alternating raw record is fixed-map-dtype-results.json.

Reproduce and render it from the repository root:

python benchmarks/bench_devices.py --seconds 2 --warmup 10 --device both \
  --output benchmarks/device-results.json
python benchmarks/render_device_table.py benchmarks/device-results.json \
  --output benchmarks/device-results.md

Do not compare unsynchronized CUDA submission time with completed CPU work. Also do not construct a camera per frame: that includes fixed-pattern generation and defeats the persistent state used by a real frame loop.