spectre-core 0.0.8__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/test/receiver.py +45 -20
- spectre_core/receivers/validators.py +67 -29
- spectre_core/web_fetch/callisto.py +1 -1
- {spectre_core-0.0.8.dist-info → spectre_core-0.0.9.dist-info}/METADATA +1 -1
- {spectre_core-0.0.8.dist-info → spectre_core-0.0.9.dist-info}/RECORD +19 -19
- 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.8.dist-info → spectre_core-0.0.9.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.8.dist-info → spectre_core-0.0.9.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.8.dist-info → spectre_core-0.0.9.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,9 @@ from spectre_core.plotting.factory import get_panel
|
|
15
15
|
from spectre_core.plotting.library.spectrogram.panel import Panel as SpectrogramPanel
|
16
16
|
|
17
17
|
class PanelStack:
|
18
|
-
def __init__(self,
|
18
|
+
def __init__(self,
|
19
|
+
time_type: str = "seconds",
|
20
|
+
figsize: Tuple[int, int] = (10, 10)):
|
19
21
|
self._time_type = time_type
|
20
22
|
self._figsize = figsize
|
21
23
|
self._panels: List[BasePanel] = []
|
@@ -0,0 +1,132 @@
|
|
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
|
+
from queue import Queue
|
9
|
+
from typing import Optional
|
10
|
+
from abc import ABC, abstractmethod
|
11
|
+
from math import floor
|
12
|
+
|
13
|
+
from watchdog.events import (
|
14
|
+
FileSystemEventHandler,
|
15
|
+
FileCreatedEvent,
|
16
|
+
)
|
17
|
+
|
18
|
+
from spectre_core.chunks.factory import get_chunk_from_tag
|
19
|
+
from spectre_core.file_handlers.configs import CaptureConfig
|
20
|
+
from spectre_core.spectrograms.spectrogram import Spectrogram
|
21
|
+
from spectre_core.spectrograms.transform import join_spectrograms
|
22
|
+
from spectre_core.spectrograms.transform import (
|
23
|
+
time_average,
|
24
|
+
frequency_average
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
class BaseEventHandler(ABC, FileSystemEventHandler):
|
29
|
+
def __init__(self,
|
30
|
+
tag: str):
|
31
|
+
self._tag = tag
|
32
|
+
|
33
|
+
self._Chunk = get_chunk_from_tag(tag)
|
34
|
+
|
35
|
+
self._capture_config = CaptureConfig(tag)
|
36
|
+
|
37
|
+
self._watch_extension = self._capture_config.get("watch_extension")
|
38
|
+
if self._watch_extension is None:
|
39
|
+
raise KeyError("The watch extension has not been specified in the capture config")
|
40
|
+
|
41
|
+
# attribute to store the next file to be processed
|
42
|
+
# (specifically, the absolute file path of the file)
|
43
|
+
self._queued_file: Optional[str] = None
|
44
|
+
|
45
|
+
# spectrogram cache stores spectrograms in memory
|
46
|
+
# such that they can be periodically written to files
|
47
|
+
# according to the joining time.
|
48
|
+
self._spectrogram: Optional[Spectrogram] = None
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
@abstractmethod
|
53
|
+
def process(self,
|
54
|
+
absolute_file_path: str) -> None:
|
55
|
+
"""Process the file stored at the input absolute file path.
|
56
|
+
|
57
|
+
To be implemented by derived classes.
|
58
|
+
"""
|
59
|
+
|
60
|
+
|
61
|
+
def on_created(self,
|
62
|
+
event: FileCreatedEvent):
|
63
|
+
"""Process a newly created batch file, only once the next batch is created.
|
64
|
+
|
65
|
+
Since we assume that the batches are non-overlapping in time, this guarantees
|
66
|
+
we avoid post processing a file while it is being written to. Files are processed
|
67
|
+
sequentially, in the order they are created.
|
68
|
+
"""
|
69
|
+
|
70
|
+
# the 'src_path' attribute holds the absolute path of the newly created file
|
71
|
+
absolute_file_path = event.src_path
|
72
|
+
|
73
|
+
# only 'notice' a file if it ends with the appropriate extension
|
74
|
+
# as defined in the capture config
|
75
|
+
if absolute_file_path.endswith(self._watch_extension):
|
76
|
+
_LOGGER.info(f"Noticed {absolute_file_path}")
|
77
|
+
|
78
|
+
# If there exists a queued file, try and process it
|
79
|
+
if self._queued_file is not None:
|
80
|
+
try:
|
81
|
+
self.process(self._queued_file)
|
82
|
+
except Exception:
|
83
|
+
_LOGGER.error(f"An error has occured while processing {self._queued_file}",
|
84
|
+
exc_info=True)
|
85
|
+
# flush any internally stored spectrogram on error to avoid lost data
|
86
|
+
self._flush_spectrogram()
|
87
|
+
# re-raise the exception to the main thread
|
88
|
+
raise
|
89
|
+
|
90
|
+
# Queue the current file for processing next
|
91
|
+
_LOGGER.info(f"Queueing {absolute_file_path} for post processing")
|
92
|
+
self._queued_file = absolute_file_path
|
93
|
+
|
94
|
+
|
95
|
+
def _average_in_time(self,
|
96
|
+
spectrogram: Spectrogram) -> Spectrogram:
|
97
|
+
_LOGGER.info("Averaging spectrogram in time")
|
98
|
+
requested_time_resolution = self._capture_config['time_resolution'] # [s]
|
99
|
+
if requested_time_resolution is None:
|
100
|
+
raise KeyError(f"Time resolution has not been specified in the capture config")
|
101
|
+
average_over = floor(requested_time_resolution/spectrogram.time_resolution) if requested_time_resolution > spectrogram.time_resolution else 1
|
102
|
+
return time_average(spectrogram, average_over)
|
103
|
+
|
104
|
+
|
105
|
+
def _average_in_frequency(self,
|
106
|
+
spectrogram: Spectrogram) -> Spectrogram:
|
107
|
+
_LOGGER.info("Averaging spectrogram in frequency")
|
108
|
+
frequency_resolution = self._capture_config['frequency_resolution'] # [Hz]
|
109
|
+
if frequency_resolution is None:
|
110
|
+
raise KeyError(f"Frequency resolution has not been specified in the capture config")
|
111
|
+
average_over = floor(frequency_resolution/spectrogram.frequency_resolution) if frequency_resolution > spectrogram.frequency_resolution else 1
|
112
|
+
return frequency_average(spectrogram, average_over)
|
113
|
+
|
114
|
+
|
115
|
+
def _join_spectrogram(self,
|
116
|
+
spectrogram: Spectrogram) -> None:
|
117
|
+
_LOGGER.info("Joining spectrogram")
|
118
|
+
if self._spectrogram is None:
|
119
|
+
self._spectrogram = spectrogram
|
120
|
+
else:
|
121
|
+
self._spectrogram = join_spectrograms([self._spectrogram, spectrogram])
|
122
|
+
|
123
|
+
if self._spectrogram.time_range >= self._capture_config['joining_time']:
|
124
|
+
self._flush_spectrogram()
|
125
|
+
|
126
|
+
|
127
|
+
def _flush_spectrogram(self) -> None:
|
128
|
+
if self._spectrogram:
|
129
|
+
_LOGGER.info(f"Flushing spectrogram to file with chunk start time {self._spectrogram.chunk_start_time}")
|
130
|
+
self._spectrogram.save()
|
131
|
+
_LOGGER.info("Flush successful, resetting spectrogram cache")
|
132
|
+
self._spectrogram = None # reset the cache
|
@@ -2,17 +2,17 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
from spectre_core.
|
6
|
-
from spectre_core.
|
5
|
+
from spectre_core.post_processing.event_handler_register import event_handler_map
|
6
|
+
from spectre_core.post_processing.base import BaseEventHandler
|
7
7
|
from spectre_core.file_handlers.configs import CaptureConfig
|
8
8
|
from spectre_core.exceptions import EventHandlerNotFoundError
|
9
9
|
|
10
10
|
def get_event_handler(event_handler_key: str) -> BaseEventHandler:
|
11
|
-
# try and fetch the capture config mount
|
12
11
|
EventHandler = event_handler_map.get(event_handler_key)
|
13
12
|
if EventHandler is None:
|
14
13
|
valid_event_handler_keys = list(event_handler_map.keys())
|
15
|
-
raise EventHandlerNotFoundError(f"No event handler found for the event handler key
|
14
|
+
raise EventHandlerNotFoundError((f"No event handler found for the event handler key '{event_handler_key}'. "
|
15
|
+
f"Please specify one of the following event handler keys: {valid_event_handler_keys}"))
|
16
16
|
return EventHandler
|
17
17
|
|
18
18
|
|
@@ -7,8 +7,8 @@ _LOGGER = getLogger(__name__)
|
|
7
7
|
|
8
8
|
import os
|
9
9
|
|
10
|
-
from spectre_core.
|
11
|
-
from spectre_core.
|
10
|
+
from spectre_core.post_processing.base import BaseEventHandler
|
11
|
+
from spectre_core.post_processing.event_handler_register import register_event_handler
|
12
12
|
|
13
13
|
@register_event_handler("fixed")
|
14
14
|
class EventHandler(BaseEventHandler):
|
@@ -16,20 +16,19 @@ class EventHandler(BaseEventHandler):
|
|
16
16
|
super().__init__(*args, **kwargs)
|
17
17
|
|
18
18
|
|
19
|
-
def process(self,
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def process(self,
|
20
|
+
absolute_file_path: str):
|
21
|
+
_LOGGER.info(f"Processing: {absolute_file_path}")
|
22
|
+
file_name = os.path.basename(absolute_file_path)
|
23
|
+
base_file_name, _ = os.path.splitext(file_name)
|
24
|
+
chunk_start_time, _ = base_file_name.split('_')
|
23
25
|
chunk = self._Chunk(chunk_start_time, self._tag)
|
24
26
|
|
25
27
|
_LOGGER.info("Creating spectrogram")
|
26
28
|
spectrogram = chunk.build_spectrogram()
|
27
29
|
|
28
|
-
_LOGGER.info("Averaging spectrogram")
|
29
30
|
spectrogram = self._average_in_time(spectrogram)
|
30
31
|
spectrogram = self._average_in_frequency(spectrogram)
|
31
|
-
|
32
|
-
_LOGGER.info("Joining spectrogram")
|
33
32
|
self._join_spectrogram(spectrogram)
|
34
33
|
|
35
34
|
bin_chunk = chunk.get_file('bin')
|
@@ -8,8 +8,8 @@ _LOGGER = getLogger(__name__)
|
|
8
8
|
import os
|
9
9
|
|
10
10
|
from spectre_core.chunks.base import BaseChunk
|
11
|
-
from spectre_core.
|
12
|
-
from spectre_core.
|
11
|
+
from spectre_core.post_processing.base import BaseEventHandler
|
12
|
+
from spectre_core.post_processing.event_handler_register import register_event_handler
|
13
13
|
|
14
14
|
@register_event_handler("sweep")
|
15
15
|
class EventHandler(BaseEventHandler):
|
@@ -19,23 +19,22 @@ class EventHandler(BaseEventHandler):
|
|
19
19
|
self.previous_chunk: BaseChunk = None # cache for previous chunk
|
20
20
|
|
21
21
|
|
22
|
-
def process(self,
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
def process(self,
|
23
|
+
absolute_file_path: str):
|
24
|
+
_LOGGER.info(f"Processing: {absolute_file_path}")
|
25
|
+
file_name = os.path.basename(absolute_file_path)
|
26
|
+
base_file_name, _ = os.path.splitext(file_name)
|
27
|
+
chunk_start_time, _ = base_file_name.split('_')
|
26
28
|
chunk = self._Chunk(chunk_start_time, self._tag)
|
27
29
|
|
28
30
|
_LOGGER.info("Creating spectrogram")
|
29
31
|
spectrogram = chunk.build_spectrogram(previous_chunk = self.previous_chunk)
|
30
32
|
|
31
|
-
_LOGGER.info("Averaging spectrogram")
|
32
33
|
spectrogram = self._average_in_time(spectrogram)
|
33
34
|
spectrogram = self._average_in_frequency(spectrogram)
|
34
|
-
|
35
|
-
_LOGGER.info("Joining spectrogram")
|
36
35
|
self._join_spectrogram(spectrogram)
|
37
36
|
|
38
|
-
# if the previous chunk has not yet been set, it means we
|
37
|
+
# if the previous chunk has not yet been set, it means we are processing the first chunk
|
39
38
|
# so we don't need to handle the previous chunk
|
40
39
|
if self.previous_chunk is None:
|
41
40
|
# instead, only set it for the next time this method is called
|
@@ -0,0 +1,40 @@
|
|
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
|
+
from watchdog.observers import Observer
|
9
|
+
from watchdog.events import FileCreatedEvent
|
10
|
+
|
11
|
+
from spectre_core.post_processing.factory import get_event_handler_from_tag
|
12
|
+
from spectre_core.cfg import CHUNKS_DIR_PATH
|
13
|
+
|
14
|
+
class PostProcessor:
|
15
|
+
def __init__(self,
|
16
|
+
tag: str):
|
17
|
+
|
18
|
+
self._observer = Observer()
|
19
|
+
|
20
|
+
EventHandler = get_event_handler_from_tag(tag)
|
21
|
+
self._event_handler = EventHandler(tag)
|
22
|
+
|
23
|
+
|
24
|
+
def start(self):
|
25
|
+
"""Start an observer to process newly created files in the chunks directory"""
|
26
|
+
self._observer.schedule(self._event_handler,
|
27
|
+
CHUNKS_DIR_PATH,
|
28
|
+
recursive=True,
|
29
|
+
event_filter=[FileCreatedEvent])
|
30
|
+
|
31
|
+
try:
|
32
|
+
_LOGGER.info("Starting the post processing thread...")
|
33
|
+
self._observer.start()
|
34
|
+
self._observer.join()
|
35
|
+
except KeyboardInterrupt:
|
36
|
+
_LOGGER.warning(("Keyboard interrupt detected. Signalling "
|
37
|
+
"the post processing thread to stop"))
|
38
|
+
self._observer.stop()
|
39
|
+
_LOGGER.warning(("Post processing thread has been successfully stopped"))
|
40
|
+
|
spectre_core/receivers/base.py
CHANGED
@@ -242,6 +242,7 @@ class SPECTREReceiver(BaseReceiver):
|
|
242
242
|
"hop": int, # STFFT window hops by so many samples
|
243
243
|
"chunk_key": str, # maps to the corresponding chunk class
|
244
244
|
"event_handler_key": str, # maps to the event handler used in post processing
|
245
|
+
"watch_extension": str, # event handlers watch for files with this extension
|
245
246
|
},
|
246
247
|
"sweep": {
|
247
248
|
"min_freq": float, # [Hz]
|
@@ -262,6 +263,7 @@ class SPECTREReceiver(BaseReceiver):
|
|
262
263
|
"hop": int, # keyword arguments for the scipy STFFT class
|
263
264
|
"chunk_key": str, # maps to the corresponding chunk class
|
264
265
|
"event_handler_key": str, # maps to the event handler used in post processing
|
266
|
+
"watch_extension": str, # event handlers watch for files with this extension
|
265
267
|
}
|
266
268
|
}
|
267
269
|
|
@@ -291,17 +293,18 @@ class SPECTREReceiver(BaseReceiver):
|
|
291
293
|
window_size = capture_config["window_size"]
|
292
294
|
hop = capture_config["hop"]
|
293
295
|
chunk_key = capture_config["chunk_key"]
|
294
|
-
event_handler_key = capture_config[
|
296
|
+
event_handler_key = capture_config["event_handler_key"]
|
297
|
+
watch_extension = capture_config["watch_extension"]
|
295
298
|
|
296
299
|
validators.center_freq_strictly_positive(min_freq)
|
297
300
|
validators.center_freq_strictly_positive(max_freq)
|
298
301
|
validators.samp_rate_strictly_positive(samp_rate)
|
299
302
|
validators.bandwidth_strictly_positive(bandwidth)
|
300
303
|
validators.nyquist_criterion(samp_rate,
|
301
|
-
|
304
|
+
bandwidth)
|
302
305
|
validators.chunk_size_strictly_positive(chunk_size)
|
303
306
|
validators.time_resolution(time_resolution,
|
304
|
-
|
307
|
+
chunk_size)
|
305
308
|
validators.window(window_type,
|
306
309
|
window_kwargs,
|
307
310
|
window_size,
|
@@ -326,6 +329,8 @@ class SPECTREReceiver(BaseReceiver):
|
|
326
329
|
samp_rate)
|
327
330
|
validators.num_samples_per_step(samples_per_step,
|
328
331
|
window_size)
|
332
|
+
validators.watch_extension(watch_extension,
|
333
|
+
"bin")
|
329
334
|
|
330
335
|
# if the api latency is defined, raise a warning if the step interval is of the same order
|
331
336
|
api_latency = self.specifications.get("api_latency")
|
@@ -350,6 +355,7 @@ class SPECTREReceiver(BaseReceiver):
|
|
350
355
|
hop = capture_config["hop"]
|
351
356
|
chunk_key = capture_config["chunk_key"]
|
352
357
|
event_handler_key = capture_config["event_handler_key"]
|
358
|
+
watch_extension = capture_config["watch_extension"]
|
353
359
|
|
354
360
|
validators.center_freq_strictly_positive(center_freq)
|
355
361
|
validators.samp_rate_strictly_positive(samp_rate)
|
@@ -369,7 +375,8 @@ class SPECTREReceiver(BaseReceiver):
|
|
369
375
|
"fixed")
|
370
376
|
validators.gain_is_negative(IF_gain)
|
371
377
|
validators.gain_is_negative(RF_gain)
|
372
|
-
|
378
|
+
validators.watch_extension(watch_extension,
|
379
|
+
"bin")
|
373
380
|
|
374
381
|
# parent class for shared methods and attributes of SDRPlay receivers
|
375
382
|
class SDRPlayReceiver(SPECTREReceiver):
|
@@ -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()
|
@@ -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
|
@@ -48,7 +57,7 @@ spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py,sha256=gR5arbjvJBJjCdQ
|
|
48
57
|
spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py,sha256=O86R3tuJ-7cZXfVECuZkds-wCxKdBKJ8MhVACJFgzeo,4130
|
49
58
|
spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py,sha256=4JgJ2QRO5DFPfqUTHBojj8ERR_4QpHyTBqnAXhOzykE,4747
|
50
59
|
spectre_core/receivers/library/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
|
-
spectre_core/receivers/library/test/receiver.py,sha256=
|
60
|
+
spectre_core/receivers/library/test/receiver.py,sha256=hr6t8jpUwVEP2zMpjORKpzqLaBllxmbqF1fQE1DLWQw,9329
|
52
61
|
spectre_core/receivers/library/test/gr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
62
|
spectre_core/receivers/library/test/gr/cosine_signal_1.py,sha256=6XgYjYMh-QNPs_UUGUQcU_VQFr6BG4OLdsW-M-RU5Ww,2943
|
54
63
|
spectre_core/receivers/library/test/gr/tagged_staircase.py,sha256=5rJHbB-3vdXjqT8DrcAGUSebaAqZ5RQtYHBWgH9iU2E,3465
|
@@ -57,18 +66,9 @@ spectre_core/spectrograms/analytical.py,sha256=c5X40YgLlutP8sbz6dqODZaCmZ98sAjub
|
|
57
66
|
spectre_core/spectrograms/array_operations.py,sha256=6qKd3y2z6Pmu_U8yxTR4FN4eMhS10KgZ8rH60B_IXqw,2577
|
58
67
|
spectre_core/spectrograms/spectrogram.py,sha256=EqeQyvjzjoKaXou4vJbPbRx85BeMPB9iiJtFZcCyimI,19488
|
59
68
|
spectre_core/spectrograms/transform.py,sha256=xo7ch2lrRkJ54cfIqbkaTHNo_AptBuK0zRELPf7SfIE,13860
|
60
|
-
spectre_core/
|
61
|
-
spectre_core/
|
62
|
-
spectre_core/
|
63
|
-
spectre_core/
|
64
|
-
spectre_core/
|
65
|
-
spectre_core
|
66
|
-
spectre_core/watchdog/library/fixed/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
67
|
-
spectre_core/watchdog/library/fixed/event_handler.py,sha256=yWWS80LukB-cTrKBsF4-pRvw2obkX2MzQ5ZGytOtmAg,1387
|
68
|
-
spectre_core/watchdog/library/sweep/event_handler.py,sha256=wDISZiQXBeqLDPxgEMo0a2QAXqQVOO7fng3yhZWSR74,2188
|
69
|
-
spectre_core/web_fetch/callisto.py,sha256=874osjbp61qFwRgV584fpSp7E-xz8g1FEelbNBKhCsw,3632
|
70
|
-
spectre_core-0.0.8.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
71
|
-
spectre_core-0.0.8.dist-info/METADATA,sha256=ltPXaVWUSk8ggdxRsRN9RLj3SIjRvNjGueNoIk3cst4,42150
|
72
|
-
spectre_core-0.0.8.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
73
|
-
spectre_core-0.0.8.dist-info/top_level.txt,sha256=-UsyjpFohXgZpgcZ9QbVeXhsIyF3Am8RxNFNDV_Ta2Y,13
|
74
|
-
spectre_core-0.0.8.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
|
@@ -1,50 +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
|
-
from queue import Queue, Empty
|
9
|
-
|
10
|
-
from watchdog.observers import Observer
|
11
|
-
|
12
|
-
from spectre_core.watchdog.factory import get_event_handler_from_tag
|
13
|
-
from spectre_core.cfg import CHUNKS_DIR_PATH
|
14
|
-
|
15
|
-
class PostProcessor:
|
16
|
-
def __init__(self,
|
17
|
-
tag: str):
|
18
|
-
self._observer: Observer = Observer()
|
19
|
-
self._exception_queue: Queue = Queue() # A thread-safe queue for exceptions
|
20
|
-
|
21
|
-
EventHandler = get_event_handler_from_tag(tag)
|
22
|
-
self._event_handler = EventHandler(tag,
|
23
|
-
self._exception_queue,
|
24
|
-
"bin")
|
25
|
-
|
26
|
-
|
27
|
-
def start(self):
|
28
|
-
_LOGGER.info("Starting post processor...")
|
29
|
-
|
30
|
-
# Schedule and start the observer
|
31
|
-
self._observer.schedule(self._event_handler,
|
32
|
-
CHUNKS_DIR_PATH,
|
33
|
-
recursive=True)
|
34
|
-
self._observer.start()
|
35
|
-
|
36
|
-
try:
|
37
|
-
# Monitor the observer and handle exceptions
|
38
|
-
while self._observer.is_alive():
|
39
|
-
try:
|
40
|
-
exc = self._exception_queue.get(block=True, timeout=0.25)
|
41
|
-
if exc:
|
42
|
-
raise exc # Propagate the exception
|
43
|
-
except Empty:
|
44
|
-
pass # Continue looping if no exception in queue
|
45
|
-
finally:
|
46
|
-
# Ensure the observer is properly stopped
|
47
|
-
self._observer.stop()
|
48
|
-
self._observer.join()
|
49
|
-
|
50
|
-
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|