spectre-core 0.0.12__py3-none-any.whl → 0.0.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. spectre_core/_file_io/__init__.py +1 -3
  2. spectre_core/_file_io/file_handlers.py +163 -58
  3. spectre_core/batches/__init__.py +10 -11
  4. spectre_core/batches/_base.py +170 -78
  5. spectre_core/batches/_batches.py +149 -99
  6. spectre_core/batches/_factory.py +56 -14
  7. spectre_core/batches/_register.py +23 -8
  8. spectre_core/batches/plugins/_batch_keys.py +16 -0
  9. spectre_core/batches/plugins/_callisto.py +183 -0
  10. spectre_core/batches/plugins/_iq_stream.py +354 -0
  11. spectre_core/capture_configs/__init__.py +17 -13
  12. spectre_core/capture_configs/_capture_config.py +93 -34
  13. spectre_core/capture_configs/_capture_modes.py +22 -0
  14. spectre_core/capture_configs/_capture_templates.py +207 -122
  15. spectre_core/capture_configs/_parameters.py +115 -42
  16. spectre_core/capture_configs/_pconstraints.py +86 -35
  17. spectre_core/capture_configs/_pnames.py +49 -0
  18. spectre_core/capture_configs/_ptemplates.py +389 -346
  19. spectre_core/capture_configs/_pvalidators.py +117 -73
  20. spectre_core/config/__init__.py +6 -8
  21. spectre_core/config/_paths.py +65 -25
  22. spectre_core/config/_time_formats.py +15 -10
  23. spectre_core/exceptions.py +2 -4
  24. spectre_core/jobs/__init__.py +14 -0
  25. spectre_core/jobs/_jobs.py +111 -0
  26. spectre_core/jobs/_workers.py +171 -0
  27. spectre_core/logs/__init__.py +17 -0
  28. spectre_core/logs/_configure.py +67 -0
  29. spectre_core/logs/_decorators.py +33 -0
  30. spectre_core/logs/_logs.py +228 -0
  31. spectre_core/logs/_process_types.py +14 -0
  32. spectre_core/plotting/__init__.py +4 -2
  33. spectre_core/plotting/_base.py +204 -102
  34. spectre_core/plotting/_format.py +17 -4
  35. spectre_core/plotting/_panel_names.py +18 -0
  36. spectre_core/plotting/_panel_stack.py +167 -53
  37. spectre_core/plotting/_panels.py +341 -141
  38. spectre_core/post_processing/__init__.py +8 -6
  39. spectre_core/post_processing/_base.py +70 -44
  40. spectre_core/post_processing/_factory.py +42 -12
  41. spectre_core/post_processing/_post_processor.py +24 -26
  42. spectre_core/post_processing/_register.py +22 -6
  43. spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
  44. spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
  45. spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
  46. spectre_core/py.typed +0 -0
  47. spectre_core/receivers/__init__.py +10 -7
  48. spectre_core/receivers/_base.py +220 -69
  49. spectre_core/receivers/_factory.py +53 -7
  50. spectre_core/receivers/_register.py +30 -9
  51. spectre_core/receivers/_spec_names.py +26 -15
  52. spectre_core/receivers/plugins/__init__.py +0 -0
  53. spectre_core/receivers/plugins/_receiver_names.py +16 -0
  54. spectre_core/receivers/plugins/_rsp1a.py +59 -0
  55. spectre_core/receivers/plugins/_rspduo.py +67 -0
  56. spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
  57. spectre_core/receivers/plugins/_test.py +218 -0
  58. spectre_core/receivers/plugins/gr/_base.py +80 -0
  59. spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
  60. spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
  61. spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
  62. spectre_core/spectrograms/__init__.py +5 -3
  63. spectre_core/spectrograms/_analytical.py +121 -66
  64. spectre_core/spectrograms/_array_operations.py +103 -36
  65. spectre_core/spectrograms/_spectrogram.py +380 -207
  66. spectre_core/spectrograms/_transform.py +197 -169
  67. spectre_core/wgetting/__init__.py +4 -2
  68. spectre_core/wgetting/_callisto.py +173 -118
  69. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
  70. spectre_core-0.0.13.dist-info/RECORD +75 -0
  71. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
  72. spectre_core/batches/library/_callisto.py +0 -96
  73. spectre_core/batches/library/_fixed_center_frequency.py +0 -133
  74. spectre_core/batches/library/_swept_center_frequency.py +0 -105
  75. spectre_core/logging/__init__.py +0 -11
  76. spectre_core/logging/_configure.py +0 -35
  77. spectre_core/logging/_decorators.py +0 -19
  78. spectre_core/logging/_log_handlers.py +0 -176
  79. spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
  80. spectre_core/receivers/gr/_base.py +0 -33
  81. spectre_core/receivers/library/_rsp1a.py +0 -61
  82. spectre_core/receivers/library/_rspduo.py +0 -69
  83. spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  84. spectre_core/receivers/library/_test.py +0 -221
  85. spectre_core-0.0.12.dist-info/RECORD +0 -64
  86. /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
  87. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
  88. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -19,35 +19,30 @@
