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.
- spectre_core/_file_io/__init__.py +1 -3
- spectre_core/_file_io/file_handlers.py +170 -65
- spectre_core/batches/__init__.py +21 -0
- spectre_core/batches/_base.py +238 -0
- spectre_core/batches/_batches.py +247 -0
- spectre_core/batches/_factory.py +69 -0
- spectre_core/batches/_register.py +30 -0
- spectre_core/batches/plugins/_batch_keys.py +16 -0
- spectre_core/batches/plugins/_callisto.py +183 -0
- spectre_core/batches/plugins/_iq_stream.py +354 -0
- spectre_core/capture_configs/__init__.py +17 -13
- spectre_core/capture_configs/_capture_config.py +93 -34
- spectre_core/capture_configs/_capture_modes.py +22 -0
- spectre_core/capture_configs/_capture_templates.py +207 -122
- spectre_core/capture_configs/_parameters.py +116 -46
- spectre_core/capture_configs/_pconstraints.py +86 -35
- spectre_core/capture_configs/_pnames.py +49 -0
- spectre_core/capture_configs/_ptemplates.py +389 -346
- spectre_core/capture_configs/_pvalidators.py +121 -77
- spectre_core/config/__init__.py +7 -9
- spectre_core/config/_paths.py +66 -26
- spectre_core/config/_time_formats.py +15 -8
- spectre_core/exceptions.py +2 -4
- spectre_core/jobs/__init__.py +14 -0
- spectre_core/jobs/_jobs.py +111 -0
- spectre_core/jobs/_workers.py +171 -0
- spectre_core/logs/__init__.py +17 -0
- spectre_core/logs/_configure.py +67 -0
- spectre_core/logs/_decorators.py +33 -0
- spectre_core/logs/_logs.py +228 -0
- spectre_core/logs/_process_types.py +14 -0
- spectre_core/plotting/__init__.py +4 -2
- spectre_core/plotting/_base.py +204 -102
- spectre_core/plotting/_format.py +17 -4
- spectre_core/plotting/_panel_names.py +18 -0
- spectre_core/plotting/_panel_stack.py +167 -53
- spectre_core/plotting/_panels.py +341 -141
- spectre_core/post_processing/__init__.py +8 -6
- spectre_core/post_processing/_base.py +71 -45
- spectre_core/post_processing/_factory.py +42 -12
- spectre_core/post_processing/_post_processor.py +27 -29
- spectre_core/post_processing/_register.py +22 -6
- spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
- spectre_core/post_processing/plugins/_swept_center_frequency.py +439 -0
- spectre_core/py.typed +0 -0
- spectre_core/receivers/__init__.py +10 -7
- spectre_core/receivers/_base.py +220 -69
- spectre_core/receivers/_factory.py +53 -7
- spectre_core/receivers/_register.py +30 -9
- spectre_core/receivers/_spec_names.py +26 -15
- spectre_core/receivers/plugins/__init__.py +0 -0
- spectre_core/receivers/plugins/_receiver_names.py +16 -0
- spectre_core/receivers/plugins/_rsp1a.py +59 -0
- spectre_core/receivers/plugins/_rspduo.py +67 -0
- spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
- spectre_core/receivers/plugins/_test.py +218 -0
- spectre_core/receivers/plugins/gr/_base.py +80 -0
- spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +45 -55
- spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +65 -78
- spectre_core/receivers/{gr → plugins/gr}/_test.py +36 -34
- spectre_core/spectrograms/__init__.py +5 -3
- spectre_core/spectrograms/_analytical.py +121 -72
- spectre_core/spectrograms/_array_operations.py +103 -36
- spectre_core/spectrograms/_spectrogram.py +410 -203
- spectre_core/spectrograms/_transform.py +199 -188
- spectre_core/wgetting/__init__.py +4 -2
- spectre_core/wgetting/_callisto.py +178 -127
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
- spectre_core-0.0.13.dist-info/RECORD +75 -0
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
- spectre_core/chunks/__init__.py +0 -22
- spectre_core/chunks/_base.py +0 -116
- spectre_core/chunks/_chunks.py +0 -200
- spectre_core/chunks/_factory.py +0 -25
- spectre_core/chunks/_register.py +0 -15
- spectre_core/chunks/library/_callisto.py +0 -98
- spectre_core/chunks/library/_fixed_center_frequency.py +0 -128
- spectre_core/chunks/library/_swept_center_frequency.py +0 -103
- spectre_core/logging/__init__.py +0 -11
- spectre_core/logging/_configure.py +0 -35
- spectre_core/logging/_decorators.py +0 -19
- spectre_core/logging/_log_handlers.py +0 -176
- spectre_core/post_processing/library/_fixed_center_frequency.py +0 -115
- spectre_core/post_processing/library/_swept_center_frequency.py +0 -382
- spectre_core/receivers/gr/_base.py +0 -33
- spectre_core/receivers/library/_rsp1a.py +0 -61
- spectre_core/receivers/library/_rspduo.py +0 -69
- spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
- spectre_core/receivers/library/_test.py +0 -221
- spectre_core-0.0.11.dist-info/RECORD +0 -64
- /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
spectre_core/plotting/_panels.py
CHANGED
@@ -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
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
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
|
46
|
-
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
|
-
|
52
|
-
|
53
|
-
|
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(
|
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 =
|
59
|
-
self.ax.step(self.frequencies
|
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
|
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
|
-
|
132
|
+
# no y-axis label if we are peak normalising
|
133
|
+
return
|
70
134
|
else:
|
71
|
-
self.ax.set_ylabel(f'{self._spectrogram.
|
135
|
+
self.ax.set_ylabel(f'{self._spectrogram.spectrum_unit.value.capitalize()}')
|
72
136
|
|
73
137
|
|
74
|
-
def bind_to_colors(
|
75
|
-
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
114
|
-
self._time_cuts: Optional[dict[float, TimeCut]] = {}
|
187
|
+
self._time_cuts: dict[float, TimeCut] = {}
|
115
188
|
|
116
189
|
|
117
|
-
|
118
|
-
|
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
|
123
|
-
peak_normalise
|
124
|
-
correct_background
|
125
|
-
return_time_type=self.
|
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
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
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 =
|
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
|
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.
|
251
|
+
self.ax.set_ylabel(f'{self._spectrogram.spectrum_unit.value.capitalize()}')
|
151
252
|
|
152
253
|
|
153
|
-
def bind_to_colors(
|
154
|
-
|
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
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
173
|
-
|
332
|
+
def _draw_dBb(self) -> None:
|
333
|
+
"""Plot the spectrogram in decibels above the background (dBb).
|
174
334
|
|
175
|
-
|
176
|
-
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
cbar.set_label('dBb')
|
388
|
+
self._draw_dBb()
|
389
|
+
else:
|
390
|
+
self._draw_normal()
|
199
391
|
|
200
392
|
|
201
|
-
def
|
202
|
-
self
|
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
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
222
|
-
|
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
|
-
|
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 .
|
10
|
-
from .
|
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
|
-
"
|
14
|
+
"FixedEventHandler", "SweptEventHandler", "start_post_processor",
|
15
|
+
"get_event_handler", "get_event_handler_cls_from_tag"
|
14
16
|
]
|