getframes 2.4.0__tar.gz → 2.6.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.4.0 → getframes-2.6.0}/CHANGELOG.md +108 -1
- {getframes-2.4.0 → getframes-2.6.0}/PKG-INFO +5 -1
- {getframes-2.4.0 → getframes-2.6.0}/README.md +4 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/bench_devices.py +9 -0
- getframes-2.6.0/benchmarks/bench_small_frames.py +192 -0
- getframes-2.6.0/benchmarks/device-results-neoverse-n1-rtx4060.json +221 -0
- getframes-2.6.0/benchmarks/device-results-neoverse-n1-rtxa400.json +126 -0
- getframes-2.6.0/benchmarks/device-results-neoverse-n1.md +39 -0
- getframes-2.6.0/benchmarks/small-frame-results.json +2118 -0
- getframes-2.6.0/benchmarks/small-frame-results.md +85 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/__about__.py +1 -1
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/__init__.py +2 -1
- getframes-2.6.0/src/getframes/_cuda.py +224 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/backend.py +45 -1
- getframes-2.6.0/src/getframes/calibrate.py +615 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/camera.py +90 -1
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/noise.py +246 -85
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_gpu.py +121 -0
- getframes-2.6.0/tests/test_wfs_calibration.py +354 -0
- getframes-2.4.0/src/getframes/calibrate.py +0 -182
- {getframes-2.4.0 → getframes-2.6.0}/.gitignore +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/LICENSE +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/__init__.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/bench_detector_workspace.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/bench_fixed_map_dtype.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/detector-workspace-results.json +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/device-results.json +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/device-results.md +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/fixed-map-dtype-results.json +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/render_device_table.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/benchmarks/run.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/01_basic_dark_frame.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/02_custom_camera.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/03_master_dark.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/04_browse_presets.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/05_visualise.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/06_photon_transfer_curve.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/07_star_field_exposure.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/08_ao_limiting_magnitude.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/09_transit_photometry.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/10_detector_realism.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/11_radiometry_and_ir.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/12_ml_dataset.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/13_crowded_field.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/14_keck_lgs_ttf_trade_study.ipynb +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/15_detector_characterization.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/16_detector_showcase.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/README.md +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/_common.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/examples/detector_showcase.webp +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/pyproject.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/analysis/__init__.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/analysis/apertures.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/analysis/characterize.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/analysis/nondestructive.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/analysis/ptc.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/cli.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/config.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/dataset.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/frame.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/observation.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/__init__.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/__init__.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/andor_cb1_0_5mp.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/andor_ikon_m934.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/andor_ixon_ultra_888.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/andor_marana_4_2b_11.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/andor_ocam2k.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/first_light_imaging_cred_one.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/generic_ccd.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/generic_cmos.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/generic_eapd.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/generic_emccd.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/generic_scmos.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/hamamatsu_orca_fusion.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/hamamatsu_orca_quest_2.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/leonardo_saphira.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/nuvu_hnu_128_omega.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/nuvu_hnu_240.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/photometrics_prime_95b.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/princeton_instruments_kuro_1200b.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/qhy530_pro_ii.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/scimeasure_little_joe_ccd39.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/tucsen_aries_6504_pro.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/presets/data/zwo_asi2600mm.toml +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/py.typed +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/__init__.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/optics.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/photometry.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/psf.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/scene.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/sources.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/thermal.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/scene/wcs.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/src/getframes/spectral.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_analysis.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_backend.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_benchmarks.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_calibrate.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_camera.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_characterize.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_cli.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_config.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_conformance.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_dataset.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_detector.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_frame.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_gain.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_noise.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_nondestructive_analysis.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_observation.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_presets.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_radiometry.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_realism.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_scale.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_scene.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_scene_enrich.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_signal.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_spectral.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_validation.py +0 -0
- {getframes-2.4.0 → getframes-2.6.0}/tests/test_workspace.py +0 -0
|
@@ -6,6 +6,112 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.6.0] - 2026-10-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`Calibration`: ADU to input-referred electrons and incident photons**
|
|
14
|
+
(`gf.Calibration`, `Camera.calibration`). `Calibration.from_camera(cam,
|
|
15
|
+
exposure, n_darks=64, seed=...)` averages exposure-matched dark frames into a
|
|
16
|
+
mean master dark on the camera's device and precision; `to_electrons` applies
|
|
17
|
+
`L^-1[(ADU - dark) * gain_e_per_adu / em_gain] / flat`, inverting the
|
|
18
|
+
configured nonlinearity (closed form or Newton) by default; `to_photons`
|
|
19
|
+
divides by the scalar QE `expose` applies, a `quantum_efficiency`, the
|
|
20
|
+
`qe_curve` at `wavelength_nm`, or its effective value over a `bandpass`;
|
|
21
|
+
`saturated` flags pixels whose charge reached the input well, the output
|
|
22
|
+
register or the ADC ceiling. It works on NumPy and CuPy frames, takes an
|
|
23
|
+
optional unit-mean flat, and handles binned frames. Tests check that the mean
|
|
24
|
+
calibrated flux matches the incident photons to 0.5% for EMCCD, qCMOS, sCMOS,
|
|
25
|
+
eAPD, CCD and CMOS presets (issue #10).
|
|
26
|
+
- **`Camera.effective_read_noise_e(binning=..., binning_mode=...)`**: the
|
|
27
|
+
per-frame read noise referred to the input (electrons collected in the
|
|
28
|
+
pixel): output read noise (the quadrature mean of a per-pixel map), kTC/reset
|
|
29
|
+
noise and avalanche-scaled noise, divided by the EM/avalanche gain. The
|
|
30
|
+
docstring and the new guide give the excess-noise-factor SNR.
|
|
31
|
+
- **Guide: [A WFS camera in a closed loop](docs/guides/wfs-closed-loop.md)**:
|
|
32
|
+
exposure vs. frame period, ADU calibration and its accuracy, input-referred
|
|
33
|
+
noise and the excess noise factor, recommended EMCCD/qCMOS/sCMOS/eAPD/CCD
|
|
34
|
+
presets, and keeping small frames cheap.
|
|
35
|
+
- `benchmarks/bench_small_frames.py`: seeded 256x256 WFS frames for one preset
|
|
36
|
+
per detector class, with before/after comparison and a SHA-256 digest of
|
|
37
|
+
seeded output for bit-identity checks; results in
|
|
38
|
+
`benchmarks/small-frame-results.md`.
|
|
39
|
+
|
|
40
|
+
### Performance
|
|
41
|
+
|
|
42
|
+
- **Photon-starved EMCCD and eAPD frames are faster on the CPU, with
|
|
43
|
+
bit-identical seeded output.** NumPy returns `Gamma(shape=0)` as exactly zero
|
|
44
|
+
without consuming a draw, but still visits every such pixel; the gain stage
|
|
45
|
+
now samples only the pixels that hold charge when they are a minority (a
|
|
46
|
+
wavefront sensor's dark background), and skips the `* 1` shape scaling at
|
|
47
|
+
`F = sqrt(2)`. Same draws, same order, same generator state afterwards;
|
|
48
|
+
seeded frames of every preset, in both precisions, are bit-identical to 2.5.0.
|
|
49
|
+
On one Neoverse-N1 core (256x256 Shack-Hartmann spots, 1 ms, in-process A/B):
|
|
50
|
+
the Gamma draw 1.43 → 0.78 ms; whole frames `andor_ocam2k` 6.57 → 5.95 ms
|
|
51
|
+
(1.10x), `leonardo_saphira` 4.79 → 4.29 ms (1.12x), `first_light_imaging_cred_one`
|
|
52
|
+
7.16 → 6.63 ms (1.08x); presets without a gain stage are unchanged. The CPU
|
|
53
|
+
chain is otherwise at its sampler floor (about 85% of a frame is NumPy's
|
|
54
|
+
Poisson, Gamma and Gaussian draws). An exact vectorised replica of NumPy's
|
|
55
|
+
small-rate Poisson for the CIC draw was tried and measured no faster, so it
|
|
56
|
+
was not kept. See `benchmarks/small-frame-results.md`.
|
|
57
|
+
|
|
58
|
+
### Documented
|
|
59
|
+
|
|
60
|
+
- The 1--2% calibrated-flux deficit seen in the dithered Shack-Hartmann
|
|
61
|
+
prototype is not a deterministic offset of the signal chain: averaged over
|
|
62
|
+
300 frames the prototype recovers 1.002 (OCAM2K), 0.999 (ORCA-Quest 2) and
|
|
63
|
+
1.001 (generic sCMOS) of the incident flux, while a single 288x288 frame
|
|
64
|
+
scatters by 1.2%, 1.0% and 3.3%. The guide lists the deterministic offsets a
|
|
65
|
+
pipeline can introduce (median master dark, scalar vs. spectral QE,
|
|
66
|
+
nonlinearity, saturation, unflattened fixed patterns) and their remedies.
|
|
67
|
+
- The Nüvü HNü 240 / 128 presets cap the *amplified* charge at their 500 / 800
|
|
68
|
+
e- `full_well_e` (no `output_full_well_e`), so at EM gain 1000 a pixel holding
|
|
69
|
+
one electron saturates; the guide warns about it (#11).
|
|
70
|
+
|
|
71
|
+
## [2.5.0] - 2026-10-07
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **Arm benchmark data point** (`benchmarks/device-results-neoverse-n1.*`): the
|
|
76
|
+
device table on an Ampere Neoverse-N1 host (16 pinned cores) with an RTX
|
|
77
|
+
4060 and an RTX A400.
|
|
78
|
+
|
|
79
|
+
### Performance
|
|
80
|
+
|
|
81
|
+
- **Small GPU frames run up to 2.6x faster, with bit-identical seeded output.**
|
|
82
|
+
WFS-sized frames were launch-bound: the host spent longer issuing about twenty
|
|
83
|
+
small CuPy kernels per frame than the GPU spent running them. The GPU path now
|
|
84
|
+
computes the photo and total expectations in one fused kernel and the whole
|
|
85
|
+
readout (full-well clip, defects, reset/avalanche/read noise, gain, bias
|
|
86
|
+
pedestal and structure, common mode, rounding, ADC saturation, `uint32`
|
|
87
|
+
conversion) in another; keeps scalar inputs (`background`, `extra_electrons`,
|
|
88
|
+
the EM-gain scale, the CIC rate) on the host instead of uploading them every
|
|
89
|
+
frame; samples Poisson counts directly in the working precision; and reuses
|
|
90
|
+
the chain's `photo + dark + extra` sum as `FrameTruth.mean_electrons`. The
|
|
91
|
+
noise is drawn by the same calls in the same order and the fused kernels
|
|
92
|
+
repeat the same floating-point operations (FMA contraction off), so every
|
|
93
|
+
seeded GPU frame and truth array is unchanged; a new GPU test compares them
|
|
94
|
+
bit for bit against the separate operations. `bench_devices.py` on an Ampere
|
|
95
|
+
Neoverse-N1 host (12 pinned cores), frames/s, 2.4.0 → now, median of three
|
|
96
|
+
interleaved runs: RTX 4060 — Pyramid 80x80 2,422 → 6,418 (2.65x),
|
|
97
|
+
Shack-Hartmann 160x160 2,424 → 6,340 (2.62x), OCAM2K 240x240 1,650 → 2,733
|
|
98
|
+
(1.66x), SAPHIRA 256x320 1,809 → 2,072 (1.15x), 1024x1024 240 → 247 (1.03x);
|
|
99
|
+
RTX A400 — Pyramid 2,403 → 6,315 (2.63x), the larger frames 1.02–1.06x.
|
|
100
|
+
Larger frames are bound by CuPy's double-precision Poisson and Gamma
|
|
101
|
+
samplers. See the [GPU guide](docs/guides/gpu.md#small-frames-fused-kernels).
|
|
102
|
+
- The NumPy path skips whole-frame identity arithmetic (adding a zero offset,
|
|
103
|
+
dividing by a unit gain, multiplying by a unit avalanche-gain map) and reuses
|
|
104
|
+
the expectation sum as the truth: 1–5% faster (1024x1024: 13.4 → 14.1 frames/s
|
|
105
|
+
on the same host), with identical seeded output. It remains bound by NumPy's
|
|
106
|
+
Poisson sampler (about 80% of a 1024x1024 frame), whose single sequential
|
|
107
|
+
stream cannot be threaded without changing seeded frames.
|
|
108
|
+
|
|
109
|
+
### Fixed
|
|
110
|
+
|
|
111
|
+
- `benchmarks/bench_devices.py` reported the CPU of Arm hosts as `aarch64`;
|
|
112
|
+
it now reads the model from `lscpu` (e.g. `Neoverse-N1`) when
|
|
113
|
+
`/proc/cpuinfo` has no model name.
|
|
114
|
+
|
|
9
115
|
## [2.4.0] - 2026-10-07
|
|
10
116
|
|
|
11
117
|
### Added
|
|
@@ -646,7 +752,8 @@ together in 1.0.
|
|
|
646
752
|
- Documentation, runnable examples, and CI (lint, type-check, test matrix, PyPI
|
|
647
753
|
release via Trusted Publishing).
|
|
648
754
|
|
|
649
|
-
[Unreleased]: https://github.com/jacotay7/getframes/compare/2.
|
|
755
|
+
[Unreleased]: https://github.com/jacotay7/getframes/compare/2.5.0...HEAD
|
|
756
|
+
[2.5.0]: https://github.com/jacotay7/getframes/compare/2.4.0...2.5.0
|
|
650
757
|
[2.4.0]: https://github.com/jacotay7/getframes/compare/2.3.0...2.4.0
|
|
651
758
|
[2.3.0]: https://github.com/jacotay7/getframes/compare/2.2.0...2.3.0
|
|
652
759
|
[2.2.0]: https://github.com/jacotay7/getframes/compare/2.1.1...2.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: getframes
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
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
5
|
Project-URL: Homepage, https://github.com/jacotay7/getframes
|
|
6
6
|
Project-URL: Documentation, https://jacotay7.github.io/getframes/
|
|
@@ -174,6 +174,10 @@ for the methodology.
|
|
|
174
174
|
- **Calibration & ground truth** — master bias/dark/flat builders and a
|
|
175
175
|
`calibrate` reduction that closes the
|
|
176
176
|
**[raw → reduced → truth loop](https://jacotay7.github.io/getframes/guides/calibration/)**.
|
|
177
|
+
- **Wavefront-sensor cameras** — `Calibration` turns ADU back into
|
|
178
|
+
input-referred electrons and photons, `Camera.effective_read_noise_e` gives
|
|
179
|
+
the input-referred noise, and a guide covers
|
|
180
|
+
**[a WFS camera in a closed loop](https://jacotay7.github.io/getframes/guides/wfs-closed-loop/)**.
|
|
177
181
|
- **Observations** — `Observation` drives time series with jitter, drift, dither
|
|
178
182
|
and persistence, carrying per-frame
|
|
179
183
|
**[truth](https://jacotay7.github.io/getframes/guides/time-series/)**.
|
|
@@ -127,6 +127,10 @@ for the methodology.
|
|
|
127
127
|
- **Calibration & ground truth** — master bias/dark/flat builders and a
|
|
128
128
|
`calibrate` reduction that closes the
|
|
129
129
|
**[raw → reduced → truth loop](https://jacotay7.github.io/getframes/guides/calibration/)**.
|
|
130
|
+
- **Wavefront-sensor cameras** — `Calibration` turns ADU back into
|
|
131
|
+
input-referred electrons and photons, `Camera.effective_read_noise_e` gives
|
|
132
|
+
the input-referred noise, and a guide covers
|
|
133
|
+
**[a WFS camera in a closed loop](https://jacotay7.github.io/getframes/guides/wfs-closed-loop/)**.
|
|
130
134
|
- **Observations** — `Observation` drives time series with jitter, drift, dither
|
|
131
135
|
and persistence, carrying per-frame
|
|
132
136
|
**[truth](https://jacotay7.github.io/getframes/guides/time-series/)**.
|
|
@@ -112,6 +112,15 @@ def _cpu_model() -> str:
|
|
|
112
112
|
return line.split(":", 1)[1].strip()
|
|
113
113
|
except OSError:
|
|
114
114
|
pass
|
|
115
|
+
# Arm /proc/cpuinfo has no "model name"; lscpu decodes the part number
|
|
116
|
+
# (e.g. "Neoverse-N1").
|
|
117
|
+
try:
|
|
118
|
+
output = subprocess.run(["lscpu"], check=True, capture_output=True, text=True).stdout
|
|
119
|
+
except (OSError, subprocess.CalledProcessError):
|
|
120
|
+
output = ""
|
|
121
|
+
for line in output.splitlines():
|
|
122
|
+
if line.startswith("Model name:"):
|
|
123
|
+
return line.split(":", 1)[1].strip()
|
|
115
124
|
return platform.processor() or "unknown CPU"
|
|
116
125
|
|
|
117
126
|
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Benchmark small, frequent wavefront-sensor frames on the CPU.
|
|
3
|
+
|
|
4
|
+
A closed-loop wavefront-sensor study calls :meth:`getframes.Camera.expose` once
|
|
5
|
+
per loop step on a small frame, so per-frame latency, not throughput on large
|
|
6
|
+
detectors, is what limits it. This script times seeded ``expose`` calls on a
|
|
7
|
+
256x256 Shack-Hartmann-like spot pattern (16x16 subapertures of 16x16 pixels,
|
|
8
|
+
one Gaussian spot each) for one preset of each WFS detector class, at a
|
|
9
|
+
photon-starved and a bright flux, in two call styles:
|
|
10
|
+
|
|
11
|
+
* ``default``: ``expose(rate, t, seed=s, include_truth=False)``;
|
|
12
|
+
* ``workspace``: the same with a reused :class:`getframes.DetectorWorkspace` and
|
|
13
|
+
a caller-owned ``out`` frame.
|
|
14
|
+
|
|
15
|
+
It also records a SHA-256 digest of a few seeded frames per configuration, so two
|
|
16
|
+
revisions can be compared for bit-identical output (``--compare``). Timings are
|
|
17
|
+
process CPU time per frame over interleaved, warmed blocks (the chain is
|
|
18
|
+
single-threaded); they are machine dependent and are not part of the test gate.
|
|
19
|
+
Each case reports the median and the best (minimum) block; on a shared host,
|
|
20
|
+
compare the best blocks, which other load can only inflate.
|
|
21
|
+
|
|
22
|
+
Run from the repository root::
|
|
23
|
+
|
|
24
|
+
python benchmarks/bench_small_frames.py --output small-frame-results.json
|
|
25
|
+
python benchmarks/bench_small_frames.py --compare before.json after.json
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import hashlib
|
|
32
|
+
import json
|
|
33
|
+
import platform
|
|
34
|
+
import statistics
|
|
35
|
+
import time
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
import numpy as np
|
|
40
|
+
|
|
41
|
+
import getframes as gf
|
|
42
|
+
|
|
43
|
+
SHAPE = (256, 256)
|
|
44
|
+
SUBAPERTURE_PX = 16
|
|
45
|
+
EXPOSURE_S = 1e-3
|
|
46
|
+
#: One preset per detector class a WFS is built from.
|
|
47
|
+
PRESETS = (
|
|
48
|
+
("EMCCD", "andor_ocam2k"),
|
|
49
|
+
("EMCCD", "andor_ixon_ultra_888"),
|
|
50
|
+
("qCMOS", "hamamatsu_orca_quest_2"),
|
|
51
|
+
("sCMOS", "generic_scmos"),
|
|
52
|
+
("eAPD", "first_light_imaging_cred_one"),
|
|
53
|
+
)
|
|
54
|
+
#: Photons per subaperture per frame: photon-starved and bright.
|
|
55
|
+
FLUXES = (100.0, 2000.0)
|
|
56
|
+
DIGEST_SEEDS = (0, 1, 2)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def spot_pattern(photons_per_subaperture: float) -> np.ndarray[Any, np.dtype[np.float64]]:
|
|
60
|
+
"""A 256x256 photon map of one Gaussian spot (sigma 1.5 px) per subaperture."""
|
|
61
|
+
coordinates = np.arange(SUBAPERTURE_PX) - (SUBAPERTURE_PX - 1) / 2
|
|
62
|
+
spot = np.exp(-(coordinates[:, None] ** 2 + coordinates[None, :] ** 2) / (2 * 1.5**2))
|
|
63
|
+
spot *= photons_per_subaperture / spot.sum()
|
|
64
|
+
reps = (SHAPE[0] // SUBAPERTURE_PX, SHAPE[1] // SUBAPERTURE_PX)
|
|
65
|
+
return np.tile(spot, reps)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def make_camera(preset: str) -> gf.Camera:
|
|
69
|
+
return gf.Camera.from_preset(preset).with_config(resolution=SHAPE, roi=None)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _block_ms(function: Any, calls: int) -> float:
|
|
73
|
+
# Process CPU time, not wall time: the chain is single-threaded, and CPU time
|
|
74
|
+
# is far less sensitive to other load on a shared host.
|
|
75
|
+
start = time.process_time_ns()
|
|
76
|
+
for index in range(calls):
|
|
77
|
+
function(index)
|
|
78
|
+
return (time.process_time_ns() - start) / calls / 1e6
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def bench_case(preset: str, flux: float, calls: int, repetitions: int) -> dict[str, Any]:
|
|
82
|
+
camera = make_camera(preset)
|
|
83
|
+
rate = spot_pattern(flux) / EXPOSURE_S
|
|
84
|
+
workspace = gf.DetectorWorkspace()
|
|
85
|
+
out = np.empty(SHAPE, dtype=np.uint32)
|
|
86
|
+
|
|
87
|
+
def default(seed: int) -> None:
|
|
88
|
+
camera.expose(rate, EXPOSURE_S, seed=seed, include_truth=False)
|
|
89
|
+
|
|
90
|
+
def reused(seed: int) -> None:
|
|
91
|
+
camera.expose(
|
|
92
|
+
rate, EXPOSURE_S, seed=seed, include_truth=False, workspace=workspace, out=out
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
for seed in range(10):
|
|
96
|
+
default(seed)
|
|
97
|
+
reused(seed)
|
|
98
|
+
samples: dict[str, list[float]] = {"default": [], "workspace": []}
|
|
99
|
+
variants = [("default", default), ("workspace", reused)]
|
|
100
|
+
for repetition in range(repetitions):
|
|
101
|
+
for name, function in variants if repetition % 2 == 0 else variants[::-1]:
|
|
102
|
+
samples[name].append(_block_ms(function, calls))
|
|
103
|
+
|
|
104
|
+
digest = hashlib.sha256()
|
|
105
|
+
for seed in DIGEST_SEEDS:
|
|
106
|
+
frame = camera.expose(rate, EXPOSURE_S, seed=seed)
|
|
107
|
+
digest.update(np.ascontiguousarray(frame.data).tobytes())
|
|
108
|
+
assert frame.truth is not None
|
|
109
|
+
digest.update(np.ascontiguousarray(frame.truth.mean_electrons).tobytes())
|
|
110
|
+
return {
|
|
111
|
+
"preset": preset,
|
|
112
|
+
"photons_per_subaperture": flux,
|
|
113
|
+
"default_ms": statistics.median(samples["default"]),
|
|
114
|
+
"workspace_ms": statistics.median(samples["workspace"]),
|
|
115
|
+
"default_best_ms": min(samples["default"]),
|
|
116
|
+
"workspace_best_ms": min(samples["workspace"]),
|
|
117
|
+
"default_samples_ms": samples["default"],
|
|
118
|
+
"workspace_samples_ms": samples["workspace"],
|
|
119
|
+
"seeded_digest": digest.hexdigest(),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def run(calls: int, repetitions: int) -> dict[str, Any]:
|
|
124
|
+
results = []
|
|
125
|
+
for sensor, preset in PRESETS:
|
|
126
|
+
for flux in FLUXES:
|
|
127
|
+
case = bench_case(preset, flux, calls, repetitions)
|
|
128
|
+
case["class"] = sensor
|
|
129
|
+
results.append(case)
|
|
130
|
+
print(
|
|
131
|
+
f"{sensor:6s} {preset:30s} {flux:7.0f} ph/subap "
|
|
132
|
+
f"default {case['default_ms']:6.2f} ms workspace {case['workspace_ms']:6.2f} ms",
|
|
133
|
+
flush=True,
|
|
134
|
+
)
|
|
135
|
+
return {
|
|
136
|
+
"schema_version": 1,
|
|
137
|
+
"getframes": gf.__version__,
|
|
138
|
+
"numpy": np.__version__,
|
|
139
|
+
"platform": platform.platform(),
|
|
140
|
+
"processor": platform.processor() or platform.machine(),
|
|
141
|
+
"shape": list(SHAPE),
|
|
142
|
+
"exposure_s": EXPOSURE_S,
|
|
143
|
+
"calls_per_block": calls,
|
|
144
|
+
"repetitions": repetitions,
|
|
145
|
+
"results": results,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def compare(before_path: Path, after_path: Path) -> str:
|
|
150
|
+
"""A Markdown table of before/after best blocks and whether seeded output matches."""
|
|
151
|
+
before = json.loads(before_path.read_text(encoding="utf-8"))
|
|
152
|
+
after = json.loads(after_path.read_text(encoding="utf-8"))
|
|
153
|
+
lines = [
|
|
154
|
+
f"Before: getframes {before['getframes']}; after: getframes {after['getframes']} "
|
|
155
|
+
f"({after['processor']}, NumPy {after['numpy']}, {after['shape'][0]}x{after['shape'][1]}).",
|
|
156
|
+
"",
|
|
157
|
+
"| Class | Preset | Photons/subap | Before (ms, best) | After (ms, best) | Speed-up | "
|
|
158
|
+
"After, workspace+out (ms, best) | Seeded output |",
|
|
159
|
+
"| --- | --- | ---: | ---: | ---: | ---: | ---: | --- |",
|
|
160
|
+
]
|
|
161
|
+
old = {(r["preset"], r["photons_per_subaperture"]): r for r in before["results"]}
|
|
162
|
+
for row in after["results"]:
|
|
163
|
+
ref = old.get((row["preset"], row["photons_per_subaperture"]))
|
|
164
|
+
if ref is None:
|
|
165
|
+
continue
|
|
166
|
+
same = "identical" if ref["seeded_digest"] == row["seeded_digest"] else "CHANGED"
|
|
167
|
+
lines.append(
|
|
168
|
+
f"| {row['class']} | {row['preset']} | {row['photons_per_subaperture']:.0f} | "
|
|
169
|
+
f"{ref['default_best_ms']:.2f} | {row['default_best_ms']:.2f} | "
|
|
170
|
+
f"{ref['default_best_ms'] / row['default_best_ms']:.2f}x | "
|
|
171
|
+
f"{row['workspace_best_ms']:.2f} | {same} |"
|
|
172
|
+
)
|
|
173
|
+
return "\n".join(lines) + "\n"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def main() -> None:
|
|
177
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
178
|
+
parser.add_argument("--calls", type=int, default=40)
|
|
179
|
+
parser.add_argument("--repetitions", type=int, default=7)
|
|
180
|
+
parser.add_argument("--output", type=Path)
|
|
181
|
+
parser.add_argument("--compare", nargs=2, type=Path, metavar=("BEFORE", "AFTER"))
|
|
182
|
+
arguments = parser.parse_args()
|
|
183
|
+
if arguments.compare:
|
|
184
|
+
print(compare(*arguments.compare), end="")
|
|
185
|
+
return
|
|
186
|
+
payload = run(arguments.calls, arguments.repetitions)
|
|
187
|
+
if arguments.output is not None:
|
|
188
|
+
arguments.output.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
main()
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"generated_at_utc": "2026-10-07T07:14:14.461693+00:00",
|
|
4
|
+
"command": "/home/jtaylor/miniforge3/envs/aosim-bench/bin/python benchmarks/bench_devices.py --seconds 2 --warmup 10 --device both --output /home/jtaylor/aosim-bench-logs/artifacts/getframes-device-rtx4060.json",
|
|
5
|
+
"revision": "7b01daf765d432ab141176548f2b11cf0e7fc11c",
|
|
6
|
+
"source_dirty": true,
|
|
7
|
+
"python": "3.13.15 | packaged by conda-forge | (main, Sep 2 2026, 22:02:03) [GCC 15.3.0]",
|
|
8
|
+
"platform": "Linux-6.17.9-76061709-generic-aarch64-with-glibc2.39",
|
|
9
|
+
"processor": "aarch64",
|
|
10
|
+
"cpu": "Neoverse-N1",
|
|
11
|
+
"gpu": "NVIDIA GeForce RTX 4060",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"getframes": "2.4.0",
|
|
14
|
+
"numpy": "2.5.3",
|
|
15
|
+
"scipy": "1.18.1",
|
|
16
|
+
"cupy": "14.2.0"
|
|
17
|
+
},
|
|
18
|
+
"methodology": {
|
|
19
|
+
"seconds_per_cell": 2.0,
|
|
20
|
+
"warmup_frames": 10,
|
|
21
|
+
"persistent_camera": true,
|
|
22
|
+
"device_resident_rate_and_output": true,
|
|
23
|
+
"include_truth": true,
|
|
24
|
+
"rng": "one generator seeded at camera construction and advanced per frame",
|
|
25
|
+
"cuda_synchronization": "before and after each timed region",
|
|
26
|
+
"construction_included": false,
|
|
27
|
+
"host_transfers_included": false
|
|
28
|
+
},
|
|
29
|
+
"results": [
|
|
30
|
+
{
|
|
31
|
+
"workflow": "pyramid_cmos_80",
|
|
32
|
+
"label": "Pyramid WFS CMOS",
|
|
33
|
+
"preset": "generic_cmos",
|
|
34
|
+
"sensor": "CMOS",
|
|
35
|
+
"shape": [
|
|
36
|
+
80,
|
|
37
|
+
80
|
|
38
|
+
],
|
|
39
|
+
"precision": "float32",
|
|
40
|
+
"exposure_s": 0.001,
|
|
41
|
+
"photon_rate_per_s": 2000000.0,
|
|
42
|
+
"device": "cpu",
|
|
43
|
+
"frames": 4295,
|
|
44
|
+
"elapsed_s": 2.0004646239976864,
|
|
45
|
+
"frame_s": 0.000465765919440672,
|
|
46
|
+
"frames_per_s": 2147.001225853703,
|
|
47
|
+
"megapixels_per_s": 13.740807845463701
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"workflow": "pyramid_cmos_80",
|
|
51
|
+
"label": "Pyramid WFS CMOS",
|
|
52
|
+
"preset": "generic_cmos",
|
|
53
|
+
"sensor": "CMOS",
|
|
54
|
+
"shape": [
|
|
55
|
+
80,
|
|
56
|
+
80
|
|
57
|
+
],
|
|
58
|
+
"precision": "float32",
|
|
59
|
+
"exposure_s": 0.001,
|
|
60
|
+
"photon_rate_per_s": 2000000.0,
|
|
61
|
+
"device": "gpu",
|
|
62
|
+
"frames": 5011,
|
|
63
|
+
"elapsed_s": 2.0001363799965475,
|
|
64
|
+
"frame_s": 0.0003991491478739867,
|
|
65
|
+
"frames_per_s": 2505.329161608795,
|
|
66
|
+
"megapixels_per_s": 16.034106634296286
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"workflow": "shack_hartmann_cmos_160",
|
|
70
|
+
"label": "Shack-Hartmann WFS CMOS",
|
|
71
|
+
"preset": "generic_cmos",
|
|
72
|
+
"sensor": "CMOS",
|
|
73
|
+
"shape": [
|
|
74
|
+
160,
|
|
75
|
+
160
|
|
76
|
+
],
|
|
77
|
+
"precision": "float32",
|
|
78
|
+
"exposure_s": 0.001,
|
|
79
|
+
"photon_rate_per_s": 2000000.0,
|
|
80
|
+
"device": "cpu",
|
|
81
|
+
"frames": 1149,
|
|
82
|
+
"elapsed_s": 2.001656759006437,
|
|
83
|
+
"frame_s": 0.001742085952137891,
|
|
84
|
+
"frames_per_s": 574.0244898782395,
|
|
85
|
+
"megapixels_per_s": 14.69502694088293
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"workflow": "shack_hartmann_cmos_160",
|
|
89
|
+
"label": "Shack-Hartmann WFS CMOS",
|
|
90
|
+
"preset": "generic_cmos",
|
|
91
|
+
"sensor": "CMOS",
|
|
92
|
+
"shape": [
|
|
93
|
+
160,
|
|
94
|
+
160
|
|
95
|
+
],
|
|
96
|
+
"precision": "float32",
|
|
97
|
+
"exposure_s": 0.001,
|
|
98
|
+
"photon_rate_per_s": 2000000.0,
|
|
99
|
+
"device": "gpu",
|
|
100
|
+
"frames": 5026,
|
|
101
|
+
"elapsed_s": 2.000239181012148,
|
|
102
|
+
"frame_s": 0.0003979783487887282,
|
|
103
|
+
"frames_per_s": 2512.699504994586,
|
|
104
|
+
"megapixels_per_s": 64.3251073278614
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"workflow": "ocam2k_emccd_240",
|
|
108
|
+
"label": "OCAM2K EMCCD",
|
|
109
|
+
"preset": "andor_ocam2k",
|
|
110
|
+
"sensor": "EMCCD",
|
|
111
|
+
"shape": [
|
|
112
|
+
240,
|
|
113
|
+
240
|
|
114
|
+
],
|
|
115
|
+
"precision": "float32",
|
|
116
|
+
"exposure_s": 0.001,
|
|
117
|
+
"photon_rate_per_s": 2000000.0,
|
|
118
|
+
"device": "cpu",
|
|
119
|
+
"frames": 288,
|
|
120
|
+
"elapsed_s": 2.003861945006065,
|
|
121
|
+
"frame_s": 0.006957853975715504,
|
|
122
|
+
"frames_per_s": 143.72247585106385,
|
|
123
|
+
"megapixels_per_s": 8.278414609021278
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"workflow": "ocam2k_emccd_240",
|
|
127
|
+
"label": "OCAM2K EMCCD",
|
|
128
|
+
"preset": "andor_ocam2k",
|
|
129
|
+
"sensor": "EMCCD",
|
|
130
|
+
"shape": [
|
|
131
|
+
240,
|
|
132
|
+
240
|
|
133
|
+
],
|
|
134
|
+
"precision": "float32",
|
|
135
|
+
"exposure_s": 0.001,
|
|
136
|
+
"photon_rate_per_s": 2000000.0,
|
|
137
|
+
"device": "gpu",
|
|
138
|
+
"frames": 3420,
|
|
139
|
+
"elapsed_s": 2.0004429029941093,
|
|
140
|
+
"frame_s": 0.000584924825436874,
|
|
141
|
+
"frames_per_s": 1709.621401781179,
|
|
142
|
+
"megapixels_per_s": 98.47419274259589
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"workflow": "saphira_eapd_256x320",
|
|
146
|
+
"label": "SAPHIRA eAPD",
|
|
147
|
+
"preset": "leonardo_saphira",
|
|
148
|
+
"sensor": "EAPD",
|
|
149
|
+
"shape": [
|
|
150
|
+
256,
|
|
151
|
+
320
|
|
152
|
+
],
|
|
153
|
+
"precision": "float32",
|
|
154
|
+
"exposure_s": 0.001,
|
|
155
|
+
"photon_rate_per_s": 2000000.0,
|
|
156
|
+
"device": "cpu",
|
|
157
|
+
"frames": 228,
|
|
158
|
+
"elapsed_s": 2.0011549520131666,
|
|
159
|
+
"frame_s": 0.00877699540356652,
|
|
160
|
+
"frames_per_s": 113.93420572986187,
|
|
161
|
+
"megapixels_per_s": 9.333490133390285
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"workflow": "saphira_eapd_256x320",
|
|
165
|
+
"label": "SAPHIRA eAPD",
|
|
166
|
+
"preset": "leonardo_saphira",
|
|
167
|
+
"sensor": "EAPD",
|
|
168
|
+
"shape": [
|
|
169
|
+
256,
|
|
170
|
+
320
|
|
171
|
+
],
|
|
172
|
+
"precision": "float32",
|
|
173
|
+
"exposure_s": 0.001,
|
|
174
|
+
"photon_rate_per_s": 2000000.0,
|
|
175
|
+
"device": "gpu",
|
|
176
|
+
"frames": 3786,
|
|
177
|
+
"elapsed_s": 2.000311462994432,
|
|
178
|
+
"frame_s": 0.000528344284995888,
|
|
179
|
+
"frames_per_s": 1892.7052461782241,
|
|
180
|
+
"megapixels_per_s": 155.05041376692012
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"workflow": "science_cmos_1024",
|
|
184
|
+
"label": "Large science CMOS",
|
|
185
|
+
"preset": "generic_cmos",
|
|
186
|
+
"sensor": "CMOS",
|
|
187
|
+
"shape": [
|
|
188
|
+
1024,
|
|
189
|
+
1024
|
|
190
|
+
],
|
|
191
|
+
"precision": "float32",
|
|
192
|
+
"exposure_s": 5.0,
|
|
193
|
+
"photon_rate_per_s": 200.0,
|
|
194
|
+
"device": "cpu",
|
|
195
|
+
"frames": 27,
|
|
196
|
+
"elapsed_s": 2.000652825983707,
|
|
197
|
+
"frame_s": 0.07409825281421137,
|
|
198
|
+
"frames_per_s": 13.495594862504088,
|
|
199
|
+
"megapixels_per_s": 14.151156878545088
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"workflow": "science_cmos_1024",
|
|
203
|
+
"label": "Large science CMOS",
|
|
204
|
+
"preset": "generic_cmos",
|
|
205
|
+
"sensor": "CMOS",
|
|
206
|
+
"shape": [
|
|
207
|
+
1024,
|
|
208
|
+
1024
|
|
209
|
+
],
|
|
210
|
+
"precision": "float32",
|
|
211
|
+
"exposure_s": 5.0,
|
|
212
|
+
"photon_rate_per_s": 200.0,
|
|
213
|
+
"device": "gpu",
|
|
214
|
+
"frames": 525,
|
|
215
|
+
"elapsed_s": 2.2030622210004367,
|
|
216
|
+
"frame_s": 0.0041963089923817845,
|
|
217
|
+
"frames_per_s": 238.30466293484497,
|
|
218
|
+
"megapixels_per_s": 249.880550241568
|
|
219
|
+
}
|
|
220
|
+
]
|
|
221
|
+
}
|