spectre-core 0.0.9__py3-none-any.whl → 0.0.10__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 (106) hide show
  1. spectre_core/__init__.py +0 -3
  2. spectre_core/_file_io/__init__.py +15 -0
  3. spectre_core/_file_io/file_handlers.py +128 -0
  4. spectre_core/capture_configs/__init__.py +29 -0
  5. spectre_core/capture_configs/_capture_config.py +85 -0
  6. spectre_core/capture_configs/_capture_templates.py +222 -0
  7. spectre_core/capture_configs/_parameters.py +110 -0
  8. spectre_core/capture_configs/_pconstraints.py +82 -0
  9. spectre_core/capture_configs/_ptemplates.py +450 -0
  10. spectre_core/capture_configs/_pvalidators.py +173 -0
  11. spectre_core/chunks/__init__.py +17 -201
  12. spectre_core/chunks/{base.py → _base.py} +15 -60
  13. spectre_core/chunks/_chunks.py +200 -0
  14. spectre_core/chunks/{factory.py → _factory.py} +6 -7
  15. spectre_core/chunks/library/{callisto/chunk.py → _callisto.py} +4 -7
  16. spectre_core/chunks/library/{fixed/chunk.py → _fixed_center_frequency.py} +7 -64
  17. spectre_core/chunks/library/_swept_center_frequency.py +103 -0
  18. spectre_core/config/__init__.py +20 -0
  19. spectre_core/config/_paths.py +77 -0
  20. spectre_core/config/_time_formats.py +15 -0
  21. spectre_core/exceptions.py +4 -5
  22. spectre_core/logging/__init__.py +11 -0
  23. spectre_core/logging/_configure.py +35 -0
  24. spectre_core/logging/_decorators.py +19 -0
  25. spectre_core/{logging.py → logging/_log_handlers.py} +13 -58
  26. spectre_core/plotting/__init__.py +7 -1
  27. spectre_core/plotting/{base.py → _base.py} +40 -20
  28. spectre_core/plotting/_format.py +18 -0
  29. spectre_core/plotting/{panel_stack.py → _panel_stack.py} +48 -48
  30. spectre_core/plotting/_panels.py +234 -0
  31. spectre_core/post_processing/__init__.py +10 -2
  32. spectre_core/post_processing/_base.py +119 -0
  33. spectre_core/post_processing/{factory.py → _factory.py} +7 -6
  34. spectre_core/post_processing/{post_processor.py → _post_processor.py} +3 -3
  35. spectre_core/post_processing/library/_fixed_center_frequency.py +115 -0
  36. spectre_core/post_processing/library/_swept_center_frequency.py +382 -0
  37. spectre_core/receivers/__init__.py +12 -2
  38. spectre_core/receivers/_base.py +352 -0
  39. spectre_core/receivers/{factory.py → _factory.py} +2 -2
  40. spectre_core/receivers/_spec_names.py +20 -0
  41. spectre_core/receivers/gr/__init__.py +3 -0
  42. spectre_core/receivers/gr/_base.py +33 -0
  43. spectre_core/receivers/gr/_rsp1a.py +158 -0
  44. spectre_core/receivers/gr/_test.py +123 -0
  45. spectre_core/receivers/library/_rsp1a.py +61 -0
  46. spectre_core/receivers/library/_test.py +221 -0
  47. spectre_core/spectrograms/__init__.py +18 -0
  48. spectre_core/spectrograms/{analytical.py → _analytical.py} +29 -27
  49. spectre_core/spectrograms/{array_operations.py → _array_operations.py} +47 -1
  50. spectre_core/spectrograms/{spectrogram.py → _spectrogram.py} +62 -35
  51. spectre_core/spectrograms/{transform.py → _transform.py} +76 -89
  52. spectre_core/{post_processing/library → wgetting}/__init__.py +4 -5
  53. spectre_core/wgetting/_callisto.py +155 -0
  54. {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/METADATA +1 -1
  55. spectre_core-0.0.10.dist-info/RECORD +63 -0
  56. spectre_core/cfg.py +0 -116
  57. spectre_core/chunks/library/__init__.py +0 -8
  58. spectre_core/chunks/library/sweep/__init__.py +0 -0
  59. spectre_core/chunks/library/sweep/chunk.py +0 -400
  60. spectre_core/dynamic_imports.py +0 -22
  61. spectre_core/file_handlers/base.py +0 -68
  62. spectre_core/file_handlers/configs.py +0 -271
  63. spectre_core/file_handlers/json.py +0 -40
  64. spectre_core/file_handlers/text.py +0 -21
  65. spectre_core/plotting/factory.py +0 -26
  66. spectre_core/plotting/format.py +0 -19
  67. spectre_core/plotting/library/__init__.py +0 -7
  68. spectre_core/plotting/library/frequency_cuts/panel.py +0 -74
  69. spectre_core/plotting/library/integral_over_frequency/panel.py +0 -34
  70. spectre_core/plotting/library/spectrogram/panel.py +0 -92
  71. spectre_core/plotting/library/time_cuts/panel.py +0 -77
  72. spectre_core/plotting/panel_register.py +0 -13
  73. spectre_core/post_processing/base.py +0 -132
  74. spectre_core/post_processing/library/fixed/__init__.py +0 -0
  75. spectre_core/post_processing/library/fixed/event_handler.py +0 -40
  76. spectre_core/post_processing/library/sweep/event_handler.py +0 -54
  77. spectre_core/receivers/base.py +0 -422
  78. spectre_core/receivers/library/__init__.py +0 -7
  79. spectre_core/receivers/library/rsp1a/__init__.py +0 -0
  80. spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
  81. spectre_core/receivers/library/rsp1a/gr/fixed.py +0 -104
  82. spectre_core/receivers/library/rsp1a/gr/sweep.py +0 -129
  83. spectre_core/receivers/library/rsp1a/receiver.py +0 -68
  84. spectre_core/receivers/library/rspduo/__init__.py +0 -0
  85. spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
  86. spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +0 -114
  87. spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +0 -131
  88. spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py +0 -120
  89. spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py +0 -119
  90. spectre_core/receivers/library/rspduo/receiver.py +0 -97
  91. spectre_core/receivers/library/test/__init__.py +0 -0
  92. spectre_core/receivers/library/test/gr/__init__.py +0 -0
  93. spectre_core/receivers/library/test/gr/cosine_signal_1.py +0 -83
  94. spectre_core/receivers/library/test/gr/tagged_staircase.py +0 -93
  95. spectre_core/receivers/library/test/receiver.py +0 -203
  96. spectre_core/receivers/validators.py +0 -231
  97. spectre_core/web_fetch/callisto.py +0 -101
  98. spectre_core-0.0.9.dist-info/RECORD +0 -74
  99. /spectre_core/chunks/{chunk_register.py → _register.py} +0 -0
  100. /spectre_core/post_processing/{event_handler_register.py → _register.py} +0 -0
  101. /spectre_core/receivers/{receiver_register.py → _register.py} +0 -0
  102. /spectre_core/{chunks/library/callisto/__init__.py → receivers/gr/_rspduo.py} +0 -0
  103. /spectre_core/{chunks/library/fixed/__init__.py → receivers/library/_rspduo.py} +0 -0
  104. {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/LICENSE +0 -0
  105. {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/WHEEL +0 -0
  106. {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0
6
+ #
7
+ # GNU Radio Python Flow Graph
8
+ # Title: Test receiver
9
+ # GNU Radio version: 3.10.1.1
10
+
11
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
12
+ # This file is part of SPECTRE
13
+ # SPDX-License-Identifier: GPL-3.0-or-later
14
+
15
+ #
16
+ # Test receiver top blocks
17
+ #
18
+
19
+ from functools import partial
20
+ from dataclasses import dataclass
21
+
22
+ from gnuradio import gr
23
+ from gnuradio import blocks
24
+ from gnuradio import spectre
25
+ from gnuradio import analog
26
+
27
+ from spectre_core.capture_configs import Parameters, PNames
28
+ from spectre_core.config import get_chunks_dir_path
29
+ from ._base import capture
30
+
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, "cosine-signal-1", catch_exceptions=True)
37
+
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)
45
+
46
+ ##################################################
47
+ # Blocks
48
+ ##################################################
49
+ self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_chunks_dir_path(),
50
+ tag,
51
+ batch_size,
52
+ samp_rate)
53
+ self.blocks_throttle_0_1 = blocks.throttle(gr.sizeof_float*1,
54
+ samp_rate,
55
+ True)
56
+ self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1,
57
+ samp_rate,
58
+ True)
59
+ self.blocks_null_source_1 = blocks.null_source(gr.sizeof_float*1)
60
+ self.blocks_float_to_complex_1 = blocks.float_to_complex(1)
61
+ self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate,
62
+ analog.GR_COS_WAVE,
63
+ frequency,
64
+ amplitude,
65
+ 0,
66
+ 0)
67
+
68
+
69
+ ##################################################
70
+ # Connections
71
+ ##################################################
72
+ self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))
73
+ self.connect((self.blocks_float_to_complex_1, 0), (self.spectre_batched_file_sink_0, 0))
74
+ self.connect((self.blocks_null_source_1, 0), (self.blocks_throttle_0_1, 0))
75
+ self.connect((self.blocks_throttle_0, 0), (self.blocks_float_to_complex_1, 0))
76
+ self.connect((self.blocks_throttle_0_1, 0), (self.blocks_float_to_complex_1, 1))
77
+
78
+
79
+ class _tagged_staircase(gr.top_block):
80
+ def __init__(self,
81
+ tag: str,
82
+ parameters: Parameters):
83
+ gr.top_block.__init__(self, "tagged-staircase", catch_exceptions=True)
84
+
85
+ ##################################################
86
+ # Unpack capture config
87
+ ##################################################
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)
94
+
95
+ ##################################################
96
+ # Blocks
97
+ ##################################################
98
+ self.spectre_tagged_staircase_0 = spectre.tagged_staircase(min_samples_per_step,
99
+ max_samples_per_step,
100
+ frequency_step,
101
+ step_increment,
102
+ samp_rate)
103
+ self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_chunks_dir_path(),
104
+ tag,
105
+ batch_size,
106
+ samp_rate,
107
+ True,
108
+ 'rx_freq',
109
+ 0) # zero means the center frequency is unset
110
+ self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex*1, samp_rate, True)
111
+
112
+
113
+ ##################################################
114
+ # Connections
115
+ ##################################################
116
+ self.connect((self.blocks_throttle_0, 0), (self.spectre_batched_file_sink_0, 0))
117
+ self.connect((self.spectre_tagged_staircase_0, 0), (self.blocks_throttle_0, 0))
118
+
119
+
120
+ @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)
@@ -0,0 +1,61 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from typing import Optional
6
+ from dataclasses import dataclass
7
+ from functools import partial
8
+
9
+ from spectre_core.capture_configs import (
10
+ CaptureModes
11
+ )
12
+ from ..gr._rsp1a import CaptureMethods
13
+ from .._spec_names import SpecNames
14
+ from .._base import SDRPlayReceiver
15
+ from .._register import register_receiver
16
+
17
+ @dataclass
18
+ class Modes:
19
+ FIXED_CENTER_FREQUENCY = CaptureModes.FIXED_CENTER_FREQUENCY
20
+ SWEPT_CENTER_FREQUENCY = CaptureModes.SWEPT_CENTER_FREQUENCY
21
+
22
+ @register_receiver("rsp1a")
23
+ class _Receiver(SDRPlayReceiver):
24
+ def __init__(self,
25
+ name: str,
26
+ mode: Optional[str]):
27
+ super().__init__(name,
28
+ mode)
29
+
30
+
31
+ def _add_specs(self) -> None:
32
+ self.add_spec( SpecNames.SAMPLE_RATE_LOWER_BOUND, 200e3 ) # Hz
33
+ self.add_spec( SpecNames.SAMPLE_RATE_UPPER_BOUND, 10e6 ) # Hz
34
+ self.add_spec( SpecNames.FREQUENCY_LOWER_BOUND , 1e3 ) # Hz
35
+ self.add_spec( SpecNames.FREQUENCY_UPPER_BOUND , 2e9 ) # Hz
36
+ self.add_spec( SpecNames.IF_GAIN_UPPER_BOUND , -20 ) # dB
37
+ self.add_spec( SpecNames.RF_GAIN_UPPER_BOUND , 0 ) # dB
38
+ self.add_spec( SpecNames.API_RETUNING_LATENCY , 50 * 1e-3 ) # s
39
+ self.add_spec( SpecNames.BANDWIDTH_OPTIONS,
40
+ [200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000])
41
+
42
+
43
+ def _add_capture_methods(self) -> None:
44
+ self.add_capture_method(Modes.FIXED_CENTER_FREQUENCY,
45
+ CaptureMethods.fixed_center_frequency)
46
+ self.add_capture_method(Modes.SWEPT_CENTER_FREQUENCY,
47
+ CaptureMethods._swept_center_frequency)
48
+
49
+
50
+ def _add_capture_templates(self):
51
+ self.add_capture_template(Modes.FIXED_CENTER_FREQUENCY,
52
+ self._get_capture_template_fixed_center_frequency())
53
+ self.add_capture_template(Modes.SWEPT_CENTER_FREQUENCY,
54
+ self._get_capture_template_swept_center_frequency())
55
+
56
+
57
+ def _add_pvalidators(self):
58
+ self.add_pvalidator(CaptureModes.FIXED_CENTER_FREQUENCY,
59
+ self._get_pvalidator_fixed_center_frequency())
60
+ self.add_pvalidator(CaptureModes.SWEPT_CENTER_FREQUENCY,
61
+ self._get_pvalidator_swept_center_frequency())
@@ -0,0 +1,221 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional, Callable
7
+
8
+ from spectre_core.capture_configs import (
9
+ CaptureTemplate, CaptureModes, Parameters, Bound, PValidators, PNames,
10
+ get_base_capture_template, make_base_capture_template, get_base_ptemplate
11
+ )
12
+ from ..gr._test import CaptureMethods
13
+ from .._spec_names import SpecNames
14
+ from .._base import BaseReceiver
15
+ from .._register import register_receiver
16
+
17
+
18
+ @dataclass
19
+ class Modes:
20
+ COSINE_SIGNAL_1 = "cosine-signal-1"
21
+ TAGGED_STAIRCASE = "tagged-staircase"
22
+
23
+
24
+ @register_receiver("test")
25
+ class _Receiver(BaseReceiver):
26
+ def __init__(self,
27
+ name: str,
28
+ mode: Optional[str]):
29
+ super().__init__(name,
30
+ mode)
31
+
32
+
33
+ def _add_specs(self) -> None:
34
+ self.add_spec( SpecNames.SAMPLE_RATE_LOWER_BOUND, 64000 )
35
+ self.add_spec( SpecNames.SAMPLE_RATE_UPPER_BOUND, 640000 )
36
+ self.add_spec( SpecNames.FREQUENCY_LOWER_BOUND , 16000 )
37
+ self.add_spec( SpecNames.FREQUENCY_UPPER_BOUND , 160000 )
38
+
39
+
40
+ def _add_capture_methods(self) -> None:
41
+ self.add_capture_method( Modes.COSINE_SIGNAL_1 , CaptureMethods.cosine_signal_1 )
42
+ self.add_capture_method( Modes.TAGGED_STAIRCASE, CaptureMethods.tagged_staircase)
43
+
44
+
45
+ def _add_pvalidators(self) -> None:
46
+ self.add_pvalidator( Modes.COSINE_SIGNAL_1 , self.__get_pvalidator_cosine_signal_1() )
47
+ self.add_pvalidator( Modes.TAGGED_STAIRCASE, self.__get_pvalidator_tagged_staircase() )
48
+
49
+
50
+ def _add_capture_templates(self) -> None:
51
+ self.add_capture_template( Modes.COSINE_SIGNAL_1 , self.__get_capture_template_cosine_signal_1() )
52
+ self.add_capture_template( Modes.TAGGED_STAIRCASE, self.__get_capture_template_tagged_staircase() )
53
+
54
+
55
+ def __get_capture_template_cosine_signal_1(self) -> CaptureTemplate:
56
+ #
57
+ # Create the base template
58
+ #
59
+ capture_template = get_base_capture_template( CaptureModes.FIXED_CENTER_FREQUENCY )
60
+ capture_template.add_ptemplate( get_base_ptemplate(PNames.AMPLITUDE) )
61
+ capture_template.add_ptemplate( get_base_ptemplate(PNames.FREQUENCY) )
62
+
63
+ #
64
+ # Update the defaults
65
+ #
66
+ capture_template.set_defaults(
67
+ (PNames.BATCH_SIZE, 3.0),
68
+ (PNames.CENTER_FREQUENCY, 16000),
69
+ (PNames.AMPLITUDE, 2.0),
70
+ (PNames.FREQUENCY, 32000),
71
+ (PNames.SAMPLE_RATE, 128000),
72
+ (PNames.WINDOW_HOP, 512),
73
+ (PNames.WINDOW_SIZE, 512),
74
+ (PNames.WINDOW_TYPE, "boxcar")
75
+ )
76
+
77
+ #
78
+ # Enforce defaults
79
+ #
80
+ capture_template.enforce_defaults(
81
+ PNames.TIME_RESOLUTION,
82
+ PNames.TIME_RANGE,
83
+ PNames.FREQUENCY_RESOLUTION,
84
+ PNames.WINDOW_TYPE
85
+ )
86
+
87
+
88
+ #
89
+ # Adding pconstraints
90
+ #
91
+ capture_template.add_pconstraint(
92
+ PNames.SAMPLE_RATE,
93
+ [
94
+ Bound(
95
+ lower_bound=self.get_spec(SpecNames.SAMPLE_RATE_LOWER_BOUND),
96
+ upper_bound=self.get_spec(SpecNames.SAMPLE_RATE_UPPER_BOUND)
97
+ )
98
+ ]
99
+ )
100
+ capture_template.add_pconstraint(
101
+ PNames.FREQUENCY,
102
+ [
103
+ Bound(
104
+ lower_bound=self.get_spec(SpecNames.FREQUENCY_LOWER_BOUND),
105
+ upper_bound=self.get_spec(SpecNames.FREQUENCY_UPPER_BOUND)
106
+ )
107
+ ]
108
+ )
109
+ return capture_template
110
+
111
+
112
+ def __get_capture_template_tagged_staircase(self) -> CaptureTemplate:
113
+ #
114
+ # Make the base template
115
+ #
116
+ capture_template = make_base_capture_template(
117
+ PNames.TIME_RESOLUTION,
118
+ PNames.FREQUENCY_RESOLUTION,
119
+ PNames.TIME_RANGE,
120
+ PNames.SAMPLE_RATE,
121
+ PNames.BATCH_SIZE,
122
+ PNames.WINDOW_TYPE,
123
+ PNames.WINDOW_HOP,
124
+ PNames.WINDOW_SIZE,
125
+ PNames.EVENT_HANDLER_KEY,
126
+ PNames.CHUNK_KEY,
127
+ PNames.WATCH_EXTENSION,
128
+ PNames.MIN_SAMPLES_PER_STEP,
129
+ PNames.MAX_SAMPLES_PER_STEP,
130
+ PNames.FREQUENCY_STEP,
131
+ PNames.STEP_INCREMENT,
132
+ PNames.OBS_ALT,
133
+ PNames.OBS_LAT,
134
+ PNames.OBS_LON,
135
+ PNames.OBJECT,
136
+ PNames.ORIGIN,
137
+ PNames.TELESCOPE,
138
+ PNames.INSTRUMENT
139
+ )
140
+
141
+ #
142
+ # Update the defaults
143
+ #
144
+ capture_template.set_defaults(
145
+ (PNames.BATCH_SIZE, 3.0),
146
+ (PNames.FREQUENCY_STEP, 128000),
147
+ (PNames.MAX_SAMPLES_PER_STEP, 5000),
148
+ (PNames.MIN_SAMPLES_PER_STEP, 4000),
149
+ (PNames.SAMPLE_RATE, 128000),
150
+ (PNames.STEP_INCREMENT, 200),
151
+ (PNames.WINDOW_HOP, 512),
152
+ (PNames.WINDOW_SIZE, 512),
153
+ (PNames.WINDOW_TYPE, "boxcar"),
154
+ (PNames.EVENT_HANDLER_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
155
+ (PNames.CHUNK_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
156
+ (PNames.WATCH_EXTENSION, "bin")
157
+ )
158
+
159
+
160
+ #
161
+ # Enforce defaults
162
+ #
163
+ capture_template.enforce_defaults(
164
+ PNames.TIME_RESOLUTION,
165
+ PNames.TIME_RANGE,
166
+ PNames.FREQUENCY_RESOLUTION,
167
+ PNames.WINDOW_TYPE,
168
+ PNames.EVENT_HANDLER_KEY,
169
+ PNames.CHUNK_KEY,
170
+ PNames.WATCH_EXTENSION
171
+ )
172
+
173
+ return capture_template
174
+
175
+
176
+ def __get_pvalidator_cosine_signal_1(self) -> Callable:
177
+ def pvalidator(parameters: Parameters):
178
+ PValidators.window(parameters)
179
+
180
+ sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
181
+ frequency = parameters.get_parameter_value(PNames.FREQUENCY)
182
+ window_size = parameters.get_parameter_value(PNames.WINDOW_SIZE)
183
+
184
+ # check that the sample rate is an integer multiple of the underlying signal frequency
185
+ if sample_rate % frequency != 0:
186
+ raise ValueError("The sampling rate must be some integer multiple of frequency")
187
+
188
+ a = sample_rate/frequency
189
+ if a < 2:
190
+ raise ValueError((f"The ratio of sampling rate over frequency must be greater than two. "
191
+ f"Got {a}"))
192
+
193
+ # analytical requirement
194
+ # if p is the number of sampled cycles, we can find that p = window_size / a
195
+ # the number of sampled cycles must be a positive natural number.
196
+ p = window_size / a
197
+ if window_size % a != 0:
198
+ raise ValueError((f"The number of sampled cycles must be a positive natural number. "
199
+ f"Computed that p={p}"))
200
+ return pvalidator
201
+
202
+
203
+ def __get_pvalidator_tagged_staircase(self) -> None:
204
+ def pvalidator(parameters: Parameters):
205
+ PValidators.window(parameters)
206
+
207
+ freq_step = parameters.get_parameter_value(PNames.FREQUENCY_STEP)
208
+ sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
209
+ min_samples_per_step = parameters.get_parameter_value(PNames.MIN_SAMPLES_PER_STEP)
210
+ max_samples_per_step = parameters.get_parameter_value(PNames.MAX_SAMPLES_PER_STEP)
211
+
212
+ if freq_step != sample_rate:
213
+ raise ValueError(f"The frequency step must be equal to the sampling rate")
214
+
215
+
216
+ if min_samples_per_step > max_samples_per_step:
217
+ raise ValueError((f"Minimum samples per step cannot be greater than the maximum samples per step. "
218
+ f"Got {min_samples_per_step}, which is greater than {max_samples_per_step}"))
219
+
220
+ return pvalidator
221
+
@@ -1,3 +1,21 @@
1
1
  # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from ._analytical import (
6
+ get_analytical_spectrogram, validate_analytically, TestResults
7
+ )
8
+ from ._spectrogram import (
9
+ Spectrogram, FrequencyCut, TimeCut, SpectrumTypes, TimeTypes
10
+ )
11
+ from ._transform import (
12
+ frequency_chop, time_chop, frequency_average, time_average,
13
+ join_spectrograms
14
+ )
15
+
16
+ __all__ = [
17
+ "get_analytical_spectrogram", "validate_analytically", "TestResults",
18
+ "Spectrogram", "FrequencyCut", "TimeCut", "SpectrumTypes", "frequency_chop",
19
+ "time_chop", "frequency_average","time_average", "join_spectrograms",
20
+ "TimeTypes"
21
+ ]
@@ -2,17 +2,21 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- from typing import Callable, Any
5
+ from typing import Callable
6
6
  from dataclasses import dataclass
7
7
 
8
8
  import numpy as np
9
9
 
10
- from spectre_core.file_handlers.configs import CaptureConfig
11
- from spectre_core.spectrograms.spectrogram import Spectrogram
12
- from spectre_core.spectrograms.array_operations import is_close
10
+ from spectre_core.capture_configs import CaptureConfig, PNames
13
11
  from spectre_core.exceptions import ModeNotFoundError
12
+ from ._spectrogram import Spectrogram
13
+ from ._array_operations import is_close
14
14
 
15
-
15
+ __all__ = [
16
+ "get_analytical_spectrogram",
17
+ "validate_analytically",
18
+ "TestResults"
19
+ ]
16
20
 
17
21
  @dataclass
18
22
  class TestResults:
@@ -36,7 +40,7 @@ class TestResults:
36
40
  return len(self.spectrum_validated) - self.num_validated_spectrums
37
41
 
38
42
 
39
- def jsonify(self) -> dict[str, Any]:
43
+ def to_dict(self) -> dict[str, bool | dict[float, bool]]:
40
44
  return {
41
45
  "times_validated": self.times_validated,
42
46
  "frequencies_validated": self.frequencies_validated,
@@ -72,14 +76,13 @@ class _AnalyticalFactory:
72
76
  by parameters in the corresponding capture config and the number of spectrums
73
77
  in the output spectrogram.
74
78
  """
75
- receiver_name, test_mode = capture_config['receiver'], capture_config['mode']
76
79
 
77
- if receiver_name != "test":
80
+ if capture_config.receiver_name != "test":
78
81
  raise ValueError(f"Input capture config must correspond to the test receiver")
79
82
 
80
- builder_method = self.builders.get(test_mode)
83
+ builder_method = self.builders.get(capture_config.receiver_mode)
81
84
  if builder_method is None:
82
- raise ModeNotFoundError(f"Test mode not found. Expected one of {self.test_modes}, but received {test_mode}")
85
+ raise ModeNotFoundError(f"Test mode not found. Expected one of {self.test_modes}, but received {capture_config.receiver_mode}")
83
86
  return builder_method(num_spectrums,
84
87
  capture_config)
85
88
 
@@ -88,14 +91,14 @@ class _AnalyticalFactory:
88
91
  num_spectrums: int,
89
92
  capture_config: CaptureConfig) -> Spectrogram:
90
93
  # Extract necessary parameters from the capture configuration.
91
- window_size = capture_config['window_size']
92
- samp_rate = capture_config['samp_rate']
93
- amplitude = capture_config['amplitude']
94
- frequency = capture_config['frequency']
95
- hop = capture_config['hop']
96
-
94
+ window_size = capture_config.get_parameter_value(PNames.WINDOW_SIZE)
95
+ sample_rate = capture_config.get_parameter_value(PNames.SAMPLE_RATE)
96
+ amplitude = capture_config.get_parameter_value(PNames.AMPLITUDE)
97
+ frequency = capture_config.get_parameter_value(PNames.FREQUENCY)
98
+ window_hop = capture_config.get_parameter_value(PNames.WINDOW_HOP)
99
+ center_frequency = capture_config.get_parameter_value(PNames.CENTER_FREQUENCY)
97
100
  # Calculate derived parameters a (sampling rate ratio) and p (sampled periods).
98
- a = int(samp_rate / frequency)
101
+ a = int(sample_rate / frequency)
99
102
  p = int(window_size / a)
100
103
 
101
104
  # Create the analytical spectrum, which is constant in time.
@@ -111,11 +114,11 @@ class _AnalyticalFactory:
111
114
  analytical_dynamic_spectra = np.ones((window_size, num_spectrums)) * spectrum[:, np.newaxis]
112
115
 
113
116
  # Compute time array.
114
- sampling_interval = 1 / samp_rate
115
- times = np.arange(num_spectrums) * hop * sampling_interval
117
+ sampling_interval = 1 / sample_rate
118
+ times = np.arange(num_spectrums) * window_hop * sampling_interval
116
119
 
117
120
  # compute the frequency array.
118
- frequencies = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval))
121
+ frequencies = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval)) + center_frequency
119
122
 
120
123
  # Return the spectrogram.
121
124
  return Spectrogram(analytical_dynamic_spectra,
@@ -129,11 +132,11 @@ class _AnalyticalFactory:
129
132
  num_spectrums: int,
130
133
  capture_config: CaptureConfig) -> Spectrogram:
131
134
  # Extract necessary parameters from the capture configuration.
132
- window_size = capture_config['window_size']
133
- min_samples_per_step = capture_config['min_samples_per_step']
134
- max_samples_per_step = capture_config['max_samples_per_step']
135
- step_increment = capture_config['step_increment']
136
- samp_rate = capture_config['samp_rate']
135
+ window_size = capture_config.get_parameter_value(PNames.WINDOW_SIZE)
136
+ min_samples_per_step = capture_config.get_parameter_value(PNames.MIN_SAMPLES_PER_STEP)
137
+ max_samples_per_step = capture_config.get_parameter_value(PNames.MAX_SAMPLES_PER_STEP)
138
+ step_increment = capture_config.get_parameter_value(PNames.STEP_INCREMENT)
139
+ samp_rate = capture_config.get_parameter_value(PNames.SAMPLE_RATE)
137
140
 
138
141
  # Calculate step sizes and derived parameters.
139
142
  num_samples_per_step = np.arange(min_samples_per_step, max_samples_per_step + 1, step_increment)
@@ -152,11 +155,10 @@ class _AnalyticalFactory:
152
155
 
153
156
  # Compute time array
154
157
  num_samples_per_sweep = sum(num_samples_per_step)
155
- midpoint_sample = sum(num_samples_per_step) // 2
156
158
  sampling_interval = 1 / samp_rate
157
159
  # compute the sample index we are "assigning" to each spectrum
158
160
  # and multiply by the sampling interval to get the equivalent physical time
159
- times = np.array([ midpoint_sample + (i * num_samples_per_sweep) for i in range(num_spectrums) ]) * sampling_interval
161
+ times = np.array([(i * num_samples_per_sweep) for i in range(num_spectrums) ]) * sampling_interval
160
162
 
161
163
  # Compute the frequency array
162
164
  baseband_frequencies = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval))
@@ -6,6 +6,53 @@ from datetime import datetime
6
6
 
7
7
  import numpy as np
8
8
 
9
+ def average_array(array: np.ndarray, average_over: int, axis=0) -> np.ndarray:
10
+
11
+ # Check if average_over is an integer
12
+ if type(average_over) != int:
13
+ raise TypeError(f"average_over must be an integer. Got {type(average_over)}")
14
+
15
+ # Get the size of the specified axis which we will average over
16
+ axis_size = array.shape[axis]
17
+ # Check if average_over is within the valid range
18
+ if not 1 <= average_over <= axis_size:
19
+ raise ValueError(f"average_over must be between 1 and the length of the axis ({axis_size})")
20
+
21
+ max_axis_index = len(np.shape(array)) - 1
22
+ if axis > max_axis_index: # zero indexing on specifying axis, so minus one
23
+ raise ValueError(f"Requested axis is out of range of array dimensions. Axis: {axis}, max axis index: {max_axis_index}")
24
+
25
+ # find the number of elements in the requested axis
26
+ num_elements = array.shape[axis]
27
+
28
+ # find the number of "full blocks" to average over
29
+ num_full_blocks = num_elements // average_over
30
+ # if num_elements is not exactly divisible by average_over, we will have some elements left over
31
+ # these remaining elements will be padded with nans to become another full block
32
+ remainder = num_elements % average_over
33
+
34
+ # if there exists a remainder, pad the last block
35
+ if remainder != 0:
36
+ # initialise an array to hold the padding shape
37
+ padding_shape = [(0, 0)] * array.ndim
38
+ # pad after the last column in the requested axis
39
+ padding_shape[axis] = (0, average_over - remainder)
40
+ # pad with nan values (so to not contribute towards the mean computation)
41
+ array = np.pad(array, padding_shape, mode='constant', constant_values=np.nan)
42
+
43
+ # initalise a list to hold the new shape
44
+ new_shape = list(array.shape)
45
+ # update the shape on the requested access (to the number of blocks we will average over)
46
+ new_shape[axis] = num_full_blocks + (1 if remainder else 0)
47
+ # insert a new dimension, with the size of each block
48
+ new_shape.insert(axis + 1, average_over)
49
+ # and reshape the array to sort the array into the relevant blocks.
50
+ reshaped_array = array.reshape(new_shape)
51
+ # average over the newly created axis, essentially averaging over the blocks.
52
+ averaged_array = np.nanmean(reshaped_array, axis=axis + 1)
53
+ # return the averaged array
54
+ return averaged_array
55
+
9
56
 
10
57
  def is_close(ar: np.ndarray,
11
58
  ar_comparison: np.ndarray,
@@ -15,7 +62,6 @@ def is_close(ar: np.ndarray,
15
62
  ar_comparison,
16
63
  atol=absolute_tolerance))
17
64
 
18
-
19
65
  def find_closest_index(
20
66
  target_value: float | datetime,
21
67
  array: np.ndarray,