Validation¶
getframes promises accurate, auditable physics, so the library is checked not
only for internal consistency but against external references — analytic forms
and published characterisations. This page summarises what is validated and how you
can reproduce the key checks yourself. The assertions live in
tests/test_validation.py
and run as part of the test gate.
What is validated¶
| Claim | Reference | Check |
|---|---|---|
| Vega magnitudes follow Pogson's law | 5 mag = 100× flux | exact |
| AB zero points | flat $f_\nu = 3631$ Jy, $N_0 = \frac{f_{\nu,0}}{h}\int T\,\frac{d\lambda}{\lambda}$ | within 5% |
| Gain stage excess noise factor | $\mathrm{Var}=nG^2(F^2-1)$ for deterministic input | within 2% |
| CTI / IPC / blooming | charge conservation + documented displacement | exact |
| PSF kernels | flux conservation | within 0.2% |
| Synthetic PTC | recovers configured gain / read noise / full well | within 5–15% |
| Reduced frame | recovers Frame.truth to the noise floor |
mean residual < 2 ADU |
| All new (1.6) paths | deterministic for a fixed seed | bit-exact |
Recover the gain from a photon transfer curve¶
A PTC is the standard way to measure a detector's conversion gain. Synthesise one and confirm it returns the numbers you configured:
import numpy as np
import getframes as gf
config = gf.CameraConfig(
name="demo", sensor_type="CMOS", resolution=(96, 96), pixel_size_um=5.0,
quantum_efficiency=1.0, full_well_e=60_000.0, bit_depth=16,
gain_e_per_adu=1.0, bias_offset_adu=100.0, read_noise_e=5.0,
dark_current_e_per_s=0.0,
)
cam = gf.Camera(config, default_temperature_c=-10.0)
ptc = gf.analysis.photon_transfer_curve(cam, np.linspace(200.0, 75_000.0, 16), exposure=1.0)
print(ptc.gain_e_per_adu, ptc.read_noise_e, ptc.full_well_adu) # ~1.0, ~5.0, ~60000
Reproduce the EMCCD excess noise factor¶
The stochastic gain stage is parameterised by the mean gain $G$ and the excess noise factor $F$. For deterministic input charge $n$, the output has mean $nG$ and variance $nG^2(F^2-1)$, so $F$ is recoverable from the moments — and an EMCCD at high gain should return the analytic $F=\sqrt 2$:
import numpy as np
from getframes import noise
rng = np.random.default_rng(0)
out = noise.apply_gain_stage(np.full(400_000, 60.0), gain=250.0,
excess_noise_factor=np.sqrt(2.0), rng=rng)
recovered_F = np.sqrt(1.0 + out.var() / (60.0 * 250.0**2))
print(recovered_F) # ~1.414
Reproducibility¶
Every generation path is seeded through a numpy.random.Generator; a given config
+ inputs + seed reproduce the same frame on the same NumPy version (the float32
fast path, dataset generation, and vectorised catalog rendering included). Bit-for-
bit output is not guaranteed across NumPy releases that change their RNG internals —
see API stability.