spectre-core 0.0.12__py3-none-any.whl → 0.0.13__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 (88) hide show
  1. spectre_core/_file_io/__init__.py +1 -3
  2. spectre_core/_file_io/file_handlers.py +163 -58
  3. spectre_core/batches/__init__.py +10 -11
  4. spectre_core/batches/_base.py +170 -78
  5. spectre_core/batches/_batches.py +149 -99
  6. spectre_core/batches/_factory.py +56 -14
  7. spectre_core/batches/_register.py +23 -8
  8. spectre_core/batches/plugins/_batch_keys.py +16 -0
  9. spectre_core/batches/plugins/_callisto.py +183 -0
  10. spectre_core/batches/plugins/_iq_stream.py +354 -0
  11. spectre_core/capture_configs/__init__.py +17 -13
  12. spectre_core/capture_configs/_capture_config.py +93 -34
  13. spectre_core/capture_configs/_capture_modes.py +22 -0
  14. spectre_core/capture_configs/_capture_templates.py +207 -122
  15. spectre_core/capture_configs/_parameters.py +115 -42
  16. spectre_core/capture_configs/_pconstraints.py +86 -35
  17. spectre_core/capture_configs/_pnames.py +49 -0
  18. spectre_core/capture_configs/_ptemplates.py +389 -346
  19. spectre_core/capture_configs/_pvalidators.py +117 -73
  20. spectre_core/config/__init__.py +6 -8
  21. spectre_core/config/_paths.py +65 -25
  22. spectre_core/config/_time_formats.py +15 -10
  23. spectre_core/exceptions.py +2 -4
  24. spectre_core/jobs/__init__.py +14 -0
  25. spectre_core/jobs/_jobs.py +111 -0
  26. spectre_core/jobs/_workers.py +171 -0
  27. spectre_core/logs/__init__.py +17 -0
  28. spectre_core/logs/_configure.py +67 -0
  29. spectre_core/logs/_decorators.py +33 -0
  30. spectre_core/logs/_logs.py +228 -0
  31. spectre_core/logs/_process_types.py +14 -0
  32. spectre_core/plotting/__init__.py +4 -2
  33. spectre_core/plotting/_base.py +204 -102
  34. spectre_core/plotting/_format.py +17 -4
  35. spectre_core/plotting/_panel_names.py +18 -0
  36. spectre_core/plotting/_panel_stack.py +167 -53
  37. spectre_core/plotting/_panels.py +341 -141
  38. spectre_core/post_processing/__init__.py +8 -6
  39. spectre_core/post_processing/_base.py +70 -44
  40. spectre_core/post_processing/_factory.py +42 -12
  41. spectre_core/post_processing/_post_processor.py +24 -26
  42. spectre_core/post_processing/_register.py +22 -6
  43. spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
  44. spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
  45. spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
  46. spectre_core/py.typed +0 -0
  47. spectre_core/receivers/__init__.py +10 -7
  48. spectre_core/receivers/_base.py +220 -69
  49. spectre_core/receivers/_factory.py +53 -7
  50. spectre_core/receivers/_register.py +30 -9
  51. spectre_core/receivers/_spec_names.py +26 -15
  52. spectre_core/receivers/plugins/__init__.py +0 -0
  53. spectre_core/receivers/plugins/_receiver_names.py +16 -0
  54. spectre_core/receivers/plugins/_rsp1a.py +59 -0
  55. spectre_core/receivers/plugins/_rspduo.py +67 -0
  56. spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
  57. spectre_core/receivers/plugins/_test.py +218 -0
  58. spectre_core/receivers/plugins/gr/_base.py +80 -0
  59. spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
  60. spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
  61. spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
  62. spectre_core/spectrograms/__init__.py +5 -3
  63. spectre_core/spectrograms/_analytical.py +121 -66
  64. spectre_core/spectrograms/_array_operations.py +103 -36
  65. spectre_core/spectrograms/_spectrogram.py +380 -207
  66. spectre_core/spectrograms/_transform.py +197 -169
  67. spectre_core/wgetting/__init__.py +4 -2
  68. spectre_core/wgetting/_callisto.py +173 -118
  69. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
  70. spectre_core-0.0.13.dist-info/RECORD +75 -0
  71. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
  72. spectre_core/batches/library/_callisto.py +0 -96
  73. spectre_core/batches/library/_fixed_center_frequency.py +0 -133
  74. spectre_core/batches/library/_swept_center_frequency.py +0 -105
  75. spectre_core/logging/__init__.py +0 -11
  76. spectre_core/logging/_configure.py +0 -35
  77. spectre_core/logging/_decorators.py +0 -19
  78. spectre_core/logging/_log_handlers.py +0 -176
  79. spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
  80. spectre_core/receivers/gr/_base.py +0 -33
  81. spectre_core/receivers/library/_rsp1a.py +0 -61
  82. spectre_core/receivers/library/_rspduo.py +0 -69
  83. spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  84. spectre_core/receivers/library/_test.py +0 -221
  85. spectre_core-0.0.12.dist-info/RECORD +0 -64
  86. /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
  87. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
  88. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -3,68 +3,126 @@
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  import numpy as np
6
- from typing import Optional, Tuple
6
+ import numpy.typing as npt
7
+ from typing import Optional, Tuple, cast
7
8
  import matplotlib.pyplot as plt