19
19
  from functools import partial
20
20
  from dataclasses import dataclass
21
21
 
22
- from gnuradio import gr
23
- from gnuradio import spectre
24
- from gnuradio import sdrplay3
25
-
26
- from spectre_core.capture_configs import Parameters, PNames
22
+ from spectre_core.capture_configs import Parameters, PName
27
23
  from spectre_core.config import get_batches_dir_path
28
- from ._base import capture
29
-
30
-
31
- class _tuner_1_fixed_center_frequency(gr.top_block):
32
- def __init__(self,
33
- tag: str,
34
- parameters: Parameters):
35
- gr.top_block.__init__(self, catch_exceptions=True)
36
- gr.top_block.__init__(self, "tuner_1_fixed", catch_exceptions=True)
24
+ from ._base import capture, spectre_top_block
25
+
26
+
27
+ class _tuner_1_fixed_center_frequency(spectre_top_block):
28
+ def flowgraph(
29
+ self,
30
+ tag: str,
31
+ parameters: Parameters
32
+ ) -> None:
33
+ # OOT Module inline imports
34
+ from gnuradio import spectre
35
+ from gnuradio import sdrplay3
37
36
 
38
- ##################################################
39
- # Unpack capture config
40
- ##################################################
41
- sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
42
- batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
43
- center_freq = parameters.get_parameter_value(PNames.CENTER_FREQUENCY)
44
- bandwidth = parameters.get_parameter_value(PNames.BANDWIDTH)
45
- if_gain = parameters.get_parameter_value(PNames.IF_GAIN)
46
- rf_gain = parameters.get_parameter_value(PNames.RF_GAIN)
47
-
48
- ##################################################
37
+ # Unpack the capture config parameters
38
+ sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
39
+ batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
40
+ center_freq = parameters.get_parameter_value(PName.CENTER_FREQUENCY)
41
+ bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
42
+ if_gain = parameters.get_parameter_value(PName.IF_GAIN)
43
+ rf_gain = parameters.get_parameter_value(PName.RF_GAIN)
44
+
49
45
  # Blocks
50
- ##################################################
51
46
  self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_batches_dir_path(),
52
47
  tag,
53
48
  batch_size,
@@ -80,33 +75,29 @@ class _tuner_1_fixed_center_frequency(gr.top_block):
80
75
  self.sdrplay3_rspduo_0.set_sample_sequence_gaps_check(False)
81
76
  self.sdrplay3_rspduo_0.set_show_gain_changes(False)
82
77
 
83
-
84
- ##################################################
85
78
  # Connections
86
- ##################################################
87
79
  self.connect((self.sdrplay3_rspduo_0, 0), (self.spectre_batched_file_sink_0, 0))
88
80
 
89
81
 
90
- class _tuner_2_fixed_center_frequency(gr.top_block):
91
- def __init__(self,
92
- tag: str,
93
- parameters: Parameters):
94
- gr.top_block.__init__(self, catch_exceptions=True)
95
- gr.top_block.__init__(self, "tuner_1_fixed", catch_exceptions=True)
82
+ class _tuner_2_fixed_center_frequency(spectre_top_block):
83
+ def flowgraph(
84
+ self,
85
+ tag: str,
86
+ parameters: Parameters
87
+ ) -> None:
88
+ # OOT Module inline imports
89
+ from gnuradio import spectre
90
+ from gnuradio import sdrplay3
96
91
 
