getframes 2.0.0__py3-none-any.whl
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/__about__.py +4 -0
- getframes/__init__.py +91 -0
- getframes/analysis/__init__.py +18 -0
- getframes/analysis/apertures.py +92 -0
- getframes/analysis/ptc.py +109 -0
- getframes/calibrate.py +182 -0
- getframes/camera.py +649 -0
- getframes/cli.py +214 -0
- getframes/config.py +420 -0
- getframes/dataset.py +294 -0
- getframes/frame.py +107 -0
- getframes/noise.py +637 -0
- getframes/observation.py +162 -0
- getframes/presets/__init__.py +90 -0
- getframes/presets/data/__init__.py +3 -0
- getframes/presets/data/andor_ikon_m934.toml +22 -0
- getframes/presets/data/andor_ixon_ultra_888.toml +22 -0
- getframes/presets/data/generic_ccd.toml +18 -0
- getframes/presets/data/generic_cmos.toml +18 -0
- getframes/presets/data/generic_eapd.toml +20 -0
- getframes/presets/data/generic_emccd.toml +20 -0
- getframes/presets/data/generic_scmos.toml +21 -0
- getframes/presets/data/hamamatsu_orca_fusion.toml +25 -0
- getframes/presets/data/leonardo_saphira.toml +32 -0
- getframes/presets/data/zwo_asi2600mm.toml +20 -0
- getframes/py.typed +0 -0
- getframes/scene/__init__.py +51 -0
- getframes/scene/optics.py +180 -0
- getframes/scene/photometry.py +311 -0
- getframes/scene/psf.py +371 -0
- getframes/scene/scene.py +205 -0
- getframes/scene/sources.py +683 -0
- getframes/scene/thermal.py +114 -0
- getframes/scene/wcs.py +110 -0
- getframes/spectral.py +449 -0
- getframes-2.0.0.dist-info/METADATA +218 -0
- getframes-2.0.0.dist-info/RECORD +40 -0
- getframes-2.0.0.dist-info/WHEEL +4 -0
- getframes-2.0.0.dist-info/entry_points.txt +2 -0
- getframes-2.0.0.dist-info/licenses/LICENSE +21 -0
getframes/observation.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Time as a first-class dimension: pointing models and the :class:`Observation`.
|
|
3
|
+
|
|
4
|
+
A single :class:`~getframes.frame.Frame` is a snapshot. An :class:`Observation` is
|
|
5
|
+
a *sequence* of frames of one scene over time, produced by
|
|
6
|
+
:meth:`getframes.Camera.observe_series`. It bundles:
|
|
7
|
+
|
|
8
|
+
* the realised :class:`~getframes.frame.Frame` stack,
|
|
9
|
+
* the per-frame timestamps,
|
|
10
|
+
* the realised per-frame pointing offsets (from a :class:`Pointing` model), and
|
|
11
|
+
* an :class:`ObservationTruth` light curve --- the injected, noise-free signal of
|
|
12
|
+
each named source at each frame, for validating photometry against ground truth.
|
|
13
|
+
|
|
14
|
+
Time variability itself lives on the sources (a
|
|
15
|
+
:class:`~getframes.scene.sources.LightCurve` on a
|
|
16
|
+
:class:`~getframes.scene.sources.PointSource`); the observation only samples them.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Iterator, Sequence
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from numpy.typing import NDArray
|
|
29
|
+
|
|
30
|
+
from .frame import Frame
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class Pointing:
|
|
35
|
+
"""A per-frame pointing model: jitter, slow drift, and a programmed dither.
|
|
36
|
+
|
|
37
|
+
The three components combine additively into a whole-field offset applied to
|
|
38
|
+
every source in the scene at each frame. Offsets are specified in arcseconds
|
|
39
|
+
(converted to pixels with the scene's plate scale) so the model is independent
|
|
40
|
+
of the detector sampling.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
jitter_arcsec:
|
|
45
|
+
RMS of a per-frame Gaussian offset drawn independently for each axis and
|
|
46
|
+
each frame. Models random tracking jitter and atmospheric tip-tilt / image
|
|
47
|
+
motion (e.g. for AO sub-apertures). ``0`` disables it.
|
|
48
|
+
drift_arcsec_per_s:
|
|
49
|
+
A constant ``(vx, vy)`` velocity giving a slow linear drift; the offset at
|
|
50
|
+
time ``t`` is ``(vx * t, vy * t)``. Models tracking error / field rotation
|
|
51
|
+
creep.
|
|
52
|
+
dither_arcsec:
|
|
53
|
+
An optional sequence of programmed ``(dx, dy)`` offsets, cycled by frame
|
|
54
|
+
index (frame ``i`` uses entry ``i % len``). Models a deliberate dither
|
|
55
|
+
pattern. ``None`` for no dither.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
jitter_arcsec: float = 0.0
|
|
59
|
+
drift_arcsec_per_s: tuple[float, float] = (0.0, 0.0)
|
|
60
|
+
dither_arcsec: Sequence[tuple[float, float]] | None = None
|
|
61
|
+
|
|
62
|
+
def __post_init__(self) -> None:
|
|
63
|
+
if self.jitter_arcsec < 0:
|
|
64
|
+
raise ValueError("jitter_arcsec must be non-negative.")
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def is_static(self) -> bool:
|
|
68
|
+
"""Whether this model never moves the field (a no-op pointing)."""
|
|
69
|
+
return (
|
|
70
|
+
self.jitter_arcsec == 0.0
|
|
71
|
+
and self.drift_arcsec_per_s == (0.0, 0.0)
|
|
72
|
+
and not self.dither_arcsec
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def offset_pixels(
|
|
76
|
+
self,
|
|
77
|
+
frame_index: int,
|
|
78
|
+
time_s: float,
|
|
79
|
+
plate_scale_arcsec_per_pixel: float,
|
|
80
|
+
rng: np.random.Generator,
|
|
81
|
+
) -> tuple[float, float]:
|
|
82
|
+
"""The realised ``(dx, dy)`` offset in pixels for one frame.
|
|
83
|
+
|
|
84
|
+
Combines drift (deterministic in ``time_s``), the cycled dither entry, and a
|
|
85
|
+
fresh Gaussian jitter draw, then converts arcseconds to pixels.
|
|
86
|
+
"""
|
|
87
|
+
dx_as = self.drift_arcsec_per_s[0] * time_s
|
|
88
|
+
dy_as = self.drift_arcsec_per_s[1] * time_s
|
|
89
|
+
if self.dither_arcsec:
|
|
90
|
+
ddx, ddy = self.dither_arcsec[frame_index % len(self.dither_arcsec)]
|
|
91
|
+
dx_as += ddx
|
|
92
|
+
dy_as += ddy
|
|
93
|
+
if self.jitter_arcsec > 0:
|
|
94
|
+
dx_as += float(rng.normal(0.0, self.jitter_arcsec))
|
|
95
|
+
dy_as += float(rng.normal(0.0, self.jitter_arcsec))
|
|
96
|
+
return dx_as / plate_scale_arcsec_per_pixel, dy_as / plate_scale_arcsec_per_pixel
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass(frozen=True)
|
|
100
|
+
class ObservationTruth:
|
|
101
|
+
"""The noise-free ground truth of an :class:`Observation`.
|
|
102
|
+
|
|
103
|
+
Attributes
|
|
104
|
+
----------
|
|
105
|
+
times_s:
|
|
106
|
+
The frame timestamps, in seconds from the start of the observation,
|
|
107
|
+
shape ``(n_frames,)``.
|
|
108
|
+
light_curve:
|
|
109
|
+
Per-source injected signal: a mapping from source name to an array of the
|
|
110
|
+
noise-free incident photons collected from that source in each frame
|
|
111
|
+
(photon rate x exposure, post-optics, pre-quantum-efficiency), shape
|
|
112
|
+
``(n_frames,)``. This is the true light curve to validate measured
|
|
113
|
+
photometry against. Unnamed sources are keyed ``"source_{index}"``.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
times_s: NDArray[np.float64]
|
|
117
|
+
light_curve: dict[str, NDArray[np.float64]]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dataclass(frozen=True)
|
|
121
|
+
class Observation:
|
|
122
|
+
"""A reproducible stack of frames of one scene over time.
|
|
123
|
+
|
|
124
|
+
Returned by :meth:`getframes.Camera.observe_series`. It is iterable and
|
|
125
|
+
indexable over its :attr:`frames`, so existing ``for frame in obs:`` style code
|
|
126
|
+
keeps working, while :attr:`truth`, :attr:`times_s`, and :attr:`offsets_pixels`
|
|
127
|
+
expose the time and pointing information.
|
|
128
|
+
|
|
129
|
+
Attributes
|
|
130
|
+
----------
|
|
131
|
+
frames:
|
|
132
|
+
The realised science :class:`~getframes.frame.Frame` stack, in time order.
|
|
133
|
+
times_s:
|
|
134
|
+
Frame timestamps in seconds, shape ``(n_frames,)``.
|
|
135
|
+
offsets_pixels:
|
|
136
|
+
The realised pointing offset ``(dx, dy)`` applied to each frame, in pixels,
|
|
137
|
+
shape ``(n_frames, 2)``.
|
|
138
|
+
truth:
|
|
139
|
+
The :class:`ObservationTruth` light curve, or ``None`` when truth was not
|
|
140
|
+
requested.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
frames: list[Frame]
|
|
144
|
+
times_s: NDArray[np.float64]
|
|
145
|
+
offsets_pixels: NDArray[np.float64]
|
|
146
|
+
truth: ObservationTruth | None = field(default=None)
|
|
147
|
+
|
|
148
|
+
def __iter__(self) -> Iterator[Frame]:
|
|
149
|
+
return iter(self.frames)
|
|
150
|
+
|
|
151
|
+
def __len__(self) -> int:
|
|
152
|
+
return len(self.frames)
|
|
153
|
+
|
|
154
|
+
def __getitem__(self, index: int) -> Frame:
|
|
155
|
+
return self.frames[index]
|
|
156
|
+
|
|
157
|
+
def __repr__(self) -> str:
|
|
158
|
+
cam = self.frames[0].metadata.get("camera", "?") if self.frames else "?"
|
|
159
|
+
return f"Observation(n_frames={len(self.frames)}, camera={cam!r})"
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
__all__ = ["Observation", "ObservationTruth", "Pointing"]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Built-in library of camera/detector presets.
|
|
3
|
+
|
|
4
|
+
Presets are stored as TOML files in :mod:`getframes.presets.data`. They are loaded
|
|
5
|
+
lazily and cached. Add a new camera by dropping a ``<name>.toml`` file into that
|
|
6
|
+
directory (see the existing files for the schema) — no code changes required.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from functools import cache, lru_cache
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
if sys.version_info >= (3, 11):
|
|
16
|
+
import tomllib
|
|
17
|
+
else: # pragma: no cover - exercised only on 3.10
|
|
18
|
+
import tomli as tomllib
|
|
19
|
+
|
|
20
|
+
from importlib import resources
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from ..config import CameraConfig
|
|
24
|
+
|
|
25
|
+
_DATA_PACKAGE = "getframes.presets.data"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@lru_cache(maxsize=1)
|
|
29
|
+
def _preset_files() -> dict[str, str]:
|
|
30
|
+
"""Map preset slug -> resource filename for every bundled ``*.toml``."""
|
|
31
|
+
files: dict[str, str] = {}
|
|
32
|
+
for entry in resources.files(_DATA_PACKAGE).iterdir():
|
|
33
|
+
if entry.name.endswith(".toml"):
|
|
34
|
+
files[entry.name[: -len(".toml")]] = entry.name
|
|
35
|
+
return dict(sorted(files.items()))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def available_presets() -> list[str]:
|
|
39
|
+
"""Return the sorted list of available preset names.
|
|
40
|
+
|
|
41
|
+
>>> from getframes import available_presets
|
|
42
|
+
>>> "andor_ikon_m934" in available_presets()
|
|
43
|
+
True
|
|
44
|
+
"""
|
|
45
|
+
return list(_preset_files())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def preset_info() -> list[dict[str, Any]]:
|
|
49
|
+
"""Return lightweight descriptors (name, manufacturer, model, sensor_type) for each preset."""
|
|
50
|
+
info: list[dict[str, Any]] = []
|
|
51
|
+
for slug in available_presets():
|
|
52
|
+
data = _read_preset(slug)
|
|
53
|
+
info.append(
|
|
54
|
+
{
|
|
55
|
+
"preset": slug,
|
|
56
|
+
"name": data.get("name", slug),
|
|
57
|
+
"manufacturer": data.get("manufacturer"),
|
|
58
|
+
"model": data.get("model"),
|
|
59
|
+
"sensor_type": data.get("sensor_type"),
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
return info
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@cache
|
|
66
|
+
def _read_preset(name: str) -> dict[str, Any]:
|
|
67
|
+
files = _preset_files()
|
|
68
|
+
if name not in files:
|
|
69
|
+
available = ", ".join(available_presets()) or "(none found)"
|
|
70
|
+
raise KeyError(f"Unknown preset {name!r}. Available presets: {available}.")
|
|
71
|
+
raw = resources.files(_DATA_PACKAGE).joinpath(files[name]).read_bytes()
|
|
72
|
+
return tomllib.loads(raw.decode("utf-8"))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_preset(name: str) -> CameraConfig:
|
|
76
|
+
"""Load a preset by name and return a :class:`~getframes.config.CameraConfig`.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
name:
|
|
81
|
+
A preset slug, e.g. ``"andor_ikon_m934"``. See :func:`available_presets`.
|
|
82
|
+
"""
|
|
83
|
+
from ..config import CameraConfig
|
|
84
|
+
|
|
85
|
+
data = dict(_read_preset(name))
|
|
86
|
+
data.setdefault("name", name)
|
|
87
|
+
return CameraConfig.from_dict(data)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
__all__ = ["available_presets", "load_preset", "preset_info"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Andor iKon-M 934 — deep-cooled scientific CCD
|
|
2
|
+
# Values are representative of published specifications; verify against your
|
|
3
|
+
# own characterisation for quantitative work.
|
|
4
|
+
name = "Andor iKon-M 934"
|
|
5
|
+
manufacturer = "Andor"
|
|
6
|
+
model = "iKon-M 934"
|
|
7
|
+
sensor_type = "CCD"
|
|
8
|
+
resolution = [1024, 1024] # (height, width) in pixels
|
|
9
|
+
pixel_size_um = 13.0
|
|
10
|
+
quantum_efficiency = 0.95 # peak QE (BV coating)
|
|
11
|
+
full_well_e = 130000.0
|
|
12
|
+
bit_depth = 16
|
|
13
|
+
gain_e_per_adu = 2.0
|
|
14
|
+
bias_offset_adu = 500.0
|
|
15
|
+
read_noise_e = 2.9 # at slow readout
|
|
16
|
+
dark_current_e_per_s = 0.00012 # at the reference temperature below
|
|
17
|
+
dark_current_ref_temp_c = -80.0
|
|
18
|
+
dark_current_doubling_temp_c = 6.3
|
|
19
|
+
dark_current_nonuniformity = 0.03
|
|
20
|
+
hot_pixel_fraction = 0.0002
|
|
21
|
+
hot_pixel_factor = 80.0
|
|
22
|
+
notes = "Deep-cooled (-80 C) back-illuminated CCD for low-light spectroscopy/imaging."
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Andor iXon Ultra 888 — back-illuminated EMCCD
|
|
2
|
+
name = "Andor iXon Ultra 888"
|
|
3
|
+
manufacturer = "Andor"
|
|
4
|
+
model = "iXon Ultra 888"
|
|
5
|
+
sensor_type = "EMCCD"
|
|
6
|
+
resolution = [1024, 1024]
|
|
7
|
+
pixel_size_um = 13.0
|
|
8
|
+
quantum_efficiency = 0.95
|
|
9
|
+
full_well_e = 80000.0
|
|
10
|
+
bit_depth = 16
|
|
11
|
+
gain_e_per_adu = 4.0 # system gain at the ADC
|
|
12
|
+
bias_offset_adu = 500.0
|
|
13
|
+
read_noise_e = 45.0 # large at the output amplifier; EM gain overcomes it
|
|
14
|
+
dark_current_e_per_s = 0.00025
|
|
15
|
+
dark_current_ref_temp_c = -80.0
|
|
16
|
+
dark_current_doubling_temp_c = 6.3
|
|
17
|
+
em_gain = 300.0
|
|
18
|
+
clock_induced_charge_e = 0.005 # spurious charge per pixel per frame
|
|
19
|
+
dark_current_nonuniformity = 0.04
|
|
20
|
+
hot_pixel_fraction = 0.0003
|
|
21
|
+
hot_pixel_factor = 60.0
|
|
22
|
+
notes = "EMCCD for single-photon-sensitive imaging; effective read noise <1 e- at high EM gain."
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# A plain, well-behaved CCD for testing and teaching.
|
|
2
|
+
name = "Generic CCD"
|
|
3
|
+
sensor_type = "CCD"
|
|
4
|
+
resolution = [512, 512]
|
|
5
|
+
pixel_size_um = 15.0
|
|
6
|
+
quantum_efficiency = 0.85
|
|
7
|
+
full_well_e = 100000.0
|
|
8
|
+
bit_depth = 16
|
|
9
|
+
gain_e_per_adu = 1.5
|
|
10
|
+
bias_offset_adu = 400.0
|
|
11
|
+
read_noise_e = 5.0
|
|
12
|
+
dark_current_e_per_s = 0.1
|
|
13
|
+
dark_current_ref_temp_c = 20.0
|
|
14
|
+
dark_current_doubling_temp_c = 6.3
|
|
15
|
+
dark_current_nonuniformity = 0.05
|
|
16
|
+
hot_pixel_fraction = 0.001
|
|
17
|
+
hot_pixel_factor = 100.0
|
|
18
|
+
notes = "Idealised CCD with typical parameters; good default for examples."
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# A plain, well-behaved CMOS for testing and teaching.
|
|
2
|
+
name = "Generic CMOS"
|
|
3
|
+
sensor_type = "CMOS"
|
|
4
|
+
resolution = [1080, 1920]
|
|
5
|
+
pixel_size_um = 5.0
|
|
6
|
+
quantum_efficiency = 0.80
|
|
7
|
+
full_well_e = 15000.0
|
|
8
|
+
bit_depth = 12
|
|
9
|
+
gain_e_per_adu = 1.0
|
|
10
|
+
bias_offset_adu = 200.0
|
|
11
|
+
read_noise_e = 2.0
|
|
12
|
+
dark_current_e_per_s = 2.0
|
|
13
|
+
dark_current_ref_temp_c = 20.0
|
|
14
|
+
dark_current_doubling_temp_c = 6.0
|
|
15
|
+
dark_current_nonuniformity = 0.04
|
|
16
|
+
hot_pixel_fraction = 0.002
|
|
17
|
+
hot_pixel_factor = 150.0
|
|
18
|
+
notes = "Idealised uncooled CMOS; higher dark current and lower full well than the CCD."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# A plain electron-avalanche photodiode (eAPD) array for testing and teaching.
|
|
2
|
+
name = "Generic eAPD"
|
|
3
|
+
sensor_type = "EAPD"
|
|
4
|
+
resolution = [256, 256]
|
|
5
|
+
pixel_size_um = 18.0
|
|
6
|
+
quantum_efficiency = 0.85
|
|
7
|
+
full_well_e = 80000.0
|
|
8
|
+
bit_depth = 16
|
|
9
|
+
gain_e_per_adu = 2.0
|
|
10
|
+
bias_offset_adu = 800.0
|
|
11
|
+
read_noise_e = 40.0 # amplifier read noise before avalanche gain
|
|
12
|
+
em_gain = 20.0 # avalanche gain
|
|
13
|
+
excess_noise_factor = 1.25
|
|
14
|
+
dark_current_e_per_s = 2.0
|
|
15
|
+
dark_current_ref_temp_c = -190.0
|
|
16
|
+
dark_current_doubling_temp_c = 8.0
|
|
17
|
+
dark_current_nonuniformity = 0.04
|
|
18
|
+
hot_pixel_fraction = 0.0005
|
|
19
|
+
hot_pixel_factor = 50.0
|
|
20
|
+
notes = "Idealised eAPD for demonstrating avalanche gain with a low excess noise factor."
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# A plain EMCCD for testing and teaching.
|
|
2
|
+
name = "Generic EMCCD"
|
|
3
|
+
sensor_type = "EMCCD"
|
|
4
|
+
resolution = [512, 512]
|
|
5
|
+
pixel_size_um = 16.0
|
|
6
|
+
quantum_efficiency = 0.90
|
|
7
|
+
full_well_e = 100000.0
|
|
8
|
+
bit_depth = 16
|
|
9
|
+
gain_e_per_adu = 5.0
|
|
10
|
+
bias_offset_adu = 500.0
|
|
11
|
+
read_noise_e = 50.0
|
|
12
|
+
dark_current_e_per_s = 0.001
|
|
13
|
+
dark_current_ref_temp_c = -70.0
|
|
14
|
+
dark_current_doubling_temp_c = 6.3
|
|
15
|
+
em_gain = 200.0
|
|
16
|
+
clock_induced_charge_e = 0.01
|
|
17
|
+
dark_current_nonuniformity = 0.05
|
|
18
|
+
hot_pixel_fraction = 0.001
|
|
19
|
+
hot_pixel_factor = 80.0
|
|
20
|
+
notes = "Idealised EMCCD for demonstrating EM gain and clock-induced charge."
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# A plain scientific CMOS (sCMOS) for testing and teaching.
|
|
2
|
+
name = "Generic sCMOS"
|
|
3
|
+
sensor_type = "SCMOS"
|
|
4
|
+
resolution = [2048, 2048]
|
|
5
|
+
pixel_size_um = 6.5
|
|
6
|
+
quantum_efficiency = 0.82
|
|
7
|
+
full_well_e = 30000.0
|
|
8
|
+
bit_depth = 16
|
|
9
|
+
gain_e_per_adu = 0.5
|
|
10
|
+
bias_offset_adu = 100.0
|
|
11
|
+
read_noise_e = 1.6 # median read noise
|
|
12
|
+
read_noise_nonuniformity = 0.3 # per-pixel spread
|
|
13
|
+
dark_current_e_per_s = 0.5
|
|
14
|
+
dark_current_ref_temp_c = -10.0
|
|
15
|
+
dark_current_doubling_temp_c = 6.0
|
|
16
|
+
prnu = 0.01
|
|
17
|
+
nonlinearity = 0.01
|
|
18
|
+
dark_current_nonuniformity = 0.03
|
|
19
|
+
hot_pixel_fraction = 0.001
|
|
20
|
+
hot_pixel_factor = 130.0
|
|
21
|
+
notes = "Idealised sCMOS demonstrating per-pixel read noise and mild nonlinearity."
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Hamamatsu ORCA-Fusion BT — back-thinned scientific CMOS (sCMOS).
|
|
2
|
+
# Representative parameters; sCMOS read noise varies pixel-to-pixel, captured here
|
|
3
|
+
# by read_noise_nonuniformity rather than a single RMS.
|
|
4
|
+
name = "Hamamatsu ORCA-Fusion BT"
|
|
5
|
+
manufacturer = "Hamamatsu"
|
|
6
|
+
model = "ORCA-Fusion BT (C15440)"
|
|
7
|
+
sensor_type = "SCMOS"
|
|
8
|
+
resolution = [2304, 2304]
|
|
9
|
+
pixel_size_um = 6.5
|
|
10
|
+
quantum_efficiency = 0.95
|
|
11
|
+
full_well_e = 18000.0
|
|
12
|
+
bit_depth = 16
|
|
13
|
+
gain_e_per_adu = 0.3
|
|
14
|
+
bias_offset_adu = 100.0
|
|
15
|
+
read_noise_e = 1.4 # median; see spread below
|
|
16
|
+
read_noise_nonuniformity = 0.35 # per-pixel read-noise spread (sCMOS)
|
|
17
|
+
dark_current_e_per_s = 0.2
|
|
18
|
+
dark_current_ref_temp_c = -10.0
|
|
19
|
+
dark_current_doubling_temp_c = 6.0
|
|
20
|
+
prnu = 0.01
|
|
21
|
+
nonlinearity = 0.01
|
|
22
|
+
dark_current_nonuniformity = 0.02
|
|
23
|
+
hot_pixel_fraction = 0.0005
|
|
24
|
+
hot_pixel_factor = 120.0
|
|
25
|
+
notes = "Back-thinned sCMOS; very high QE and low median read noise with a per-pixel spread."
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Leonardo SAPHIRA — HgCdTe electron-avalanche photodiode (eAPD) IR array.
|
|
2
|
+
# Widely used as an adaptive-optics wavefront-sensor and fringe-tracker detector.
|
|
3
|
+
# Values are representative; the avalanche gain is user-tunable via the bias.
|
|
4
|
+
name = "Leonardo SAPHIRA"
|
|
5
|
+
manufacturer = "Leonardo"
|
|
6
|
+
model = "SAPHIRA (Mark 13/14)"
|
|
7
|
+
sensor_type = "EAPD"
|
|
8
|
+
resolution = [256, 320] # (height, width)
|
|
9
|
+
pixel_size_um = 24.0
|
|
10
|
+
quantum_efficiency = 0.90 # HgCdTe, broad near-IR response
|
|
11
|
+
full_well_e = 100000.0
|
|
12
|
+
bit_depth = 16
|
|
13
|
+
gain_e_per_adu = 2.5 # system gain at the ADC
|
|
14
|
+
bias_offset_adu = 1000.0
|
|
15
|
+
read_noise_e = 45.0 # CDS read noise at the amplifier (pre-avalanche)
|
|
16
|
+
em_gain = 50.0 # avalanche gain M; effective read noise ~ 45/M ~ 1 e-
|
|
17
|
+
excess_noise_factor = 1.3 # eAPDs are far quieter than EMCCDs (sqrt(2))
|
|
18
|
+
dark_current_e_per_s = 5.0 # incl. dark-current avalanche / glow, cooled (~80 K)
|
|
19
|
+
dark_current_ref_temp_c = -193.0
|
|
20
|
+
dark_current_doubling_temp_c = 8.0
|
|
21
|
+
clock_induced_charge_e = 0.0
|
|
22
|
+
dark_current_nonuniformity = 0.05
|
|
23
|
+
hot_pixel_fraction = 0.0005
|
|
24
|
+
hot_pixel_factor = 50.0
|
|
25
|
+
notes = "Sub-electron effective read noise at high avalanche gain; near-unity excess noise."
|
|
26
|
+
|
|
27
|
+
# Wavelength-resolved quantum efficiency (HgCdTe, ~2.5 um cutoff). Optional; only
|
|
28
|
+
# used in spectral mode (Camera.observe with a band that has a spectral response).
|
|
29
|
+
# Representative shape --- supply a measured curve for quantitative IR work.
|
|
30
|
+
[qe_curve]
|
|
31
|
+
wavelength_nm = [800.0, 1000.0, 1500.0, 2000.0, 2400.0, 2500.0]
|
|
32
|
+
qe = [0.45, 0.85, 0.90, 0.90, 0.80, 0.30]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# ZWO ASI2600MM Pro — Sony IMX571 back-illuminated CMOS
|
|
2
|
+
name = "ZWO ASI2600MM Pro"
|
|
3
|
+
manufacturer = "ZWO"
|
|
4
|
+
model = "ASI2600MM Pro (IMX571)"
|
|
5
|
+
sensor_type = "CMOS"
|
|
6
|
+
resolution = [4176, 6248]
|
|
7
|
+
pixel_size_um = 3.76
|
|
8
|
+
quantum_efficiency = 0.91
|
|
9
|
+
full_well_e = 50000.0
|
|
10
|
+
bit_depth = 16
|
|
11
|
+
gain_e_per_adu = 0.25 # unity-gain regime; low-noise mode
|
|
12
|
+
bias_offset_adu = 500.0
|
|
13
|
+
read_noise_e = 1.5 # at high conversion gain
|
|
14
|
+
dark_current_e_per_s = 0.0022
|
|
15
|
+
dark_current_ref_temp_c = -10.0
|
|
16
|
+
dark_current_doubling_temp_c = 6.0
|
|
17
|
+
dark_current_nonuniformity = 0.02
|
|
18
|
+
hot_pixel_fraction = 0.0005
|
|
19
|
+
hot_pixel_factor = 120.0
|
|
20
|
+
notes = "Popular cooled astrophotography/scientific CMOS; very low read noise."
|
getframes/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""The scene/optics layer: turn astrophysical inputs into a photon-rate map.
|
|
3
|
+
|
|
4
|
+
Build a :class:`Scene` from sources, a :class:`PSF`, and a :class:`Telescope`,
|
|
5
|
+
then either call :meth:`Scene.photon_rate_map` yourself or hand the scene to
|
|
6
|
+
:meth:`getframes.Camera.observe` to get a realistic frame.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from .optics import RadialDistortion, Telescope, Vignetting
|
|
12
|
+
from .photometry import Bandpass, Extinction
|
|
13
|
+
from .psf import PSF, AiryPSF, ArrayPSF, EllipticalGaussianPSF, GaussianPSF, MoffatPSF
|
|
14
|
+
from .scene import Scene
|
|
15
|
+
from .sources import (
|
|
16
|
+
Catalog,
|
|
17
|
+
CatalogEntry,
|
|
18
|
+
ExtendedSource,
|
|
19
|
+
LightCurve,
|
|
20
|
+
PointSource,
|
|
21
|
+
Sky,
|
|
22
|
+
Source,
|
|
23
|
+
UniformIllumination,
|
|
24
|
+
)
|
|
25
|
+
from .thermal import Thermal
|
|
26
|
+
from .wcs import WCSInfo
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"PSF",
|
|
30
|
+
"AiryPSF",
|
|
31
|
+
"ArrayPSF",
|
|
32
|
+
"Bandpass",
|
|
33
|
+
"Catalog",
|
|
34
|
+
"CatalogEntry",
|
|
35
|
+
"EllipticalGaussianPSF",
|
|
36
|
+
"ExtendedSource",
|
|
37
|
+
"Extinction",
|
|
38
|
+
"GaussianPSF",
|
|
39
|
+
"LightCurve",
|
|
40
|
+
"MoffatPSF",
|
|
41
|
+
"PointSource",
|
|
42
|
+
"RadialDistortion",
|
|
43
|
+
"Scene",
|
|
44
|
+
"Sky",
|
|
45
|
+
"Source",
|
|
46
|
+
"Telescope",
|
|
47
|
+
"Thermal",
|
|
48
|
+
"UniformIllumination",
|
|
49
|
+
"Vignetting",
|
|
50
|
+
"WCSInfo",
|
|
51
|
+
]
|