8
9
  from matplotlib.figure import Figure
9
10
  from matplotlib.axes import Axes
10
11
 
11
- from spectre_core.spectrograms import TimeTypes
12
- from ._base import BasePanel
13
- from ._format import PanelFormat, DEFAULT_FORMAT
14
- from ._panels import PanelNames
12
+ from spectre_core.spectrograms import TimeType
13
+ from ._base import BasePanel, XAxisType
14
+ from ._format import PanelFormat
15
+ from ._panels import PanelName, SpectrogramPanel, TimeCutsPanel, FrequencyCutsPanel
15
16
 
16
17
 
17
- def _is_cuts_panel(panel: BasePanel) -> bool:
18
- return (panel.name == PanelNames.FREQUENCY_CUTS or panel.name == PanelNames.TIME_CUTS)
18
+ def _is_cuts_panel(
19
+ panel: BasePanel
20
+ ) -> bool:
21
+ """Check if a panel contains spectrogram cuts.
19
22
 
23
+ :param panel: The panel to check.
24
+ :return: True if the panel is a frequency or time cuts panel, otherwise False.
25
+ """
26
+ return panel.name in {PanelName.FREQUENCY_CUTS, PanelName.TIME_CUTS}
20
27
 
21
- def _is_spectrogram_panel(panel: BasePanel) -> bool:
22
- return (panel.name == PanelNames.SPECTROGRAM)
28
+
29
+ def _is_spectrogram_panel(
30
+ panel: BasePanel
31
+ ) -> bool:
32
+ """Check if a panel is a spectrogram panel.
33
+
34
+ :param panel: The panel to check.
35
+ :return: True if the panel is a spectrogram panel, otherwise False.
36
+ """
37
+ return panel.name == PanelName.SPECTROGRAM
23
38
 
24
39
 
25
40
  class PanelStack:
26
- def __init__(self,
27
- panel_format: PanelFormat = DEFAULT_FORMAT,
28
- time_type: str = TimeTypes.SECONDS,
29
- figsize: Tuple[int, int] = (10, 10)):
41
+ """Visualise spectrogram data in a stack of panels."""
42
+ def __init__(
43
+ self,
44
+ panel_format: PanelFormat = PanelFormat(),
45
+ time_type: TimeType = TimeType.RELATIVE,
46
+ figsize: Tuple[int, int] = (10, 10)
47
+ ) -> None:
48
+ """Initialize an instance of `PanelStack`.
49
+
50
+ :param panel_format: Formatting applied across all panels in the stack. Defaults to `PanelFormat()`.
51
+ :param time_type: The type of time assigned to spectrograms, defaults to `TimeType.RELATIVE`.
52
+ :param figsize: The size of the `matplotlib` figure as (width, height). Defaults to (10, 10).
53
+ """
30
54
  self._panel_format = panel_format
31
55
  self._time_type = time_type
32
56
  self._figsize = figsize
33
57
 
34
- self._panels: list[BasePanel] = []
58
+ self._panels : list[BasePanel] = []
35
59
  self._superimposed_panels: list[BasePanel] = []
60
+
36
61
  self._fig: Optional[Figure] = None
37
- self._axs: Optional[np.ndarray[Axes]] = None
62
+ self._axs: Optional[np.ndarray] = None
38
63
 
39
64
 
40
65
  @property
41
- def time_type(self) -> str:
66
+ def time_type(
67
+ self
68
+ ) -> TimeType:
69
+ """The type of time we assign to the spectrograms"""
42
70
  return self._time_type
43
71
 
44
72
 
45
73
  @property
46
- def panels(self) -> list[BasePanel]:
47
- return sorted(self._panels, key=lambda panel: panel.x_axis_type)
74
+ def panels(
75
+ self
76
+ ) -> list[BasePanel]:
77
+ """Get the panels in the stack, sorted by their `XAxisType`."""
78
+ return list(sorted(self._panels, key=lambda panel: panel.xaxis_type.value))
48
79
 
