spectre-core 0.0.21__py3-none-any.whl → 0.0.23__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.
- spectre_core/_file_io/__init__.py +5 -5
- spectre_core/_file_io/file_handlers.py +61 -107
- spectre_core/batches/__init__.py +21 -4
- spectre_core/batches/_base.py +86 -135
- spectre_core/batches/_batches.py +56 -100
- spectre_core/batches/_factory.py +22 -21
- spectre_core/batches/_register.py +9 -9
- spectre_core/batches/plugins/_batch_keys.py +8 -7
- spectre_core/batches/plugins/_callisto.py +66 -98
- spectre_core/batches/plugins/_iq_stream.py +106 -170
- spectre_core/capture_configs/__init__.py +47 -18
- spectre_core/capture_configs/_capture_config.py +26 -53
- spectre_core/capture_configs/_capture_modes.py +9 -7
- spectre_core/capture_configs/_capture_templates.py +51 -111
- spectre_core/capture_configs/_parameters.py +38 -75
- spectre_core/capture_configs/_pconstraints.py +41 -41
- spectre_core/capture_configs/_pnames.py +37 -35
- spectre_core/capture_configs/_ptemplates.py +261 -348
- spectre_core/capture_configs/_pvalidators.py +99 -102
- spectre_core/config/__init__.py +14 -9
- spectre_core/config/_paths.py +19 -36
- spectre_core/config/_time_formats.py +7 -6
- spectre_core/exceptions.py +39 -1
- spectre_core/jobs/__init__.py +4 -7
- spectre_core/jobs/_duration.py +12 -0
- spectre_core/jobs/_jobs.py +73 -44
- spectre_core/jobs/_workers.py +56 -106
- spectre_core/logs/__init__.py +8 -3
- spectre_core/logs/_configure.py +14 -18
- spectre_core/logs/_decorators.py +7 -5
- spectre_core/logs/_logs.py +38 -90
- spectre_core/logs/_process_types.py +6 -4
- spectre_core/plotting/__init__.py +14 -4
- spectre_core/plotting/_base.py +65 -139
- spectre_core/plotting/_format.py +11 -9
- spectre_core/plotting/_panel_names.py +8 -6
- spectre_core/plotting/_panel_stack.py +83 -116
- spectre_core/plotting/_panels.py +121 -156
- spectre_core/post_processing/__init__.py +7 -4
- spectre_core/post_processing/_base.py +69 -69
- spectre_core/post_processing/_factory.py +15 -12
- spectre_core/post_processing/_post_processor.py +17 -13
- spectre_core/post_processing/_register.py +11 -8
- spectre_core/post_processing/plugins/_event_handler_keys.py +5 -4
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +55 -48
- spectre_core/post_processing/plugins/_swept_center_frequency.py +200 -175
- spectre_core/receivers/__init__.py +10 -3
- spectre_core/receivers/_base.py +83 -149
- spectre_core/receivers/_factory.py +21 -31
- spectre_core/receivers/_register.py +8 -11
- spectre_core/receivers/_spec_names.py +18 -16
- spectre_core/receivers/plugins/_b200mini.py +48 -61
- spectre_core/receivers/plugins/_receiver_names.py +9 -7
- spectre_core/receivers/plugins/_rsp1a.py +45 -41
- spectre_core/receivers/plugins/_rspduo.py +60 -45
- spectre_core/receivers/plugins/_sdrplay_receiver.py +68 -84
- spectre_core/receivers/plugins/_test.py +137 -130
- spectre_core/receivers/plugins/_usrp.py +94 -86
- spectre_core/receivers/plugins/gr/__init__.py +2 -2
- spectre_core/receivers/plugins/gr/_base.py +15 -23
- spectre_core/receivers/plugins/gr/_rsp1a.py +53 -60
- spectre_core/receivers/plugins/gr/_rspduo.py +78 -90
- spectre_core/receivers/plugins/gr/_test.py +50 -58
- spectre_core/receivers/plugins/gr/_usrp.py +61 -59
- spectre_core/spectrograms/__init__.py +22 -14
- spectre_core/spectrograms/_analytical.py +109 -100
- spectre_core/spectrograms/_array_operations.py +40 -47
- spectre_core/spectrograms/_spectrogram.py +290 -323
- spectre_core/spectrograms/_transform.py +107 -74
- spectre_core/wgetting/__init__.py +2 -4
- spectre_core/wgetting/_callisto.py +88 -94
- {spectre_core-0.0.21.dist-info → spectre_core-0.0.23.dist-info}/METADATA +9 -23
- spectre_core-0.0.23.dist-info/RECORD +79 -0
- {spectre_core-0.0.21.dist-info → spectre_core-0.0.23.dist-info}/WHEEL +1 -1
- spectre_core-0.0.21.dist-info/RECORD +0 -78
- {spectre_core-0.0.21.dist-info → spectre_core-0.0.23.dist-info}/licenses/LICENSE +0 -0
- {spectre_core-0.0.21.dist-info → spectre_core-0.0.23.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,20 @@
|
|
1
|
-
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
1
|
+
# SPDX-FileCopyrightText: © 2024-2025 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
5
|
from enum import Enum
|
6
6
|
|
7
|
+
|
7
8
|
class PanelName(Enum):
|
8
9
|
"""Literal corresponding to a fully implemented `BasePanel` subclass.
|
9
|
-
|
10
|
+
|
10
11
|
:ivar SPECTROGRAM: Panel for visualising the full spectrogram.
|
11
12
|
:ivar FREQUENCY_CUTS: Panel for visualising individual spectrums in a spectrogram.
|
12
13
|
:ivar TIME_CUTS: Panel for visualising spectrogram data as time series of spectral components.
|
13
14
|
:ivar INTEGRAL_OVER_FREQUENCY: Panel for visualising the spectrogram integrated over frequency.
|
14
15
|
"""
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
|
17
|
+
SPECTROGRAM = "spectrogram"
|
18
|
+
FREQUENCY_CUTS = "frequency_cuts"
|
19
|
+
TIME_CUTS = "time_cuts"
|
20
|
+
INTEGRAL_OVER_FREQUENCY = "integral_over_frequency"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
1
|
+
# SPDX-FileCopyrightText: © 2024-2025 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
@@ -15,13 +15,10 @@ from spectre_core.spectrograms import TimeType
|
|
15
15
|
from spectre_core.config import TimeFormat, get_batches_dir_path
|
16
16
|
from ._base import BasePanel, XAxisType
|
17
17
|
from ._format import PanelFormat
|
18
|
-
from ._panels import
|
19
|
-
PanelName, SpectrogramPanel, TimeCutsPanel, FrequencyCutsPanel
|
20
|
-
)
|
18
|
+
from ._panels import PanelName, SpectrogramPanel, TimeCutsPanel, FrequencyCutsPanel
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
) -> bool:
|
20
|
+
|
21
|
+
def _is_cuts_panel(panel: BasePanel) -> bool:
|
25
22
|
"""Check if a panel contains spectrogram cuts.
|
26
23
|
|
27
24
|
:param panel: The panel to check.
|
@@ -30,9 +27,7 @@ def _is_cuts_panel(
|
|
30
27
|
return panel.name in {PanelName.FREQUENCY_CUTS, PanelName.TIME_CUTS}
|
31
28
|
|
32
29
|
|
33
|
-
def _is_spectrogram_panel(
|
34
|
-
panel: BasePanel
|
35
|
-
) -> bool:
|
30
|
+
def _is_spectrogram_panel(panel: BasePanel) -> bool:
|
36
31
|
"""Check if a panel is a spectrogram panel.
|
37
32
|
|
38
33
|
:param panel: The panel to check.
|
@@ -43,12 +38,13 @@ def _is_spectrogram_panel(
|
|
43
38
|
|
44
39
|
class PanelStack:
|
45
40
|
"""Visualise spectrogram data in a stack of panels."""
|
41
|
+
|
46
42
|
def __init__(
|
47
|
-
self,
|
43
|
+
self,
|
48
44
|
panel_format: PanelFormat = PanelFormat(),
|
49
45
|
time_type: TimeType = TimeType.RELATIVE,
|
50
46
|
figsize: Tuple[int, int] = (15, 8),
|
51
|
-
non_interactive: bool = False
|
47
|
+
non_interactive: bool = False,
|
52
48
|
) -> None:
|
53
49
|
"""Initialize an instance of `PanelStack`.
|
54
50
|
|
@@ -59,92 +55,79 @@ class PanelStack:
|
|
59
55
|
self._panel_format = panel_format
|
60
56
|
self._time_type = time_type
|
61
57
|
self._figsize = figsize
|
62
|
-
|
58
|
+
|
63
59
|
if non_interactive:
|
64
60
|
# Use a non-interactive matplotlib backend, which can only write files.
|
65
|
-
use(
|
61
|
+
use("agg")
|
66
62
|
|
67
|
-
self._panels
|
63
|
+
self._panels: list[BasePanel] = []
|
68
64
|
self._superimposed_panels: list[BasePanel] = []
|
69
|
-
|
65
|
+
|
70
66
|
self._fig: Optional[Figure] = None
|
71
67
|
self._axs: Optional[np.ndarray] = None
|
72
68
|
|
73
|
-
|
74
69
|
@property
|
75
|
-
def time_type(
|
76
|
-
self
|
77
|
-
) -> TimeType:
|
70
|
+
def time_type(self) -> TimeType:
|
78
71
|
"""The type of time we assign to the spectrograms"""
|
79
72
|
return self._time_type
|
80
73
|
|
81
|
-
|
82
74
|
@property
|
83
|
-
def panels(
|
84
|
-
self
|
85
|
-
) -> list[BasePanel]:
|
75
|
+
def panels(self) -> list[BasePanel]:
|
86
76
|
"""Get the panels in the stack, sorted by their `XAxisType`."""
|
87
77
|
return list(sorted(self._panels, key=lambda panel: panel.xaxis_type.value))
|
88
78
|
|
89
|
-
|
90
79
|
@property
|
91
|
-
def fig(
|
92
|
-
self
|
93
|
-
) -> Figure:
|
80
|
+
def fig(self) -> Figure:
|
94
81
|
"""Get the shared `matplotlib` figure for the panel stack.
|
95
82
|
|
96
83
|
:raises ValueError: If the axes have not been initialized.
|
97
84
|
"""
|
98
85
|
if self._fig is None:
|
99
|
-
raise ValueError(
|
86
|
+
raise ValueError(
|
87
|
+
f"An unexpected error has occured, `fig` must be set for the panel stack."
|
88
|
+
)
|
100
89
|
return self._fig
|
101
90
|
|
102
|
-
|
103
91
|
@property
|
104
|
-
def axs(
|
105
|
-
self
|
106
|
-
) -> np.ndarray:
|
92
|
+
def axs(self) -> np.ndarray:
|
107
93
|
"""Get the `matplotlib` axes in the stack.
|
108
94
|
|
109
95
|
:return: An array of `matplotlib.axes.Axes`, one for each panel in the stack.
|
110
96
|
:raises ValueError: If the axes have not been initialized.
|
111
97
|
"""
|
112
98
|
if self._axs is None:
|
113
|
-
raise ValueError(
|
99
|
+
raise ValueError(
|
100
|
+
f"An unexpected error has occured, `axs` must be set for the panel stack."
|
101
|
+
)
|
114
102
|
return np.atleast_1d(self._axs)
|
115
103
|
|
116
|
-
|
117
104
|
@property
|
118
|
-
def num_panels(
|
119
|
-
self
|
120
|
-
) -> int:
|
105
|
+
def num_panels(self) -> int:
|
121
106
|
"""Get the number of panels in the stack."""
|
122
107
|
return len(self._panels)
|
123
108
|
|
124
|
-
|
125
109
|
def add_panel(
|
126
|
-
self,
|
110
|
+
self,
|
127
111
|
panel: BasePanel,
|
128
112
|
identifier: Optional[str] = None,
|
129
|
-
panel_format: Optional[PanelFormat] = None
|
113
|
+
panel_format: Optional[PanelFormat] = None,
|
130
114
|
) -> None:
|
131
115
|
"""Add a panel to the stack.
|
132
116
|
|
133
117
|
:param panel: An instance of a `BasePanel` subclass to be added to the stack.
|
134
118
|
:param identifier: An optional string to link the panel with others for superimposing.
|
135
119
|
"""
|
136
|
-
panel.panel_format = panel_format or self._panel_format
|
137
|
-
panel.time_type
|
138
|
-
if identifier:
|
120
|
+
panel.panel_format = panel_format or self._panel_format
|
121
|
+
panel.time_type = self._time_type
|
122
|
+
if identifier:
|
139
123
|
panel.identifier = identifier
|
140
124
|
self._panels.append(panel)
|
141
125
|
|
142
|
-
|
143
126
|
def superimpose_panel(
|
144
|
-
self,
|
127
|
+
self,
|
145
128
|
panel: BasePanel,
|
146
129
|
identifier: Optional[str] = None,
|
147
|
-
panel_format: Optional[PanelFormat] = None
|
130
|
+
panel_format: Optional[PanelFormat] = None,
|
148
131
|
) -> None:
|
149
132
|
"""Superimpose a panel onto an existing panel in the stack.
|
150
133
|
|
@@ -154,49 +137,42 @@ class PanelStack:
|
|
154
137
|
if identifier:
|
155
138
|
panel.identifier = identifier
|
156
139
|
panel.panel_format = panel_format or self._panel_format
|
157
|
-
panel.time_type
|
140
|
+
panel.time_type = self._time_type
|
158
141
|
self._superimposed_panels.append(panel)
|
159
142
|
|
160
|
-
|
161
|
-
def _init_plot_style(
|
162
|
-
self
|
163
|
-
) -> None:
|
143
|
+
def _init_plot_style(self) -> None:
|
164
144
|
"""Initialize the global plot style for the stack.
|
165
145
|
|
166
146
|
This method sets `matplotlib` styles and font sizes based on the `panel_format`.
|
167
147
|
"""
|
168
148
|
plt.style.use(self._panel_format.style)
|
169
|
-
|
170
|
-
plt.rc(
|
171
|
-
|
172
|
-
plt.rc(
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
plt.rc(
|
179
|
-
plt.rc(
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
) -> None:
|
149
|
+
|
150
|
+
plt.rc("font", size=self._panel_format.small_size)
|
151
|
+
|
152
|
+
plt.rc(
|
153
|
+
"axes",
|
154
|
+
titlesize=self._panel_format.medium_size,
|
155
|
+
labelsize=self._panel_format.medium_size,
|
156
|
+
)
|
157
|
+
|
158
|
+
plt.rc("xtick", labelsize=self._panel_format.small_size)
|
159
|
+
plt.rc("ytick", labelsize=self._panel_format.small_size)
|
160
|
+
|
161
|
+
plt.rc("legend", fontsize=self._panel_format.small_size)
|
162
|
+
plt.rc("figure", titlesize=self._panel_format.large_size)
|
163
|
+
|
164
|
+
def _create_figure_and_axes(self) -> None:
|
185
165
|
"""Create the `matplotlib` figure and axes for the panel stack.
|
186
166
|
|
187
167
|
This initializes a figure with a specified number of vertically stacked axes.
|
188
168
|
"""
|
189
|
-
self._fig, self._axs = plt.subplots(
|
190
|
-
|
191
|
-
|
192
|
-
layout="constrained")
|
193
|
-
|
169
|
+
self._fig, self._axs = plt.subplots(
|
170
|
+
self.num_panels, 1, figsize=self._figsize, layout="constrained"
|
171
|
+
)
|
194
172
|
|
195
|
-
def _assign_axes(
|
196
|
-
self
|
197
|
-
) -> None:
|
173
|
+
def _assign_axes(self) -> None:
|
198
174
|
"""Assign each axes in the figure to some panel in the stack.
|
199
|
-
|
175
|
+
|
200
176
|
Axes are shared between panels with common `xaxis_type`.
|
201
177
|
"""
|
202
178
|
shared_axes: dict[XAxisType, Axes] = {}
|
@@ -204,15 +180,11 @@ class PanelStack:
|
|
204
180
|
panel.ax = self.axs[i]
|
205
181
|
panel.fig = self._fig
|
206
182
|
if panel.xaxis_type in shared_axes:
|
207
|
-
panel.ax.sharex(
|
183
|
+
panel.ax.sharex(shared_axes[panel.xaxis_type])
|
208
184
|
else:
|
209
185
|
shared_axes[panel.xaxis_type] = panel.ax
|
210
186
|
|
211
|
-
|
212
|
-
def _overlay_cuts(
|
213
|
-
self,
|
214
|
-
cuts_panel: BasePanel
|
215
|
-
) -> None:
|
187
|
+
def _overlay_cuts(self, cuts_panel: BasePanel) -> None:
|
216
188
|
"""Overlay cuts onto corresponding spectrogram panels.
|
217
189
|
|
218
190
|
For a given cuts panel, locate any spectrogram panels in the stack with a matching tag
|
@@ -224,82 +196,77 @@ class PanelStack:
|
|
224
196
|
if _is_spectrogram_panel(panel) and panel.tag == cuts_panel.tag:
|
225
197
|
panel = cast(SpectrogramPanel, panel)
|
226
198
|
if cuts_panel.name == PanelName.FREQUENCY_CUTS:
|
227
|
-
panel.overlay_frequency_cuts(
|
199
|
+
panel.overlay_frequency_cuts(cast(FrequencyCutsPanel, cuts_panel))
|
228
200
|
elif cuts_panel.name == PanelName.TIME_CUTS:
|
229
|
-
panel.overlay_time_cuts(
|
230
|
-
|
201
|
+
panel.overlay_time_cuts(cast(TimeCutsPanel, cuts_panel))
|
231
202
|
|
232
|
-
def _overlay_superimposed_panels(
|
233
|
-
self
|
234
|
-
) -> None:
|
203
|
+
def _overlay_superimposed_panels(self) -> None:
|
235
204
|
"""Superimpose panels onto matching panels in the stack.
|
236
205
|
|
237
|
-
For each superimposed panel, find a matching panel in the stack with the same name
|
238
|
-
and identifier. Share the axes and figure, then draw the superimposed panel. If the
|
206
|
+
For each superimposed panel, find a matching panel in the stack with the same name
|
207
|
+
and identifier. Share the axes and figure, then draw the superimposed panel. If the
|
239
208
|
panel contains cuts, overlay those cuts onto the corresponding spectrogram panels.
|
240
209
|
"""
|
241
210
|
for super_panel in self._superimposed_panels:
|
242
211
|
for panel in self._panels:
|
243
|
-
if panel.name == super_panel.name and (
|
212
|
+
if panel.name == super_panel.name and (
|
213
|
+
panel.identifier == super_panel.identifier
|
214
|
+
):
|
244
215
|
super_panel.ax, super_panel.fig = panel.ax, self._fig
|
245
216
|
super_panel.draw()
|
246
217
|
if _is_cuts_panel(super_panel):
|
247
218
|
self._overlay_cuts(super_panel)
|
248
219
|
|
249
|
-
def _make_figure(
|
250
|
-
self
|
251
|
-
) -> None:
|
220
|
+
def _make_figure(self) -> None:
|
252
221
|
"""Make the panel stack figure."""
|
253
222
|
if self.num_panels < 1:
|
254
223
|
raise ValueError(f"There must be at least one panel in the stack.")
|
255
|
-
|
224
|
+
|
256
225
|
self._init_plot_style()
|
257
226
|
self._create_figure_and_axes()
|
258
227
|
self._assign_axes()
|
259
|
-
|
228
|
+
|
260
229
|
last_panel_per_axis = {panel.xaxis_type: panel for panel in self.panels}
|
261
|
-
|
230
|
+
|
262
231
|
for panel in self.panels:
|
263
232
|
panel.draw()
|
264
233
|
panel.annotate_yaxis()
|
265
|
-
|
234
|
+
|
266
235
|
if panel == last_panel_per_axis[panel.xaxis_type]:
|
267
236
|
panel.annotate_xaxis()
|
268
237
|
else:
|
269
238
|
panel.hide_xaxis_labels()
|
270
|
-
|
239
|
+
|
271
240
|
if _is_cuts_panel(panel):
|
272
241
|
self._overlay_cuts(panel)
|
273
|
-
|
242
|
+
|
274
243
|
self._overlay_superimposed_panels()
|
275
244
|
|
276
|
-
def show(
|
277
|
-
self
|
278
|
-
) -> None:
|
245
|
+
def show(self) -> None:
|
279
246
|
"""Display the panel stack figure."""
|
280
247
|
self._make_figure()
|
281
248
|
plt.show()
|
282
|
-
|
283
|
-
|
249
|
+
|
284
250
|
def save(
|
285
251
|
self,
|
286
252
|
) -> str:
|
287
|
-
"""Save the panel stack figure as a batch file under the tag of the first
|
288
|
-
panel in the stack. The file format is `png`.
|
289
|
-
|
253
|
+
"""Save the panel stack figure as a batch file under the tag of the first
|
254
|
+
panel in the stack. The file format is `png`.
|
255
|
+
|
290
256
|
:return: The file path of the newly created batch file containing the figure.
|
291
257
|
"""
|
292
258
|
self._make_figure()
|
293
259
|
first_panel = self._panels[0]
|
294
|
-
|
295
|
-
start_dt = cast(
|
296
|
-
|
260
|
+
|
261
|
+
start_dt = cast(
|
262
|
+
datetime, first_panel.spectrogram.start_datetime.astype(datetime)
|
263
|
+
)
|
264
|
+
batch_name = (
|
265
|
+
f"{start_dt.strftime(TimeFormat.DATETIME)}_{first_panel.spectrogram.tag}"
|
266
|
+
)
|
297
267
|
batch_file_path = os.path.join(
|
298
268
|
get_batches_dir_path(start_dt.year, start_dt.month, start_dt.day),
|
299
|
-
f"{batch_name}.png"
|
269
|
+
f"{batch_name}.png",
|
300
270
|
)
|
301
271
|
plt.savefig(batch_file_path)
|
302
272
|
return batch_file_path
|
303
|
-
|
304
|
-
|
305
|
-
|