getframes
Realistic synthetic camera frames for scientific imaging pipelines.
getframes gives you a clean, small API for generating physically realistic frames
from CCD, CMOS, EMCCD, eAPD, and sCMOS detectors — with
accurate, auditable noise physics (read noise, dark current, shot noise,
fixed-pattern non-uniformity, a unified stochastic gain stage, clock-induced
charge, nonlinearity, and cosmic rays) so you can build and validate
image-processing pipelines against ground truth.
It generates dark, bias, and flat frames, and renders star fields through a PSF and telescope into a realistic science frame — the full photon → electron → ADU signal path, with optional opt-in spectral mode.
Status: stable.
getframes2.0 freezes the full public surface — the detector, scene, calibration, observation, radiometry, and dataset APIs — under Semantic Versioning; see API stability. An optional CuPy backend now keeps the photon → electron → ADU detector path and truth arrays on NVIDIA GPUs.
Install
pip install getframes
pip install 'getframes[gpu]' # optional CUDA 12.x / CuPy detector backend
From source (for development):
git clone https://github.com/jacotay7/getframes
cd getframes
pip install -e ".[dev]"
Quick start
import getframes as gf
# Pick a camera from the built-in preset library...
cam = gf.Camera.from_preset("andor_ikon_m934")
# ...and generate a reproducible dark frame.
frame = cam.dark_frame(exposure=60.0, temperature=-60.0, seed=0)
frame.data # 2-D numpy array of ADU, shape (1024, 1024)
frame.stats() # {'mean': ..., 'median': ..., 'std': ..., 'min': ..., 'max': ...}
frame.metadata # camera/exposure/temperature provenance
Frame is array-like, so it drops straight into NumPy:
import numpy as np
master_dark = np.mean([np.asarray(f) for f in cam.dark_series(60.0, n_frames=20, seed=1)], axis=0)
Define your own camera
cam = gf.Camera(
gf.CameraConfig(
name="My Lab CMOS",
sensor_type="CMOS",
resolution=(2048, 2048), # (height, width)
pixel_size_um=6.5,
quantum_efficiency=0.82,
full_well_e=30_000,
bit_depth=12,
gain_e_per_adu=0.8,
bias_offset_adu=300,
read_noise_e=1.8,
dark_current_e_per_s=0.5, # at the reference temperature
dark_current_ref_temp_c=20.0,
dark_current_doubling_temp_c=6.0,
)
)
frame = cam.dark_frame(exposure=30.0, temperature=-10.0)
Run the detector on a GPU
import cupy as cp
import getframes as gf
cam = gf.Camera.from_preset("andor_ocam2k", device="gpu", precision="float32")
rate = cp.full(cam.resolution, 2.0e6, dtype=cp.float32) # photons/s/pixel
frame = cam.expose(rate, exposure=1.0e-3, seed=0)
frame.data # CuPy uint32 ADU array; no host copy
frame.truth.mean_electrons # CuPy truth array
host_adu = gf.to_numpy(frame.data) # explicit device-to-host boundary
CPU and GPU use independent random-number streams: a seed repeats exactly on a fixed backend, while parity across backends means matching detector statistics, not identical pixels. See the GPU guide.
CPU/GPU throughput
Warm bulk-frame throughput on an AMD Ryzen 9 9950X3D and NVIDIA RTX 5090
(float32, truth enabled, persistent camera, device-resident input/output, no
host transfers) is:
| Workflow | Native shape | CPU (frames/s) | GPU (frames/s) | Speedup |
|---|---|---|---|---|
| Pyramid WFS CMOS | 80x80 | 5,264.9 | 10,565.4 | 2.01x |
| Shack-Hartmann WFS CMOS | 160x160 | 1,372.0 | 10,538.1 | 7.68x |
| OCAM2K EMCCD | 240x240 | 347.7 | 8,057.1 | 23.17x |
| SAPHIRA eAPD | 256x320 | 283.2 | 7,481.8 | 26.42x |
| Large science CMOS | 1024x1024 | 31.5 | 1,431.9 | 45.39x |
Higher is better. CUDA was synchronized around every timed region; construction was excluded. Even the smallest case reaches about 2x, while larger arrays and gain-stage detectors expose much more parallel work. See the full snapshot, raw JSON, and GPU benchmark methodology.
Observe a simulated star field
Render astronomical sources through a PSF and telescope, then expose them on a detector — the full photon → electron → ADU path:
scene = gf.Scene(
shape=(256, 256),
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=128, y=128, magnitude=20.0)],
sky=gf.Sky(surface_brightness_mag_arcsec2=21.0),
)
cam = gf.Camera.from_preset("zwo_asi2600mm").with_config(resolution=(256, 256))
frame = cam.observe(scene, exposure=300.0, seed=0) # a realistic science frame
You can also drive the detector directly with a photon-rate map (a scalar for a
uniform flat, or a per-pixel array): cam.expose(photon_rate, exposure).
Browse the preset library
from getframes import available_presets
from getframes.presets import preset_info
available_presets() # ['andor_ikon_m934', 'andor_ixon_ultra_888', 'generic_ccd', ...]
preset_info() # rich descriptors for each preset
| Preset | Sensor | Notes |
|---|---|---|
andor_ikon_m934 |
CCD | Deep-cooled back-illuminated scientific CCD |
andor_ixon_ultra_888 |
EMCCD | Single-photon-sensitive EMCCD |
leonardo_saphira |
EAPD | HgCdTe avalanche IR array (AO wavefront sensing) |
zwo_asi2600mm |
CMOS | Sony IMX571 cooled CMOS |
hamamatsu_orca_fusion |
sCMOS | Back-thinned sCMOS with per-pixel read noise |
andor_marana_4_2b_11 |
sCMOS | 11 µm-pixel, back-illuminated scientific CMOS |
photometrics_prime_95b |
sCMOS | 11 µm-pixel, 95% peak-QE scientific CMOS |
princeton_instruments_kuro_1200b |
sCMOS | Back-illuminated scientific CMOS |
qhy530_pro_ii |
CMOS | Global-shutter Sony IMX530 scientific camera |
tucsen_aries_6504_pro |
sCMOS | Sub-electron sensitive-mode read noise |
generic_ccd / generic_cmos / generic_emccd / generic_eapd / generic_scmos |
— | Idealised references for teaching/testing |
How the dark-frame model works
The dark signal chain (see getframes/noise.py):
- Dark current vs. temperature —
D(T) = D_ref · 2^((T − T_ref) / T_double) - Fixed-pattern non-uniformity (DSNU) and hot pixels modulate the per-pixel mean
- Shot noise — Poisson statistics on the dark electrons
- Clock-induced charge (EMCCD) — small Poisson term
- EM gain (EMCCD) — stochastic multiplication with realistic excess noise
- Read noise — Gaussian at the output amplifier
- Digitisation — gain conversion to ADU, bias pedestal, saturation, quantisation
All randomness flows through a seeded numpy.random.Generator, so every frame is
reproducible.
Documentation
- Guides & API reference (built with MkDocs) — getting started, the noise model, observing scenes, spectral mode, and presets
- API stability & versioning
- Runnable examples — PTC, star-field exposure planning, AO limiting magnitude, transit photometry, detector realism
Roadmap
1.0 is shipped: the full photon → electron → ADU signal path (dark, bias, flat, and rendered scenes) across CCD / CMOS / EMCCD / eAPD / sCMOS, with a unified gain stage, detector-realism effects, opt-in spectral mode, analysis helpers, and a frozen API.
The 2.0 plan moves from a frame to an observation — closing the raw → reduced → ground-truth validation loop, making time-series (variability, jitter, persistence) and richer scenes (extended sources, catalogs, sky coordinates) first-class, and deepening detector and radiometric fidelity. See docs/roadmap.md for the full critique, phased plan, and worked examples.
Contributing
Contributions — especially new camera presets — are welcome. See CONTRIBUTING.md. Run the checks locally with:
ruff check . && ruff format --check . && mypy && pytest
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file getframes-2.1.1.tar.gz.
File metadata
- Download URL: getframes-2.1.1.tar.gz
- Upload date:
- Size: 1.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4876d3848ba3df7828fb731a1c71f59a50417c123ee1697c8b34ee63edecce8e
|
|
| MD5 |
5a0beb0fbcbe305f05ff18505c93cf74
|
|
| BLAKE2b-256 |
1b9e2f5cbdf190d7aa6cf4a0b73f3d118bb733f0673a541137ba180656601cf5
|
Provenance
The following attestation bundles were made for getframes-2.1.1.tar.gz:
Publisher:
release.yml on jacotay7/getframes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
getframes-2.1.1.tar.gz -
Subject digest:
4876d3848ba3df7828fb731a1c71f59a50417c123ee1697c8b34ee63edecce8e - Sigstore transparency entry: 2256476164
- Sigstore integration time:
-
Permalink:
jacotay7/getframes@1e0c6c5834c693f642c4c92adb46fa9160144145 -
Branch / Tag:
refs/tags/2.1.1 - Owner: https://github.com/jacotay7
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1e0c6c5834c693f642c4c92adb46fa9160144145 -
Trigger Event:
push
-
Statement type:
File details
Details for the file getframes-2.1.1-py3-none-any.whl.
File metadata
- Download URL: getframes-2.1.1-py3-none-any.whl
- Upload date:
- Size: 114.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3880a35f390a713d27c64daa67bfaeb7ddeb411a94e2d564662fbcca7a2583cf
|
|
| MD5 |
95934af6bf98460ff9c24bb9a3334357
|
|
| BLAKE2b-256 |
fa681be73ed3c981242a72d574243612ec6f6fd87bc4f3dce92dc3d3ccf07665
|
Provenance
The following attestation bundles were made for getframes-2.1.1-py3-none-any.whl:
Publisher:
release.yml on jacotay7/getframes
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
getframes-2.1.1-py3-none-any.whl -
Subject digest:
3880a35f390a713d27c64daa67bfaeb7ddeb411a94e2d564662fbcca7a2583cf - Sigstore transparency entry: 2256476171
- Sigstore integration time:
-
Permalink:
jacotay7/getframes@1e0c6c5834c693f642c4c92adb46fa9160144145 -
Branch / Tag:
refs/tags/2.1.1 - Owner: https://github.com/jacotay7
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1e0c6c5834c693f642c4c92adb46fa9160144145 -
Trigger Event:
push
-
Statement type: