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.
Files changed (78) hide show
  1. spectre_core/__init__.py +5 -0
  2. spectre_core/_file_io/__init__.py +4 -4
  3. spectre_core/_file_io/file_handlers.py +60 -106
  4. spectre_core/batches/__init__.py +20 -3
  5. spectre_core/batches/_base.py +85 -134
  6. spectre_core/batches/_batches.py +55 -99
  7. spectre_core/batches/_factory.py +21 -20
  8. spectre_core/batches/_register.py +8 -8
  9. spectre_core/batches/plugins/_batch_keys.py +7 -6
  10. spectre_core/batches/plugins/_callisto.py +65 -97
  11. spectre_core/batches/plugins/_iq_stream.py +105 -169
  12. spectre_core/capture_configs/__init__.py +46 -17
  13. spectre_core/capture_configs/_capture_config.py +25 -52
  14. spectre_core/capture_configs/_capture_modes.py +8 -6
  15. spectre_core/capture_configs/_capture_templates.py +50 -110
  16. spectre_core/capture_configs/_parameters.py +37 -74
  17. spectre_core/capture_configs/_pconstraints.py +40 -40
  18. spectre_core/capture_configs/_pnames.py +36 -34
  19. spectre_core/capture_configs/_ptemplates.py +260 -347
  20. spectre_core/capture_configs/_pvalidators.py +99 -102
  21. spectre_core/config/__init__.py +19 -8
  22. spectre_core/config/_paths.py +25 -47
  23. spectre_core/config/_time_formats.py +6 -5
  24. spectre_core/exceptions.py +38 -0
  25. spectre_core/jobs/__init__.py +3 -6
  26. spectre_core/jobs/_duration.py +12 -0
  27. spectre_core/jobs/_jobs.py +72 -43
  28. spectre_core/jobs/_workers.py +55 -105
  29. spectre_core/logs/__init__.py +7 -2
  30. spectre_core/logs/_configure.py +13 -17
  31. spectre_core/logs/_decorators.py +6 -4
  32. spectre_core/logs/_logs.py +37 -89
  33. spectre_core/logs/_process_types.py +5 -3
  34. spectre_core/plotting/__init__.py +19 -3
  35. spectre_core/plotting/_base.py +112 -177
  36. spectre_core/plotting/_format.py +10 -8
  37. spectre_core/plotting/_panel_names.py +7 -5
  38. spectre_core/plotting/_panel_stack.py +138 -130
  39. spectre_core/plotting/_panels.py +152 -162
  40. spectre_core/post_processing/__init__.py +6 -3
  41. spectre_core/post_processing/_base.py +41 -55
  42. spectre_core/post_processing/_factory.py +14 -11
  43. spectre_core/post_processing/_post_processor.py +16 -12
  44. spectre_core/post_processing/_register.py +10 -7
  45. spectre_core/post_processing/plugins/_event_handler_keys.py +4 -3
  46. spectre_core/post_processing/plugins/_fixed_center_frequency.py +54 -47
  47. spectre_core/post_processing/plugins/_swept_center_frequency.py +199 -174
  48. spectre_core/receivers/__init__.py +9 -2
  49. spectre_core/receivers/_base.py +82 -148
  50. spectre_core/receivers/_factory.py +20 -30
  51. spectre_core/receivers/_register.py +7 -10
  52. spectre_core/receivers/_spec_names.py +17 -15
  53. spectre_core/receivers/plugins/_b200mini.py +47 -60
  54. spectre_core/receivers/plugins/_receiver_names.py +8 -6
  55. spectre_core/receivers/plugins/_rsp1a.py +44 -40
  56. spectre_core/receivers/plugins/_rspduo.py +59 -44
  57. spectre_core/receivers/plugins/_sdrplay_receiver.py +67 -83
  58. spectre_core/receivers/plugins/_test.py +136 -129
  59. spectre_core/receivers/plugins/_usrp.py +93 -85
  60. spectre_core/receivers/plugins/gr/__init__.py +1 -1
  61. spectre_core/receivers/plugins/gr/_base.py +14 -22
  62. spectre_core/receivers/plugins/gr/_rsp1a.py +53 -60
  63. spectre_core/receivers/plugins/gr/_rspduo.py +77 -89
  64. spectre_core/receivers/plugins/gr/_test.py +49 -57
  65. spectre_core/receivers/plugins/gr/_usrp.py +61 -59
  66. spectre_core/spectrograms/__init__.py +21 -13
  67. spectre_core/spectrograms/_analytical.py +108 -99
  68. spectre_core/spectrograms/_array_operations.py +39 -46
  69. spectre_core/spectrograms/_spectrogram.py +293 -324
  70. spectre_core/spectrograms/_transform.py +106 -73
  71. spectre_core/wgetting/__init__.py +1 -3
  72. spectre_core/wgetting/_callisto.py +87 -93
  73. {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/METADATA +9 -23
  74. spectre_core-0.0.24.dist-info/RECORD +79 -0
  75. {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/WHEEL +1 -1
  76. spectre_core-0.0.22.dist-info/RECORD +0 -78
  77. {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/licenses/LICENSE +0 -0
  78. {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
- def _is_cuts_panel(
23
- panel: BasePanel
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('agg')
61
+ use("agg")
66
62
 
67
- self._panels : list[BasePanel] = []
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 time_type(
76
- self
77
- ) -> TimeType:
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 panels(
84
- self
85
- ) -> list[BasePanel]:
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 fig(
92
- self
93
- ) -> Figure:
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(f"An unexpected error has occured, `fig` must be set for the panel stack.")
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(f"An unexpected error has occured, `axs` must be set for the panel stack.")
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
- @property
118
- def num_panels(
119
- self
120
- ) -> int:
121
- """Get the number of panels in the stack."""
122
- return len(self._panels)
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 = panel_format or self._panel_format
137
- panel.time_type = self._time_type
138
- if identifier:
139
- panel.identifier = 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 = identifier
156
- panel.panel_format = panel_format or self._panel_format
157
- panel.time_type = self._time_type
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('font', size=self._panel_format.small_size)
171
-
172
- plt.rc('axes', titlesize=self._panel_format.medium_size,
173
- labelsize=self._panel_format.medium_size)
174
-
175
- plt.rc('xtick', labelsize=self._panel_format.small_size)
176
- plt.rc('ytick', labelsize=self._panel_format.small_size)
177
-
178
- plt.rc('legend', fontsize=self._panel_format.small_size)
179
- plt.rc('figure', titlesize=self._panel_format.large_size)
180
-
181
-
182
- def _create_figure_and_axes(
183
- self
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(self.num_panels,
190
- 1,
191
- figsize=self._figsize,
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 `xaxis_type`.
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
- panel.ax = self.axs[i]
205
- panel.fig = self._fig
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.ax.sharex( shared_axes[panel.xaxis_type] )
222
+ panel.sharex(shared_axes[panel.xaxis_type])
208
223
  else:
209
- shared_axes[panel.xaxis_type] = panel.ax
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( cast(FrequencyCutsPanel, cuts_panel) )
238
+ panel.overlay_frequency_cuts(cast(FrequencyCutsPanel, cuts_panel))
228
239
  elif cuts_panel.name == PanelName.TIME_CUTS:
229
- panel.overlay_time_cuts( cast(TimeCutsPanel, cuts_panel) )
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 (panel.identifier == super_panel.identifier):
244
- super_panel.ax, super_panel.fig = panel.ax, self._fig
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
- plt.show()
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(datetime, first_panel.spectrogram.start_datetime.astype(datetime))
296
- batch_name = f"{start_dt.strftime(TimeFormat.DATETIME)}_{first_panel.spectrogram.tag}"
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
- plt.savefig(batch_file_path)
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
-