49
80
 
50
81
  @property
51
- def fig(self) -> Optional[Figure]:
82
+ def fig(
83
+ self
84
+ ) -> Figure:
85
+ """Get the shared `matplotlib` figure for the panel stack.
86
+
87
+ :raises ValueError: If the axes have not been initialized.
88
+ """
89
+ if self._fig is None:
90
+ raise ValueError(f"An unexpected error has occured, `fig` must be set for the panel stack.")
52
91
  return self._fig
53
92
 
54
93
 
55
94
  @property
56
- def axs(self) -> Optional[np.ndarray[Axes]]:
95
+ def axs(
96
+ self
97
+ ) -> np.ndarray:
98
+ """Get the `matplotlib` axes in the stack.
99
+
100
+ :return: An array of `matplotlib.axes.Axes`, one for each panel in the stack.
101
+ :raises ValueError: If the axes have not been initialized.
102
+ """
103
+ if self._axs is None:
104
+ raise ValueError(f"An unexpected error has occured, `axs` must be set for the panel stack.")
57
105
  return np.atleast_1d(self._axs)
58
106
 
59
107
 
60
108
  @property
61
- def num_panels(self) -> int:
109
+ def num_panels(
110
+ self
111
+ ) -> int:
112
+ """Get the number of panels in the stack."""
62
113
  return len(self._panels)
63
114
 
64
115
 
65
- def add_panel(self,
66
- panel: BasePanel,
67
- identifier: Optional[str] = None) -> None:
116
+ def add_panel(
117
+ self,
118
+ panel: BasePanel,
119
+ identifier: Optional[str] = None
120
+ ) -> None:
121
+ """Add a panel to the stack.
122
+
123
+ :param panel: An instance of a `BasePanel` subclass to be added to the stack.
124
+ :param identifier: An optional string to link the panel with others for superimposing.
125
+ """
68
126
  panel.panel_format = self._panel_format
69
127
  panel.time_type = self._time_type
70
128
  if identifier:
@@ -72,54 +130,102 @@ class PanelStack:
72
130
  self._panels.append(panel)
73
131
 
74
132
 
75
- def superimpose_panel(self,
76
- panel: BasePanel,
77
- identifier: Optional[str] = None) -> None:
133
+ def superimpose_panel(
134
+ self,
135
+ panel: BasePanel,
136
+ identifier: Optional[str] = None
137
+ ) -> None:
138
+ """Superimpose a panel onto an existing panel in the stack.
139
+
140
+ :param panel: The panel to superimpose.
141
+ :param identifier: An optional identifier to link panels during superimposing, defaults to None
142
+ """
78
143
  if identifier:
79
144
  panel.identifier = identifier
80
145
  panel.panel_format = self._panel_format
81
146
  self._superimposed_panels.append(panel)
82
147
 
83
148
 
84
- def _init_plot_style(self) -> None:
85
- plt.style.use(self._panel_format.style)
149
+ def _init_plot_style(
150
+ self
151
+ ) -> None:
152
+ """Initialize the global plot style for the stack.
86
153
 
87
- plt.rc('font' , size=self._panel_format.small_size)
88
- plt.rc('axes' , titlesize=self._panel_format.medium_size,
89
- labelsize=self._panel_format.medium_size)
90
- plt.rc('xtick' , labelsize=self._panel_format.small_size)
91
- plt.rc('ytick' , labelsize=self._panel_format.small_size)
154
+ This method sets `matplotlib` styles and font sizes based on the `panel_format`.
155
+ """
156
+ plt.style.use(self._panel_format.style)
157
+
158
+ plt.rc('font', size=self._panel_format.small_size)
159
+
160
+ plt.rc('axes', titlesize=self._panel_format.medium_size,
161
+ labelsize=self._panel_format.medium_size)
162
+
163
+ plt.rc('xtick', labelsize=self._panel_format.small_size)
164
+ plt.rc('ytick', labelsize=self._panel_format.small_size)
165
+
92
166
  plt.rc('legend', fontsize=self._panel_format.small_size)
93
167
  plt.rc('figure', titlesize=self._panel_format.large_size)
94
168
 
95
169
 
96
- def _create_figure_and_axes(self) -> None:
170
+ def _create_figure_and_axes(
171
+ self
172
+ ) -> None:
173
+ """Create the `matplotlib` figure and axes for the panel stack.
174
+
175
+ This initializes a figure with a specified number of vertically stacked axes.
176
+ """
97
177
  self._fig, self._axs = plt.subplots(self.num_panels,
98
178
  1,
99
179
  figsize=self._figsize,
100
180
  layout="constrained")
