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