getframes 2.0.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.
Files changed (76) hide show
  1. getframes-2.0.0/.gitignore +224 -0
  2. getframes-2.0.0/CHANGELOG.md +253 -0
  3. getframes-2.0.0/LICENSE +21 -0
  4. getframes-2.0.0/PKG-INFO +218 -0
  5. getframes-2.0.0/README.md +174 -0
  6. getframes-2.0.0/benchmarks/run.py +126 -0
  7. getframes-2.0.0/examples/01_basic_dark_frame.py +83 -0
  8. getframes-2.0.0/examples/02_custom_camera.py +99 -0
  9. getframes-2.0.0/examples/03_master_dark.py +89 -0
  10. getframes-2.0.0/examples/04_browse_presets.py +80 -0
  11. getframes-2.0.0/examples/05_visualise.py +75 -0
  12. getframes-2.0.0/examples/06_photon_transfer_curve.py +112 -0
  13. getframes-2.0.0/examples/07_star_field_exposure.py +143 -0
  14. getframes-2.0.0/examples/08_ao_limiting_magnitude.py +151 -0
  15. getframes-2.0.0/examples/09_transit_photometry.py +120 -0
  16. getframes-2.0.0/examples/10_detector_realism.py +108 -0
  17. getframes-2.0.0/examples/11_radiometry_and_ir.py +117 -0
  18. getframes-2.0.0/examples/12_ml_dataset.py +99 -0
  19. getframes-2.0.0/examples/13_crowded_field.py +123 -0
  20. getframes-2.0.0/examples/README.md +33 -0
  21. getframes-2.0.0/examples/_common.py +104 -0
  22. getframes-2.0.0/pyproject.toml +138 -0
  23. getframes-2.0.0/src/getframes/__about__.py +4 -0
  24. getframes-2.0.0/src/getframes/__init__.py +91 -0
  25. getframes-2.0.0/src/getframes/analysis/__init__.py +18 -0
  26. getframes-2.0.0/src/getframes/analysis/apertures.py +92 -0
  27. getframes-2.0.0/src/getframes/analysis/ptc.py +109 -0
  28. getframes-2.0.0/src/getframes/calibrate.py +182 -0
  29. getframes-2.0.0/src/getframes/camera.py +649 -0
  30. getframes-2.0.0/src/getframes/cli.py +214 -0
  31. getframes-2.0.0/src/getframes/config.py +420 -0
  32. getframes-2.0.0/src/getframes/dataset.py +294 -0
  33. getframes-2.0.0/src/getframes/frame.py +107 -0
  34. getframes-2.0.0/src/getframes/noise.py +637 -0
  35. getframes-2.0.0/src/getframes/observation.py +162 -0
  36. getframes-2.0.0/src/getframes/presets/__init__.py +90 -0
  37. getframes-2.0.0/src/getframes/presets/data/__init__.py +3 -0
  38. getframes-2.0.0/src/getframes/presets/data/andor_ikon_m934.toml +22 -0
  39. getframes-2.0.0/src/getframes/presets/data/andor_ixon_ultra_888.toml +22 -0
  40. getframes-2.0.0/src/getframes/presets/data/generic_ccd.toml +18 -0
  41. getframes-2.0.0/src/getframes/presets/data/generic_cmos.toml +18 -0
  42. getframes-2.0.0/src/getframes/presets/data/generic_eapd.toml +20 -0
  43. getframes-2.0.0/src/getframes/presets/data/generic_emccd.toml +20 -0
  44. getframes-2.0.0/src/getframes/presets/data/generic_scmos.toml +21 -0
  45. getframes-2.0.0/src/getframes/presets/data/hamamatsu_orca_fusion.toml +25 -0
  46. getframes-2.0.0/src/getframes/presets/data/leonardo_saphira.toml +32 -0
  47. getframes-2.0.0/src/getframes/presets/data/zwo_asi2600mm.toml +20 -0
  48. getframes-2.0.0/src/getframes/py.typed +0 -0
  49. getframes-2.0.0/src/getframes/scene/__init__.py +51 -0
  50. getframes-2.0.0/src/getframes/scene/optics.py +180 -0
  51. getframes-2.0.0/src/getframes/scene/photometry.py +311 -0
  52. getframes-2.0.0/src/getframes/scene/psf.py +371 -0
  53. getframes-2.0.0/src/getframes/scene/scene.py +205 -0
  54. getframes-2.0.0/src/getframes/scene/sources.py +683 -0
  55. getframes-2.0.0/src/getframes/scene/thermal.py +114 -0
  56. getframes-2.0.0/src/getframes/scene/wcs.py +110 -0
  57. getframes-2.0.0/src/getframes/spectral.py +449 -0
  58. getframes-2.0.0/tests/test_analysis.py +56 -0
  59. getframes-2.0.0/tests/test_calibrate.py +222 -0
  60. getframes-2.0.0/tests/test_camera.py +103 -0
  61. getframes-2.0.0/tests/test_cli.py +93 -0
  62. getframes-2.0.0/tests/test_config.py +101 -0
  63. getframes-2.0.0/tests/test_dataset.py +81 -0
  64. getframes-2.0.0/tests/test_detector.py +208 -0
  65. getframes-2.0.0/tests/test_gain.py +94 -0
  66. getframes-2.0.0/tests/test_noise.py +64 -0
  67. getframes-2.0.0/tests/test_observation.py +241 -0
  68. getframes-2.0.0/tests/test_presets.py +37 -0
  69. getframes-2.0.0/tests/test_radiometry.py +317 -0
  70. getframes-2.0.0/tests/test_realism.py +105 -0
  71. getframes-2.0.0/tests/test_scale.py +147 -0
  72. getframes-2.0.0/tests/test_scene.py +122 -0
  73. getframes-2.0.0/tests/test_scene_enrich.py +237 -0
  74. getframes-2.0.0/tests/test_signal.py +110 -0
  75. getframes-2.0.0/tests/test_spectral.py +246 -0
  76. getframes-2.0.0/tests/test_validation.py +191 -0
