spectre-core 0.0.11__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 (94) hide show
  1. spectre_core/_file_io/__init__.py +1 -3
  2. spectre_core/_file_io/file_handlers.py +170 -65
  3. spectre_core/batches/__init__.py +21 -0
  4. spectre_core/batches/_base.py +238 -0
  5. spectre_core/batches/_batches.py +247 -0
  6. spectre_core/batches/_factory.py +69 -0
  7. spectre_core/batches/_register.py +30 -0
  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 +116 -46
  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 +121 -77
  20. spectre_core/config/__init__.py +7 -9
  21. spectre_core/config/_paths.py +66 -26
  22. spectre_core/config/_time_formats.py +15 -8
  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 +71 -45
  40. spectre_core/post_processing/_factory.py +42 -12
  41. spectre_core/post_processing/_post_processor.py +27 -29
  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/plugins/_swept_center_frequency.py +439 -0
  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 +45 -55
  60. spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +65 -78
  61. spectre_core/receivers/{gr → plugins/gr}/_test.py +36 -34
  62. spectre_core/spectrograms/__init__.py +5 -3
  63. spectre_core/spectrograms/_analytical.py +121 -72
  64. spectre_core/spectrograms/_array_operations.py +103 -36
  65. spectre_core/spectrograms/_spectrogram.py +410 -203
  66. spectre_core/spectrograms/_transform.py +199 -188
  67. spectre_core/wgetting/__init__.py +4 -2
  68. spectre_core/wgetting/_callisto.py +178 -127
  69. {spectre_core-0.0.11.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.11.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
  72. spectre_core/chunks/__init__.py +0 -22
  73. spectre_core/chunks/_base.py +0 -116
  74. spectre_core/chunks/_chunks.py +0 -200
  75. spectre_core/chunks/_factory.py +0 -25
  76. spectre_core/chunks/_register.py +0 -15
  77. spectre_core/chunks/library/_callisto.py +0 -98
  78. spectre_core/chunks/library/_fixed_center_frequency.py +0 -128
  79. spectre_core/chunks/library/_swept_center_frequency.py +0 -103
  80. spectre_core/logging/__init__.py +0 -11
  81. spectre_core/logging/_configure.py +0 -35
  82. spectre_core/logging/_decorators.py +0 -19
  83. spectre_core/logging/_log_handlers.py +0 -176
  84. spectre_core/post_processing/library/_fixed_center_frequency.py +0 -115
  85. spectre_core/post_processing/library/_swept_center_frequency.py +0 -382
  86. spectre_core/receivers/gr/_base.py +0 -33
  87. spectre_core/receivers/library/_rsp1a.py +0 -61
  88. spectre_core/receivers/library/_rspduo.py +0 -69
  89. spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  90. spectre_core/receivers/library/_test.py +0 -221
  91. spectre_core-0.0.11.dist-info/RECORD +0 -64
  92. /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
  93. {spectre_core-0.0.11.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
  94. {spectre_core-0.0.11.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -2,166 +2,326 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- from typing import Optional
5
+ from typing import TypeVar, Tuple, Iterator, Optional
6
6
  from datetime import datetime
7
- from dataclasses import dataclass
8
- from warnings import warn
9
7
 
10
8
  from matplotlib.colors import LogNorm
9
+ from matplotlib import cm
11
10
  import numpy as np
11
+ import numpy.typing as npt
12
12
 
13
13
  from spectre_core.spectrograms import Spectrogram, FrequencyCut, TimeCut
14
- from ._base import BasePanel, BaseSpectrumPanel, BaseTimeSeriesPanel
15
-
16
-
17
- @dataclass(frozen=True)
18
- class PanelNames:
19
- SPECTROGRAM : str = "spectrogram"
20
- FREQUENCY_CUTS : str = "frequency_cuts"
21
- TIME_CUTS : str = "time_cuts"
22
- INTEGRAL_OVER_FREQUENCY: str = "integral_over_frequency"
23
-
24
-
25
- class _FrequencyCutsPanel(BaseSpectrumPanel):
26
- def __init__(self,
27
- spectrogram: Spectrogram,
28
- *times: list[float | str],
29
- dBb: bool = False,
30
- peak_normalise: bool = False):
31
- super().__init__(PanelNames.FREQUENCY_CUTS,
14
+ from ._base import BaseSpectrumPanel, BaseTimeSeriesPanel
15
+ from ._panel_names import PanelName
16
+
17
+
18
+ T = TypeVar('T')
19
+ def _bind_to_colors(
20
+ values: list[T],
21
+ cmap: str = "winter"
22
+ ) -> Iterator[Tuple[T, npt.NDArray[np.float32]]]:
23
+ """
24
+ Assign RGBA colors to a list of values using a colormap.
25
+
26
+ Each value is mapped linearly to a subset of the unit interval and then converted
27
+ to an RGBA color using the specified colormap.
28
+
29
+ :param values: List of values to map to colors.
30
+ :param cmap: Name of the Matplotlib colormap to use. Defaults to "winter".
31
+ :return: An iterator of tuples, each containing a value and its corresponding RGBA color.
32
+ """
33
+ colormap = cm.get_cmap(cmap)
34
+ rgbas = colormap(np.linspace(0.1, 0.9, len(values)))
35
+ return zip(values, rgbas)
36
+
37
+
38
+ class FrequencyCutsPanel(BaseSpectrumPanel):
39
+ """
40
+ Panel for visualising spectrogram data as frequency cuts.
41
+
42
+ This panel plots spectrums corresponding to specific time instances
43
+ in the spectrogram. Each cut is drawn as a line plot, optionally normalized
44
+ or converted to decibels above the background.
45
+ """
46
+ def __init__(
47
+ self,
48
+ spectrogram: Spectrogram,
49
+ *times: float | str,
50
+ dBb: bool = False,
51
+ peak_normalise: bool = False
52
+ ) -> None:
53
+ """Initialise an instance of `FrequencyCutsPanel`.
54
+
55
+ :param spectrogram: The spectrogram to be visualised.
56
+ :param *times: Times at which to take frequency cuts. Can be floats (relative time) or
57
+ strings (formatted datetimes).
58
+ :param dBb: If True, plots the spectrums in decibels above the background. Defaults to False.
59
+ :param peak_normalise: If True, normalizes each spectrum such that its peak value is 1.
60
+ Ignored if `dBb` is True. Defaults to False.
61
+ """
62
+ super().__init__(PanelName.FREQUENCY_CUTS,
32
63
  spectrogram)
64
+
65
+ if len(times) == 0:
66
+ raise ValueError(f"You must specify the time of at least one cut in `*times`")
33
67
  self._times = times
68
+
34
69
  self._dBb = dBb
35
70
  self._peak_normalise = peak_normalise
36
- # map each time cut to the corresponding FrequencyCut dataclass
37
- self._frequency_cuts: Optional[dict[float | datetime, FrequencyCut]] = {}
71
+ self._frequency_cuts: dict[float | datetime, FrequencyCut] = {}
72
+
38
73
 
74
+ def get_frequency_cuts(
75
+ self
76
+ ) -> dict[float | datetime, FrequencyCut]:
77
+ """
78
+ Get the frequency cuts for the specified times.
39
79
 
40
- @property
41
- def frequency_cuts(self) -> dict[float | str, FrequencyCut]:
80
+ Computes and caches the spectrum for each requested time. The results are
81
+ stored as a mapping from time to the corresponding `FrequencyCut`.
82
+
83
+ :return: A dictionary mapping each time to its corresponding frequency cut.
84
+ """
42
85
  if not self._frequency_cuts:
43
86
  for time in self._times:
44
87
  frequency_cut = self._spectrogram.get_frequency_cut(time,
45
- dBb = self._dBb,
46
- peak_normalise = self._peak_normalise)
88
+ dBb=self._dBb,
89
+ peak_normalise=self._peak_normalise)
47
90
  self._frequency_cuts[frequency_cut.time] = frequency_cut
48
91
  return self._frequency_cuts
92
+
93
+
94
+ def get_cut_times(
95
+ self
96
+ ) -> list[float | datetime]:
97
+ """
98
+ Get the exact times of the frequency cuts.
49
99
 
100
+ Returns the closest matches to the times specified in the constructor.
50
101
 
51
- @property
52
- def times(self) -> list[float | datetime]:
53
- return list(self.frequency_cuts.keys())
102
+ :return: A list of times corresponding to the stored frequency cuts.
103
+ """
104
+ frequency_cuts = self.get_frequency_cuts()
105
+ return list(frequency_cuts.keys())
54
106
 
55
107
 
56
- def draw(self):
108
+ def draw(
109
+ self
110
+ ) -> None:
111
+ """Draw the frequency cuts onto the panel."""
112
+ frequency_cuts = self.get_frequency_cuts()
57
113
  for time, color in self.bind_to_colors():
58
- frequency_cut = self.frequency_cuts[time]
59
- self.ax.step(self.frequencies*1e-6, # convert to MHz
114
+ frequency_cut = frequency_cuts[time]
115
+ self.ax.step(self.frequencies, # convert to MHz
60
116
  frequency_cut.cut,
61
117
  where='mid',
62
118
  color = color)
63
119
 
64
120
 
65
- def annotate_y_axis(self) -> None:
121
+ def annotate_yaxis(
122
+ self
123
+ ) -> None:
124
+ """Annotate the y-axis of the panel based on the current state.
125
+
126
+ The y-axis label reflects whether the data is in decibels above the background (`dBb`),
127
+ normalized to peak values, or in the original units of the spectrogram.
128
+ """
66
129
  if self._dBb:
67
130
  self.ax.set_ylabel('dBb')
68
131
  elif self._peak_normalise:
69
- return # no y-axis label
132
+ # no y-axis label if we are peak normalising
133
+ return
70
134
  else:
71
- self.ax.set_ylabel(f'{self._spectrogram.spectrum_type.capitalize()}')
135
+ self.ax.set_ylabel(f'{self._spectrogram.spectrum_unit.value.capitalize()}')
72
136
 
73
137
 
74
- def bind_to_colors(self):
75
- return super().bind_to_colors(self.times, cmap = self.panel_format.cuts_cmap)
76
-
138
+ def bind_to_colors(
139
+ self
140
+ ) -> Iterator[Tuple[float | datetime, npt.NDArray[np.float32]]]:
141
+ """
142
+ Bind each frequency cut time to an RGBA color.
77
143
 
78
- class _IntegralOverFrequencyPanel(BaseTimeSeriesPanel):
79
- def __init__(self,
80
- spectrogram: Spectrogram,
81
- peak_normalise: bool = False,
82
- background_subtract: bool = False):
83
- super().__init__(PanelNames.INTEGRAL_OVER_FREQUENCY,
84
- spectrogram)
85
- self._peak_normalise = peak_normalise
86
- self._background_subtract = background_subtract
87
-
88
-
89
- def draw(self):
90
- I = self._spectrogram.integrate_over_frequency(correct_background = self._background_subtract,
91
- peak_normalise = self._peak_normalise)
92
- self.ax.step(self.times, I, where="mid", color = self.panel_format.integral_color)
93
-
94
-
95
- def annotate_y_axis(self):
96
- # no y-axis label
97
- return
144
+ Colors are assigned using the colormap specified in the panel format.
98
145
 
99
-
100
- class _TimeCutsPanel(BaseTimeSeriesPanel):
101
- def __init__(self,
102
- spectrogram: Spectrogram,
103
- *frequencies: list[float],
104
- dBb: bool = False,
105
- peak_normalise: bool = False,
106
- background_subtract: bool = False):
107
- super().__init__(PanelNames.TIME_CUTS,
146
+ :return: An iterator of tuples, each containing a cut time and its corresponding RGBA color.
147
+ """
148
+ return _bind_to_colors(self.get_cut_times(), cmap = self.panel_format.line_cmap)
149
+
150
+
151
+ class TimeCutsPanel(BaseTimeSeriesPanel):
152
+ """
153
+ Panel for visualising spectrogram data as time series of spectral components.
154
+
155
+ This panel plots the time evolution of spectral components at specific
156
+ frequencies in the spectrogram. Each time series is drawn as a line plot,
157
+ optionally normalized, background-subtracted, or converted to decibels above the background.
158
+ """
159
+ def __init__(
160
+ self,
161
+ spectrogram: Spectrogram,
162
+ *frequencies: float,
163
+ dBb: bool = False,
164
+ peak_normalise: bool = False,
165
+ background_subtract: bool = False
166
+ ) -> None:
167
+ """Initialise an instance of `TimeCutsPanel`.
168
+
169
+ :param spectrogram: The spectrogram to be visualised.
170
+ :param *frequencies: Frequencies at which to extract time series.
171
+ :param dBb: If True, returns the cuts in decibels above the background. Defaults to False.
172
+ :param peak_normalise: If True, normalizes each time series so its peak value is 1.
173
+ Ignored if `dBb` is True. Defaults to False.
174
+ :param background_subtract: If True, subtracts the background from each time series.
175
+ Ignored if `dBb` is True. Defaults to False.
176
+ """
177
+ super().__init__(PanelName.TIME_CUTS,
108
178
  spectrogram)
179
+
180
+ if len(frequencies) == 0:
181
+ raise ValueError(f"You must specify the frequency of at least one cut in `*frequencies`.")
109
182
  self._frequencies = frequencies
183
+
110
184
  self._dBb = dBb
111
185
  self._peak_normalise = peak_normalise
112
186
  self._background_subtract = background_subtract
113
- # map each cut frequency to the corresponding TimeCut dataclass
114
- self._time_cuts: Optional[dict[float, TimeCut]] = {}
187
+ self._time_cuts: dict[float, TimeCut] = {}
115
188
 
116
189
 
117
- @property
118
- def time_cuts(self) -> dict[float, TimeCut]:
190
+ def get_time_cuts(
191
+ self
192
+ ) -> dict[float, TimeCut]:
193
+ """
194
+ Get the time cuts for the specified frequencies.
195
+
196
+ Computes and caches the time series for each requested frequency. The results
197
+ are stored as a mapping from frequency to `TimeCut`.
198
+
199
+ :return: A dictionary mapping each frequency to its corresponding time cut.
200
+ """
119
201
  if not self._time_cuts:
120
202
  for frequency in self._frequencies:
121
203
  time_cut = self._spectrogram.get_time_cut(frequency,
122
- dBb = self._dBb,
123
- peak_normalise = self._peak_normalise,
124
- correct_background = self._background_subtract,
125
- return_time_type=self._time_type)
204
+ dBb=self._dBb,
205
+ peak_normalise=self._peak_normalise,
206
+ correct_background=self._background_subtract,
207
+ return_time_type=self.time_type)
126
208
  self._time_cuts[time_cut.frequency] = time_cut
127
209
  return self._time_cuts
128
210
 
129
211
 
130
- @property
131
- def frequencies(self) -> list[float]:
132
- return list(self.time_cuts.keys())
212
+ def get_frequencies(
213
+ self
214
+ ) -> list[float]:
215
+ """
216
+ Get the exact frequencies for the spectral components being plotted.
133
217
 
218
+ :return: A list of frequencies corresponding to the stored time cuts.
219
+ """
220
+ time_cuts = self.get_time_cuts()
221
+ return list(time_cuts.keys())
134
222
 
135
- def draw(self):
223
+
224
+ def draw(
225
+ self
226
+ ) -> None:
227
+ """Draw the time series for each spectral component onto the panel."""
228
+ time_cuts = self.get_time_cuts()
136
229
  for frequency, color in self.bind_to_colors():
137
- time_cut = self.time_cuts[frequency]
230
+ time_cut = time_cuts[frequency]
138
231
  self.ax.step(self.times,
139
232
  time_cut.cut,
140
233
  where='mid',
141
234
  color = color)
142
235
 
143
236
 
144
- def annotate_y_axis(self) -> None:
237
+ def annotate_yaxis(
238
+ self
239
+ ) -> None:
240
+ """
241
+ Annotate the y-axis of the panel based on the current state.
242
+
243
+ The y-axis label reflects whether the data is in decibels above the background (`dBb`),
244
+ normalized to peak values, or in the original units of the spectrogram.
245
+ """
145
246
  if self._dBb:
146
247
  self.ax.set_ylabel('dBb')
147
248
  elif self._peak_normalise:
148
- return # no y-axis label
249
+ return # no y-axis label if we are peak normalising.
149
250
  else:
150
- self.ax.set_ylabel(f'{self._spectrogram.spectrum_type.capitalize()}')
251
+ self.ax.set_ylabel(f'{self._spectrogram.spectrum_unit.value.capitalize()}')
151
252
 
152
253
 
153
- def bind_to_colors(self):
154
- return super().bind_to_colors(self.frequencies, cmap = self.panel_format.cuts_cmap)
254
+ def bind_to_colors(
255
+ self
256
+ ) -> Iterator[Tuple[float, npt.NDArray[np.float32]]]:
257
+ """
258
+ Bind each spectral component's frequency to an RGBA color.
259
+
260
+ Colors are assigned using the colormap specified in the panel format.
261
+
262
+ :return: An iterator of tuples, each containing a frequency and its corresponding RGBA color.
263
+ """
264
+ return _bind_to_colors(self.get_frequencies(), cmap = self.panel_format.line_cmap)
155
265
 
156
266
 
157
- class _SpectrogramPanel(BaseTimeSeriesPanel):
267
+ class IntegralOverFrequencyPanel(BaseTimeSeriesPanel):
268
+ """Panel for visualising the spectrogram integrated over frequency.
269
+
270
+ This panel plots the spectrogram numerically integrated over frequency as a time
271
+ series. The result can be normalized to its peak value or adjusted by subtracting
272
+ the background.
273
+ """
158
274
  def __init__(self,
159
275
  spectrogram: Spectrogram,
160
- log_norm: bool = False,
161
- dBb: bool = False,
162
- vmin: float | None = -1,
163
- vmax: float | None = 2):
164
- super().__init__(PanelNames.SPECTROGRAM,
276
+ peak_normalise: bool = False,
277
+ background_subtract: bool = False):
278
+ """Initialise an instance of `IntegralOverFrequencyPanel`.
279
+
280
+ :param spectrogram: The spectrogram to be visualised.
281
+ :param peak_normalise: If True, normalizes the integral so its peak value is 1. Defaults to False.
282
+ :param background_subtract: If True, subtracts the background after computing the integral. Defaults to False.
283
+ """
284
+ super().__init__(PanelName.INTEGRAL_OVER_FREQUENCY,
285
+ spectrogram)
286
+ self._peak_normalise = peak_normalise
287
+ self._background_subtract = background_subtract
288
+
289
+
290
+ def draw(self):
291
+ """Integrate the spectrogram over frequency and plot the result."""
292
+ I = self._spectrogram.integrate_over_frequency(correct_background=self._background_subtract,
293
+ peak_normalise=self._peak_normalise)
294
+ self.ax.step(self.times, I, where="mid", color = self.panel_format.line_color)
295
+
296
+
297
+ def annotate_yaxis(self):
298
+ """This panel does not annotate the y-axis."""
299
+
300
+
301
+ class SpectrogramPanel(BaseTimeSeriesPanel):
302
+ """
303
+ Panel for visualising the full spectrogram.
304
+
305
+ This panel plots the spectrogram as a colormap, with optional log normalization or
306
+ in units of decibels above the background.
307
+ """
308
+ def __init__(
309
+ self,
310
+ spectrogram: Spectrogram,
311
+ log_norm: bool = False,
312
+ dBb: bool = False,
313
+ vmin: Optional[float] = None,
314
+ vmax: Optional[float] = None,
315
+ ) -> None:
316
+ """Initialise an instance of `SpectrogramPanel`.
317
+
318
+ :param spectrogram: The spectrogram to be visualised.
319
+ :param log_norm: If True, normalizes the values to the 0-1 range on a logarithmic scale. Defaults to False.
320
+ :param dBb: If True, plots the spectrogram in decibels above the background. Defaults to False.
321
+ :param vmin: Minimum value for the colormap. Only applies if `dBb` is True. Defaults to None.
322
+ :param vmax: Maximum value for the colormap. Only applies if `dBb` is True. Defaults to None.
323
+ """
324
+ super().__init__(PanelName.SPECTROGRAM,
165
325
  spectrogram)
166
326
  self._log_norm = log_norm
167
327
  self._dBb = dBb
@@ -169,66 +329,106 @@ class _SpectrogramPanel(BaseTimeSeriesPanel):
169
329
  self._vmax = vmax
170
330
 
171
331
 
172
- def draw(self):
173
- dynamic_spectra = self._spectrogram.dynamic_spectra_as_dBb if self._dBb else self._spectrogram.dynamic_spectra
332
+ def _draw_dBb(self) -> None:
333
+ """Plot the spectrogram in decibels above the background (dBb).
174
334
 
175
- norm = LogNorm(vmin=np.nanmin(dynamic_spectra[dynamic_spectra > 0]),
176
- vmax=np.nanmax(dynamic_spectra)) if self._log_norm else None
335
+ This method handles plotting the spectrogram with dBb scaling, applying
336
+ colormap bounds (`vmin` and `vmax`) and adding a colorbar to the panel.
337
+ """
338
+ dynamic_spectra = self._spectrogram.compute_dynamic_spectra_dBb()
177
339
 
178
-
179
- if self._log_norm and (self._vmin or self._vmax):
180
- warn("vmin/vmax will be ignored while using log_norm.")
181
- self._vmin = None
182
- self._vmax = None
183
-
184
- # Plot the spectrogram with kwargs
185
- pcm = self.ax.pcolormesh(self.times,
186
- self._spectrogram.frequencies * 1e-6,
187
- dynamic_spectra,
188
- vmin=self._vmin,
189
- vmax=self._vmax,
190
- norm=norm,
191
- cmap=self.panel_format.spectrogram_cmap)
340
+ # use defaults if neither vmin or vmax is specified
341
+ vmin = self._vmin or -1
342
+ vmax = self._vmax or 2
343
+
344
+ # Plot the spectrogram
345
+ pcm = self.ax.pcolormesh(
346
+ self.times,
347
+ self._spectrogram.frequencies,
348
+ dynamic_spectra,
349
+ vmin=vmin,
350
+ vmax=vmax,
351
+ cmap=self.panel_format.spectrogram_cmap,
352
+ )
353
+
354
+ # Add colorbar
355
+ cbar_ticks = np.linspace(vmin, vmax, 6)
356
+ cbar = self.fig.colorbar(pcm, ax=self.ax, ticks=cbar_ticks)
357
+ cbar.set_label("dBb")
358
+
359
+
360
+ def _draw_normal(self) -> None:
361
+ """Plot the spectrogram with optional logarithmic normalization.
362
+
363
+ This method handles plotting the spectrogram without dBb scaling, using
364
+ linear or log normalization based on the `log_norm` attribute.
365
+ """
366
+ dynamic_spectra = self._spectrogram.dynamic_spectra
192
367
 
193
- # Add colorbar if dBb is used
368
+
369
+ if self._log_norm:
370
+ norm = LogNorm(vmin=np.nanmin(dynamic_spectra[dynamic_spectra > 0]),
371
+ vmax=np.nanmax(dynamic_spectra))
372
+ else:
373
+ norm = None
374
+
375
+ # Plot the spectrogram
376
+ self.ax.pcolormesh(
377
+ self.times,
378
+ self._spectrogram.frequencies,
379
+ dynamic_spectra,
380
+ cmap=self.panel_format.spectrogram_cmap,
381
+ norm=norm,
382
+ )
383
+
384
+
385
+ def draw(self) -> None:
386
+ """Draw the spectrogram onto the panel."""
194
387
  if self._dBb:
195
- cbar = self.fig.colorbar(pcm,
196
- ax=self.ax,
197
- ticks=np.linspace(self._vmin, self._vmax, 6, dtype=int))
198
- cbar.set_label('dBb')
388
+ self._draw_dBb()
389
+ else:
390
+ self._draw_normal()
199
391
 
200
392
 
201
- def annotate_y_axis(self) -> None:
202
- self.ax.set_ylabel('Frequency [MHz]')
393
+ def annotate_yaxis(
394
+ self
395
+ ) -> None:
396
+ """Annotate the yaxis, assuming units of Hz."""
397
+ self.ax.set_ylabel('Frequency [Hz]')
203
398
  return
204
399
 
205
400
 
206
- def overlay_cuts(self, cuts_panel: BasePanel) -> None:
207
- if cuts_panel.name == PanelNames.TIME_CUTS:
208
- self._overlay_time_cuts(cuts_panel)
209
- elif cuts_panel.name == PanelNames.FREQUENCY_CUTS:
210
- self._overlay_frequency_cuts(cuts_panel)
211
-
212
-
213
- def _overlay_time_cuts(self, time_cuts_panel: _TimeCutsPanel) -> None:
214
- for frequency, color in time_cuts_panel.bind_to_colors():
215
- self.ax.axhline(frequency*1e-6, # convert to MHz
401
+ def overlay_time_cuts(
402
+ self,
403
+ cuts_panel: TimeCutsPanel
404
+ ) -> None:
405
+ """
406
+ Overlay horizontal lines on the spectrogram to indicate time cuts.
407
+
408
+ The lines correspond to the frequencies of the cuts on a `TimeCutsPanel`.
409
+ Colors are matched to the lines on the `TimeCutsPanel`.
410
+
411
+ :param cuts_panel: The `TimeCutsPanel` containing the cut frequencies to overlay.
412
+ """
413
+ for frequency, color in cuts_panel.bind_to_colors():
414
+ self.ax.axhline(frequency,
216
415
  color = color,
217
- linewidth=self.panel_format.line_width
218
- )
416
+ linewidth=self.panel_format.line_width)
219
417
 
220
418
 
221
- def _overlay_frequency_cuts(self, frequency_cuts_panel: _FrequencyCutsPanel) -> None:
222
- for time, color in frequency_cuts_panel.bind_to_colors():
419
+ def overlay_frequency_cuts(
420
+ self,
421
+ cuts_panel: FrequencyCutsPanel
422
+ ) -> None:
423
+ """
424
+ Overlay vertical lines on the spectrogram to indicate frequency cuts.
425
+
426
+ The lines correspond to the times of the cuts on a `FrequencyCutsPanel`.
427
+ Colors are matched to the lines on the `FrequencyCutsPanel`.
428
+
429
+ :param cuts_panel: The `FrequencyCutsPanel` containing the cut times to overlay.
430
+ """
431
+ for time, color in cuts_panel.bind_to_colors():
223
432
  self.ax.axvline(time,
224
433
  color = color,
225
- linewidth=self.panel_format.line_width
226
- )
227
-
228
-
229
- @dataclass(frozen=True)
230
- class Panels:
231
- TimeCuts = _TimeCutsPanel
232
- FrequencyCuts = _FrequencyCutsPanel
233
- Spectrogram = _SpectrogramPanel
234
- IntegralOverFrequency = _IntegralOverFrequencyPanel
434
+ linewidth=self.panel_format.line_width)
@@ -2,13 +2,15 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- # event handler class decorators take effect on import
6
- from .library._fixed_center_frequency import _EventHandler
7
- from .library._swept_center_frequency import _EventHandler
5
+ """Real-time, extensible post-processing of SDR data into spectrograms."""
8
6
 
9
- from ._factory import get_event_handler_from_tag
10
- from ._post_processor import PostProcessor
7
+ from .plugins._fixed_center_frequency import FixedEventHandler
8
+ from .plugins._swept_center_frequency import SweptEventHandler
9
+
10
+ from ._factory import get_event_handler, get_event_handler_cls_from_tag
11
+ from ._post_processor import start_post_processor
11
12
 
12
13
  __all__ = [
13
- "PostProcessor", "get_event_handler_from_tag"
14
+ "FixedEventHandler", "SweptEventHandler", "start_post_processor",
15
+ "get_event_handler", "get_event_handler_cls_from_tag"
14
16
  ]