97
- ##################################################
98
- # Unpack capture config
99
- ##################################################
100
- sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
101
- batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
102
- center_freq = parameters.get_parameter_value(PNames.CENTER_FREQUENCY)
103
- bandwidth = parameters.get_parameter_value(PNames.BANDWIDTH)
104
- if_gain = parameters.get_parameter_value(PNames.IF_GAIN)
105
- rf_gain = parameters.get_parameter_value(PNames.RF_GAIN)
106
-
107
- ##################################################
92
+ # Unpack the capture config parameters
93
+ sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
94
+ batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
95
+ center_freq = parameters.get_parameter_value(PName.CENTER_FREQUENCY)
96
+ bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
97
+ if_gain = parameters.get_parameter_value(PName.IF_GAIN)
98
+ rf_gain = parameters.get_parameter_value(PName.RF_GAIN)
99
+
108
100
  # Blocks
109
- ##################################################
110
101
  self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_batches_dir_path(),
111
102
  tag,
112
103
  batch_size,
@@ -141,34 +132,32 @@ class _tuner_2_fixed_center_frequency(gr.top_block):
141
132
  self.sdrplay3_rspduo_0.set_show_gain_changes(False)
142
133
 
143
134
 
144
- ##################################################
145
135
  # Connections
146
- ##################################################
147
136
  self.connect((self.sdrplay3_rspduo_0, 0), (self.spectre_batched_file_sink_0, 0))
148
137
 
149
138
 
150
- class _tuner_1_swept_center_frequency(gr.top_block):
151
- def __init__(self,
152
- tag: str,
153
- parameters: Parameters):
154
- gr.top_block.__init__(self, catch_exceptions=True)
155
-
156
- ##################################################
157
- # Unpack capture config
158
- ##################################################
159
- sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
160
- bandwidth = parameters.get_parameter_value(PNames.BANDWIDTH)
161
- min_frequency = parameters.get_parameter_value(PNames.MIN_FREQUENCY)
162
- max_frequency = parameters.get_parameter_value(PNames.MAX_FREQUENCY)
163
- frequency_step = parameters.get_parameter_value(PNames.FREQUENCY_STEP)
164
- samples_per_step = parameters.get_parameter_value(PNames.SAMPLES_PER_STEP)
165
- if_gain = parameters.get_parameter_value(PNames.IF_GAIN)
166
- rf_gain = parameters.get_parameter_value(PNames.RF_GAIN)
167
- batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
168
-
169
- ##################################################
139
+ class _tuner_1_swept_center_frequency(spectre_top_block):
140
+ def flowgraph(
141
+ self,
142
+ tag: str,
143
+ parameters: Parameters
144
+ ) -> None:
145
+ # OOT Module inline imports
146
+ from gnuradio import spectre
147
+ from gnuradio import sdrplay3
148
+
149
+ # Unpack the capture config parameters
150
+ sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
151
+ bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
152
+ min_frequency = parameters.get_parameter_value(PName.MIN_FREQUENCY)
153
+ max_frequency = parameters.get_parameter_value(PName.MAX_FREQUENCY)
154
+ frequency_step = parameters.get_parameter_value(PName.FREQUENCY_STEP)
155
+ samples_per_step = parameters.get_parameter_value(PName.SAMPLES_PER_STEP)
156
+ if_gain = parameters.get_parameter_value(PName.IF_GAIN)
157
+ rf_gain = parameters.get_parameter_value(PName.RF_GAIN)
158
+ batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
159
+
170
160
  # Blocks
