spectre-core 0.0.22__py3-none-any.whl → 0.0.24__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. spectre_core/__init__.py +5 -0
  2. spectre_core/_file_io/__init__.py +4 -4
  3. spectre_core/_file_io/file_handlers.py +60 -106
  4. spectre_core/batches/__init__.py +20 -3
  5. spectre_core/batches/_base.py +85 -134
  6. spectre_core/batches/_batches.py +55 -99
  7. spectre_core/batches/_factory.py +21 -20
  8. spectre_core/batches/_register.py +8 -8
  9. spectre_core/batches/plugins/_batch_keys.py +7 -6
  10. spectre_core/batches/plugins/_callisto.py +65 -97
  11. spectre_core/batches/plugins/_iq_stream.py +105 -169
  12. spectre_core/capture_configs/__init__.py +46 -17
  13. spectre_core/capture_configs/_capture_config.py +25 -52
  14. spectre_core/capture_configs/_capture_modes.py +8 -6
  15. spectre_core/capture_configs/_capture_templates.py +50 -110
  16. spectre_core/capture_configs/_parameters.py +37 -74
  17. spectre_core/capture_configs/_pconstraints.py +40 -40
  18. spectre_core/capture_configs/_pnames.py +36 -34
  19. spectre_core/capture_configs/_ptemplates.py +260 -347
  20. spectre_core/capture_configs/_pvalidators.py +99 -102
  21. spectre_core/config/__init__.py +19 -8
  22. spectre_core/config/_paths.py +25 -47
  23. spectre_core/config/_time_formats.py +6 -5
  24. spectre_core/exceptions.py +38 -0
  25. spectre_core/jobs/__init__.py +3 -6
  26. spectre_core/jobs/_duration.py +12 -0
  27. spectre_core/jobs/_jobs.py +72 -43
  28. spectre_core/jobs/_workers.py +55 -105
  29. spectre_core/logs/__init__.py +7 -2
  30. spectre_core/logs/_configure.py +13 -17
  31. spectre_core/logs/_decorators.py +6 -4
  32. spectre_core/logs/_logs.py +37 -89
  33. spectre_core/logs/_process_types.py +5 -3
  34. spectre_core/plotting/__init__.py +19 -3
  35. spectre_core/plotting/_base.py +112 -177
  36. spectre_core/plotting/_format.py +10 -8
  37. spectre_core/plotting/_panel_names.py +7 -5
  38. spectre_core/plotting/_panel_stack.py +138 -130
  39. spectre_core/plotting/_panels.py +152 -162
  40. spectre_core/post_processing/__init__.py +6 -3
  41. spectre_core/post_processing/_base.py +41 -55
  42. spectre_core/post_processing/_factory.py +14 -11
  43. spectre_core/post_processing/_post_processor.py +16 -12
  44. spectre_core/post_processing/_register.py +10 -7
  45. spectre_core/post_processing/plugins/_event_handler_keys.py +4 -3
  46. spectre_core/post_processing/plugins/_fixed_center_frequency.py +54 -47
  47. spectre_core/post_processing/plugins/_swept_center_frequency.py +199 -174
  48. spectre_core/receivers/__init__.py +9 -2
  49. spectre_core/receivers/_base.py +82 -148
  50. spectre_core/receivers/_factory.py +20 -30
  51. spectre_core/receivers/_register.py +7 -10
  52. spectre_core/receivers/_spec_names.py +17 -15
  53. spectre_core/receivers/plugins/_b200mini.py +47 -60
  54. spectre_core/receivers/plugins/_receiver_names.py +8 -6
  55. spectre_core/receivers/plugins/_rsp1a.py +44 -40
  56. spectre_core/receivers/plugins/_rspduo.py +59 -44
  57. spectre_core/receivers/plugins/_sdrplay_receiver.py +67 -83
  58. spectre_core/receivers/plugins/_test.py +136 -129
  59. spectre_core/receivers/plugins/_usrp.py +93 -85
  60. spectre_core/receivers/plugins/gr/__init__.py +1 -1
  61. spectre_core/receivers/plugins/gr/_base.py +14 -22
  62. spectre_core/receivers/plugins/gr/_rsp1a.py +53 -60
  63. spectre_core/receivers/plugins/gr/_rspduo.py +77 -89
  64. spectre_core/receivers/plugins/gr/_test.py +49 -57
  65. spectre_core/receivers/plugins/gr/_usrp.py +61 -59
  66. spectre_core/spectrograms/__init__.py +21 -13
  67. spectre_core/spectrograms/_analytical.py +108 -99
  68. spectre_core/spectrograms/_array_operations.py +39 -46
  69. spectre_core/spectrograms/_spectrogram.py +293 -324
  70. spectre_core/spectrograms/_transform.py +106 -73
  71. spectre_core/wgetting/__init__.py +1 -3
  72. spectre_core/wgetting/_callisto.py +87 -93
  73. {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/METADATA +9 -23
  74. spectre_core-0.0.24.dist-info/RECORD +79 -0
  75. {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/WHEEL +1 -1
  76. spectre_core-0.0.22.dist-info/RECORD +0 -78
  77. {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/licenses/LICENSE +0 -0
  78. {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/top_level.txt +0 -0
@@ -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 = parameters.get_parameter_value(PName.SAMPLE_RATE)
29
- gain = parameters.get_parameter_value(PName.GAIN)
30
- center_freq = parameters.get_parameter_value(PName.CENTER_FREQUENCY)
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 = parameters.get_parameter_value(PName.WIRE_FORMAT)
33
- batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
34
- bandwidth = parameters.get_parameter_value(PName.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(("", '', master_clock_rate)),
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(get_batches_dir_path(),
58
- tag,
59
- batch_size,
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 = parameters.get_parameter_value(PName.SAMPLE_RATE)
81
- bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
82
- min_frequency = parameters.get_parameter_value(PName.MIN_FREQUENCY)
83
- max_frequency = parameters.get_parameter_value(PName.MAX_FREQUENCY)
84
- frequency_step = parameters.get_parameter_value(PName.FREQUENCY_STEP)
85
- samples_per_step = parameters.get_parameter_value(PName.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(PName.MASTER_CLOCK_RATE)
88
- wire_format = parameters.get_parameter_value(PName.WIRE_FORMAT)
89
- gain = parameters.get_parameter_value(PName.GAIN)
90
- batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
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(f"USRP frequency sweep modes will not work as expected until a known bug is fixed in the USRP source block. "
94
- f"Please refer to this GitHub issue for more information: https://github.com/gnuradio/gnuradio/issues/7725")
95
- master_clock_rate = f"master_clock_rate={master_clock_rate}"
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(("", '', master_clock_rate)),
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(min_frequency,
116
- max_frequency,
117
- frequency_step,
118
- sample_rate,
119
- samples_per_step,
120
- 'freq')
121
-
122
- self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_batches_dir_path(),
123
- tag,
124
- batch_size,
125
- sample_rate,
126
- True,
127
- 'rx_freq',
128
- min_frequency)
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((self.spectre_sweep_driver_0, 'retune_command'), (self.uhd_usrp_source_0, 'command'))
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(capture, top_block_cls=_swept_center_frequency, max_noutput_items=1024)
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
- get_analytical_spectrogram, validate_analytically, TestResults
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, time_chop, frequency_average, time_average,
15
- join_spectrograms
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", "validate_analytically", "TestResults",
20
- "Spectrogram", "FrequencyCut", "TimeCut", "SpectrumUnit", "frequency_chop",
21
- "time_chop", "frequency_average","time_average", "join_spectrograms",
22
- "TimeType"
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" : self.times_validated,
43
+ "times_validated": self.times_validated,
51
44
  "frequencies_validated": self.frequencies_validated,
52
- "spectrum_validated" : self.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
- def __init__(
59
- self
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" : self._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(f"Input capture config must correspond to the test receiver")
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(f"Test mode not found. Expected one of '{self.test_modes}', but received '{capture_config.receiver_mode}'")
109
- return builder_method(num_spectrums,
110
- capture_config)
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 = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
121
- sample_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
122
- frequency = cast(int, capture_config.get_parameter_value(PName.FREQUENCY))
123
- window_hop = cast(int, capture_config.get_parameter_value(PName.WINDOW_HOP))
124
- amplitude = cast(float, capture_config.get_parameter_value(PName.AMPLITUDE))
125
- center_frequency = cast(float, capture_config.get_parameter_value(PName.CENTER_FREQUENCY))
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 = np.ones((window_size, num_spectrums)) * spectrum[:, np.newaxis]
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 = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval)) + center_frequency
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(analytical_dynamic_spectra,
152
- times,
153
- frequencies,
154
- 'analytically-derived-spectrogram',
155
- SpectrumUnit.AMPLITUDE)
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 = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
166
- min_samples_per_step = cast(int, capture_config.get_parameter_value(PName.MIN_SAMPLES_PER_STEP))
167
- max_samples_per_step = cast(int, capture_config.get_parameter_value(PName.MAX_SAMPLES_PER_STEP))
168
- step_increment = cast(int, capture_config.get_parameter_value(PName.STEP_INCREMENT))
169
- samp_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
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(min_samples_per_step, max_samples_per_step + 1, step_increment)
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 = np.ones((window_size * num_steps, num_spectrums)) * spectrum[:, np.newaxis]
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 = np.array([(i * num_samples_per_sweep) for i in range(num_spectrums) ]) * sampling_interval
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(np.fft.fftfreq(window_size, sampling_interval))
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] = baseband_frequencies + (samp_rate / 2) + (samp_rate * i)
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(analytical_dynamic_spectra,
203
- times,
204
- frequencies,
205
- 'analytically-derived-spectrogram',
206
- SpectrumUnit.AMPLITUDE)
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(spectrogram.num_times,
242
- capture_config)
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(is_close(analytical_spectrogram.times,
247
- spectrogram.times,
248
- absolute_tolerance))
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(is_close(analytical_spectrogram.frequencies,
251
- spectrogram.frequencies,
252
- absolute_tolerance))
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(is_close(analytical_spectrum,
260
- spectrum,
261
- absolute_tolerance))
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