spectre-core 0.0.22__py3-none-any.whl → 0.0.23__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 +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 +13 -8
- spectre_core/config/_paths.py +18 -35
- 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 +13 -3
- spectre_core/plotting/_base.py +64 -138
- spectre_core/plotting/_format.py +10 -8
- spectre_core/plotting/_panel_names.py +7 -5
- spectre_core/plotting/_panel_stack.py +82 -115
- spectre_core/plotting/_panels.py +120 -155
- 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 +289 -322
- 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.23.dist-info}/METADATA +9 -23
- spectre_core-0.0.23.dist-info/RECORD +79 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.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.23.dist-info}/licenses/LICENSE +0 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
#
|
2
2
|
# USRP top blocks
|
3
3
|
#
|
4
4
|
|
@@ -7,6 +7,7 @@ from dataclasses import dataclass
|
|
7
7
|
import time
|
8
8
|
|
9
9
|
from logging import getLogger
|
10
|
+
|
10
11
|
_LOGGER = getLogger(__name__)
|
11
12
|
|
12
13
|
from spectre_core.capture_configs import Parameters, PName
|
@@ -15,32 +16,28 @@ from ._base import capture, spectre_top_block
|
|
15
16
|
|
16
17
|
|
17
18
|
class _fixed_center_frequency(spectre_top_block):
|
18
|
-
def flowgraph(
|
19
|
-
self,
|
20
|
-
tag: str,
|
21
|
-
parameters: Parameters
|
22
|
-
) -> None:
|
19
|
+
def flowgraph(self, tag: str, parameters: Parameters) -> None:
|
23
20
|
# OOT moudle inline imports
|
24
21
|
from gnuradio import spectre
|
25
22
|
from gnuradio import uhd
|
26
23
|
|
27
|
-
# Unpack capture config parameters
|
28
|
-
sample_rate
|
29
|
-
gain
|
30
|
-
center_freq
|
24
|
+
# Unpack capture config parameters
|
25
|
+
sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
|
26
|
+
gain = parameters.get_parameter_value(PName.GAIN)
|
27
|
+
center_freq = parameters.get_parameter_value(PName.CENTER_FREQUENCY)
|
31
28
|
master_clock_rate = parameters.get_parameter_value(PName.MASTER_CLOCK_RATE)
|
32
|
-
wire_format
|
33
|
-
batch_size
|
34
|
-
bandwidth
|
29
|
+
wire_format = parameters.get_parameter_value(PName.WIRE_FORMAT)
|
30
|
+
batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
|
31
|
+
bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
|
35
32
|
|
36
33
|
# Blocks
|
37
|
-
master_clock_rate = f"master_clock_rate={master_clock_rate}"
|
34
|
+
master_clock_rate = f"master_clock_rate={master_clock_rate}"
|
38
35
|
self.uhd_usrp_source_0 = uhd.usrp_source(
|
39
|
-
",".join(("",
|
36
|
+
",".join(("", "", master_clock_rate)),
|
40
37
|
uhd.stream_args(
|
41
38
|
cpu_format="fc32",
|
42
39
|
otw_format=wire_format,
|
43
|
-
args=
|
40
|
+
args="",
|
44
41
|
channels=[0],
|
45
42
|
),
|
46
43
|
)
|
@@ -54,51 +51,47 @@ class _fixed_center_frequency(spectre_top_block):
|
|
54
51
|
self.uhd_usrp_source_0.set_auto_dc_offset(False, 0)
|
55
52
|
self.uhd_usrp_source_0.set_auto_iq_balance(False, 0)
|
56
53
|
self.uhd_usrp_source_0.set_gain(gain, 0)
|
57
|
-
self.spectre_batched_file_sink_0 = spectre.batched_file_sink(
|
58
|
-
|
59
|
-
|
60
|
-
sample_rate, False,
|
61
|
-
'rx_freq',
|
62
|
-
0)
|
63
|
-
|
54
|
+
self.spectre_batched_file_sink_0 = spectre.batched_file_sink(
|
55
|
+
get_batches_dir_path(), tag, batch_size, sample_rate, False, "rx_freq", 0
|
56
|
+
)
|
64
57
|
|
65
58
|
# Connections
|
66
59
|
self.connect((self.uhd_usrp_source_0, 0), (self.spectre_batched_file_sink_0, 0))
|
67
60
|
|
68
61
|
|
69
62
|
class _swept_center_frequency(spectre_top_block):
|
70
|
-
def flowgraph(
|
71
|
-
self,
|
72
|
-
tag: str,
|
73
|
-
parameters: Parameters
|
74
|
-
) -> None:
|
63
|
+
def flowgraph(self, tag: str, parameters: Parameters) -> None:
|
75
64
|
# OOT module inline imports
|
76
65
|
from gnuradio import spectre
|
77
66
|
from gnuradio import uhd
|
78
67
|
|
79
68
|
# Unpack capture config parameters
|
80
|
-
sample_rate
|
81
|
-
bandwidth
|
82
|
-
min_frequency
|
83
|
-
max_frequency
|
84
|
-
frequency_step
|
85
|
-
samples_per_step
|
69
|
+
sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
|
70
|
+
bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
|
71
|
+
min_frequency = parameters.get_parameter_value(PName.MIN_FREQUENCY)
|
72
|
+
max_frequency = parameters.get_parameter_value(PName.MAX_FREQUENCY)
|
73
|
+
frequency_step = parameters.get_parameter_value(PName.FREQUENCY_STEP)
|
74
|
+
samples_per_step = parameters.get_parameter_value(PName.SAMPLES_PER_STEP)
|
86
75
|
master_clock_rate = parameters.get_parameter_value(PName.MASTER_CLOCK_RATE)
|
87
|
-
master_clock_rate = master_clock_rate = parameters.get_parameter_value(
|
88
|
-
|
89
|
-
|
90
|
-
|
76
|
+
master_clock_rate = master_clock_rate = parameters.get_parameter_value(
|
77
|
+
PName.MASTER_CLOCK_RATE
|
78
|
+
)
|
79
|
+
wire_format = parameters.get_parameter_value(PName.WIRE_FORMAT)
|
80
|
+
gain = parameters.get_parameter_value(PName.GAIN)
|
81
|
+
batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
|
91
82
|
|
92
83
|
# Blocks
|
93
|
-
_LOGGER.warning(
|
94
|
-
|
95
|
-
|
84
|
+
_LOGGER.warning(
|
85
|
+
f"USRP frequency sweep modes will not work as expected until a known bug is fixed in the USRP source block. "
|
86
|
+
f"Please refer to this GitHub issue for more information: https://github.com/gnuradio/gnuradio/issues/7725"
|
87
|
+
)
|
88
|
+
master_clock_rate = f"master_clock_rate={master_clock_rate}"
|
96
89
|
self.uhd_usrp_source_0 = uhd.usrp_source(
|
97
|
-
",".join(("",
|
90
|
+
",".join(("", "", master_clock_rate)),
|
98
91
|
uhd.stream_args(
|
99
92
|
cpu_format="fc32",
|
100
93
|
otw_format=wire_format,
|
101
|
-
args=
|
94
|
+
args="",
|
102
95
|
channels=[0],
|
103
96
|
),
|
104
97
|
)
|
@@ -112,23 +105,30 @@ class _swept_center_frequency(spectre_top_block):
|
|
112
105
|
self.uhd_usrp_source_0.set_auto_iq_balance(False, 0)
|
113
106
|
self.uhd_usrp_source_0.set_gain(gain, 0)
|
114
107
|
|
115
|
-
self.spectre_sweep_driver_0 = spectre.sweep_driver(
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
108
|
+
self.spectre_sweep_driver_0 = spectre.sweep_driver(
|
109
|
+
min_frequency,
|
110
|
+
max_frequency,
|
111
|
+
frequency_step,
|
112
|
+
sample_rate,
|
113
|
+
samples_per_step,
|
114
|
+
"freq",
|
115
|
+
)
|
116
|
+
|
117
|
+
self.spectre_batched_file_sink_0 = spectre.batched_file_sink(
|
118
|
+
get_batches_dir_path(),
|
119
|
+
tag,
|
120
|
+
batch_size,
|
121
|
+
sample_rate,
|
122
|
+
True,
|
123
|
+
"rx_freq",
|
124
|
+
min_frequency,
|
125
|
+
)
|
129
126
|
|
130
127
|
# Connections
|
131
|
-
self.msg_connect(
|
128
|
+
self.msg_connect(
|
129
|
+
(self.spectre_sweep_driver_0, "retune_command"),
|
130
|
+
(self.uhd_usrp_source_0, "command"),
|
131
|
+
)
|
132
132
|
self.connect((self.uhd_usrp_source_0, 0), (self.spectre_batched_file_sink_0, 0))
|
133
133
|
self.connect((self.uhd_usrp_source_0, 0), (self.spectre_sweep_driver_0, 0))
|
134
134
|
|
@@ -136,4 +136,6 @@ class _swept_center_frequency(spectre_top_block):
|
|
136
136
|
@dataclass(frozen=True)
|
137
137
|
class CaptureMethod:
|
138
138
|
fixed_center_frequency = partial(capture, top_block_cls=_fixed_center_frequency)
|
139
|
-
swept_center_frequency = partial(
|
139
|
+
swept_center_frequency = partial(
|
140
|
+
capture, top_block_cls=_swept_center_frequency, max_noutput_items=1024
|
141
|
+
)
|
@@ -4,20 +4,28 @@
|
|
4
4
|
|
5
5
|
"""Create and transform spectrogram data."""
|
6
6
|
|
7
|
-
from ._analytical import
|
8
|
-
|
9
|
-
)
|
10
|
-
from ._spectrogram import (
|
11
|
-
Spectrogram, FrequencyCut, TimeCut, SpectrumUnit, TimeType
|
12
|
-
)
|
7
|
+
from ._analytical import get_analytical_spectrogram, validate_analytically, TestResults
|
8
|
+
from ._spectrogram import Spectrogram, FrequencyCut, TimeCut, SpectrumUnit, TimeType
|
13
9
|
from ._transform import (
|
14
|
-
frequency_chop,
|
15
|
-
|
10
|
+
frequency_chop,
|
11
|
+
time_chop,
|
12
|
+
frequency_average,
|
13
|
+
time_average,
|
14
|
+
join_spectrograms,
|
16
15
|
)
|
17
16
|
|
18
17
|
__all__ = [
|
19
|
-
"get_analytical_spectrogram",
|
20
|
-
"
|
21
|
-
"
|
22
|
-
"
|
23
|
-
|
18
|
+
"get_analytical_spectrogram",
|
19
|
+
"validate_analytically",
|
20
|
+
"TestResults",
|
21
|
+
"Spectrogram",
|
22
|
+
"FrequencyCut",
|
23
|
+
"TimeCut",
|
24
|
+
"SpectrumUnit",
|
25
|
+
"frequency_chop",
|
26
|
+
"time_chop",
|
27
|
+
"frequency_average",
|
28
|
+
"time_average",
|
29
|
+
"join_spectrograms",
|
30
|
+
"TimeType",
|
31
|
+
]
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# SPDX-FileCopyrightText: © 2024-2025 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
3
2
|
# This file is part of SPECTRE
|
4
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
@@ -13,6 +12,7 @@ from spectre_core.exceptions import ModeNotFoundError
|
|
13
12
|
from ._spectrogram import Spectrogram, SpectrumUnit
|
14
13
|
from ._array_operations import is_close
|
15
14
|
|
15
|
+
|
16
16
|
@dataclass
|
17
17
|
class TestResults:
|
18
18
|
"""
|
@@ -22,73 +22,56 @@ class TestResults:
|
|
22
22
|
:ivar frequencies_validated: Whether the frequency arrays match.
|
23
23
|
:ivar spectrum_validated: Maps the relative time of each spectrum to its match results.
|
24
24
|
"""
|
25
|
+
|
25
26
|
times_validated: bool = False
|
26
27
|
frequencies_validated: bool = False
|
27
28
|
spectrum_validated: dict[float, bool] = field(default_factory=dict)
|
28
29
|
|
29
30
|
@property
|
30
|
-
def num_validated_spectrums(
|
31
|
-
self
|
32
|
-
) -> int:
|
31
|
+
def num_validated_spectrums(self) -> int:
|
33
32
|
"""Returns the count of spectrums that successfully passed validation."""
|
34
33
|
return sum(is_validated for is_validated in self.spectrum_validated.values())
|
35
34
|
|
36
|
-
|
37
35
|
@property
|
38
|
-
def num_invalid_spectrums(
|
39
|
-
self
|
40
|
-
) -> int:
|
36
|
+
def num_invalid_spectrums(self) -> int:
|
41
37
|
"""Returns the count of spectrums that failed validation."""
|
42
38
|
return len(self.spectrum_validated) - self.num_validated_spectrums
|
43
|
-
|
44
|
-
|
45
|
-
def to_dict(
|
46
|
-
self
|
47
|
-
) -> dict[str, bool | dict[float, bool]]:
|
39
|
+
|
40
|
+
def to_dict(self) -> dict[str, bool | dict[float, bool]]:
|
48
41
|
"""Converts the instance into a serialisable dictionary."""
|
49
42
|
return {
|
50
|
-
"times_validated"
|
43
|
+
"times_validated": self.times_validated,
|
51
44
|
"frequencies_validated": self.frequencies_validated,
|
52
|
-
"spectrum_validated"
|
45
|
+
"spectrum_validated": self.spectrum_validated,
|
53
46
|
}
|
54
47
|
|
55
48
|
|
56
49
|
class _AnalyticalFactory:
|
57
50
|
"""Factory for creating analytical spectrograms."""
|
58
|
-
|
59
|
-
|
60
|
-
) -> None:
|
51
|
+
|
52
|
+
def __init__(self) -> None:
|
61
53
|
"""Initialises an instance of the `_AnalyticalFactory` class."""
|
62
54
|
self._builders: dict[str, Callable[[int, CaptureConfig], Spectrogram]] = {
|
63
|
-
"cosine_signal_1"
|
64
|
-
"tagged_staircase": self._tagged_staircase
|
55
|
+
"cosine_signal_1": self._cosine_signal_1,
|
56
|
+
"tagged_staircase": self._tagged_staircase,
|
65
57
|
}
|
66
58
|
|
67
|
-
|
68
59
|
@property
|
69
|
-
def builders(
|
70
|
-
self
|
71
|
-
) -> dict[str, Callable[[int, CaptureConfig], Spectrogram]]:
|
60
|
+
def builders(self) -> dict[str, Callable[[int, CaptureConfig], Spectrogram]]:
|
72
61
|
"""
|
73
62
|
Provides a mapping from each `Test` receiver mode to its corresponding builder method.
|
74
63
|
|
75
64
|
Each builder method generates the expected spectrograms for a session run in the associated mode.
|
76
65
|
"""
|
77
66
|
return self._builders
|
78
|
-
|
79
67
|
|
80
68
|
@property
|
81
|
-
def test_modes(
|
82
|
-
self
|
83
|
-
) -> list[str]:
|
69
|
+
def test_modes(self) -> list[str]:
|
84
70
|
"""Returns the available modes for the `Test` receiver."""
|
85
71
|
return list(self.builders.keys())
|
86
|
-
|
87
72
|
|
88
73
|
def get_spectrogram(
|
89
|
-
self,
|
90
|
-
num_spectrums: int,
|
91
|
-
capture_config: CaptureConfig
|
74
|
+
self, num_spectrums: int, capture_config: CaptureConfig
|
92
75
|
) -> Spectrogram:
|
93
76
|
"""
|
94
77
|
Generates an analytical spectrogram based on the capture config for a `Test` receiver.
|
@@ -101,29 +84,31 @@ class _AnalyticalFactory:
|
|
101
84
|
:return: The expected spectrogram for running a session with the `Test` receiver in the specified mode.
|
102
85
|
"""
|
103
86
|
if capture_config.receiver_name != "test":
|
104
|
-
raise ValueError(
|
105
|
-
|
87
|
+
raise ValueError(
|
88
|
+
f"Input capture config must correspond to the test receiver"
|
89
|
+
)
|
90
|
+
|
106
91
|
builder_method = self.builders.get(capture_config.receiver_mode)
|
107
92
|
if builder_method is None:
|
108
|
-
raise ModeNotFoundError(
|
109
|
-
|
110
|
-
|
111
|
-
|
93
|
+
raise ModeNotFoundError(
|
94
|
+
f"Test mode not found. Expected one of '{self.test_modes}', but received '{capture_config.receiver_mode}'"
|
95
|
+
)
|
96
|
+
return builder_method(num_spectrums, capture_config)
|
112
97
|
|
113
98
|
def _cosine_signal_1(
|
114
|
-
self,
|
115
|
-
num_spectrums: int,
|
116
|
-
capture_config: CaptureConfig
|
99
|
+
self, num_spectrums: int, capture_config: CaptureConfig
|
117
100
|
) -> Spectrogram:
|
118
101
|
"""Creates the expected spectrogram for the `Test` receiver operating in the mode `cosine-signal-1`."""
|
119
102
|
# Extract necessary parameters from the capture config.
|
120
|
-
window_size
|
121
|
-
sample_rate
|
122
|
-
frequency
|
123
|
-
window_hop
|
124
|
-
amplitude
|
125
|
-
center_frequency = cast(
|
126
|
-
|
103
|
+
window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
|
104
|
+
sample_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
|
105
|
+
frequency = cast(int, capture_config.get_parameter_value(PName.FREQUENCY))
|
106
|
+
window_hop = cast(int, capture_config.get_parameter_value(PName.WINDOW_HOP))
|
107
|
+
amplitude = cast(float, capture_config.get_parameter_value(PName.AMPLITUDE))
|
108
|
+
center_frequency = cast(
|
109
|
+
float, capture_config.get_parameter_value(PName.CENTER_FREQUENCY)
|
110
|
+
)
|
111
|
+
|
127
112
|
# Calculate derived parameters a (sampling rate ratio) and p (sampled periods).
|
128
113
|
a = int(sample_rate / frequency)
|
129
114
|
p = int(window_size / a)
|
@@ -138,38 +123,50 @@ class _AnalyticalFactory:
|
|
138
123
|
spectrum = np.fft.fftshift(spectrum)
|
139
124
|
|
140
125
|
# Populate the spectrogram with identical spectra.
|
141
|
-
analytical_dynamic_spectra =
|
126
|
+
analytical_dynamic_spectra = (
|
127
|
+
np.ones((window_size, num_spectrums)) * spectrum[:, np.newaxis]
|
128
|
+
)
|
142
129
|
|
143
130
|
# Compute time array.
|
144
131
|
sampling_interval = 1 / sample_rate
|
145
132
|
times = np.arange(num_spectrums) * window_hop * sampling_interval
|
146
133
|
|
147
134
|
# compute the frequency array.
|
148
|
-
frequencies =
|
135
|
+
frequencies = (
|
136
|
+
np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval))
|
137
|
+
+ center_frequency
|
138
|
+
)
|
149
139
|
|
150
140
|
# Return the spectrogram.
|
151
|
-
return Spectrogram(
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
141
|
+
return Spectrogram(
|
142
|
+
analytical_dynamic_spectra,
|
143
|
+
times,
|
144
|
+
frequencies,
|
145
|
+
"analytically-derived-spectrogram",
|
146
|
+
SpectrumUnit.AMPLITUDE,
|
147
|
+
)
|
157
148
|
|
158
149
|
def _tagged_staircase(
|
159
|
-
self,
|
160
|
-
num_spectrums: int,
|
161
|
-
capture_config: CaptureConfig
|
150
|
+
self, num_spectrums: int, capture_config: CaptureConfig
|
162
151
|
) -> Spectrogram:
|
163
152
|
"""Creates the expected spectrogram for the `Test` receiver operating in the mode `tagged-staircase`."""
|
164
153
|
# Extract necessary parameters from the capture config.
|
165
|
-
window_size
|
166
|
-
min_samples_per_step = cast(
|
167
|
-
|
168
|
-
|
169
|
-
|
154
|
+
window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
|
155
|
+
min_samples_per_step = cast(
|
156
|
+
int, capture_config.get_parameter_value(PName.MIN_SAMPLES_PER_STEP)
|
157
|
+
)
|
158
|
+
max_samples_per_step = cast(
|
159
|
+
int, capture_config.get_parameter_value(PName.MAX_SAMPLES_PER_STEP)
|
160
|
+
)
|
161
|
+
step_increment = cast(
|
162
|
+
int, capture_config.get_parameter_value(PName.STEP_INCREMENT)
|
163
|
+
)
|
164
|
+
samp_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
|
170
165
|
|
171
166
|
# Calculate step sizes and derived parameters.
|
172
|
-
num_samples_per_step = np.arange(
|
167
|
+
num_samples_per_step = np.arange(
|
168
|
+
min_samples_per_step, max_samples_per_step + 1, step_increment
|
169
|
+
)
|
173
170
|
num_steps = len(num_samples_per_step)
|
174
171
|
|
175
172
|
# Create the analytical spectrum, constant in time.
|
@@ -178,42 +175,52 @@ class _AnalyticalFactory:
|
|
178
175
|
for i in range(num_steps):
|
179
176
|
step_count += 1
|
180
177
|
spectral_amplitude = window_size * step_count
|
181
|
-
spectrum[int(window_size/2) + i*window_size] = spectral_amplitude
|
178
|
+
spectrum[int(window_size / 2) + i * window_size] = spectral_amplitude
|
182
179
|
|
183
180
|
# Populate the spectrogram with identical spectra.
|
184
|
-
analytical_dynamic_spectra =
|
181
|
+
analytical_dynamic_spectra = (
|
182
|
+
np.ones((window_size * num_steps, num_spectrums)) * spectrum[:, np.newaxis]
|
183
|
+
)
|
185
184
|
|
186
185
|
# Compute time array
|
187
186
|
num_samples_per_sweep = sum(num_samples_per_step)
|
188
187
|
sampling_interval = 1 / samp_rate
|
189
188
|
# compute the sample index we are "assigning" to each spectrum
|
190
189
|
# and multiply by the sampling interval to get the equivalent physical time
|
191
|
-
times =
|
190
|
+
times = (
|
191
|
+
np.array([(i * num_samples_per_sweep) for i in range(num_spectrums)])
|
192
|
+
* sampling_interval
|
193
|
+
)
|
192
194
|
|
193
195
|
# Compute the frequency array
|
194
|
-
baseband_frequencies = np.fft.fftshift(
|
196
|
+
baseband_frequencies = np.fft.fftshift(
|
197
|
+
np.fft.fftfreq(window_size, sampling_interval)
|
198
|
+
)
|
195
199
|
frequencies = np.empty((window_size * num_steps), dtype=np.float32)
|
196
200
|
for i in range(num_steps):
|
197
201
|
lower_bound = i * window_size
|
198
202
|
upper_bound = (i + 1) * window_size
|
199
|
-
frequencies[lower_bound:upper_bound] =
|
203
|
+
frequencies[lower_bound:upper_bound] = (
|
204
|
+
baseband_frequencies + (samp_rate / 2) + (samp_rate * i)
|
205
|
+
)
|
200
206
|
|
201
207
|
# Return the spectrogram.
|
202
|
-
return Spectrogram(
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
+
return Spectrogram(
|
209
|
+
analytical_dynamic_spectra,
|
210
|
+
times,
|
211
|
+
frequencies,
|
212
|
+
"analytically-derived-spectrogram",
|
213
|
+
SpectrumUnit.AMPLITUDE,
|
214
|
+
)
|
215
|
+
|
208
216
|
|
209
217
|
def get_analytical_spectrogram(
|
210
|
-
num_spectrums: int,
|
211
|
-
capture_config: CaptureConfig
|
218
|
+
num_spectrums: int, capture_config: CaptureConfig
|
212
219
|
) -> Spectrogram:
|
213
|
-
"""Each mode of the `Test` receiver generates a known synthetic signal. Based on this, we can
|
214
|
-
derive an analytical solution that predicts the expected spectrogram for a session in that mode.
|
215
|
-
|
216
|
-
This function constructs the analytical spectrogram using the capture config for a `Test`
|
220
|
+
"""Each mode of the `Test` receiver generates a known synthetic signal. Based on this, we can
|
221
|
+
derive an analytical solution that predicts the expected spectrogram for a session in that mode.
|
222
|
+
|
223
|
+
This function constructs the analytical spectrogram using the capture config for a `Test`
|
217
224
|
receiver operating in a specific mode.
|
218
225
|
|
219
226
|
:param num_spectrums: The number of spectrums in the output spectrogram.
|
@@ -221,14 +228,11 @@ def get_analytical_spectrogram(
|
|
221
228
|
:return: The expected, analytically derived spectrogram for the specified mode of the `Test` receiver.
|
222
229
|
"""
|
223
230
|
factory = _AnalyticalFactory()
|
224
|
-
return factory.get_spectrogram(num_spectrums,
|
225
|
-
capture_config)
|
231
|
+
return factory.get_spectrogram(num_spectrums, capture_config)
|
226
232
|
|
227
233
|
|
228
234
|
def validate_analytically(
|
229
|
-
spectrogram: Spectrogram,
|
230
|
-
capture_config: CaptureConfig,
|
231
|
-
absolute_tolerance: float
|
235
|
+
spectrogram: Spectrogram, capture_config: CaptureConfig, absolute_tolerance: float
|
232
236
|
) -> TestResults:
|
233
237
|
"""Validate a spectrogram generated during sessions with a `Test` receiver operating
|
234
238
|
in a particular mode.
|
@@ -238,26 +242,31 @@ def validate_analytically(
|
|
238
242
|
:param absolute_tolerance: Tolerance level for numerical comparisons.
|
239
243
|
:return: A `TestResults` object summarising the validation outcome.
|
240
244
|
"""
|
241
|
-
analytical_spectrogram = get_analytical_spectrogram(
|
242
|
-
|
245
|
+
analytical_spectrogram = get_analytical_spectrogram(
|
246
|
+
spectrogram.num_times, capture_config
|
247
|
+
)
|
243
248
|
|
244
249
|
test_results = TestResults()
|
245
250
|
|
246
|
-
test_results.times_validated = bool(
|
247
|
-
|
248
|
-
|
251
|
+
test_results.times_validated = bool(
|
252
|
+
is_close(analytical_spectrogram.times, spectrogram.times, absolute_tolerance)
|
253
|
+
)
|
249
254
|
|
250
|
-
test_results.frequencies_validated = bool(
|
251
|
-
|
252
|
-
|
255
|
+
test_results.frequencies_validated = bool(
|
256
|
+
is_close(
|
257
|
+
analytical_spectrogram.frequencies,
|
258
|
+
spectrogram.frequencies,
|
259
|
+
absolute_tolerance,
|
260
|
+
)
|
261
|
+
)
|
253
262
|
|
254
263
|
test_results.spectrum_validated = {}
|
255
264
|
for i in range(spectrogram.num_times):
|
256
265
|
time = spectrogram.times[i]
|
257
266
|
analytical_spectrum = analytical_spectrogram.dynamic_spectra[:, i]
|
258
267
|
spectrum = spectrogram.dynamic_spectra[:, i]
|
259
|
-
test_results.spectrum_validated[time] = bool(
|
260
|
-
|
261
|
-
|
268
|
+
test_results.spectrum_validated[time] = bool(
|
269
|
+
is_close(analytical_spectrum, spectrum, absolute_tolerance)
|
270
|
+
)
|
262
271
|
|
263
|
-
return test_results
|
272
|
+
return test_results
|