@@ -0,0 +1,224 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # macOS
210
+ .DS_Store
211
+
212
+ # Generated frames
213
+ *.fits
214
+
215
+ # PyPI configuration file
216
+ .pypirc
217
+
218
+ # Marimo
219
+ marimo/_static/
220
+ marimo/_lsp/
221
+ __marimo__/
222
+
223
+ # Streamlit
224
+ .streamlit/secrets.toml
@@ -0,0 +1,253 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
5
+ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [2.0.0] - 2026-06-29
10
+
11
+ The 2.0 cut promotes the full surface grown across 1.1–1.6 to **stable** and lands
12
+ the one planned dependency change. There are **no breaking API removals**: code
13
+ written against 1.x continues to work.
14
+
15
+ ### Changed
16
+
17
+ - **`astropy` is now a core dependency** (roadmap decision #2). It powers FITS I/O,
18
+ WCS pixel↔world projection, and catalogs; it is still imported lazily inside the
19
+ functions that use it so `import getframes` stays fast. `matplotlib` remains the
20
+ only `examples` extra.
21
+ - **API stability**: the detector, scene, calibration, observation, radiometry, and
22
+ dataset APIs are now frozen under SemVer for the 2.x series (see
23
+ `docs/stability.md`).
24
+
25
+ ### Added
26
+
27
+ - A **validation suite** (`tests/test_validation.py`) and a
28
+ [validation guide](docs/guides/validation.md) asserting the physics against
29
+ analytic / published references: AB & Vega zero points, the gain-stage excess
30
+ noise factor, CTI/IPC/blooming charge conservation, PSF flux conservation, PTC
31
+ parameter recovery, and reduced-frame truth recovery.
32
+ - Three worked **examples** for the newer features: `11_radiometry_and_ir.py`,
33
+ `12_ml_dataset.py`, and `13_crowded_field.py`.
34
+
35
+ ### Added (1.x features, first released in 2.0)
36
+
37
+ - **Scale & datasets** (roadmap phase 1.6): generate large detectors and bulk
38
+ raw+truth training data, all additive.
39
+ - **float32 fast path**: `Camera(..., precision="float32")` runs the whole signal
40
+ chain (and each frame's ground truth) in single precision, halving the per-pixel
41
+ memory for large detectors and bulk generation. The digitised ADU stay integer;
42
+ `noise.simulate_frame` and `Scene.photon_rate_map` gain a `float_dtype`/`dtype`
43
+ argument (`float64` exact default).
44
+ - **Vectorised multi-source rendering**: `GaussianPSF.add_sources` deposits a whole
45
+ catalog in one batched, memory-chunked NumPy expression (exact match to the
46
+ per-source path), so a 10⁵-star `Catalog` no longer loops in Python. The base
47
+ `PSF.add_sources` falls back to a loop for other PSFs.
48
+ - **Dataset generator** (`getframes.dataset`): `pairs(camera=, scenes=, exposure=)`
49
+ streams `{"raw": ADU, "truth": e-}` pairs reproducibly to disk via
50
+ `PairDataset.to_npz` (or `to_arrays`), and `random_star_fields(n, shape, ...)` is
51
+ a re-iterable source of random star-field scenes to feed it.
52
+ - **`getframes` CLI**: a console entry point with `presets`, `generate config.toml
53
+ -o frame.fits`, and `dataset config.toml -o train/` subcommands, so an experiment
54
+ is a shareable TOML file (`getframes.cli`).
55
+ - **Benchmarks**: `benchmarks/run.py`, a dependency-light throughput harness for the
56
+ signal chain, catalog rendering, and dataset generation (not part of the gate).
57
+ - **Radiometry & the infrared** (roadmap phase 1.5): quantitative photometry and
58
+ honest IR backgrounds, all additive.
59
+ - **AB system**: `Bandpass.ab(band)` alongside the Vega-system `Bandpass.johnson`,
60
+ with the zero point computed from the band's transmission shape (3631 Jy
61
+ reference). Ships **SDSS ugriz**, **Gaia** (`gaia_g`/`gaia_bp`/`gaia_rp`), and
62
+ **2MASS** (`J`/`H`/`Ks`) bands as tophat responses.
63
+ - **Real transmission products**: `SpectralBandpass.from_file` /
64
+ `Spectrum.from_file` / `QE.from_file` load measured curves; `product(...)` and
65
+ `SpectralBandpass.from_product(...)` fold filter x QE x atmosphere into one
66
+ response.
67
+ - **Extinction**: `Extinction(a_v, r_v)` applies a Cardelli–Clayton–Mathis (1989)
68
+ interstellar extinction curve — `transmission`, `redden(sed)`,
69
+ `band_attenuation_mag(band)`.
70
+ - **Spectral flux integration**: `SED.from_flux_density(...)` builds an *absolute*
71
+ SED (photons/s/m²/nm). Sources gain a `flux_sed` brightness option (alongside
72
+ `magnitude`/`photon_rate`) whose integral over the band sets the rate, via
73
+ `Telescope.photon_rate_from_sed` / `Bandpass.photon_flux_from_sed`.
74
+ - **Thermal background & glow**: `Thermal(temperature_k, emissivity)` is a graybody
75
+ background (the IR analogue of `Sky`) attached to a `Scene`;
76
+ `CameraConfig.detector_glow_e_per_s` adds exposure-scaled, dark-removable
77
+ detector self-emission.
78
+ - **astropy.units interop**: the spectral constructors accept `astropy.units`
79
+ quantities for wavelength/flux (optional; plain arrays assumed to be nm).
80
+ - **Detector depth** (roadmap phase 1.4): the artifacts a real calibration
81
+ pipeline must survive, all off by default and additive on `CameraConfig`.
82
+ - **CTI**: `cti` smears charge by a CCD's charge-transfer inefficiency, deferring
83
+ a `cti * n_transfers` fraction into a trailing tail away from the readout
84
+ register (`noise.apply_cti`).
85
+ - **Blooming**: `blooming=True` bleeds charge above `full_well_e` symmetrically
86
+ along the column, charge-conserving (`noise.apply_blooming`).
87
+ - **IPC**: `ipc_coupling` applies a charge-conserving 3x3 inter-pixel-capacitance
88
+ kernel (`noise.apply_ipc`).
89
+ - **kTC/reset noise**: `reset_noise_e` adds a per-pixel, per-frame Gaussian charge
90
+ uncertainty alongside read noise.
91
+ - **Multi-amplifier readout**: `amplifier_layout=(n_rows, n_cols)` tiles the
92
+ sensor into amplifier blocks, each with its own fixed gain
93
+ (`amp_gain_nonuniformity`) and offset (`amp_offset_spread_adu`) error —
94
+ producing quadrant seams.
95
+ - **Cosmic-ray tracks**: `cosmic_ray_track_length_px` upgrades cosmic rays from
96
+ single pixels to extended tracks (exponential length, random direction).
97
+ - **Defects & structured bias**: `bad_column_fraction` / `dead_pixel_fraction`
98
+ impose a fixed map of dead columns/pixels that collect no charge;
99
+ `bias_structure_amplitude_adu` adds a fixed gradient-plus-column bias pattern on
100
+ top of the flat pedestal.
101
+ - **Polynomial nonlinearity**: `nonlinearity_coeffs=(c1, c2, ...)` generalises the
102
+ single-parameter `nonlinearity` to an arbitrary measured response curve.
103
+ - **Richer scenes** (roadmap phase 1.3): build crowded, structured fields beyond
104
+ point sources on a flat sky.
105
+ - `ExtendedSource` for resolved sources: `ExtendedSource.sersic(...)` (a Sersic
106
+ surface-brightness profile, optionally elliptical with a position angle) and
107
+ `ExtendedSource.from_array(image, ...)` (an arbitrary normalised cutout).
108
+ - `UniformIllumination`: a spatially flat, PSF-free illumination — a clean flat
109
+ field for photon-transfer-curve work.
110
+ - `Catalog.from_table(table, ...)` places many sources at once from any
111
+ column-indexable table (astropy `Table`, pandas, or a dict). Entries may be
112
+ given by pixel `(x, y)` or sky `(ra, dec)`; with a scene `WCSInfo`, RA/Dec is
113
+ projected to pixels (the WCS now *does* something, not just tags).
114
+ - New PSFs: `AiryPSF` (diffraction-limited, with optional central obstruction),
115
+ `ArrayPSF` (a user kernel, e.g. from an AO simulation, with sub-pixel shifting),
116
+ and `EllipticalGaussianPSF` (independent major/minor widths and a position
117
+ angle).
118
+ - `Telescope` gains optional `vignetting` (`Vignetting` illumination falloff) and
119
+ `distortion` (`RadialDistortion` barrel/pincushion) models.
120
+ - `Scene.add(*sources)` appends sources; `Scene.sources` now accepts any
121
+ `Source` (point, extended, catalog, or uniform).
122
+ - **Time as a first-class dimension** (roadmap phase 1.2): observe a scene over
123
+ time and validate the result against a ground-truth light curve.
124
+ - `getframes.Observation` (and `ObservationTruth`): the iterable stack returned
125
+ by `Camera.observe_series`, carrying the frames, per-frame timestamps, realised
126
+ pointing offsets, and the per-source truth `light_curve`.
127
+ - `LightCurve` (owned by the source): `PointSource` gains optional `brightness`
128
+ (a `LightCurve`) and `name` fields. `LightCurve.box` / `sinusoidal` /
129
+ `constant` / `from_function` make a source vary in time; `observe_series`
130
+ samples it at each frame's timestamp.
131
+ - `Pointing`: per-frame field offsets from jitter (Gaussian, also models
132
+ atmospheric tip-tilt / image motion), slow linear drift, and a programmed
133
+ dither pattern. `Camera.observe_series(..., jitter_arcsec=...)` is a shortcut.
134
+ - **Persistence / latent images** (the deferred 1.0 item): new
135
+ `CameraConfig.persistence_fraction` and `persistence_decay` carry trapped
136
+ charge across the frames of an `Observation` (IR arrays). `Camera.expose` and
137
+ `noise.simulate_frame` gain an `extra_electrons` argument that injects this
138
+ latent charge before shot noise.
139
+ - `Scene.photon_rate_map` / `photoelectron_rate_map` gain optional `time_s` and
140
+ `offset_xy` arguments (backwards-compatible no-ops by default).
141
+ - **Calibration loop** (`getframes.calibrate` module, roadmap phase 1.1): combine
142
+ frames into masters and reduce raw frames against them.
143
+ - `combine(frames, method=...)` stacks frames into a master (`"mean"`,
144
+ `"median"`, or `"sigma_clip"`), reducing random noise by ~`sqrt(n)`.
145
+ - `calibrate(raw, *, bias, dark, flat, dark_scale)` performs standard
146
+ exposure-matched reduction `(raw - dark) / normalised(flat)`, so a reduced
147
+ frame can be compared directly against `Frame.truth`.
148
+ - `Camera.master_bias`, `Camera.master_dark`, and `Camera.master_flat`
149
+ (with optional `bias=` subtraction) build calibration masters from a series.
150
+ - **Series symmetry**: `Camera.expose_series` and `Camera.observe_series` mirror
151
+ `dark_series` (independent-but-reproducible derived seeds; per-frame metadata).
152
+ - `CameraConfig.fixed_pattern_seed`: seeds the sensor's fixed-pattern noise.
153
+
154
+ ### Changed
155
+
156
+ - `Camera.observe_series` now returns an `Observation` rather than a lazy
157
+ iterator. The `Observation` is iterable and indexable over its frames, so
158
+ `for frame in cam.observe_series(...)` and `list(cam.observe_series(...))` keep
159
+ working; the new `cadence`, `pointing`, and `jitter_arcsec` keyword arguments
160
+ are additive.
161
+ - **Fixed-pattern noise is now genuinely fixed.** PRNU, DSNU, and the hot-pixel
162
+ map are drawn from a deterministic per-sensor stream (keyed on
163
+ `CameraConfig.fixed_pattern_seed`) instead of the per-frame RNG, so the pattern
164
+ repeats across every frame a camera produces — which is what lets a master flat
165
+ or dark actually remove it. This changes the exact per-pixel output (for a given
166
+ `seed`) of any config with `prnu`, `dark_current_nonuniformity`, or hot pixels;
167
+ statistical behaviour is unchanged. `noise.dark_signal_map` and
168
+ `noise.photo_signal_map` no longer take an `rng` argument.
169
+
170
+ ## [1.0.0] - 2026-06-28
171
+
172
+ First stable release. The public API is now frozen under
173
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html): the names exported
174
+ from `import getframes` (and `getframes.analysis`) will not change incompatibly
175
+ without a major-version bump. See [API stability](docs/stability.md).
176
+
177
+ This release consolidates the work from the 0.2–0.6 development series — the
178
+ photon/signal path, the unified gain stage, the scene/optics layer, analysis
179
+ helpers, detector-realism effects, and opt-in spectral mode — into a supported,
180
+ documented surface. Everything below was developed across those phases and ships
181
+ together in 1.0.
182
+
183
+ ### Added
184
+
185
+ - **Spectral mode** (opt-in, additive): a new `getframes.spectral` module with
186
+ `Spectrum`, `SED` (flat/blackbody/power-law shapes), `QE` (wavelength-resolved
187
+ quantum efficiency), and `SpectralBandpass` (tophat / Johnson responses).
188
+ `Bandpass.johnson` now ships a spectral `response` by default and gains
189
+ `Bandpass.effective_qe`. Setting `CameraConfig.qe_curve` makes `Camera.observe`
190
+ switch to a colour-dependent **effective QE**, folding each source's SED with the
191
+ band response and the detector QE curve; the magnitude-to-photon-rate conversion
192
+ is unchanged (the SED shape only affects the photon-to-electron step). Presets
193
+ may carry a `[qe_curve]` table (added to `leonardo_saphira`).
194
+ - **WCS tagging** via `WCSInfo` (TAN projection): emits FITS WCS header cards with
195
+ no third-party dependency (written into the observed `Frame` metadata and FITS
196
+ output) and offers astropy-backed `pixel_to_world` / `world_to_pixel`. A `Scene`
197
+ may carry an optional `wcs`.
198
+ - `PointSource.sed` and `Sky.sed` optional spectral energy distributions; an
199
+ optional `quantum_efficiency` override on `Camera.expose` / `noise.simulate_frame`.
200
+ - **Detector-realism effects**, all off by default: nonlinearity (`nonlinearity`),
201
+ cosmic rays (`cosmic_ray_rate_per_cm2_s`), and per-pixel sCMOS read noise
202
+ (`read_noise_nonuniformity`). New `SensorType.SCMOS` and presets
203
+ `hamamatsu_orca_fusion` and `generic_scmos`.
204
+ - **Analysis helpers** (`getframes.analysis`): `aperture_sum`, `centroid`, and a
205
+ `photon_transfer_curve` that fits gain and read noise. Pure NumPy; used by the
206
+ examples and handy for quick checks.
207
+ - **Scene/optics layer** (`getframes.scene`) and `Camera.observe(scene, ...)`:
208
+ render astronomical sources through a PSF and telescope into an incident
209
+ photon-rate map, then expose it. New public types: `Scene`, `Telescope`,
210
+ `Bandpass` (Johnson UBVRI zero points), `PointSource`, `Sky`, and the PSFs
211
+ `GaussianPSF` (exact, flux-conserving) and `MoffatPSF`.
212
+ - **Unified stochastic gain stage** (`noise.apply_gain_stage`): one model for both
213
+ EMCCD electron multiplication and eAPD avalanche gain, parameterised by mean gain
214
+ and excess noise factor `F` via a Gamma model (`alpha = 1/(F^2-1)`). Reproduces
215
+ the requested `F` exactly; recovers the previous EMCCD model at `F = sqrt(2)`.
216
+ - `SensorType.EAPD` and `CameraConfig.excess_noise_factor` /
217
+ `gain_excess_noise_factor` / `has_gain_stage`.
218
+ - eAPD presets: `leonardo_saphira` (SAPHIRA IR array) and `generic_eapd`.
219
+ - **Photon/signal path** (`Camera.expose`): generate frames from an incident
220
+ photon rate (scalar or per-pixel map), with quantum-efficiency conversion,
221
+ photo-response non-uniformity (PRNU), shot noise, dark current, optional EM
222
+ gain, read noise, and digitisation.
223
+ - `Camera.flat_frame` and `Camera.bias_frame` convenience wrappers.
224
+ - `FrameTruth`: noise-free ground truth (mean electron maps) attached to frames
225
+ for pipeline validation; available on `Frame.truth`.
226
+ - `noise.simulate_frame` / `noise.photo_signal_map` / `noise.frame_electrons`
227
+ building blocks. `noise.generate_dark_frame` is now the `photon_rate = 0` case.
228
+ - `CameraConfig.prnu` field.
229
+
230
+ ### Changed
231
+
232
+ - `scipy` is now a core dependency (groundwork for the scene/optics layer).
233
+
234
+ ## [0.1.0] - 2026-06-27
235
+
236
+ ### Added
237
+
238
+ - Initial release.
239
+ - `Camera`, `CameraConfig`, `Frame`, and `SensorType` public API.
240
+ - Dark-frame generation for CCD, CMOS, and EMCCD sensors with read noise, dark
241
+ current (temperature-scaled), shot noise, fixed-pattern non-uniformity (DSNU),
242
+ hot pixels, EM gain, and clock-induced charge.
243
+ - `Camera.dark_series` for generating reproducible frame stacks.
244
+ - Preset library with `load_preset`, `available_presets`, and `preset_info`,
245
+ including Andor iKon-M 934, Andor iXon Ultra 888, ZWO ASI2600MM, and generic
246
+ CCD/CMOS/EMCCD references.
247
+ - Optional FITS export via `Frame.to_fits`.
248
+ - Documentation, runnable examples, and CI (lint, type-check, test matrix, PyPI
249
+ release via Trusted Publishing).
250
+
251
+ [Unreleased]: https://github.com/jacotay7/getframes/compare/v1.0.0...HEAD
252
+ [1.0.0]: https://github.com/jacotay7/getframes/compare/v0.1.0...v1.0.0
253
+ [0.1.0]: https://github.com/jacotay7/getframes/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jacob Taylor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.