spectre-core 0.0.7__py3-none-any.whl → 0.0.9__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/plotting/panel_stack.py +3 -1
- spectre_core/{watchdog → post_processing}/__init__.py +1 -1
- spectre_core/post_processing/base.py +132 -0
- spectre_core/{watchdog → post_processing}/factory.py +4 -4
- spectre_core/{watchdog → post_processing}/library/fixed/event_handler.py +8 -9
- spectre_core/{watchdog → post_processing}/library/sweep/event_handler.py +9 -10
- spectre_core/post_processing/post_processor.py +40 -0
- spectre_core/receivers/base.py +11 -4
- spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +6 -2
- spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +8 -12
- spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py +120 -0
- spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py +119 -0
- spectre_core/receivers/library/rspduo/receiver.py +34 -5
- spectre_core/receivers/library/test/receiver.py +45 -20
- spectre_core/receivers/validators.py +67 -29
- spectre_core/web_fetch/callisto.py +1 -1
- {spectre_core-0.0.7.dist-info → spectre_core-0.0.9.dist-info}/METADATA +2 -2
- {spectre_core-0.0.7.dist-info → spectre_core-0.0.9.dist-info}/RECORD +24 -22
- spectre_core/watchdog/base.py +0 -105
- spectre_core/watchdog/post_processor.py +0 -50
- /spectre_core/{watchdog → post_processing}/event_handler_register.py +0 -0
- /spectre_core/{watchdog → post_processing}/library/__init__.py +0 -0
- /spectre_core/{watchdog → post_processing}/library/fixed/__init__.py +0 -0
- {spectre_core-0.0.7.dist-info → spectre_core-0.0.9.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.7.dist-info → spectre_core-0.0.9.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.7.dist-info → spectre_core-0.0.9.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,12 @@
|
|
4
4
|
|
5
5
|
from spectre_core.receivers.base import SDRPlayReceiver
|
6
6
|
from spectre_core.receivers.receiver_register import register_receiver
|
7
|
-
from spectre_core.receivers.library.rspduo.gr import
|
7
|
+
from spectre_core.receivers.library.rspduo.gr import (
|
8
|
+
tuner_1_fixed,
|
9
|
+
tuner_1_sweep,
|
10
|
+
tuner_2_fixed,
|
11
|
+
tuner_2_sweep
|
12
|
+
)
|
8
13
|
from spectre_core.file_handlers.configs import CaptureConfig
|
9
14
|
|
10
15
|
|
@@ -17,14 +22,18 @@ class Receiver(SDRPlayReceiver):
|
|
17
22
|
def _set_capture_methods(self) -> None:
|
18
23
|
self._capture_methods = {
|
19
24
|
"tuner-1-fixed": self.__tuner_1_fixed,
|
20
|
-
"tuner-1-sweep": self.__tuner_1_sweep
|
25
|
+
"tuner-1-sweep": self.__tuner_1_sweep,
|
26
|
+
"tuner-2-fixed": self.__tuner_2_fixed,
|
27
|
+
"tuner-2-sweep": self.__tuner_2_sweep
|
21
28
|
}
|
22
29
|
|
23
30
|
|
24
31
|
def _set_validators(self) -> None:
|
25
32
|
self._validators = {
|
26
33
|
"tuner-1-fixed": self.__tuner_1_fixed_validator,
|
27
|
-
"tuner-1-sweep": self.__tuner_1_sweep_validator
|
34
|
+
"tuner-1-sweep": self.__tuner_1_sweep_validator,
|
35
|
+
"tuner-2-fixed": self.__tuner_2_fixed_validator,
|
36
|
+
"tuner-2-sweep": self.__tuner_2_sweep_validator,
|
28
37
|
}
|
29
38
|
return
|
30
39
|
|
@@ -33,6 +42,8 @@ class Receiver(SDRPlayReceiver):
|
|
33
42
|
self._type_templates = {
|
34
43
|
"tuner-1-fixed": self._get_default_type_template("fixed"),
|
35
44
|
"tuner-1-sweep": self._get_default_type_template("sweep"),
|
45
|
+
"tuner-2-fixed": self._get_default_type_template("fixed"),
|
46
|
+
"tuner-2-sweep": self._get_default_type_template("sweep"),
|
36
47
|
}
|
37
48
|
|
38
49
|
def _set_specifications(self) -> None:
|
@@ -55,14 +66,32 @@ class Receiver(SDRPlayReceiver):
|
|
55
66
|
|
56
67
|
def __tuner_1_sweep(self, capture_config: CaptureConfig) -> None:
|
57
68
|
tuner_1_sweep.main(capture_config)
|
69
|
+
|
70
|
+
|
71
|
+
def __tuner_2_fixed(self, capture_config: CaptureConfig) -> None:
|
72
|
+
tuner_2_fixed.main(capture_config)
|
58
73
|
|
59
74
|
|
75
|
+
def __tuner_2_sweep(self, capture_config: CaptureConfig) -> None:
|
76
|
+
tuner_2_sweep.main(capture_config)
|
77
|
+
|
78
|
+
|
60
79
|
def __tuner_1_fixed_validator(self, capture_config: CaptureConfig) -> None:
|
61
80
|
self._default_fixed_validator(capture_config)
|
62
81
|
self._sdrplay_validator(capture_config)
|
63
|
-
|
64
|
-
|
82
|
+
|
83
|
+
|
65
84
|
def __tuner_1_sweep_validator(self, capture_config: CaptureConfig) -> None:
|
85
|
+
self._default_sweep_validator(capture_config)
|
86
|
+
self._sdrplay_validator(capture_config)
|
87
|
+
|
88
|
+
|
89
|
+
def __tuner_2_fixed_validator(self, capture_config: CaptureConfig) -> None:
|
90
|
+
self._default_fixed_validator(capture_config)
|
91
|
+
self._sdrplay_validator(capture_config)
|
92
|
+
|
93
|
+
|
94
|
+
def __tuner_2_sweep_validator(self, capture_config: CaptureConfig) -> None:
|
66
95
|
self._default_sweep_validator(capture_config)
|
67
96
|
self._sdrplay_validator(capture_config)
|
68
97
|
|
@@ -46,6 +46,7 @@ class Receiver(SPECTREReceiver):
|
|
46
46
|
"hop": int, # STFFT hop shifts window by so many samples
|
47
47
|
"chunk_key": str, # maps to the corresponding chunk class
|
48
48
|
"event_handler_key": str, # maps to the event handler used in post processing
|
49
|
+
"watch_extension": str # event handlers watch for files with this extension
|
49
50
|
},
|
50
51
|
"tagged-staircase": {
|
51
52
|
"samp_rate": int, # [Hz]
|
@@ -63,6 +64,7 @@ class Receiver(SPECTREReceiver):
|
|
63
64
|
"hop": int, # keyword arguments for scipy STFFT class
|
64
65
|
"chunk_key": str, # maps to the corresponding chunk class
|
65
66
|
"event_handler_key": str, # maps to the event handler used in post processing
|
67
|
+
"watch_extension": str # event handlers watch for files with this extension
|
66
68
|
}
|
67
69
|
}
|
68
70
|
|
@@ -93,27 +95,35 @@ class Receiver(SPECTREReceiver):
|
|
93
95
|
event_handler_key = capture_config["event_handler_key"]
|
94
96
|
time_resolution = capture_config["time_resolution"]
|
95
97
|
frequency_resolution = capture_config["frequency_resolution"]
|
98
|
+
watch_extension = capture_config["watch_extension"]
|
96
99
|
|
97
100
|
validators.samp_rate_strictly_positive(samp_rate)
|
98
101
|
validators.chunk_size_strictly_positive(chunk_size)
|
99
102
|
validators.time_resolution(time_resolution, chunk_size)
|
100
103
|
validators.window(window_type,
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
104
|
+
{},
|
105
|
+
window_size,
|
106
|
+
chunk_size,
|
107
|
+
samp_rate)
|
105
108
|
validators.hop(hop)
|
106
|
-
validators.chunk_key(chunk_key,
|
107
|
-
|
109
|
+
validators.chunk_key(chunk_key,
|
110
|
+
"fixed")
|
111
|
+
validators.event_handler_key(event_handler_key,
|
112
|
+
"fixed")
|
113
|
+
validators.watch_extension(watch_extension,
|
114
|
+
"bin")
|
108
115
|
|
109
116
|
if samp_rate < self.specifications.get("samp_rate_lower_bound"):
|
110
|
-
raise ValueError(f"Sample rate must be greater than or equal to
|
117
|
+
raise ValueError((f"Sample rate must be greater than or equal to "
|
118
|
+
f"{self.specifications.get('samp_rate_lower_bound')}"))
|
111
119
|
|
112
120
|
if time_resolution != 0:
|
113
|
-
raise ValueError(f"Time resolution must be zero.
|
121
|
+
raise ValueError(f"Time resolution must be zero. "
|
122
|
+
f"Got {time_resolution} [s]")
|
114
123
|
|
115
124
|
if frequency_resolution != 0:
|
116
|
-
raise ValueError(f"Frequency resolution must be zero.
|
125
|
+
raise ValueError((f"Frequency resolution must be zero. "
|
126
|
+
f"Got {frequency_resolution}"))
|
117
127
|
|
118
128
|
# check that the sample rate is an integer multiple of the underlying signal frequency
|
119
129
|
if samp_rate % frequency != 0:
|
@@ -121,22 +131,26 @@ class Receiver(SPECTREReceiver):
|
|
121
131
|
|
122
132
|
a = samp_rate/frequency
|
123
133
|
if a < 2:
|
124
|
-
raise ValueError(f"The ratio of sampling rate over frequency must be a natural number greater than two.
|
134
|
+
raise ValueError((f"The ratio of sampling rate over frequency must be a natural number greater than two. "
|
135
|
+
f"Got {a}"))
|
125
136
|
|
126
137
|
# ensuring the window type is rectangular
|
127
138
|
if window_type != "boxcar":
|
128
|
-
raise ValueError(f"The window type must be
|
139
|
+
raise ValueError((f"The window type must be 'boxcar'. "
|
140
|
+
f"Got {window_type}"))
|
129
141
|
|
130
142
|
# analytical requirement
|
131
143
|
# if p is the number of sampled cycles, we can find that p = window_size / a
|
132
144
|
# the number of sampled cycles must be a positive natural number.
|
133
145
|
p = window_size / a
|
134
146
|
if window_size % a != 0:
|
135
|
-
raise ValueError(f"The number of sampled cycles must be a positive natural number.
|
147
|
+
raise ValueError((f"The number of sampled cycles must be a positive natural number. "
|
148
|
+
f"Computed that p={p}"))
|
136
149
|
|
137
150
|
|
138
151
|
if amplitude <= 0:
|
139
|
-
raise ValueError(f"The amplitude must be strictly positive.
|
152
|
+
raise ValueError((f"The amplitude must be strictly positive. "
|
153
|
+
f"Got {amplitude}"))
|
140
154
|
|
141
155
|
|
142
156
|
def __tagged_staircase_validator(self, capture_config: CaptureConfig) -> None:
|
@@ -153,26 +167,37 @@ class Receiver(SPECTREReceiver):
|
|
153
167
|
chunk_key = capture_config["chunk_key"]
|
154
168
|
event_handler_key = capture_config["event_handler_key"]
|
155
169
|
time_resolution = capture_config["time_resolution"]
|
156
|
-
|
170
|
+
watch_extension = capture_config["watch_extension"]
|
171
|
+
|
157
172
|
validators.samp_rate_strictly_positive(samp_rate)
|
158
173
|
validators.chunk_size_strictly_positive(chunk_size)
|
159
174
|
validators.time_resolution(time_resolution, chunk_size)
|
160
|
-
validators.window(window_type,
|
175
|
+
validators.window(window_type,
|
176
|
+
window_kwargs,
|
177
|
+
window_size,
|
178
|
+
chunk_size,
|
179
|
+
samp_rate)
|
161
180
|
validators.hop(hop)
|
162
181
|
validators.chunk_key(chunk_key, "sweep")
|
163
182
|
validators.event_handler_key(event_handler_key, "sweep")
|
164
|
-
|
183
|
+
validators.watch_extension(watch_extension,
|
184
|
+
"bin")
|
185
|
+
|
165
186
|
if freq_step != samp_rate:
|
166
187
|
raise ValueError(f"The frequency step must be equal to the sampling rate")
|
167
188
|
|
168
189
|
if min_samples_per_step <= 0:
|
169
|
-
raise ValueError(f"Minimum samples per step must be strictly positive.
|
190
|
+
raise ValueError((f"Minimum samples per step must be strictly positive. "
|
191
|
+
f"Got {min_samples_per_step}"))
|
170
192
|
|
171
193
|
if max_samples_per_step <= 0:
|
172
|
-
raise ValueError(f"Maximum samples per step must be strictly positive.
|
194
|
+
raise ValueError((f"Maximum samples per step must be strictly positive. "
|
195
|
+
f"Got {max_samples_per_step}"))
|
173
196
|
|
174
197
|
if step_increment <= 0:
|
175
|
-
raise ValueError(f"Step increment must be strictly positive.
|
198
|
+
raise ValueError((f"Step increment must be strictly positive. "
|
199
|
+
f"Got {step_increment}"))
|
176
200
|
|
177
201
|
if min_samples_per_step > max_samples_per_step:
|
178
|
-
raise ValueError(f"Minimum samples per step cannot be greater than the maximum samples per step.
|
202
|
+
raise ValueError((f"Minimum samples per step cannot be greater than the maximum samples per step. "
|
203
|
+
f"Got {min_samples_per_step}, which is greater than {max_samples_per_step}"))
|
@@ -14,34 +14,39 @@ from scipy.signal import get_window
|
|
14
14
|
def closed_upper_bound_RF_gain(RF_gain: float,
|
15
15
|
RF_gain_upper_bound: float) -> None:
|
16
16
|
if not (RF_gain <= RF_gain_upper_bound):
|
17
|
-
raise ValueError(f"RF gain must be
|
17
|
+
raise ValueError((f"RF gain must be less than or equal to {RF_gain_upper_bound} [dB]. "
|
18
|
+
f"Got {RF_gain} [dB]"))
|
18
19
|
|
19
20
|
|
20
21
|
def closed_upper_bound_IF_gain(IF_gain: float,
|
21
22
|
IF_gain_upper_bound: float) -> None:
|
22
23
|
if not (IF_gain <= IF_gain_upper_bound):
|
23
|
-
raise ValueError(f"IF gain must be
|
24
|
+
raise ValueError((f"IF gain must be less than or equal to {IF_gain_upper_bound} [dB]. "
|
25
|
+
f"Got {IF_gain} [dB]"))
|
24
26
|
|
25
27
|
|
26
28
|
def closed_confine_center_freq(center_freq: float,
|
27
29
|
center_freq_lower_bound: float,
|
28
30
|
center_freq_upper_bound: float) -> None:
|
29
31
|
if not (center_freq_lower_bound <= center_freq <= center_freq_upper_bound):
|
30
|
-
raise ValueError(f"Center frequency must be between {center_freq_lower_bound*1e-3} [kHz] and {center_freq_upper_bound*1e-9} [GHz].
|
32
|
+
raise ValueError((f"Center frequency must be between {center_freq_lower_bound*1e-3} [kHz] and {center_freq_upper_bound*1e-9} [GHz]. "
|
33
|
+
f"Got {center_freq*1e-6} [MHz]"))
|
31
34
|
|
32
35
|
|
33
36
|
def closed_confine_samp_rate(samp_rate: int,
|
34
37
|
samp_rate_lower_bound: int,
|
35
38
|
samp_rate_upper_bound: int) -> None:
|
36
39
|
if not (samp_rate_lower_bound <= samp_rate <= samp_rate_upper_bound):
|
37
|
-
raise ValueError(f"Sampling rate must be between {samp_rate_lower_bound*1e-6} [MHz] and {samp_rate_upper_bound*1e-6} [MHz].
|
40
|
+
raise ValueError((f"Sampling rate must be between {samp_rate_lower_bound*1e-6} [MHz] and {samp_rate_upper_bound*1e-6} [MHz]. "
|
41
|
+
f"Got {samp_rate*1e-6} [MHz]"))
|
38
42
|
|
39
43
|
|
40
44
|
def closed_confine_bandwidth(bandwidth: float,
|
41
45
|
bandwidth_lower_bound: float,
|
42
46
|
bandwidth_upper_bound: float) -> None:
|
43
47
|
if not (bandwidth_lower_bound <= bandwidth <= bandwidth_upper_bound):
|
44
|
-
raise ValueError(f"Bandwidth must be between {bandwidth_lower_bound*1e-3} [kHz] and {bandwidth_upper_bound*1e-6} [MHz].
|
48
|
+
raise ValueError((f"Bandwidth must be between {bandwidth_lower_bound*1e-3} [kHz] and {bandwidth_upper_bound*1e-6} [MHz]. "
|
49
|
+
f"Got {bandwidth*1e-6} [MHz]"))
|
45
50
|
|
46
51
|
|
47
52
|
def is_power_of_two(n):
|
@@ -55,84 +60,103 @@ def window(window_type: str,
|
|
55
60
|
samp_rate: float) -> None:
|
56
61
|
|
57
62
|
if not is_power_of_two(window_size):
|
58
|
-
raise ValueError(f"Window size must be some power of two.
|
63
|
+
raise ValueError((f"Window size must be some power of two. "
|
64
|
+
f"Got {window_size} [samples]"))
|
59
65
|
|
60
|
-
|
61
|
-
if
|
62
|
-
raise ValueError("
|
66
|
+
window_interval = window_size*(1/samp_rate)
|
67
|
+
if window_interval > chunk_size:
|
68
|
+
raise ValueError((f"The windowing interval must be strictly less than the chunk size. "
|
69
|
+
f"Computed the windowing interval to be {window_interval} [s], "
|
70
|
+
f"but the chunk size is {chunk_size} [s]"))
|
63
71
|
|
64
72
|
try:
|
65
73
|
window_params = (window_type,
|
66
74
|
*window_kwargs.values())
|
67
75
|
_ = get_window(window_params, window_size)
|
68
76
|
except Exception as e:
|
69
|
-
raise Exception(f"An error has occurred while validating the window.
|
77
|
+
raise Exception((f"An error has occurred while validating the window. "
|
78
|
+
f"Got {str(e)}"))
|
70
79
|
|
71
80
|
|
72
81
|
def hop(hop: int):
|
73
82
|
if hop < 0:
|
74
|
-
raise ValueError(f"
|
83
|
+
raise ValueError((f"Window hop must be strictly positive. "
|
84
|
+
f"Got {hop} [samples]"))
|
75
85
|
|
76
86
|
|
77
87
|
def center_freq_strictly_positive(center_freq: float):
|
78
88
|
if center_freq <= 0:
|
79
|
-
raise ValueError(f"Center frequency must be strictly positive.
|
89
|
+
raise ValueError((f"Center frequency must be strictly positive. "
|
90
|
+
f"Got {center_freq*1e-6} [MHz]"))
|
80
91
|
|
81
92
|
|
82
93
|
def bandwidth_strictly_positive(bandwidth: float) -> None:
|
83
94
|
if bandwidth < 0:
|
84
|
-
raise ValueError(f"Bandwidth must be non-negative.
|
95
|
+
raise ValueError((f"Bandwidth must be non-negative. "
|
96
|
+
f"Got {bandwidth*1e-6} [MHz]"))
|
85
97
|
|
86
98
|
|
87
99
|
def nyquist_criterion(samp_rate: int,
|
88
100
|
bandwidth: float) -> None:
|
89
101
|
if samp_rate < bandwidth:
|
90
|
-
raise ValueError("Sample rate must be greater than or equal to the bandwidth"
|
102
|
+
raise ValueError((f"Sample rate must be greater than or equal to the bandwidth. "
|
103
|
+
f"Got sample rate {samp_rate} [Hz], and bandwidth {bandwidth} [Hz]"))
|
91
104
|
|
92
105
|
|
93
106
|
def samp_rate_strictly_positive(samp_rate: int) -> None:
|
94
|
-
if samp_rate
|
95
|
-
raise ValueError(f"Sample rate must be strictly positive.
|
107
|
+
if samp_rate <= 0:
|
108
|
+
raise ValueError((f"Sample rate must be strictly positive. "
|
109
|
+
f"Got {samp_rate} [Hz]"))
|
96
110
|
|
97
111
|
|
98
112
|
def chunk_size_strictly_positive(chunk_size: int) -> None:
|
99
113
|
if chunk_size <= 0:
|
100
|
-
raise ValueError(f"Chunk size must be strictly positive.
|
114
|
+
raise ValueError((f"Chunk size must be strictly positive. "
|
115
|
+
f"Got {chunk_size} [s]"))
|
101
116
|
|
102
117
|
|
103
118
|
def time_resolution(time_resolution: float,
|
104
119
|
chunk_size: int) -> None:
|
105
120
|
if time_resolution < 0:
|
106
|
-
raise ValueError(f"Time resolution must be non-negative.
|
121
|
+
raise ValueError((f"Time resolution must be non-negative. "
|
122
|
+
f"Got {time_resolution} [s]"))
|
107
123
|
|
108
124
|
if time_resolution > chunk_size:
|
109
|
-
raise ValueError("Time resolution must be less than or equal to chunk size"
|
125
|
+
raise ValueError(f"Time resolution must be less than or equal to chunk size. "
|
126
|
+
f"Got time resolution {time_resolution} [s], "
|
127
|
+
f"and chunk size {chunk_size} [s]")
|
110
128
|
|
111
129
|
|
112
130
|
def frequency_resolution(frequency_resolution: float,
|
113
131
|
bandwidth: float = None) -> None:
|
114
132
|
if frequency_resolution < 0:
|
115
|
-
raise ValueError(f"Frequency resolution must be non-negative.
|
133
|
+
raise ValueError((f"Frequency resolution must be non-negative. "
|
134
|
+
f"Got {frequency_resolution} [Hz]"))
|
116
135
|
|
117
136
|
if bandwidth is not None and frequency_resolution >= bandwidth:
|
118
|
-
raise ValueError(f"Frequency resolution must be less than the bandwidth.
|
137
|
+
raise ValueError((f"Frequency resolution must be less than the bandwidth. "
|
138
|
+
f"Got frequency resolution to be {frequency_resolution} [Hz], "
|
139
|
+
f"with bandwidth {bandwidth} [Hz]"))
|
119
140
|
|
120
141
|
|
121
142
|
def chunk_key(chunk_key: str,
|
122
143
|
expected_chunk_key: str) -> None:
|
123
144
|
if chunk_key != expected_chunk_key:
|
124
|
-
raise ValueError(f"Expected
|
145
|
+
raise ValueError((f"Expected {expected_chunk_key} for the chunk key. "
|
146
|
+
f"Got {chunk_key}"))
|
125
147
|
|
126
148
|
|
127
149
|
def event_handler_key(event_handler_key: str,
|
128
150
|
expected_event_handler_key: str) -> None:
|
129
151
|
if event_handler_key != expected_event_handler_key:
|
130
|
-
raise ValueError(f"Expected
|
152
|
+
raise ValueError((f"Expected {expected_event_handler_key} for the event handler key. "
|
153
|
+
f"Got {event_handler_key}"))
|
131
154
|
|
132
155
|
|
133
156
|
def gain_is_negative(gain: float) -> None:
|
134
157
|
if gain > 0:
|
135
|
-
raise ValueError(f"Gain must be non-positive.
|
158
|
+
raise ValueError(f"Gain must be non-positive. "
|
159
|
+
f"Got {gain} [dB]")
|
136
160
|
|
137
161
|
|
138
162
|
def _compute_num_steps_per_sweep(min_freq: float,
|
@@ -151,7 +175,8 @@ def num_steps_per_sweep(min_freq: float,
|
|
151
175
|
samp_rate,
|
152
176
|
freq_step)
|
153
177
|
if num_steps_per_sweep <= 1:
|
154
|
-
raise ValueError(f"We need strictly greater than one
|
178
|
+
raise ValueError((f"We need strictly greater than one step per sweep. "
|
179
|
+
f"Computed {num_steps_per_sweep} step per sweep"))
|
155
180
|
|
156
181
|
|
157
182
|
def sweep_interval(min_freq: float,
|
@@ -167,19 +192,25 @@ def sweep_interval(min_freq: float,
|
|
167
192
|
num_samples_per_sweep = num_steps_per_sweep * samples_per_step
|
168
193
|
sweep_interval = num_samples_per_sweep * 1/samp_rate
|
169
194
|
if sweep_interval > chunk_size:
|
170
|
-
raise ValueError(f"Sweep interval must be less than the chunk size.
|
195
|
+
raise ValueError((f"Sweep interval must be less than the chunk size. "
|
196
|
+
f"The computed sweep interval is {sweep_interval} [s], "
|
197
|
+
f"but the given chunk size is {chunk_size} [s]"))
|
171
198
|
|
172
199
|
|
173
200
|
def num_samples_per_step(samples_per_step: int,
|
174
201
|
window_size: int) -> None:
|
175
202
|
if window_size >= samples_per_step:
|
176
|
-
raise ValueError(f"Window size must be strictly less than the number of samples per step.
|
203
|
+
raise ValueError((f"Window size must be strictly less than the number of samples per step. "
|
204
|
+
f"Got window size {window_size} [samples], which is more than or equal "
|
205
|
+
f"to the number of samples per step {samples_per_step}"))
|
177
206
|
|
178
207
|
|
179
208
|
def non_overlapping_steps(freq_step: float,
|
180
209
|
samp_rate: int) -> None:
|
181
210
|
if freq_step < samp_rate:
|
182
|
-
raise NotImplementedError(f"SPECTRE does not yet support spectral steps overlapping in frequency.
|
211
|
+
raise NotImplementedError(f"SPECTRE does not yet support spectral steps overlapping in frequency. "
|
212
|
+
f"Got frequency step {freq_step/1e6} [MHz] which is less than the sample "
|
213
|
+
f"rate {samp_rate/1e6} [MHz]")
|
183
214
|
|
184
215
|
|
185
216
|
def step_interval(samples_per_step: int,
|
@@ -187,7 +218,14 @@ def step_interval(samples_per_step: int,
|
|
187
218
|
api_latency: float) -> None:
|
188
219
|
step_interval = samples_per_step * 1/samp_rate # [s]
|
189
220
|
if step_interval < api_latency:
|
190
|
-
warning_message = f"The computed step interval of {step_interval} [s] is of the order of empirically
|
221
|
+
warning_message = (f"The computed step interval of {step_interval} [s] is of the order of empirically "
|
222
|
+
f"derived api latency {api_latency} [s]; you may experience undefined behaviour!")
|
191
223
|
warnings.warn(warning_message)
|
192
224
|
_LOGGER.warning(warning_message)
|
193
225
|
|
226
|
+
|
227
|
+
def watch_extension(watch_extension: str,
|
228
|
+
target_extension: str) -> None:
|
229
|
+
if watch_extension != target_extension:
|
230
|
+
raise ValueError((f"Expected {target_extension} for the watch extension. "
|
231
|
+
f"Got {watch_extension}"))
|
@@ -94,7 +94,7 @@ def fetch_chunks(instrument_code: Optional[str],
|
|
94
94
|
os.mkdir(temp_dir)
|
95
95
|
|
96
96
|
if instrument_code not in CALLISTO_INSTRUMENT_CODES:
|
97
|
-
raise ValueError(f"No match found for
|
97
|
+
raise ValueError(f"No match found for '{instrument_code}'. Expected one of {CALLISTO_INSTRUMENT_CODES}")
|
98
98
|
|
99
99
|
download_callisto_data(instrument_code, year, month, day)
|
100
100
|
unzip_to_chunks()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: spectre-core
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.9
|
4
4
|
Summary: The core Python package used by the spectre program.
|
5
5
|
Maintainer-email: Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -698,7 +698,7 @@ Requires-Dist: watchdog==4.0.0
|
|
698
698
|
|
699
699
|
:loudspeaker: **This project is under active development. Contributors welcome.** :loudspeaker:
|
700
700
|
|
701
|
-
```spectre-core``` provides a SDR receiver-agnostic digital signal processing framework. It is the core Python package used by the [`spectre`](https://github.com/jcfitzpatrick12/spectre.git) program.
|
701
|
+
```spectre-core``` provides a SDR receiver-agnostic digital signal processing framework. It is the core Python package used by the [`spectre`](https://github.com/jcfitzpatrick12/spectre.git) program.
|
702
702
|
|
703
703
|
|
704
704
|
## Installation
|
@@ -23,17 +23,26 @@ spectre_core/plotting/base.py,sha256=4HhPPP7BNe5_SUAl1Ee52_QP62Zzh3kmNJwLzCHKG3c
|
|
23
23
|
spectre_core/plotting/factory.py,sha256=pYil7PbTs3e08ek6nq-y8mSEtNu7DI6uloiCwsuIoOw,940
|
24
24
|
spectre_core/plotting/format.py,sha256=Et-uc4juDl_2spLRZOzqaUVBP8-J3LPnV5M6CllM74E,512
|
25
25
|
spectre_core/plotting/panel_register.py,sha256=rsmG2hXnvVOy6vfWsEZlZQAXej7bRtqtvjnXObyUCg4,351
|
26
|
-
spectre_core/plotting/panel_stack.py,sha256=
|
26
|
+
spectre_core/plotting/panel_stack.py,sha256=fB1kz9NgsnMl4l41cncNPaVmg5DtYRRvO4lhX8OLOOo,5239
|
27
27
|
spectre_core/plotting/library/__init__.py,sha256=lQhj4kme2eKb-JSrHvLWkAjI7eVqexOxxUguo8LUXno,270
|
28
28
|
spectre_core/plotting/library/frequency_cuts/panel.py,sha256=LtCWNad4Z1gxBOxi_Ni1d6KZxsgoR5Mbm2tWXN7VDqI,2772
|
29
29
|
spectre_core/plotting/library/integral_over_frequency/panel.py,sha256=tvro2MCtY4Q0q7WdMUz9eW5Cvrweeqqse20q3x4D4fM,1274
|
30
30
|
spectre_core/plotting/library/spectrogram/panel.py,sha256=CAaPz7sDYoWZ3-4Jb1kVRu9bvJYaBRiXvoMkV7QXWqk,3556
|
31
31
|
spectre_core/plotting/library/time_cuts/panel.py,sha256=u9Sbnwy6ex61y5Jl-D77HlYvuuXdK8_YB-o2gCovCTY,2947
|
32
|
+
spectre_core/post_processing/__init__.py,sha256=pRzy46C32j9sfQjbCJKuVw0tQjuOErfrievq2m1Vx8c,239
|
33
|
+
spectre_core/post_processing/base.py,sha256=h0qtoigD6ZBkHm0_wV1lJX55AwppKC5htfXVWUWMMLQ,5403
|
34
|
+
spectre_core/post_processing/event_handler_register.py,sha256=DwlkU92IFkZ1_qvGfhep_OfuqTOIR_efY5qFpn1perw,498
|
35
|
+
spectre_core/post_processing/factory.py,sha256=7KZBysPymsYhpW4KI4H1jvjssmZY1mIvhkln91joeUM,1133
|
36
|
+
spectre_core/post_processing/post_processor.py,sha256=dQX71pUh83i6mNedTk-EVAYnxjAUs6S2Q8HkCXkiwns,1428
|
37
|
+
spectre_core/post_processing/library/__init__.py,sha256=vEwAnAV-sv7WcNYOdnjr1JVqZYr29Wr2cv01eoxwdmg,282
|
38
|
+
spectre_core/post_processing/library/fixed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
+
spectre_core/post_processing/library/fixed/event_handler.py,sha256=wfOdahnY78Movu0ESbuUy-QmpBbXhNk6_DKzsXS9G0Y,1394
|
40
|
+
spectre_core/post_processing/library/sweep/event_handler.py,sha256=ap7K8hOxgb8wsX9uJfEDW7lYXCyNKFlsva7vinW_6Ak,2194
|
32
41
|
spectre_core/receivers/__init__.py,sha256=kKfhqrGs9sSPGLbrpTqScv816iPZOvT3ry3zSMcqLkM,227
|
33
|
-
spectre_core/receivers/base.py,sha256=
|
42
|
+
spectre_core/receivers/base.py,sha256=szW7BfJEQiZ7dEiD_Oyk2lG16Kns3zKmnl_lIqKHP1o,16478
|
34
43
|
spectre_core/receivers/factory.py,sha256=aE-Yw_cnlkhRe5HxK0JqhDzd2AwZcKmB2QkAKwaq27Y,873
|
35
44
|
spectre_core/receivers/receiver_register.py,sha256=xHcRnT-3NQxyIWL3nyT3P9qT14Wl5liM9HbflOvOUAM,617
|
36
|
-
spectre_core/receivers/validators.py,sha256=
|
45
|
+
spectre_core/receivers/validators.py,sha256=ZWnFsZJsbHBB-ZIrp3WK8BPqb4ev7TYm6VPQPbljTE4,9965
|
37
46
|
spectre_core/receivers/library/__init__.py,sha256=xmtF5p3_ZkGfso_pKnxSgUcXXFLEBwERGPq1Pek7cOU,274
|
38
47
|
spectre_core/receivers/library/rsp1a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
48
|
spectre_core/receivers/library/rsp1a/receiver.py,sha256=xs_aNMhwIYD83KwutizjBziyu9XsfHqGqvQXOFcglz4,2224
|
@@ -41,12 +50,14 @@ spectre_core/receivers/library/rsp1a/gr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
41
50
|
spectre_core/receivers/library/rsp1a/gr/fixed.py,sha256=P39JwuOe-ydk9m8BNOHFDpzaqjenRnTIA7tpECshfUM,3369
|
42
51
|
spectre_core/receivers/library/rsp1a/gr/sweep.py,sha256=9ZAoI0u1814lOM_cVwJnLUNSgM3VMstfvWKP67dVEV4,4741
|
43
52
|
spectre_core/receivers/library/rspduo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
|
-
spectre_core/receivers/library/rspduo/receiver.py,sha256=
|
53
|
+
spectre_core/receivers/library/rspduo/receiver.py,sha256=EMBFWwFjoIP2buwwamyb3uaYqMQwQHMzNi-we7_OfcU,3377
|
45
54
|
spectre_core/receivers/library/rspduo/gr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
|
-
spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py,sha256=
|
47
|
-
spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py,sha256=
|
55
|
+
spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py,sha256=xO9F_GBwgNnebrxoZYPwbXLL2HmH0CNLrVlpQ6Z7bx0,3887
|
56
|
+
spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py,sha256=gR5arbjvJBJjCdQnpAdwNd7bEtnvDbhL_ttUhKk0Zss,5066
|
57
|
+
spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py,sha256=O86R3tuJ-7cZXfVECuZkds-wCxKdBKJ8MhVACJFgzeo,4130
|
58
|
+
spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py,sha256=4JgJ2QRO5DFPfqUTHBojj8ERR_4QpHyTBqnAXhOzykE,4747
|
48
59
|
spectre_core/receivers/library/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
|
-
spectre_core/receivers/library/test/receiver.py,sha256=
|
60
|
+
spectre_core/receivers/library/test/receiver.py,sha256=hr6t8jpUwVEP2zMpjORKpzqLaBllxmbqF1fQE1DLWQw,9329
|
50
61
|
spectre_core/receivers/library/test/gr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
62
|
spectre_core/receivers/library/test/gr/cosine_signal_1.py,sha256=6XgYjYMh-QNPs_UUGUQcU_VQFr6BG4OLdsW-M-RU5Ww,2943
|
52
63
|
spectre_core/receivers/library/test/gr/tagged_staircase.py,sha256=5rJHbB-3vdXjqT8DrcAGUSebaAqZ5RQtYHBWgH9iU2E,3465
|
@@ -55,18 +66,9 @@ spectre_core/spectrograms/analytical.py,sha256=c5X40YgLlutP8sbz6dqODZaCmZ98sAjub
|
|
55
66
|
spectre_core/spectrograms/array_operations.py,sha256=6qKd3y2z6Pmu_U8yxTR4FN4eMhS10KgZ8rH60B_IXqw,2577
|
56
67
|
spectre_core/spectrograms/spectrogram.py,sha256=EqeQyvjzjoKaXou4vJbPbRx85BeMPB9iiJtFZcCyimI,19488
|
57
68
|
spectre_core/spectrograms/transform.py,sha256=xo7ch2lrRkJ54cfIqbkaTHNo_AptBuK0zRELPf7SfIE,13860
|
58
|
-
spectre_core/
|
59
|
-
spectre_core/
|
60
|
-
spectre_core/
|
61
|
-
spectre_core/
|
62
|
-
spectre_core/
|
63
|
-
spectre_core
|
64
|
-
spectre_core/watchdog/library/fixed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
65
|
-
spectre_core/watchdog/library/fixed/event_handler.py,sha256=yWWS80LukB-cTrKBsF4-pRvw2obkX2MzQ5ZGytOtmAg,1387
|
66
|
-
spectre_core/watchdog/library/sweep/event_handler.py,sha256=wDISZiQXBeqLDPxgEMo0a2QAXqQVOO7fng3yhZWSR74,2188
|
67
|
-
spectre_core/web_fetch/callisto.py,sha256=874osjbp61qFwRgV584fpSp7E-xz8g1FEelbNBKhCsw,3632
|
68
|
-
spectre_core-0.0.7.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
69
|
-
spectre_core-0.0.7.dist-info/METADATA,sha256=mSmKzIO9I8ps1yKp-jJjZ_D_OhDQH_lUKfgzeW2-VFc,42149
|
70
|
-
spectre_core-0.0.7.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
71
|
-
spectre_core-0.0.7.dist-info/top_level.txt,sha256=-UsyjpFohXgZpgcZ9QbVeXhsIyF3Am8RxNFNDV_Ta2Y,13
|
72
|
-
spectre_core-0.0.7.dist-info/RECORD,,
|
69
|
+
spectre_core/web_fetch/callisto.py,sha256=yW0NkmHqfUf2uYgJjSRxou6e65_mYciPhHwQIvKLK_w,3630
|
70
|
+
spectre_core-0.0.9.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
71
|
+
spectre_core-0.0.9.dist-info/METADATA,sha256=15Uh828ojJg6w-HCrIv7nka9wT_eVhv9svE33hmHI7k,42150
|
72
|
+
spectre_core-0.0.9.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
73
|
+
spectre_core-0.0.9.dist-info/top_level.txt,sha256=-UsyjpFohXgZpgcZ9QbVeXhsIyF3Am8RxNFNDV_Ta2Y,13
|
74
|
+
spectre_core-0.0.9.dist-info/RECORD,,
|
spectre_core/watchdog/base.py
DELETED
@@ -1,105 +0,0 @@
|
|
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 logging import getLogger
|
6
|
-
_LOGGER = getLogger(__name__)
|
7
|
-
|
8
|
-
import os
|
9
|
-
import time
|
10
|
-
from queue import Queue
|
11
|
-
from typing import Any
|
12
|
-
from abc import ABC, abstractmethod
|
13
|
-
from math import floor
|
14
|
-
|
15
|
-
from watchdog.events import FileSystemEventHandler
|
16
|
-
|
17
|
-
from spectre_core.chunks.factory import get_chunk_from_tag
|
18
|
-
from spectre_core.file_handlers.configs import CaptureConfig
|
19
|
-
from spectre_core.spectrograms.spectrogram import Spectrogram
|
20
|
-
from spectre_core.spectrograms.transform import join_spectrograms
|
21
|
-
from spectre_core.spectrograms.transform import (
|
22
|
-
time_average,
|
23
|
-
frequency_average
|
24
|
-
)
|
25
|
-
|
26
|
-
|
27
|
-
class BaseEventHandler(ABC, FileSystemEventHandler):
|
28
|
-
def __init__(self,
|
29
|
-
tag: str,
|
30
|
-
exception_queue: Queue,
|
31
|
-
extension: str):
|
32
|
-
self._tag = tag
|
33
|
-
self._Chunk = get_chunk_from_tag(tag)
|
34
|
-
|
35
|
-
self._capture_config = CaptureConfig(tag)
|
36
|
-
|
37
|
-
self._extension = extension
|
38
|
-
self._exception_queue = exception_queue # Queue to propagate exceptions
|
39
|
-
|
40
|
-
self._spectrogram: Spectrogram = None # spectrogram cache
|
41
|
-
|
42
|
-
|
43
|
-
@abstractmethod
|
44
|
-
def process(self, file_path: str) -> None:
|
45
|
-
pass
|
46
|
-
|
47
|
-
|
48
|
-
def on_created(self, event):
|
49
|
-
if not event.is_directory and event.src_path.endswith(self._extension):
|
50
|
-
_LOGGER.info(f"Noticed: {event.src_path}")
|
51
|
-
try:
|
52
|
-
self._wait_until_stable(event.src_path)
|
53
|
-
self.process(event.src_path)
|
54
|
-
except Exception as e:
|
55
|
-
_LOGGER.error(f"An error has occured while processing {event.src_path}",
|
56
|
-
exc_info=True)
|
57
|
-
self._flush_spectrogram() # flush the internally stored spectrogram
|
58
|
-
# Capture the exception and propagate it through the queue
|
59
|
-
self._exception_queue.put(e)
|
60
|
-
|
61
|
-
|
62
|
-
def _wait_until_stable(self, file_path: str):
|
63
|
-
_LOGGER.info(f"Waiting for file stability: {file_path}")
|
64
|
-
size = -1
|
65
|
-
while True:
|
66
|
-
current_size = os.path.getsize(file_path)
|
67
|
-
if current_size == size:
|
68
|
-
_LOGGER.info(f"File is now stable: {file_path}")
|
69
|
-
break # File is stable when the size hasn't changed
|
70
|
-
size = current_size
|
71
|
-
time.sleep(0.25)
|
72
|
-
|
73
|
-
|
74
|
-
def _average_in_time(self, spectrogram: Spectrogram) -> Spectrogram:
|
75
|
-
requested_time_resolution = self._capture_config.get('time_resolution') # [s]
|
76
|
-
if requested_time_resolution is None:
|
77
|
-
raise KeyError(f"Time resolution has not been specified in the capture config!")
|
78
|
-
average_over = floor(requested_time_resolution/spectrogram.time_resolution) if requested_time_resolution > spectrogram.time_resolution else 1
|
79
|
-
return time_average(spectrogram, average_over)
|
80
|
-
|
81
|
-
|
82
|
-
def _average_in_frequency(self, spectrogram: Spectrogram) -> Spectrogram:
|
83
|
-
frequency_resolution = self._capture_config.get('frequency_resolution') # [Hz]
|
84
|
-
if frequency_resolution is None:
|
85
|
-
raise KeyError(f"Frequency resolution has not been specified in the capture config!")
|
86
|
-
average_over = floor(frequency_resolution/spectrogram.frequency_resolution) if frequency_resolution > spectrogram.frequency_resolution else 1
|
87
|
-
return frequency_average(spectrogram, average_over)
|
88
|
-
|
89
|
-
|
90
|
-
def _join_spectrogram(self, spectrogram: Spectrogram) -> None:
|
91
|
-
if self._spectrogram is None:
|
92
|
-
self._spectrogram = spectrogram
|
93
|
-
else:
|
94
|
-
self._spectrogram = join_spectrograms([self._spectrogram, spectrogram])
|
95
|
-
|
96
|
-
if self._spectrogram.time_range >= self._capture_config.get("joining_time"):
|
97
|
-
self._flush_spectrogram()
|
98
|
-
|
99
|
-
|
100
|
-
def _flush_spectrogram(self) -> None:
|
101
|
-
if self._spectrogram:
|
102
|
-
_LOGGER.info(f"Flushing spectrogram to file with chunk start time {self._spectrogram.chunk_start_time}")
|
103
|
-
self._spectrogram.save()
|
104
|
-
_LOGGER.info("Flush successful, resetting spectrogram cache")
|
105
|
-
self._spectrogram = None # reset the cache
|