getframes 2.1.1__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {getframes-2.1.1 → getframes-2.3.0}/.gitignore +3 -1
- {getframes-2.1.1 → getframes-2.3.0}/CHANGELOG.md +249 -1
- getframes-2.3.0/PKG-INFO +216 -0
- getframes-2.3.0/README.md +170 -0
- getframes-2.3.0/benchmarks/bench_detector_workspace.py +122 -0
- {getframes-2.1.1 → getframes-2.3.0}/benchmarks/bench_devices.py +10 -4
- getframes-2.3.0/benchmarks/bench_fixed_map_dtype.py +140 -0
- getframes-2.3.0/benchmarks/detector-workspace-results.json +41 -0
- {getframes-2.1.1 → getframes-2.3.0}/benchmarks/device-results.json +55 -55
- {getframes-2.1.1 → getframes-2.3.0}/benchmarks/device-results.md +7 -7
- getframes-2.3.0/benchmarks/fixed-map-dtype-results.json +85 -0
- getframes-2.3.0/examples/15_detector_characterization.py +259 -0
- getframes-2.3.0/examples/16_detector_showcase.py +514 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/README.md +10 -0
- getframes-2.3.0/examples/detector_showcase.webp +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/pyproject.toml +3 -2
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/__about__.py +1 -1
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/__init__.py +16 -0
- getframes-2.3.0/src/getframes/analysis/__init__.py +50 -0
- getframes-2.3.0/src/getframes/analysis/characterize.py +678 -0
- getframes-2.3.0/src/getframes/analysis/nondestructive.py +384 -0
- getframes-2.3.0/src/getframes/camera.py +1521 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/cli.py +6 -1
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/config.py +332 -10
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/frame.py +4 -2
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/noise.py +681 -72
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/andor_cb1_0_5mp.toml +15 -0
- getframes-2.3.0/src/getframes/presets/data/andor_marana_4_2b_11.toml +51 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/andor_ocam2k.toml +3 -0
- getframes-2.3.0/src/getframes/presets/data/first_light_imaging_cred_one.toml +139 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/generic_scmos.toml +3 -1
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/hamamatsu_orca_fusion.toml +3 -1
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/hamamatsu_orca_quest_2.toml +14 -0
- getframes-2.3.0/src/getframes/presets/data/leonardo_saphira.toml +36 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/photometrics_prime_95b.toml +17 -8
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/princeton_instruments_kuro_1200b.toml +17 -8
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/tucsen_aries_6504_pro.toml +14 -1
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/optics.py +3 -2
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/photometry.py +43 -5
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/psf.py +47 -34
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/thermal.py +2 -5
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/spectral.py +10 -1
- getframes-2.3.0/tests/test_camera.py +606 -0
- getframes-2.3.0/tests/test_characterize.py +389 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_config.py +76 -0
- getframes-2.3.0/tests/test_conformance.py +83 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_detector.py +116 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_gain.py +64 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_gpu.py +55 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_noise.py +23 -1
- getframes-2.3.0/tests/test_nondestructive_analysis.py +73 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_presets.py +34 -1
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_radiometry.py +32 -0
- getframes-2.3.0/tests/test_realism.py +380 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_scale.py +31 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_scene.py +23 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_signal.py +79 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_spectral.py +13 -2
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_validation.py +58 -0
- getframes-2.3.0/tests/test_workspace.py +188 -0
- getframes-2.1.1/PKG-INFO +0 -271
- getframes-2.1.1/README.md +0 -225
- getframes-2.1.1/src/getframes/analysis/__init__.py +0 -19
- getframes-2.1.1/src/getframes/camera.py +0 -805
- getframes-2.1.1/src/getframes/presets/data/andor_marana_4_2b_11.toml +0 -38
- getframes-2.1.1/src/getframes/presets/data/leonardo_saphira.toml +0 -32
- getframes-2.1.1/tests/test_camera.py +0 -103
- getframes-2.1.1/tests/test_realism.py +0 -105
- {getframes-2.1.1 → getframes-2.3.0}/LICENSE +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/benchmarks/__init__.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/benchmarks/render_device_table.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/benchmarks/run.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/01_basic_dark_frame.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/02_custom_camera.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/03_master_dark.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/04_browse_presets.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/05_visualise.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/06_photon_transfer_curve.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/07_star_field_exposure.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/08_ao_limiting_magnitude.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/09_transit_photometry.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/10_detector_realism.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/11_radiometry_and_ir.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/12_ml_dataset.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/13_crowded_field.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/14_keck_lgs_ttf_trade_study.ipynb +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/examples/_common.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/analysis/apertures.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/analysis/ptc.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/backend.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/calibrate.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/dataset.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/observation.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/__init__.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/__init__.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/andor_ikon_m934.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/andor_ixon_ultra_888.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/generic_ccd.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/generic_cmos.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/generic_eapd.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/generic_emccd.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/nuvu_hnu_128_omega.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/nuvu_hnu_240.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/qhy530_pro_ii.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/scimeasure_little_joe_ccd39.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/presets/data/zwo_asi2600mm.toml +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/py.typed +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/__init__.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/scene.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/sources.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/src/getframes/scene/wcs.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_analysis.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_benchmarks.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_calibrate.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_cli.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_dataset.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_frame.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_observation.py +0 -0
- {getframes-2.1.1 → getframes-2.3.0}/tests/test_scene_enrich.py +0 -0
|
@@ -6,6 +6,252 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.3.0] - 2026-10-07
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`MoffatPSF`, `EllipticalGaussianPSF` and `AiryPSF` no longer put light that
|
|
14
|
+
falls off the frame back onto it.** They normalised their stamp *after*
|
|
15
|
+
clipping it to the frame, so a source on the edge column deposited its full
|
|
16
|
+
flux instead of the part that lands on the detector (a Moffat star centred on
|
|
17
|
+
column 0 kept 100% of its light, where `GaussianPSF` correctly keeps about
|
|
18
|
+
half). The stamp is now normalised before clipping, which follows the AO stack
|
|
19
|
+
convention that light lost at a detector edge is lost, not renormalised
|
|
20
|
+
(aocore CONVENTIONS 3.3). Sources wholly inside the frame are unchanged.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **Generic primitives now come from `aocore`.** getframes is part of an
|
|
25
|
+
adaptive-optics stack whose shared conventions and primitives live in
|
|
26
|
+
[`aocore`](https://github.com/jacotay7/aocore), now a core dependency
|
|
27
|
+
(`aocore>=0.1.2,<0.2`). `noise.block_sum` is `aocore.block_sum`, re-exported
|
|
28
|
+
so existing imports keep working; it accepts everything it did before and now
|
|
29
|
+
also takes a `(fy, fx)` factor and leading batch axes, and raises a clear
|
|
30
|
+
`ValueError` for a factor below 1 or a shape that does not divide (previously
|
|
31
|
+
a bare reshape error, or a `ZeroDivisionError` for a factor of 0).
|
|
32
|
+
`Frame.binned` sums through it. `AiryPSF` and `Thermal` take their radians per
|
|
33
|
+
arcsecond from `aocore.ARCSEC_TO_RAD`, and `Vignetting` its centred grid from
|
|
34
|
+
`aocore.coordinate_grid`. Every output is bit-for-bit unchanged.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- **Conformance tests** (`tests/test_conformance.py`) run the
|
|
39
|
+
`aocore.conformance` checks that apply to a detector package: every PSF model
|
|
40
|
+
deposits unit flux, a source at `(n - 1) / 2` and the vignetting pattern are
|
|
41
|
+
centred on the optical axis for odd and even windows.
|
|
42
|
+
|
|
43
|
+
## [2.2.0] - 2026-08-24
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
|
|
47
|
+
- **`CameraConfig.read_noise_correlated_fraction`.** The part of read noise that
|
|
48
|
+
is common to every read of a nondestructive ramp, and so cancels when two
|
|
49
|
+
reads are differenced. Without it every read was an independent draw and
|
|
50
|
+
correlated double sampling could only ever cost a factor of `sqrt(2)` — but a
|
|
51
|
+
real CDS frame is often *quieter* than a single read, because reference-level
|
|
52
|
+
drift, bias settling, and 1/f noise persist between the two reads and
|
|
53
|
+
differencing removes them. Defaults to `0`, which is the previous behaviour.
|
|
54
|
+
Only a differenced measurement can constrain it; single-read data cannot.
|
|
55
|
+
|
|
56
|
+
- **Vega-system J, H, and Ks bands.** `Bandpass.johnson` previously stopped at
|
|
57
|
+
I, so any near-infrared work had to leave the Vega system for AB — a
|
|
58
|
+
different magnitude for the same star, by +0.89 (J), +1.37 (H), and +1.84
|
|
59
|
+
(Ks). The zero points are derived from the 2MASS absolute calibration (Cohen,
|
|
60
|
+
Wheaton & Megeath 2003) through the same integral the AB path uses, so the
|
|
61
|
+
two systems now differ only in the reference spectrum and not in the
|
|
62
|
+
convention. `SpectralBandpass.johnson` gained the matching tophats, which are
|
|
63
|
+
the same shapes the AB survey table already used: a filter's shape is a
|
|
64
|
+
property of the filter, not of the magnitude system.
|
|
65
|
+
- **Correlated double sampling as a first-class readout mode.**
|
|
66
|
+
`Camera.correlated_double_sample` resets, reads the pedestal, integrates, reads
|
|
67
|
+
again, and returns the signed `int32` ADU difference — the frame a camera in
|
|
68
|
+
CDS mode actually delivers. It shares one reset-correlated readout core with
|
|
69
|
+
`nondestructive_series`, so kTC noise and fixed bias structure cancel, read
|
|
70
|
+
noise grows by `sqrt(2)`, common mode is partly removed through its AR(1)
|
|
71
|
+
correlation, and reset settling leaves its pedestal-to-signal residual. The
|
|
72
|
+
interval-proportional bias rate survives differencing by construction and is
|
|
73
|
+
documented as a dark-subtractable pedestal. `pedestal_interval_s` models a
|
|
74
|
+
finite reset-to-read delay. `correlated_double_sample_spectral` is the
|
|
75
|
+
wavelength-resolved twin, standing to it as `expose_spectral` does to
|
|
76
|
+
`expose`; both spectral entry points now share one QE-folding step, so a cube
|
|
77
|
+
cannot have QE applied twice or applied differently per readout mode.
|
|
78
|
+
- **Global-reset nondestructive read series for eAPD arrays.**
|
|
79
|
+
`Camera.nondestructive_series` and `dark_nondestructive_series` accumulate
|
|
80
|
+
Poisson charge between resets, preserve a ramp's kTC realization, redraw read
|
|
81
|
+
noise on every sample, and expose ramp/read timing in frame metadata.
|
|
82
|
+
- **A measured C-RED One preset and hybrid-array readout structure.** The new
|
|
83
|
+
`first_light_imaging_cred_one` preset separates the complete camera from the
|
|
84
|
+
bare `leonardo_saphira` detector and models its 32 interleaved output channels,
|
|
85
|
+
edge-dependent pedestal/noise, stable per-channel offsets and noise scales, and
|
|
86
|
+
correlated frame-wide readout pedestal. Its spatial and temporal dark-frame
|
|
87
|
+
parameters are constrained by the July 2026 CRED1 dark stack.
|
|
88
|
+
- **Reset-aware stack characterization.** `nondestructive_stack_statistics`
|
|
89
|
+
measures strong-reset cadence, ramp slopes, common mode, CDS/pixel noise,
|
|
90
|
+
interleaved channels, and edge structure without treating correlated reads as
|
|
91
|
+
independent exposures. `ramp_photon_transfer` fits conversion gain and
|
|
92
|
+
split-ramp response nonuniformity from repeated NDR ramps.
|
|
93
|
+
- **`examples/16_detector_showcase.py`** renders an animated WebP that exposes one
|
|
94
|
+
incident photon field on a CCD, EMCCD, sCMOS, and eAPD side by side, each panel
|
|
95
|
+
overlaid with the frame rate that camera sustained on the running machine. The
|
|
96
|
+
clip is the README header image.
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- **Refined the measured C-RED One response across gain and read rate.** Fixed
|
|
101
|
+
avalanche-gain nonuniformity, sublinear gain noise, NDR read-interval scaling,
|
|
102
|
+
observed 42/3/5-read electronic cadences, reset settling, and separate broad
|
|
103
|
+
side/narrow row-edge pedestal terms now reproduce the local operating-point
|
|
104
|
+
stacks. The C-RED One and SAPHIRA QE curves were corrected to the manufacturer
|
|
105
|
+
plot: 60% through 950 nm, linear to 80% at 1450 nm, then 80% to 2500 nm.
|
|
106
|
+
|
|
107
|
+
- **The README follows the documentation-first structure** used across the sibling
|
|
108
|
+
projects: docs link, showcase clip, install, quickstart, benchmarks, then the
|
|
109
|
+
feature list. The preset table and the dark-frame model walkthrough now live
|
|
110
|
+
only in the presets and noise-model guides, which they already duplicated.
|
|
111
|
+
- **Refreshed `benchmarks/device-results.{json,md}`** on the RTX 5090 / Ryzen 9
|
|
112
|
+
9950X3D reference machine against getframes 2.1.1, NumPy 2.2.6, and CuPy 14.1.1.
|
|
113
|
+
|
|
114
|
+
### Fixed
|
|
115
|
+
|
|
116
|
+
- **Benchmark provenance now records the CuPy version** when CuPy is installed
|
|
117
|
+
under a CUDA-specific wheel name (`cupy-cuda12x`/`cupy-cuda11x`). The metadata
|
|
118
|
+
block previously reported `cupy: null` on exactly the machines that had
|
|
119
|
+
produced the GPU column.
|
|
120
|
+
|
|
121
|
+
- **Persistent cameras now cache the dark-signal expectation.** Repeated exposures
|
|
122
|
+
at the same exposure time, temperature, precision, and detector configuration
|
|
123
|
+
reuse the full-detector DSNU/hot-pixel/glow map; changing either physical key
|
|
124
|
+
rebuilds it. Seeded stochastic frames and detector truth remain unchanged.
|
|
125
|
+
|
|
126
|
+
- **sCMOS per-pixel read noise is now a fixed property of the sensor.** The
|
|
127
|
+
per-pixel read-noise RMS map implied by `read_noise_nonuniformity` was drawn from
|
|
128
|
+
the *per-frame* generator, so it was re-randomised in every frame. Single-frame
|
|
129
|
+
spatial statistics were unaffected, but every pixel ended up with the same
|
|
130
|
+
expected noise *through time*, which is not how an sCMOS behaves: each pixel has
|
|
131
|
+
its own source-follower and column ADC. The map is now built once from
|
|
132
|
+
`fixed_pattern_seed` and cached in `FixedPatternMaps`, alongside PRNU and DSNU.
|
|
133
|
+
Verified against dark stacks from three real back-illuminated sCMOS cameras
|
|
134
|
+
(KURO 1200B, Prime 95B, Marana 4.2B-11): splitting a stack in half and
|
|
135
|
+
correlating the two per-pixel temporal-variance maps gives r = 0.89–0.94 on the
|
|
136
|
+
real detectors and r = 0.004 with the old model, now r ≈ 0.96.
|
|
137
|
+
**This changes generated pixel values** for any configuration with
|
|
138
|
+
`read_noise_nonuniformity > 0`; `read_noise_e` and the spatial statistics of a
|
|
139
|
+
single frame are unchanged.
|
|
140
|
+
|
|
141
|
+
### Added
|
|
142
|
+
|
|
143
|
+
- `DetectorWorkspace` plus optional `workspace=` / caller-owned `out=` execution
|
|
144
|
+
on scalar and spectral camera exposures. Full-detector ROI inputs, private
|
|
145
|
+
photo/total scratch, and digitised destinations can now be reused without
|
|
146
|
+
allowing returned truth or default frames to alias mutable scratch. The
|
|
147
|
+
workspace is device/shape/precision-bound and rejects concurrent use.
|
|
148
|
+
- `CameraConfig.charge_diffusion_fwhm_px`, `charge_diffusion_kernel()`, and
|
|
149
|
+
`apply_charge_diffusion()` make lateral detector charge spreading available to
|
|
150
|
+
focal-plane simulators at their own oversampling. The OCAM2K preset declares
|
|
151
|
+
its measured 0.37-pixel FWHM; under-resolved kernels fail explicitly, while
|
|
152
|
+
native-resolution camera frames state in metadata that diffusion was not
|
|
153
|
+
applied rather than silently becoming a numerical no-op.
|
|
154
|
+
- Full-detector region-of-interest simulation through
|
|
155
|
+
`CameraConfig.roi=(left, top, width, height)`. Cameras accept and return
|
|
156
|
+
ROI-shaped arrays while evaluating detector physics and fixed patterns on the
|
|
157
|
+
native sensor before cropping. `Camera.sensor_resolution`,
|
|
158
|
+
`CameraConfig.output_resolution`, and active amplifier-boundary properties make
|
|
159
|
+
the full-versus-ROI geometry explicit. Exact full-detector split pixels remain
|
|
160
|
+
available when an ROI is active.
|
|
161
|
+
- **`getframes.analysis.characterize`: detector characterisation from frame
|
|
162
|
+
stacks.** Where `photon_transfer_curve` drives a *simulated* camera, this works
|
|
163
|
+
on stacks that already exist -- raw data off a real detector, or simulated
|
|
164
|
+
frames. `stack_statistics` reduces any iterable of frames (arrays, `Frame`s, a
|
|
165
|
+
`dark_series` generator, your own file reader) to per-pixel temporal mean and
|
|
166
|
+
variance in one streaming pass, so stacks larger than memory are fine.
|
|
167
|
+
`characterize_dark` then measures conversion gain, read noise (with its
|
|
168
|
+
per-pixel map, log-normal width and RTS tail), dark current, bias and DSNU from
|
|
169
|
+
darks alone -- no flat field needed, because dark charge is Poisson and so
|
|
170
|
+
serves as the PTC charge source. `characterize_flat` adds full well, PRNU and
|
|
171
|
+
linearity. `DarkCharacterization.to_config()` returns a `CameraConfig`, closing
|
|
172
|
+
the loop: measure a real camera, then simulate it. `StackStats.split=True`
|
|
173
|
+
additionally gives `temporal_repeatability`, the split-half test that separates
|
|
174
|
+
genuine per-pixel noise structure from chi-squared sampling scatter.
|
|
175
|
+
New guide (`docs/guides/characterization.md`) and example
|
|
176
|
+
(`examples/15_detector_characterization.py`).
|
|
177
|
+
- `read_noise_rts_fraction` / `read_noise_rts_factor`: an optional second,
|
|
178
|
+
noisier read-noise population modelling the random-telegraph-signal (RTS) pixels
|
|
179
|
+
of a real sCMOS array. Measured on three real sensors, ~0.5% of pixels sit above
|
|
180
|
+
3x the median read noise where a single log-normal predicts ~0.01%; these are the
|
|
181
|
+
pixels that limit faint-source detection. Defaults to off.
|
|
182
|
+
- `detector_glow_edge_scale_px`: makes `detector_glow_e_per_s` edge-concentrated
|
|
183
|
+
with an exponential falloff, instead of uniform, modelling amplifier glow emitted
|
|
184
|
+
at the array periphery. Renormalised so the array mean is unchanged; still fixed
|
|
185
|
+
and exposure-scaling, so an exposure-matched master dark removes it. Defaults to
|
|
186
|
+
`0` (uniform, the previous behaviour).
|
|
187
|
+
|
|
188
|
+
### Changed
|
|
189
|
+
|
|
190
|
+
- Float32 cameras now cache amplifier gain/offset and structured-bias maps in
|
|
191
|
+
float32 instead of retaining detector-sized float64 coefficients. The float64
|
|
192
|
+
reference path is unchanged. A matched 2048x2048 structured-digitization
|
|
193
|
+
benchmark improved by 1.328x on the local CPU and 1.382x on a Quadro P620 while
|
|
194
|
+
persistent coefficient storage fell from 96 MiB to 48 MiB.
|
|
195
|
+
|
|
196
|
+
- The `princeton_instruments_kuro_1200b`, `photometrics_prime_95b`, and
|
|
197
|
+
`andor_marana_4_2b_11` presets now carry **measured** conversion gain, read noise,
|
|
198
|
+
dark current, bias offset, and non-uniformity terms, fitted from a per-pixel dark
|
|
199
|
+
photon-transfer analysis of real frames rather than taken from datasheets. The
|
|
200
|
+
largest corrections: conversion gain (1.25-1.3 -> 0.77-0.87 e-/ADU, the low-signal
|
|
201
|
+
leg of these dual-gain modes) and `dark_current_nonuniformity` (0.03 -> 0.11-0.33,
|
|
202
|
+
which had been roughly an order of magnitude too low). Each preset documents the
|
|
203
|
+
operating mode and temperature the values apply to.
|
|
204
|
+
- `dark_current_nonuniformity` raised to `0.23` on the remaining sCMOS presets
|
|
205
|
+
(`generic_scmos`, `hamamatsu_orca_fusion`, `hamamatsu_orca_quest_2`,
|
|
206
|
+
`tucsen_aries_6504_pro`, `andor_cb1_0_5mp`), which previously carried 0.02-0.03 or
|
|
207
|
+
omitted the field entirely. `0.23` is the median of the three cameras measured
|
|
208
|
+
against real dark stacks (0.11, 0.23, 0.33); each preset documents that it is a
|
|
209
|
+
realistic default carried over from characterised hardware rather than a figure
|
|
210
|
+
from that camera's datasheet. The same four conventional sCMOS presets also gain
|
|
211
|
+
the measured RTS population (`read_noise_rts_fraction = 0.016`, factor 2.65), and
|
|
212
|
+
`andor_cb1_0_5mp` / `hamamatsu_orca_quest_2` gain a `read_noise_nonuniformity` of
|
|
213
|
+
0.2 where they previously had none at all. `hamamatsu_orca_quest_2` deliberately
|
|
214
|
+
keeps no RTS population --- photon-number resolution depends on a tightly screened
|
|
215
|
+
read-noise distribution, and importing a tail measured on conventional 11 um sCMOS
|
|
216
|
+
would misrepresent it.
|
|
217
|
+
- `andor_marana_4_2b_11` gains its measured hot-pixel population
|
|
218
|
+
(`hot_pixel_fraction = 1e-4` above 10x the median dark rate).
|
|
219
|
+
- `docs/guides/validation.md` documents how to validate a preset against a real dark
|
|
220
|
+
stack: measuring conversion gain from darks alone (no flats needed), and the
|
|
221
|
+
split-half test for repeatable per-pixel read noise.
|
|
222
|
+
- **`examples/16_detector_showcase.py` now shows each detector in the regime it is
|
|
223
|
+
built for** rather than putting all four on one shared V-band field at a shared
|
|
224
|
+
200 ms exposure. The old framing was a tidy controlled comparison but an unfair
|
|
225
|
+
showcase: once `leonardo_saphira` carried a realistic near-infrared
|
|
226
|
+
dark-plus-background ceiling, its panel was dark-noise dominated in a visible
|
|
227
|
+
200 ms exposure (star/noise 0.93 against 7.4-22.6 for the other three), and no
|
|
228
|
+
exposure time fixes that, because amplified dark noise grows as `sqrt(t)` while
|
|
229
|
+
the star signal grows as `t`. The panels are now a 5 s deep-sky CCD field, a
|
|
230
|
+
500 Hz EMCCD wavefront sensor, 100 ms wide-field sCMOS, and the C-RED One eAPD
|
|
231
|
+
doing H-band AO in correlated double sampling at its 1750 Hz maximum frame
|
|
232
|
+
rate — which the clip measures at ~1,789 frames/s on an RTX 5090. The clip is
|
|
233
|
+
the README header image and exercises this release's CDS path, C-RED One preset,
|
|
234
|
+
and Vega H band.
|
|
235
|
+
|
|
236
|
+
### Fixed
|
|
237
|
+
|
|
238
|
+
- **The detector showcase no longer scales a panel to its own hot pixels.** The
|
|
239
|
+
clip took its per-panel display maximum at a hardcoded 99.95th percentile, which
|
|
240
|
+
is exactly the `hot_pixel_fraction` that `leonardo_saphira` declares, so the
|
|
241
|
+
display range was pinned to a defect population sitting near 20,000 ADU behind
|
|
242
|
+
the avalanche gain stage and every physical feature crushed to black. The
|
|
243
|
+
percentile is now taken from below the preset's declared defect fraction.
|
|
244
|
+
- **The detector showcase subtracts a measured dark pedestal rather than the bias
|
|
245
|
+
offset alone.** Bias is not the zero point once a detector carries real dark
|
|
246
|
+
current through a gain stage — 80 e-/s at 50x avalanche gain is ~400 ADU per
|
|
247
|
+
200 ms frame — and a CDS frame sits on its own exposure-dependent bias-rate
|
|
248
|
+
pedestal rather than on the bias offset at all.
|
|
249
|
+
- Declared the license as a PEP 639 SPDX expression (`license = "MIT"` plus
|
|
250
|
+
`license-files`) instead of the deprecated `license = { text = "MIT" }` table,
|
|
251
|
+
and dropped the now-redundant `License ::` classifier. The built distribution
|
|
252
|
+
carries `License-Expression: MIT` and `License-File: LICENSE`. No change to the
|
|
253
|
+
license itself.
|
|
254
|
+
|
|
9
255
|
## [2.1.1] - 2026-07-26
|
|
10
256
|
|
|
11
257
|
### Added
|
|
@@ -346,7 +592,9 @@ together in 1.0.
|
|
|
346
592
|
- Documentation, runnable examples, and CI (lint, type-check, test matrix, PyPI
|
|
347
593
|
release via Trusted Publishing).
|
|
348
594
|
|
|
349
|
-
[Unreleased]: https://github.com/jacotay7/getframes/compare/2.
|
|
595
|
+
[Unreleased]: https://github.com/jacotay7/getframes/compare/2.3.0...HEAD
|
|
596
|
+
[2.3.0]: https://github.com/jacotay7/getframes/compare/2.2.0...2.3.0
|
|
597
|
+
[2.2.0]: https://github.com/jacotay7/getframes/compare/2.1.1...2.2.0
|
|
350
598
|
[2.1.1]: https://github.com/jacotay7/getframes/compare/2.1.0...2.1.1
|
|
351
599
|
[2.1.0]: https://github.com/jacotay7/getframes/compare/2.0.0...2.1.0
|
|
352
600
|
[2.0.0]: https://github.com/jacotay7/getframes/compare/1.0.0...2.0.0
|
getframes-2.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: getframes
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: Generate physically realistic synthetic camera frames (CCD/CMOS/EMCCD/eAPD/sCMOS) — dark, bias, flat, and rendered star fields — with auditable noise physics for scientific imaging pipelines.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jacotay7/getframes
|
|
6
|
+
Project-URL: Documentation, https://jacotay7.github.io/getframes/
|
|
7
|
+
Project-URL: Repository, https://github.com/jacotay7/getframes
|
|
8
|
+
Project-URL: Issues, https://github.com/jacotay7/getframes/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/jacotay7/getframes/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Jacob Taylor <jacobataylor7@gmail.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: astronomy,camera,ccd,cmos,detector,emccd,imaging,noise,simulation
|
|
14
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: aocore<0.2,>=0.1.2
|
|
27
|
+
Requires-Dist: astropy>=5.0
|
|
28
|
+
Requires-Dist: numpy>=1.23
|
|
29
|
+
Requires-Dist: scipy>=1.10
|
|
30
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
37
|
+
Provides-Extra: docs
|
|
38
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
39
|
+
Requires-Dist: mkdocs>=1.5; extra == 'docs'
|
|
40
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
41
|
+
Provides-Extra: examples
|
|
42
|
+
Requires-Dist: matplotlib>=3.7; extra == 'examples'
|
|
43
|
+
Provides-Extra: gpu
|
|
44
|
+
Requires-Dist: cupy-cuda12x>=13.0; extra == 'gpu'
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
|
|
47
|
+
# getframes
|
|
48
|
+
|
|
49
|
+
[](https://github.com/jacotay7/getframes/actions/workflows/ci.yml)
|
|
50
|
+
[](https://pypi.org/project/getframes/)
|
|
51
|
+
[](https://pypi.org/project/getframes/)
|
|
52
|
+
[](https://jacotay7.github.io/getframes/)
|
|
53
|
+
[](LICENSE)
|
|
54
|
+
|
|
55
|
+
**Documentation: [jacotay7.github.io/getframes](https://jacotay7.github.io/getframes/)**
|
|
56
|
+
|
|
57
|
+
**Realistic synthetic camera frames for scientific imaging pipelines.**
|
|
58
|
+
|
|
59
|
+
<p align="center">
|
|
60
|
+
<img src="examples/detector_showcase.webp" width="503" alt="Animated detector showcase: a CCD, EMCCD, sCMOS and C-RED One eAPD each simulated in the regime it is built for — deep-sky, AO wavefront sensing, wide-field, and near-infrared CDS — with live throughput.">
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
`getframes` generates the frames a real detector would have produced: the full
|
|
64
|
+
**photon → electron → ADU** signal path for **CCD**, **CMOS**, **EMCCD**,
|
|
65
|
+
**eAPD** and **sCMOS** sensors, with auditable noise physics (read noise, dark
|
|
66
|
+
current, shot noise, fixed-pattern non-uniformity, a unified stochastic gain
|
|
67
|
+
stage, clock-induced charge, nonlinearity, cosmic rays). It produces **dark**,
|
|
68
|
+
**bias** and **flat** frames, and renders **star fields** through a PSF and
|
|
69
|
+
telescope into a realistic science frame — so you can build and validate
|
|
70
|
+
image-processing pipelines against ground truth. It runs on NumPy by default and
|
|
71
|
+
switches to CUDA (via CuPy) with a single argument.
|
|
72
|
+
|
|
73
|
+
## Install
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install getframes # CPU (NumPy + SciPy + astropy + aocore)
|
|
77
|
+
pip install 'getframes[gpu]' # + CuPy for CUDA 12.x
|
|
78
|
+
pip install -e '.[dev]' # from a clone, for development
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quickstart
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
import getframes as gf
|
|
85
|
+
|
|
86
|
+
cam = gf.Camera.from_preset("andor_ikon_m934") # 21 presets, or your own CameraConfig
|
|
87
|
+
frame = cam.dark_frame(exposure=60.0, temperature=-60.0, seed=0)
|
|
88
|
+
|
|
89
|
+
frame.data # (1024, 1024) array of ADU
|
|
90
|
+
frame.stats() # {'mean': ..., 'median': ..., 'std': ..., 'min': ..., 'max': ...}
|
|
91
|
+
frame.metadata # camera/exposure/temperature provenance
|
|
92
|
+
|
|
93
|
+
scene = gf.Scene( # render a sky, then expose it
|
|
94
|
+
shape=(256, 256),
|
|
95
|
+
optics=gf.Telescope(
|
|
96
|
+
aperture_diameter_m=2.5,
|
|
97
|
+
throughput=0.3,
|
|
98
|
+
plate_scale_arcsec_per_pixel=0.4,
|
|
99
|
+
band=gf.Bandpass.johnson("V"),
|
|
100
|
+
),
|
|
101
|
+
psf=gf.MoffatPSF(fwhm_arcsec=1.1, beta=3.0),
|
|
102
|
+
sources=[gf.PointSource(x=128, y=128, magnitude=20.0)],
|
|
103
|
+
sky=gf.Sky(surface_brightness_mag_arcsec2=21.0),
|
|
104
|
+
)
|
|
105
|
+
frame = cam.with_config(resolution=(256, 256)).observe(scene, exposure=300.0, seed=0)
|
|
106
|
+
|
|
107
|
+
import cupy as cp # and the same path on a GPU
|
|
108
|
+
|
|
109
|
+
cam = gf.Camera.from_preset("andor_ocam2k", device="gpu", precision="float32")
|
|
110
|
+
rate = cp.full(cam.resolution, 2.0e6, dtype=cp.float32) # photons/s/pixel
|
|
111
|
+
frame = cam.expose(rate, exposure=1.0e-3, seed=0) # CuPy ADU, no host copy
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
See **[Getting started](https://jacotay7.github.io/getframes/guides/getting-started/)**
|
|
115
|
+
for the full walkthrough,
|
|
116
|
+
**[Observing scenes](https://jacotay7.github.io/getframes/guides/scenes/)** for
|
|
117
|
+
sources, PSFs and telescopes,
|
|
118
|
+
**[Camera presets](https://jacotay7.github.io/getframes/guides/presets/)** for the
|
|
119
|
+
preset library, and
|
|
120
|
+
**[The noise model](https://jacotay7.github.io/getframes/guides/noise-model/)** for
|
|
121
|
+
the physics behind every stage.
|
|
122
|
+
|
|
123
|
+
## Benchmarks
|
|
124
|
+
|
|
125
|
+
Warm bulk-frame throughput on an AMD Ryzen 9 9950X3D and an NVIDIA RTX 5090
|
|
126
|
+
(`float32`, truth enabled, persistent camera, device-resident input/output, no
|
|
127
|
+
host transfers). The raw artifact and its invocation are
|
|
128
|
+
[versioned with the benchmarks](benchmarks/device-results.json):
|
|
129
|
+
|
|
130
|
+
| Workflow | Native shape | CPU (frames/s) | GPU (frames/s) | Speedup |
|
|
131
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
132
|
+
| Pyramid WFS CMOS | 80×80 | 5,240 | 11,514 | 2.20× |
|
|
133
|
+
| Shack-Hartmann WFS CMOS | 160×160 | 1,386 | 11,471 | 8.27× |
|
|
134
|
+
| OCAM2K EMCCD | 240×240 | 357 | 8,045 | 22.53× |
|
|
135
|
+
| SAPHIRA eAPD | 256×320 | 280 | 7,497 | 26.74× |
|
|
136
|
+
| Large science CMOS | 1024×1024 | 31 | 1,453 | 47.21× |
|
|
137
|
+
|
|
138
|
+
Higher is better; CUDA was synchronized around every timed region and
|
|
139
|
+
construction was excluded. Even the smallest case reaches about 2×, while larger
|
|
140
|
+
arrays and gain-stage detectors expose much more parallel work. Reproduce the
|
|
141
|
+
table with
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
python benchmarks/bench_devices.py --seconds 2 --warmup 10 --device both
|
|
145
|
+
python benchmarks/run.py # the CPU hot-path sweep
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
See the [full snapshot](benchmarks/device-results.md) and the
|
|
149
|
+
**[GPU guide](https://jacotay7.github.io/getframes/guides/gpu/#reference-throughput)**
|
|
150
|
+
for the methodology.
|
|
151
|
+
|
|
152
|
+
## Features
|
|
153
|
+
|
|
154
|
+
- **Five detector families** — CCD, CMOS, EMCCD, eAPD and sCMOS, from a library
|
|
155
|
+
of sourced **[presets](https://jacotay7.github.io/getframes/guides/presets/)**
|
|
156
|
+
(`andor_ikon_m934`, `andor_ocam2k`, `leonardo_saphira`,
|
|
157
|
+
`first_light_imaging_cred_one`,
|
|
158
|
+
`andor_marana_4_2b_11`, `zwo_asi2600mm`, …) or any `CameraConfig` you define.
|
|
159
|
+
- **Auditable noise physics** — dark current vs. temperature, shot noise, a
|
|
160
|
+
unified stochastic gain stage (EM and avalanche) with realistic excess noise,
|
|
161
|
+
clock-induced charge, per-pixel sCMOS read noise, polynomial nonlinearity,
|
|
162
|
+
saturation and quantisation, each a small documented pure function in
|
|
163
|
+
**[the noise model](https://jacotay7.github.io/getframes/guides/noise-model/)**.
|
|
164
|
+
- **Detector realism** — CTI, blooming, IPC, kTC/reset noise, multi-amplifier
|
|
165
|
+
readout, cosmic-ray tracks, defect and structured-bias maps, vignetting and
|
|
166
|
+
radial distortion.
|
|
167
|
+
- **Fixed patterns that behave like silicon** — PRNU, DSNU, hot pixels, defects
|
|
168
|
+
and amplifier structure are keyed on `fixed_pattern_seed`, so they repeat in
|
|
169
|
+
every frame and are genuinely removable by a master frame.
|
|
170
|
+
- **Scenes** — point, extended and catalog sources, Gaussian/Moffat/Airy/array
|
|
171
|
+
PSFs, a `Telescope` with Vega (Johnson) and AB (ugriz, Gaia, 2MASS) bandpasses,
|
|
172
|
+
extinction, graybody thermal background, WCS pixel↔world, and light curves.
|
|
173
|
+
- **Calibration & ground truth** — master bias/dark/flat builders and a
|
|
174
|
+
`calibrate` reduction that closes the
|
|
175
|
+
**[raw → reduced → truth loop](https://jacotay7.github.io/getframes/guides/calibration/)**.
|
|
176
|
+
- **Observations** — `Observation` drives time series with jitter, drift, dither
|
|
177
|
+
and persistence, carrying per-frame
|
|
178
|
+
**[truth](https://jacotay7.github.io/getframes/guides/time-series/)**.
|
|
179
|
+
- **Spectral mode** (opt-in) — QE curves, relative or absolute SEDs, transmission
|
|
180
|
+
products and wavelength-resolved exposure; see
|
|
181
|
+
**[Spectral mode](https://jacotay7.github.io/getframes/guides/spectral/)** and
|
|
182
|
+
**[Radiometry & the infrared](https://jacotay7.github.io/getframes/guides/radiometry/)**.
|
|
183
|
+
- **Analysis on real data too** — aperture sums, centroids, photon-transfer
|
|
184
|
+
curves, independent-stack characterization, and reset-aware nondestructive-ramp
|
|
185
|
+
analysis run on measured detector frames as readily as on simulated ones.
|
|
186
|
+
- **Scale & datasets** — a float32 fast path, vectorised multi-source rendering,
|
|
187
|
+
a streaming raw+truth `dataset` generator and a `getframes` CLI; see
|
|
188
|
+
**[Scale & datasets](https://jacotay7.github.io/getframes/guides/datasets/)**.
|
|
189
|
+
- **GPU-optional** — every camera takes `device="gpu"` (CuPy) and keeps the
|
|
190
|
+
detector path and truth arrays device-resident. CPU and GPU have independent
|
|
191
|
+
RNG streams, so a `seed` repeats exactly on a fixed backend while parity across
|
|
192
|
+
backends means matching statistics, not identical pixels.
|
|
193
|
+
- **Reproducible and typed** — all randomness flows through a camera-owned seeded
|
|
194
|
+
generator, never global state; `mypy --strict` passes; every public name is
|
|
195
|
+
frozen under [SemVer](https://jacotay7.github.io/getframes/stability/) as of 2.0.
|
|
196
|
+
- **Validated** — noise models are checked against published forms in CI; see
|
|
197
|
+
**[Validation](https://jacotay7.github.io/getframes/guides/validation/)**.
|
|
198
|
+
|
|
199
|
+
See the **[API reference](https://jacotay7.github.io/getframes/reference/)** for
|
|
200
|
+
every public function and class, the
|
|
201
|
+
**[runnable examples](examples/)** for PTC, exposure planning, AO limiting
|
|
202
|
+
magnitude, transit photometry and detector realism, and the
|
|
203
|
+
**[roadmap](https://jacotay7.github.io/getframes/roadmap/)** for what is next.
|
|
204
|
+
|
|
205
|
+
## Contributing
|
|
206
|
+
|
|
207
|
+
Contributions — especially new camera presets — are welcome. See
|
|
208
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). Run the checks locally with:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
ruff check . && ruff format --check . && mypy && pytest
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# getframes
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jacotay7/getframes/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/getframes/)
|
|
5
|
+
[](https://pypi.org/project/getframes/)
|
|
6
|
+
[](https://jacotay7.github.io/getframes/)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
**Documentation: [jacotay7.github.io/getframes](https://jacotay7.github.io/getframes/)**
|
|
10
|
+
|
|
11
|
+
**Realistic synthetic camera frames for scientific imaging pipelines.**
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="examples/detector_showcase.webp" width="503" alt="Animated detector showcase: a CCD, EMCCD, sCMOS and C-RED One eAPD each simulated in the regime it is built for — deep-sky, AO wavefront sensing, wide-field, and near-infrared CDS — with live throughput.">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
`getframes` generates the frames a real detector would have produced: the full
|
|
18
|
+
**photon → electron → ADU** signal path for **CCD**, **CMOS**, **EMCCD**,
|
|
19
|
+
**eAPD** and **sCMOS** sensors, with auditable noise physics (read noise, dark
|
|
20
|
+
current, shot noise, fixed-pattern non-uniformity, a unified stochastic gain
|
|
21
|
+
stage, clock-induced charge, nonlinearity, cosmic rays). It produces **dark**,
|
|
22
|
+
**bias** and **flat** frames, and renders **star fields** through a PSF and
|
|
23
|
+
telescope into a realistic science frame — so you can build and validate
|
|
24
|
+
image-processing pipelines against ground truth. It runs on NumPy by default and
|
|
25
|
+
switches to CUDA (via CuPy) with a single argument.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install getframes # CPU (NumPy + SciPy + astropy + aocore)
|
|
31
|
+
pip install 'getframes[gpu]' # + CuPy for CUDA 12.x
|
|
32
|
+
pip install -e '.[dev]' # from a clone, for development
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quickstart
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import getframes as gf
|
|
39
|
+
|
|
40
|
+
cam = gf.Camera.from_preset("andor_ikon_m934") # 21 presets, or your own CameraConfig
|
|
41
|
+
frame = cam.dark_frame(exposure=60.0, temperature=-60.0, seed=0)
|
|
42
|
+
|
|
43
|
+
frame.data # (1024, 1024) array of ADU
|
|
44
|
+
frame.stats() # {'mean': ..., 'median': ..., 'std': ..., 'min': ..., 'max': ...}
|
|
45
|
+
frame.metadata # camera/exposure/temperature provenance
|
|
46
|
+
|
|
47
|
+
scene = gf.Scene( # render a sky, then expose it
|
|
48
|
+
shape=(256, 256),
|
|
49
|
+
optics=gf.Telescope(
|
|
50
|
+
aperture_diameter_m=2.5,
|
|
51
|
+
throughput=0.3,
|
|
52
|
+
plate_scale_arcsec_per_pixel=0.4,
|
|
53
|
+
band=gf.Bandpass.johnson("V"),
|
|
54
|
+
),
|
|
55
|
+
psf=gf.MoffatPSF(fwhm_arcsec=1.1, beta=3.0),
|
|
56
|
+
sources=[gf.PointSource(x=128, y=128, magnitude=20.0)],
|
|
57
|
+
sky=gf.Sky(surface_brightness_mag_arcsec2=21.0),
|
|
58
|
+
)
|
|
59
|
+
frame = cam.with_config(resolution=(256, 256)).observe(scene, exposure=300.0, seed=0)
|
|
60
|
+
|
|
61
|
+
import cupy as cp # and the same path on a GPU
|
|
62
|
+
|
|
63
|
+
cam = gf.Camera.from_preset("andor_ocam2k", device="gpu", precision="float32")
|
|
64
|
+
rate = cp.full(cam.resolution, 2.0e6, dtype=cp.float32) # photons/s/pixel
|
|
65
|
+
frame = cam.expose(rate, exposure=1.0e-3, seed=0) # CuPy ADU, no host copy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
See **[Getting started](https://jacotay7.github.io/getframes/guides/getting-started/)**
|
|
69
|
+
for the full walkthrough,
|
|
70
|
+
**[Observing scenes](https://jacotay7.github.io/getframes/guides/scenes/)** for
|
|
71
|
+
sources, PSFs and telescopes,
|
|
72
|
+
**[Camera presets](https://jacotay7.github.io/getframes/guides/presets/)** for the
|
|
73
|
+
preset library, and
|
|
74
|
+
**[The noise model](https://jacotay7.github.io/getframes/guides/noise-model/)** for
|
|
75
|
+
the physics behind every stage.
|
|
76
|
+
|
|
77
|
+
## Benchmarks
|
|
78
|
+
|
|
79
|
+
Warm bulk-frame throughput on an AMD Ryzen 9 9950X3D and an NVIDIA RTX 5090
|
|
80
|
+
(`float32`, truth enabled, persistent camera, device-resident input/output, no
|
|
81
|
+
host transfers). The raw artifact and its invocation are
|
|
82
|
+
[versioned with the benchmarks](benchmarks/device-results.json):
|
|
83
|
+
|
|
84
|
+
| Workflow | Native shape | CPU (frames/s) | GPU (frames/s) | Speedup |
|
|
85
|
+
| --- | ---: | ---: | ---: | ---: |
|
|
86
|
+
| Pyramid WFS CMOS | 80×80 | 5,240 | 11,514 | 2.20× |
|
|
87
|
+
| Shack-Hartmann WFS CMOS | 160×160 | 1,386 | 11,471 | 8.27× |
|
|
88
|
+
| OCAM2K EMCCD | 240×240 | 357 | 8,045 | 22.53× |
|
|
89
|
+
| SAPHIRA eAPD | 256×320 | 280 | 7,497 | 26.74× |
|
|
90
|
+
| Large science CMOS | 1024×1024 | 31 | 1,453 | 47.21× |
|
|
91
|
+
|
|
92
|
+
Higher is better; CUDA was synchronized around every timed region and
|
|
93
|
+
construction was excluded. Even the smallest case reaches about 2×, while larger
|
|
94
|
+
arrays and gain-stage detectors expose much more parallel work. Reproduce the
|
|
95
|
+
table with
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
python benchmarks/bench_devices.py --seconds 2 --warmup 10 --device both
|
|
99
|
+
python benchmarks/run.py # the CPU hot-path sweep
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See the [full snapshot](benchmarks/device-results.md) and the
|
|
103
|
+
**[GPU guide](https://jacotay7.github.io/getframes/guides/gpu/#reference-throughput)**
|
|
104
|
+
for the methodology.
|
|
105
|
+
|
|
106
|
+
## Features
|
|
107
|
+
|
|
108
|
+
- **Five detector families** — CCD, CMOS, EMCCD, eAPD and sCMOS, from a library
|
|
109
|
+
of sourced **[presets](https://jacotay7.github.io/getframes/guides/presets/)**
|
|
110
|
+
(`andor_ikon_m934`, `andor_ocam2k`, `leonardo_saphira`,
|
|
111
|
+
`first_light_imaging_cred_one`,
|
|
112
|
+
`andor_marana_4_2b_11`, `zwo_asi2600mm`, …) or any `CameraConfig` you define.
|
|
113
|
+
- **Auditable noise physics** — dark current vs. temperature, shot noise, a
|
|
114
|
+
unified stochastic gain stage (EM and avalanche) with realistic excess noise,
|
|
115
|
+
clock-induced charge, per-pixel sCMOS read noise, polynomial nonlinearity,
|
|
116
|
+
saturation and quantisation, each a small documented pure function in
|
|
117
|
+
**[the noise model](https://jacotay7.github.io/getframes/guides/noise-model/)**.
|
|
118
|
+
- **Detector realism** — CTI, blooming, IPC, kTC/reset noise, multi-amplifier
|
|
119
|
+
readout, cosmic-ray tracks, defect and structured-bias maps, vignetting and
|
|
120
|
+
radial distortion.
|
|
121
|
+
- **Fixed patterns that behave like silicon** — PRNU, DSNU, hot pixels, defects
|
|
122
|
+
and amplifier structure are keyed on `fixed_pattern_seed`, so they repeat in
|
|
123
|
+
every frame and are genuinely removable by a master frame.
|
|
124
|
+
- **Scenes** — point, extended and catalog sources, Gaussian/Moffat/Airy/array
|
|
125
|
+
PSFs, a `Telescope` with Vega (Johnson) and AB (ugriz, Gaia, 2MASS) bandpasses,
|
|
126
|
+
extinction, graybody thermal background, WCS pixel↔world, and light curves.
|
|
127
|
+
- **Calibration & ground truth** — master bias/dark/flat builders and a
|
|
128
|
+
`calibrate` reduction that closes the
|
|
129
|
+
**[raw → reduced → truth loop](https://jacotay7.github.io/getframes/guides/calibration/)**.
|
|
130
|
+
- **Observations** — `Observation` drives time series with jitter, drift, dither
|
|
131
|
+
and persistence, carrying per-frame
|
|
132
|
+
**[truth](https://jacotay7.github.io/getframes/guides/time-series/)**.
|
|
133
|
+
- **Spectral mode** (opt-in) — QE curves, relative or absolute SEDs, transmission
|
|
134
|
+
products and wavelength-resolved exposure; see
|
|
135
|
+
**[Spectral mode](https://jacotay7.github.io/getframes/guides/spectral/)** and
|
|
136
|
+
**[Radiometry & the infrared](https://jacotay7.github.io/getframes/guides/radiometry/)**.
|
|
137
|
+
- **Analysis on real data too** — aperture sums, centroids, photon-transfer
|
|
138
|
+
curves, independent-stack characterization, and reset-aware nondestructive-ramp
|
|
139
|
+
analysis run on measured detector frames as readily as on simulated ones.
|
|
140
|
+
- **Scale & datasets** — a float32 fast path, vectorised multi-source rendering,
|
|
141
|
+
a streaming raw+truth `dataset` generator and a `getframes` CLI; see
|
|
142
|
+
**[Scale & datasets](https://jacotay7.github.io/getframes/guides/datasets/)**.
|
|
143
|
+
- **GPU-optional** — every camera takes `device="gpu"` (CuPy) and keeps the
|
|
144
|
+
detector path and truth arrays device-resident. CPU and GPU have independent
|
|
145
|
+
RNG streams, so a `seed` repeats exactly on a fixed backend while parity across
|
|
146
|
+
backends means matching statistics, not identical pixels.
|
|
147
|
+
- **Reproducible and typed** — all randomness flows through a camera-owned seeded
|
|
148
|
+
generator, never global state; `mypy --strict` passes; every public name is
|
|
149
|
+
frozen under [SemVer](https://jacotay7.github.io/getframes/stability/) as of 2.0.
|
|
150
|
+
- **Validated** — noise models are checked against published forms in CI; see
|
|
151
|
+
**[Validation](https://jacotay7.github.io/getframes/guides/validation/)**.
|
|
152
|
+
|
|
153
|
+
See the **[API reference](https://jacotay7.github.io/getframes/reference/)** for
|
|
154
|
+
every public function and class, the
|
|
155
|
+
**[runnable examples](examples/)** for PTC, exposure planning, AO limiting
|
|
156
|
+
magnitude, transit photometry and detector realism, and the
|
|
157
|
+
**[roadmap](https://jacotay7.github.io/getframes/roadmap/)** for what is next.
|
|
158
|
+
|
|
159
|
+
## Contributing
|
|
160
|
+
|
|
161
|
+
Contributions — especially new camera presets — are welcome. See
|
|
162
|
+
[CONTRIBUTING.md](CONTRIBUTING.md). Run the checks locally with:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
ruff check . && ruff format --check . && mypy && pytest
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT — see [LICENSE](LICENSE).
|