171
- ##################################################
172
161
  self.spectre_sweep_driver_0 = spectre.sweep_driver(min_frequency,
173
162
  max_frequency,
174
163
  frequency_step,
@@ -212,16 +201,14 @@ class _tuner_1_swept_center_frequency(gr.top_block):
212
201
  self.sdrplay3_rspduo_0.set_show_gain_changes(False)
213
202
 
214
203
 
215
- ##################################################
216
204
  # Connections
217
- ##################################################
218
205
  self.msg_connect((self.spectre_sweep_driver_0, 'freq'), (self.sdrplay3_rspduo_0, 'freq'))
219
206
  self.connect((self.sdrplay3_rspduo_0, 0), (self.spectre_batched_file_sink_0, 0))
220
207
  self.connect((self.sdrplay3_rspduo_0, 0), (self.spectre_sweep_driver_0, 0))
221
208
 
222
209
 
223
210
  @dataclass(frozen=True)
224
- class CaptureMethods:
211
+ class CaptureMethod:
225
212
  tuner_1_fixed_center_frequency = partial(capture, top_block_cls=_tuner_1_fixed_center_frequency)
226
213
  tuner_2_fixed_center_frequency = partial(capture, top_block_cls=_tuner_2_fixed_center_frequency)
227
- tuner_1_swept_center_frequency = partial(capture, top_block_cls=_tuner_1_swept_center_frequency)
214
+ tuner_1_swept_center_frequency = partial(capture, top_block_cls=_tuner_1_swept_center_frequency, max_noutput_items=1024)
@@ -17,35 +17,34 @@
17
17
  #
18
18
 
19
19
  from functools import partial
20
+ from typing import Callable
20
21
  from dataclasses import dataclass
21
22
 
22
23
  from gnuradio import gr
23
24
  from gnuradio import blocks
24
- from gnuradio import spectre
25
25
  from gnuradio import analog
26
26
 
27
- from spectre_core.capture_configs import Parameters, PNames
27
+ from spectre_core.capture_configs import Parameters, PName
28
28
  from spectre_core.config import get_batches_dir_path
29
- from ._base import capture
29
+ from ._base import capture, spectre_top_block
30
30
 
31
31
 
32
- class _cosine_signal_1(gr.top_block):
33
- def __init__(self,
34
- tag: str,
35
- parameters: Parameters):
36
- gr.top_block.__init__(self, catch_exceptions=True)
32
+ class _cosine_signal_1(spectre_top_block):
33
+ def flowgraph(
34
+ self,
35
+ tag: str,
36
+ parameters: Parameters
37
+ ) -> None:
38
+ # Inline imports
39
+ from gnuradio import spectre
37
40
 
38
- ##################################################
39
- # Unpack capture config
40
- ##################################################
41
- samp_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
42
- batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
43
- frequency = parameters.get_parameter_value(PNames.FREQUENCY)
44
- amplitude = parameters.get_parameter_value(PNames.AMPLITUDE)
41
+ # Unpack the capture config parameters
42
+ samp_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
43
+ batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
44
+ frequency = parameters.get_parameter_value(PName.FREQUENCY)
45
+ amplitude = parameters.get_parameter_value(PName.AMPLITUDE)
45
46
 
46
- ##################################################
47
47
  # Blocks
48
- ##################################################
49
48
  self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_batches_dir_path(),
50
49
  tag,
51
50
  batch_size,
@@ -76,21 +75,24 @@ class _cosine_signal_1(gr.top_block):
76
75
  self.connect((self.blocks_throttle_0_1, 0), (self.blocks_float_to_complex_1, 1))
77
76
 
78
77
 
79
- class _tagged_staircase(gr.top_block):
80
- def __init__(self,
81
- tag: str,
82
- parameters: Parameters):
83
- gr.top_block.__init__(self, catch_exceptions=True)
78
+ class _tagged_staircase(spectre_top_block):
79
+ def flowgraph(
80
+ self,
81
+ tag: str,
82
+ parameters: Parameters
83
+ ) -> None:
84
+ # Inline imports
85
+ from gnuradio import spectre
84
86
 
85
87
  ##################################################
86
88
  # Unpack capture config
87
89
  ##################################################
88
- step_increment = parameters.get_parameter_value(PNames.STEP_INCREMENT)
89
- samp_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
90
- min_samples_per_step = parameters.get_parameter_value(PNames.MIN_SAMPLES_PER_STEP)
91
- max_samples_per_step = parameters.get_parameter_value(PNames.MAX_SAMPLES_PER_STEP)
92
- frequency_step = parameters.get_parameter_value(PNames.FREQUENCY_STEP)
93
- batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
90
+ step_increment = parameters.get_parameter_value(PName.STEP_INCREMENT)
91
+ samp_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
92
+ min_samples_per_step = parameters.get_parameter_value(PName.MIN_SAMPLES_PER_STEP)
93
+ max_samples_per_step = parameters.get_parameter_value(PName.MAX_SAMPLES_PER_STEP)
94
+ frequency_step = parameters.get_parameter_value(PName.FREQUENCY_STEP)
95
+ batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
94
96
 
95
97
  ##################################################
96
98
  # Blocks
@@ -118,6 +120,6 @@ class _tagged_staircase(gr.top_block):
118
120
 
119
121
 
120
122
  @dataclass(frozen=True)
121
- class CaptureMethods:
122
- cosine_signal_1 = partial(capture, top_block_cls=_cosine_signal_1)
123
- tagged_staircase = partial(capture, top_block_cls=_tagged_staircase)
123
+ class CaptureMethod:
124
+ cosine_signal_1 : Callable[[str, Parameters], None] = partial(capture, top_block_cls=_cosine_signal_1)
125
+ tagged_staircase: Callable[[str, Parameters], None] = partial(capture, top_block_cls=_tagged_staircase)
@@ -2,11 +2,13 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
+ """Create and transform spectrogram data."""
6
+
5
7
  from ._analytical import (
6
8
  get_analytical_spectrogram, validate_analytically, TestResults
7
9
  )
8
10
  from ._spectrogram import (
9
- Spectrogram, FrequencyCut, TimeCut, SpectrumTypes, TimeTypes
11
+ Spectrogram, FrequencyCut, TimeCut, SpectrumUnit, TimeType
10
12
  )
11
13
  from ._transform import (
12
14
  frequency_chop, time_chop, frequency_average, time_average,
@@ -15,7 +17,7 @@ from ._transform import (
15
17
 
16
18
  __all__ = [
17
19
  "get_analytical_spectrogram", "validate_analytically", "TestResults",
18
- "Spectrogram", "FrequencyCut", "TimeCut", "SpectrumTypes", "frequency_chop",
20
+ "Spectrogram", "FrequencyCut", "TimeCut", "SpectrumUnit", "frequency_chop",
19
21
  "time_chop", "frequency_average","time_average", "join_spectrograms",
20
- "TimeTypes"
22
+ "TimeType"
21
23
  ]
@@ -1,96 +1,129 @@
1
+
1
2
  # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
3
  # This file is part of SPECTRE
3
4
  # SPDX-License-Identifier: GPL-3.0-or-later
4
5
 
5
- from typing import Callable
6
- from dataclasses import dataclass
6
+ from typing import Callable, cast
7
+ from dataclasses import dataclass, field
7
8
 
8
9
  import numpy as np
9
10
 
10
- from spectre_core.capture_configs import CaptureConfig, PNames
11
+ from spectre_core.capture_configs import CaptureConfig, PName
11
12
  from spectre_core.exceptions import ModeNotFoundError
12
- from ._spectrogram import Spectrogram
13
+ from ._spectrogram import Spectrogram, SpectrumUnit
13
14
  from ._array_operations import is_close
14
15
 
15
16
  @dataclass
16
17
  class TestResults:
17
- # Whether the times array matches analytically
18
- times_validated: bool = False
19
- # Whether the frequencies array matches analytically
20
- frequencies_validated: bool = False
21
- # Maps each time to whether the corresponding spectrum matched analytically
22
- spectrum_validated: dict[float, bool] = None
18
+ """
19
+ Summarise the validation results when comparing two spectrograms.
23
20
 
21
+ :ivar times_validated: Whether the time arrays match.
22
+ :ivar frequencies_validated: Whether the frequency arrays match.
23
+ :ivar spectrum_validated: Maps the relative time of each spectrum to its match results.
24
+ """
25
+ times_validated: bool = False
26
+ frequencies_validated: bool = False
27
+ spectrum_validated: dict[float, bool] = field(default_factory=dict)
24
28
 
25
29
  @property
26
- def num_validated_spectrums(self) -> int:
27
- """Counts the number of validated spectrums."""
30
+ def num_validated_spectrums(
31
+ self
32
+ ) -> int:
33
+ """Returns the count of spectrums that successfully passed validation."""
28
34
  return sum(is_validated for is_validated in self.spectrum_validated.values())
29
35
 
30
36
 
31
37
  @property
32
- def num_invalid_spectrums(self) -> int:
33
- """Counts the number of spectrums that are not validated."""
38
+ def num_invalid_spectrums(
39
+ self
40
+ ) -> int:
41
+ """Returns the count of spectrums that failed validation."""
34
42
  return len(self.spectrum_validated) - self.num_validated_spectrums
35
43
 
36
-
37
- def to_dict(self) -> dict[str, bool | dict[float, bool]]:
44
+
45
+ def to_dict(
46
+ self
47
+ ) -> dict[str, bool | dict[float, bool]]:
48
+ """Converts the instance into a serialisable dictionary."""
38
49
  return {
39
- "times_validated": self.times_validated,
50
+ "times_validated" : self.times_validated,
40
51
  "frequencies_validated": self.frequencies_validated,
41
- "spectrum_validated": self.spectrum_validated
52
+ "spectrum_validated" : self.spectrum_validated
42
53
  }
43
54
 
44
55
 
45
56
  class _AnalyticalFactory:
46
- def __init__(self):
47
- self._builders: dict[str, Callable] = {
48
- "cosine-signal-1": self.cosine_signal_1,
49
- "tagged-staircase": self.tagged_staircase
57
+ """Factory for creating analytical spectrograms."""
58
+ def __init__(
59
+ self
60
+ ) -> None:
61
+ """Initialises an instance of the `_AnalyticalFactory` class."""
62
+ self._builders: dict[str, Callable[[int, CaptureConfig], Spectrogram]] = {
63
+ "cosine_signal_1" : self._cosine_signal_1,
64
+ "tagged_staircase": self._tagged_staircase
50
65
  }
51
- self._test_modes = list(self.builders.keys())
52
66
 
53
67
 
54
68
  @property
55
- def builders(self) -> dict[str, Callable]:
69
+ def builders(
70
+ self
71
+ ) -> dict[str, Callable[[int, CaptureConfig], Spectrogram]]:
72
+ """
73
+ Provides a mapping from each `Test` receiver mode to its corresponding builder method.
74
+
75
+ Each builder method generates the expected spectrograms for a session run in the associated mode.
76
+ """
56
77
  return self._builders
57
78
 
58
79
 
59
80
  @property
60
- def test_modes(self) -> list[str]:
61
- return self._test_modes
81
+ def test_modes(
82
+ self
83
+ ) -> list[str]:
84
+ """Returns the available modes for the `Test` receiver."""
85
+ return list(self.builders.keys())
62
86
 
63
87
 
64
- def get_spectrogram(self,
65
- num_spectrums: int,
66
- capture_config: CaptureConfig) -> Spectrogram:
67
- """Get an analytical spectrogram based on a test receiver capture config.
68
-
69
- The anaytically derived spectrogram should be able to be fully determined
70
- by parameters in the corresponding capture config and the number of spectrums
71
- in the output spectrogram.
88
+ def get_spectrogram(
89
+ self,
90
+ num_spectrums: int,
91
+ capture_config: CaptureConfig
92
+ ) -> Spectrogram:
93
+ """
94
+ Generates an analytical spectrogram based on the capture config for a `Test` receiver.
95
+
96
+ :param num_spectrums: The number of spectrums to include in the output spectrogram.
97
+ :param capture_config: The capture config used for `Test` receiver data capture.
98
+ :raises ValueError: Raised if the capture config is not associated with a `Test` receiver.
99
+ :raises ModeNotFoundError: Raised if the specified `Test` mode in the capture config lacks
100
+ a corresponding builder method.
101
+ :return: The expected spectrogram for running a session with the `Test` receiver in the specified mode.
72
102
  """
73
-
74
103
  if capture_config.receiver_name != "test":
75
104
  raise ValueError(f"Input capture config must correspond to the test receiver")
76
105
 
77
106
  builder_method = self.builders.get(capture_config.receiver_mode)
78
107
  if builder_method is None:
79
- raise ModeNotFoundError(f"Test mode not found. Expected one of {self.test_modes}, but received {capture_config.receiver_mode}")
108
+ raise ModeNotFoundError(f"Test mode not found. Expected one of '{self.test_modes}', but received '{capture_config.receiver_mode}'")
80
109
  return builder_method(num_spectrums,
81
110
  capture_config)
82
111
 
83
112
 
84
- def cosine_signal_1(self,
85
- num_spectrums: int,
86
- capture_config: CaptureConfig) -> Spectrogram:
87
- # Extract necessary parameters from the capture configuration.
88
- window_size = capture_config.get_parameter_value(PNames.WINDOW_SIZE)
89
- sample_rate = capture_config.get_parameter_value(PNames.SAMPLE_RATE)
90
- amplitude = capture_config.get_parameter_value(PNames.AMPLITUDE)
91
- frequency = capture_config.get_parameter_value(PNames.FREQUENCY)
92
- window_hop = capture_config.get_parameter_value(PNames.WINDOW_HOP)
93
- center_frequency = capture_config.get_parameter_value(PNames.CENTER_FREQUENCY)
113
+ def _cosine_signal_1(
114
+ self,
115
+ num_spectrums: int,
116
+ capture_config: CaptureConfig
117
+ ) -> Spectrogram:
118
+ """Creates the expected spectrogram for the `Test` receiver operating in the mode `cosine-signal-1`."""
119
+ # 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
+
94
127
  # Calculate derived parameters a (sampling rate ratio) and p (sampled periods).
95
128
  a = int(sample_rate / frequency)
96
129
  p = int(window_size / a)
@@ -119,18 +152,21 @@ class _AnalyticalFactory:
119
152
  times,
120
153
  frequencies,
121
154
  'analytically-derived-spectrogram',
122
- spectrum_type="amplitude")
123
-
124
-
125
- def tagged_staircase(self,
126
- num_spectrums: int,
127
- capture_config: CaptureConfig) -> Spectrogram:
128
- # Extract necessary parameters from the capture configuration.
129
- window_size = capture_config.get_parameter_value(PNames.WINDOW_SIZE)
130
- min_samples_per_step = capture_config.get_parameter_value(PNames.MIN_SAMPLES_PER_STEP)
131
- max_samples_per_step = capture_config.get_parameter_value(PNames.MAX_SAMPLES_PER_STEP)
132
- step_increment = capture_config.get_parameter_value(PNames.STEP_INCREMENT)
133
- samp_rate = capture_config.get_parameter_value(PNames.SAMPLE_RATE)
155
+ SpectrumUnit.AMPLITUDE)
156
+
157
+
158
+ def _tagged_staircase(
159
+ self,
160
+ num_spectrums: int,
161
+ capture_config: CaptureConfig
162
+ ) -> Spectrogram:
163
+ """Creates the expected spectrogram for the `Test` receiver operating in the mode `tagged-staircase`."""
164
+ # 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))
134
170
 
135
171
  # Calculate step sizes and derived parameters.
136
172
  num_samples_per_step = np.arange(min_samples_per_step, max_samples_per_step + 1, step_increment)
@@ -156,7 +192,7 @@ class _AnalyticalFactory:
156
192
 
157
193
  # Compute the frequency array
158
194
  baseband_frequencies = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval))
159
- frequencies = np.empty((window_size * num_steps))
195
+ frequencies = np.empty((window_size * num_steps), dtype=np.float32)
160
196
  for i in range(num_steps):
161
197
  lower_bound = i * window_size
162
198
  upper_bound = (i + 1) * window_size
@@ -167,25 +203,44 @@ class _AnalyticalFactory:
167
203
  times,
168
204
  frequencies,
169
205
  'analytically-derived-spectrogram',
170
- spectrum_type="amplitude")
206
+ SpectrumUnit.AMPLITUDE)
171
207
 
172
208
 
173
- def get_analytical_spectrogram(num_spectrums: int,
174
- capture_config: CaptureConfig) -> Spectrogram:
209
+ def get_analytical_spectrogram(
210
+ num_spectrums: int,
211
+ capture_config: CaptureConfig
212
+ ) -> 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.
175
215
 
216
+ This function constructs the analytical spectrogram using the capture config for a `Test`
217
+ receiver operating in a specific mode.
218
+
219
+ :param num_spectrums: The number of spectrums in the output spectrogram.
220
+ :param capture_config: The capture config used for data capture.
221
+ :return: The expected, analytically derived spectrogram for the specified mode of the `Test` receiver.
222
+ """
176
223
  factory = _AnalyticalFactory()
177
224
  return factory.get_spectrogram(num_spectrums,
178
225
  capture_config)
179
226
 
180
227
 
181
- def validate_analytically(spectrogram: Spectrogram,
182
- capture_config: CaptureConfig,
183
- absolute_tolerance: float) -> TestResults:
228
+ def validate_analytically(
229
+ spectrogram: Spectrogram,
230
+ capture_config: CaptureConfig,
231
+ absolute_tolerance: float
232
+ ) -> TestResults:
233
+ """Validate a spectrogram generated during sessions with a `Test` receiver operating
234
+ in a particular mode.
184
235
 
236
+ :param spectrogram: The spectrogram to be validated.
237
+ :param capture_config: The capture config used for data capture.
238
+ :param absolute_tolerance: Tolerance level for numerical comparisons.
239
+ :return: A `TestResults` object summarising the validation outcome.
240
+ """
185
241
  analytical_spectrogram = get_analytical_spectrogram(spectrogram.num_times,
186
242
  capture_config)
187
243
 
188
-
189
244
  test_results = TestResults()
190
245
 
191
246
  test_results.times_validated = bool(is_close(analytical_spectrogram.times,