101
181
 
102
182
 
103
- def _assign_axes(self) -> None:
104
- shared_axes = {}
183
+ def _assign_axes(
184
+ self
185
+ ) -> None:
186
+ """Assign each axes in the figure to some panel in the stack.
187
+
188
+ Axes are shared between panels with common `xaxis_type`.
189
+ """
190
+ shared_axes: dict[XAxisType, Axes] = {}
105
191
  for i, panel in enumerate(self.panels):
106
192
  panel.ax = self.axs[i]
107
193
  panel.fig = self._fig
108
- if panel.x_axis_type in shared_axes:
109
- panel.ax.sharex(shared_axes[panel.x_axis_type])
194
+ if panel.xaxis_type in shared_axes:
195
+ panel.ax.sharex( shared_axes[panel.xaxis_type] )
110
196
  else:
111
- shared_axes[panel.x_axis_type] = panel.ax
197
+ shared_axes[panel.xaxis_type] = panel.ax
112
198
 
113
199
 
114
- def _overlay_cuts(self, cuts_panel: BasePanel) -> None:
115
- """Given a cuts panel, finds any corresponding spectrogram panels and adds the appropriate overlay"""
116
- for panel in self.panels:
117
- is_corresponding_panel = _is_spectrogram_panel(panel) and (panel.tag == cuts_panel.tag)
118
- if is_corresponding_panel:
119
- panel.overlay_cuts(cuts_panel)
200
+ def _overlay_cuts(
201
+ self,
202
+ cuts_panel: BasePanel
203
+ ) -> None:
204
+ """Overlay cuts onto corresponding spectrogram panels.
120
205
 
206
+ For a given cuts panel, locate any spectrogram panels in the stack with a matching tag
207
+ and add the appropriate frequency or time cuts overlay.
121
208
 
122
- def _overlay_superimposed_panels(self) -> None:
209
+ :param cuts_panel: A panel containing frequency or time cuts.
210
+ """
211
+ for panel in self.panels:
212
+ if _is_spectrogram_panel(panel) and panel.tag == cuts_panel.tag:
213
+ panel = cast(SpectrogramPanel, panel)
214
+ if cuts_panel.name == PanelName.FREQUENCY_CUTS:
215
+ panel.overlay_frequency_cuts( cast(FrequencyCutsPanel, cuts_panel) )
216
+ elif cuts_panel.name == PanelName.TIME_CUTS:
217
+ panel.overlay_time_cuts( cast(TimeCutsPanel, cuts_panel) )
218
+
219
+
220
+ def _overlay_superimposed_panels(
221
+ self
222
+ ) -> None:
223
+ """Superimpose panels onto matching panels in the stack.
224
+
225
+ For each superimposed panel, find a matching panel in the stack with the same name
226
+ and identifier. Share the axes and figure, then draw the superimposed panel. If the
227
+ panel contains cuts, overlay those cuts onto the corresponding spectrogram panels.
228
+ """
123
229
  for super_panel in self._superimposed_panels:
124
230
  for panel in self._panels:
125
231
  if panel.name == super_panel.name and (panel.identifier == super_panel.identifier):
@@ -129,19 +235,27 @@ class PanelStack:
129
235
  self._overlay_cuts(super_panel)
130
236
 
131
237
 
132
- def show(self) -> None:
238
+ def show(
239
+ self
240
+ ) -> None:
241
+ """Display the panel stack figure."""
133
242
  self._init_plot_style()
134
243
  self._create_figure_and_axes()
135
244
  self._assign_axes()
136
- last_panel_per_axis = {panel.x_axis_type: panel for panel in self.panels}
245
+
246
+ last_panel_per_axis = {panel.xaxis_type: panel for panel in self.panels}
247
+
137
248
  for panel in self.panels:
138
249
  panel.draw()
139
- panel.annotate_y_axis()
140
- if panel == last_panel_per_axis[panel.x_axis_type]:
141
- panel.annotate_x_axis()
250
+ panel.annotate_yaxis()
251
+
252
+ if panel == last_panel_per_axis[panel.xaxis_type]:
253
+ panel.annotate_xaxis()
142
254
  else:
143
- panel.hide_x_axis_labels()
255
+ panel.hide_xaxis_labels()
256
+
144
257
  if _is_cuts_panel(panel):
145
258
  self._overlay_cuts(panel)
259
+
146
260
  self._overlay_superimposed_panels()
147
261
  plt.show()