spectre-core 0.0.9__py3-none-any.whl → 0.0.11__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/__init__.py +0 -3
- spectre_core/_file_io/__init__.py +15 -0
- spectre_core/_file_io/file_handlers.py +128 -0
- spectre_core/capture_configs/__init__.py +29 -0
- spectre_core/capture_configs/_capture_config.py +85 -0
- spectre_core/capture_configs/_capture_templates.py +222 -0
- spectre_core/capture_configs/_parameters.py +110 -0
- spectre_core/capture_configs/_pconstraints.py +82 -0
- spectre_core/capture_configs/_ptemplates.py +450 -0
- spectre_core/capture_configs/_pvalidators.py +171 -0
- spectre_core/chunks/__init__.py +17 -201
- spectre_core/chunks/{base.py → _base.py} +15 -60
- spectre_core/chunks/_chunks.py +200 -0
- spectre_core/chunks/{factory.py → _factory.py} +6 -7
- spectre_core/chunks/library/{callisto/chunk.py → _callisto.py} +4 -7
- spectre_core/chunks/library/{fixed/chunk.py → _fixed_center_frequency.py} +7 -64
- spectre_core/chunks/library/_swept_center_frequency.py +103 -0
- spectre_core/config/__init__.py +20 -0
- spectre_core/config/_paths.py +77 -0
- spectre_core/config/_time_formats.py +15 -0
- spectre_core/exceptions.py +4 -5
- spectre_core/logging/__init__.py +11 -0
- spectre_core/logging/_configure.py +35 -0
- spectre_core/logging/_decorators.py +19 -0
- spectre_core/{logging.py → logging/_log_handlers.py} +13 -58
- spectre_core/plotting/__init__.py +7 -1
- spectre_core/plotting/{base.py → _base.py} +40 -20
- spectre_core/plotting/_format.py +18 -0
- spectre_core/plotting/{panel_stack.py → _panel_stack.py} +48 -48
- spectre_core/plotting/_panels.py +234 -0
- spectre_core/post_processing/__init__.py +10 -2
- spectre_core/post_processing/_base.py +119 -0
- spectre_core/post_processing/{factory.py → _factory.py} +7 -6
- spectre_core/post_processing/{post_processor.py → _post_processor.py} +3 -3
- spectre_core/post_processing/library/_fixed_center_frequency.py +115 -0
- spectre_core/post_processing/library/_swept_center_frequency.py +382 -0
- spectre_core/receivers/__init__.py +13 -2
- spectre_core/receivers/_base.py +180 -0
- spectre_core/receivers/{factory.py → _factory.py} +2 -2
- spectre_core/receivers/_spec_names.py +20 -0
- spectre_core/receivers/gr/__init__.py +3 -0
- spectre_core/receivers/gr/_base.py +33 -0
- spectre_core/receivers/gr/_rsp1a.py +158 -0
- spectre_core/receivers/gr/_rspduo.py +227 -0
- spectre_core/receivers/gr/_test.py +123 -0
- spectre_core/receivers/library/_rsp1a.py +61 -0
- spectre_core/receivers/library/_rspduo.py +69 -0
- spectre_core/receivers/library/_sdrplay_receiver.py +185 -0
- spectre_core/receivers/library/_test.py +221 -0
- spectre_core/spectrograms/__init__.py +18 -0
- spectre_core/spectrograms/{analytical.py → _analytical.py} +29 -27
- spectre_core/spectrograms/{array_operations.py → _array_operations.py} +47 -1
- spectre_core/spectrograms/{spectrogram.py → _spectrogram.py} +62 -35
- spectre_core/spectrograms/{transform.py → _transform.py} +76 -89
- spectre_core/{post_processing/library → wgetting}/__init__.py +4 -5
- spectre_core/wgetting/_callisto.py +155 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/METADATA +1 -1
- spectre_core-0.0.11.dist-info/RECORD +64 -0
- spectre_core/cfg.py +0 -116
- spectre_core/chunks/library/__init__.py +0 -8
- spectre_core/chunks/library/callisto/__init__.py +0 -0
- spectre_core/chunks/library/fixed/__init__.py +0 -0
- spectre_core/chunks/library/sweep/__init__.py +0 -0
- spectre_core/chunks/library/sweep/chunk.py +0 -400
- spectre_core/dynamic_imports.py +0 -22
- spectre_core/file_handlers/base.py +0 -68
- spectre_core/file_handlers/configs.py +0 -271
- spectre_core/file_handlers/json.py +0 -40
- spectre_core/file_handlers/text.py +0 -21
- spectre_core/plotting/factory.py +0 -26
- spectre_core/plotting/format.py +0 -19
- spectre_core/plotting/library/__init__.py +0 -7
- spectre_core/plotting/library/frequency_cuts/panel.py +0 -74
- spectre_core/plotting/library/integral_over_frequency/panel.py +0 -34
- spectre_core/plotting/library/spectrogram/panel.py +0 -92
- spectre_core/plotting/library/time_cuts/panel.py +0 -77
- spectre_core/plotting/panel_register.py +0 -13
- spectre_core/post_processing/base.py +0 -132
- spectre_core/post_processing/library/fixed/__init__.py +0 -0
- spectre_core/post_processing/library/fixed/event_handler.py +0 -40
- spectre_core/post_processing/library/sweep/event_handler.py +0 -54
- spectre_core/receivers/base.py +0 -422
- spectre_core/receivers/library/__init__.py +0 -7
- spectre_core/receivers/library/rsp1a/__init__.py +0 -0
- spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
- spectre_core/receivers/library/rsp1a/gr/fixed.py +0 -104
- spectre_core/receivers/library/rsp1a/gr/sweep.py +0 -129
- spectre_core/receivers/library/rsp1a/receiver.py +0 -68
- spectre_core/receivers/library/rspduo/__init__.py +0 -0
- spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
- spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +0 -114
- spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +0 -131
- spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py +0 -120
- spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py +0 -119
- spectre_core/receivers/library/rspduo/receiver.py +0 -97
- spectre_core/receivers/library/test/__init__.py +0 -0
- spectre_core/receivers/library/test/gr/__init__.py +0 -0
- spectre_core/receivers/library/test/gr/cosine_signal_1.py +0 -83
- spectre_core/receivers/library/test/gr/tagged_staircase.py +0 -93
- spectre_core/receivers/library/test/receiver.py +0 -203
- spectre_core/receivers/validators.py +0 -231
- spectre_core/web_fetch/callisto.py +0 -101
- spectre_core-0.0.9.dist-info/RECORD +0 -74
- /spectre_core/chunks/{chunk_register.py → _register.py} +0 -0
- /spectre_core/post_processing/{event_handler_register.py → _register.py} +0 -0
- /spectre_core/receivers/{receiver_register.py → _register.py} +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/top_level.txt +0 -0
@@ -1,132 +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
|
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
|
File without changes
|
@@ -1,40 +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
|
-
|
10
|
-
from spectre_core.post_processing.base import BaseEventHandler
|
11
|
-
from spectre_core.post_processing.event_handler_register import register_event_handler
|
12
|
-
|
13
|
-
@register_event_handler("fixed")
|
14
|
-
class EventHandler(BaseEventHandler):
|
15
|
-
def __init__(self, *args, **kwargs):
|
16
|
-
super().__init__(*args, **kwargs)
|
17
|
-
|
18
|
-
|
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('_')
|
25
|
-
chunk = self._Chunk(chunk_start_time, self._tag)
|
26
|
-
|
27
|
-
_LOGGER.info("Creating spectrogram")
|
28
|
-
spectrogram = chunk.build_spectrogram()
|
29
|
-
|
30
|
-
spectrogram = self._average_in_time(spectrogram)
|
31
|
-
spectrogram = self._average_in_frequency(spectrogram)
|
32
|
-
self._join_spectrogram(spectrogram)
|
33
|
-
|
34
|
-
bin_chunk = chunk.get_file('bin')
|
35
|
-
_LOGGER.info(f"Deleting {bin_chunk.file_path}")
|
36
|
-
bin_chunk.delete()
|
37
|
-
|
38
|
-
hdr_chunk = chunk.get_file('hdr')
|
39
|
-
_LOGGER.info(f"Deleting {hdr_chunk.file_path}")
|
40
|
-
hdr_chunk.delete()
|
@@ -1,54 +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
|
-
|
10
|
-
from spectre_core.chunks.base import BaseChunk
|
11
|
-
from spectre_core.post_processing.base import BaseEventHandler
|
12
|
-
from spectre_core.post_processing.event_handler_register import register_event_handler
|
13
|
-
|
14
|
-
@register_event_handler("sweep")
|
15
|
-
class EventHandler(BaseEventHandler):
|
16
|
-
def __init__(self, *args, **kwargs):
|
17
|
-
super().__init__(*args, **kwargs)
|
18
|
-
|
19
|
-
self.previous_chunk: BaseChunk = None # cache for previous chunk
|
20
|
-
|
21
|
-
|
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('_')
|
28
|
-
chunk = self._Chunk(chunk_start_time, self._tag)
|
29
|
-
|
30
|
-
_LOGGER.info("Creating spectrogram")
|
31
|
-
spectrogram = chunk.build_spectrogram(previous_chunk = self.previous_chunk)
|
32
|
-
|
33
|
-
spectrogram = self._average_in_time(spectrogram)
|
34
|
-
spectrogram = self._average_in_frequency(spectrogram)
|
35
|
-
self._join_spectrogram(spectrogram)
|
36
|
-
|
37
|
-
# if the previous chunk has not yet been set, it means we are processing the first chunk
|
38
|
-
# so we don't need to handle the previous chunk
|
39
|
-
if self.previous_chunk is None:
|
40
|
-
# instead, only set it for the next time this method is called
|
41
|
-
self.previous_chunk = chunk
|
42
|
-
|
43
|
-
# otherwise the previous chunk is defined (and by this point has already been processed)
|
44
|
-
else:
|
45
|
-
bin_chunk = self.previous_chunk.get_file('bin')
|
46
|
-
_LOGGER.info(f"Deleting {bin_chunk.file_path}")
|
47
|
-
bin_chunk.delete()
|
48
|
-
|
49
|
-
hdr_chunk = self.previous_chunk.get_file('hdr')
|
50
|
-
_LOGGER.info(f"Deleting {hdr_chunk.file_path}")
|
51
|
-
hdr_chunk.delete()
|
52
|
-
|
53
|
-
# and reassign the current chunk to be used as the previous chunk at the next call of this method
|
54
|
-
self.previous_chunk = chunk
|
spectre_core/receivers/base.py
DELETED
@@ -1,422 +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 abc import ABC, abstractmethod
|
6
|
-
from typing import Callable, Any, Optional
|
7
|
-
|
8
|
-
from spectre_core.receivers import validators
|
9
|
-
from spectre_core.file_handlers.configs import (
|
10
|
-
CaptureConfig,
|
11
|
-
validate_against_type_template,
|
12
|
-
type_cast_params
|
13
|
-
)
|
14
|
-
from spectre_core.exceptions import (
|
15
|
-
TemplateNotFoundError,
|
16
|
-
ModeNotFoundError,
|
17
|
-
SpecificationNotFoundError
|
18
|
-
)
|
19
|
-
|
20
|
-
|
21
|
-
class BaseReceiver(ABC):
|
22
|
-
def __init__(self, name: str, mode: Optional[str] = None):
|
23
|
-
self._name = name
|
24
|
-
|
25
|
-
self._capture_methods: dict[str, Callable] = None
|
26
|
-
self._set_capture_methods()
|
27
|
-
|
28
|
-
self._type_templates: dict[str, dict[str, Any]] = None
|
29
|
-
self._set_type_templates()
|
30
|
-
|
31
|
-
self._validators: dict[str, Callable] = None
|
32
|
-
self._set_validators()
|
33
|
-
|
34
|
-
self._specifications: dict[str, Any] = None
|
35
|
-
self._set_specifications()
|
36
|
-
|
37
|
-
self.mode = mode
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
@abstractmethod
|
42
|
-
def _set_capture_methods(self) -> None:
|
43
|
-
pass
|
44
|
-
|
45
|
-
|
46
|
-
@abstractmethod
|
47
|
-
def _set_validators(self) -> None:
|
48
|
-
pass
|
49
|
-
|
50
|
-
@abstractmethod
|
51
|
-
def _set_type_templates(self) -> None:
|
52
|
-
pass
|
53
|
-
|
54
|
-
|
55
|
-
@abstractmethod
|
56
|
-
def _set_specifications(self) -> None:
|
57
|
-
pass
|
58
|
-
|
59
|
-
|
60
|
-
@property
|
61
|
-
def name(self) -> str:
|
62
|
-
return self._name
|
63
|
-
|
64
|
-
|
65
|
-
@property
|
66
|
-
def capture_methods(self) -> dict[str, Callable]:
|
67
|
-
return self._capture_methods
|
68
|
-
|
69
|
-
|
70
|
-
@property
|
71
|
-
def validators(self) -> dict[str, Callable]:
|
72
|
-
return self._validators
|
73
|
-
|
74
|
-
|
75
|
-
@property
|
76
|
-
def type_templates(self) -> dict[str, dict[str, Any]]:
|
77
|
-
return self._type_templates
|
78
|
-
|
79
|
-
|
80
|
-
@property
|
81
|
-
def specifications(self) -> dict[dict, Any]:
|
82
|
-
return self._specifications
|
83
|
-
|
84
|
-
|
85
|
-
@property
|
86
|
-
def valid_modes(self) -> None:
|
87
|
-
capture_method_modes = list(self.capture_methods.keys())
|
88
|
-
validator_modes = list(self.validators.keys())
|
89
|
-
type_template_modes = list(self.type_templates.keys())
|
90
|
-
|
91
|
-
if capture_method_modes == validator_modes == type_template_modes:
|
92
|
-
return capture_method_modes
|
93
|
-
else:
|
94
|
-
raise ValueError(f"Mode mismatch for the receiver {self.name}. Could not define valid modes")
|
95
|
-
|
96
|
-
@property
|
97
|
-
def mode(self) -> str:
|
98
|
-
return self._mode
|
99
|
-
|
100
|
-
|
101
|
-
@mode.setter
|
102
|
-
def mode(self, value: Optional[str]) -> None:
|
103
|
-
if (value is not None) and value not in self.valid_modes:
|
104
|
-
raise ModeNotFoundError((f"{value} is not a defined mode for the receiver {self.name}. "
|
105
|
-
f"Expected one of {self.valid_modes}"))
|
106
|
-
self._mode = value
|
107
|
-
|
108
|
-
|
109
|
-
@property
|
110
|
-
def mode_is_set(self) -> bool:
|
111
|
-
return (self._mode is not None)
|
112
|
-
|
113
|
-
|
114
|
-
@property
|
115
|
-
def capture_method(self) -> Callable:
|
116
|
-
return self.capture_methods[self.mode]
|
117
|
-
|
118
|
-
|
119
|
-
@property
|
120
|
-
def validator(self) -> Callable:
|
121
|
-
return self.validators[self.mode]
|
122
|
-
|
123
|
-
|
124
|
-
@property
|
125
|
-
def type_template(self) -> dict[str, Any]:
|
126
|
-
return self._type_templates[self.mode]
|
127
|
-
|
128
|
-
|
129
|
-
def get_specification(self,
|
130
|
-
specification_key: str) -> Any:
|
131
|
-
specification = self.specifications.get(specification_key)
|
132
|
-
if specification is None:
|
133
|
-
expected_specifications = list(self.specifications.keys())
|
134
|
-
raise SpecificationNotFoundError(f"Invalid specification '{specification_key}'. Expected one of {expected_specifications}")
|
135
|
-
return specification
|
136
|
-
|
137
|
-
|
138
|
-
def validate_capture_config(self,
|
139
|
-
capture_config: CaptureConfig) -> None:
|
140
|
-
# validate against the active type template
|
141
|
-
validate_against_type_template(capture_config,
|
142
|
-
self.type_template,
|
143
|
-
ignore_keys=["receiver", "mode", "tag"])
|
144
|
-
# validate against receiver-specific constraints
|
145
|
-
self.validator(capture_config)
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
def start_capture(self,
|
150
|
-
tag: str) -> None:
|
151
|
-
capture_config = self.load_capture_config(tag)
|
152
|
-
self.capture_method(capture_config)
|
153
|
-
|
154
|
-
|
155
|
-
def save_params(self,
|
156
|
-
params: list[str],
|
157
|
-
tag: str,
|
158
|
-
force: bool = False) -> None:
|
159
|
-
d = type_cast_params(params,
|
160
|
-
self.type_template)
|
161
|
-
|
162
|
-
validate_against_type_template(d,
|
163
|
-
self.type_template)
|
164
|
-
|
165
|
-
self.save_capture_config(d,
|
166
|
-
tag,
|
167
|
-
force = force)
|
168
|
-
|
169
|
-
|
170
|
-
def save_capture_config(self,
|
171
|
-
d: dict[str, Any],
|
172
|
-
tag: str,
|
173
|
-
force: bool = False) -> None:
|
174
|
-
|
175
|
-
self.validate_capture_config(d)
|
176
|
-
|
177
|
-
d.update({"receiver": self.name,
|
178
|
-
"mode": self.mode,
|
179
|
-
"tag": tag})
|
180
|
-
|
181
|
-
capture_config = CaptureConfig(tag)
|
182
|
-
capture_config.save(d,
|
183
|
-
force = force)
|
184
|
-
|
185
|
-
|
186
|
-
def load_capture_config(self,
|
187
|
-
tag: str) -> CaptureConfig:
|
188
|
-
|
189
|
-
capture_config = CaptureConfig(tag)
|
190
|
-
|
191
|
-
if capture_config["receiver"] != self.name:
|
192
|
-
raise ValueError(f"Capture config receiver mismatch for tag {tag}. Expected {self.name}, got {capture_config['receiver']}")
|
193
|
-
|
194
|
-
if capture_config["mode"] != self.mode:
|
195
|
-
raise ValueError(f"Mode mismatch for the tag {tag}. Expected {self.mode}, got {capture_config['mode']}")
|
196
|
-
|
197
|
-
self.validate_capture_config(capture_config)
|
198
|
-
return capture_config
|
199
|
-
|
200
|
-
|
201
|
-
def get_create_capture_config_cmd(self,
|
202
|
-
tag: str,
|
203
|
-
as_string: bool = False) -> str:
|
204
|
-
"""Get a command which can be used to create a capture config with the SPECTRE CLI."""
|
205
|
-
command_as_list = ["spectre", "create", "capture-config",
|
206
|
-
"--tag", tag,
|
207
|
-
"--receiver", self.name,
|
208
|
-
"--mode", self.mode]
|
209
|
-
for key, value in self.type_template.items():
|
210
|
-
command_as_list.extend(["-p", f"{key}={value.__name__}"])
|
211
|
-
|
212
|
-
return " ".join(command_as_list) if as_string else command_as_list
|
213
|
-
|
214
|
-
|
215
|
-
# optional parent class which provides default templates and validators
|
216
|
-
class SPECTREReceiver(BaseReceiver):
|
217
|
-
def __init__(self, *args, **kwargs):
|
218
|
-
self.__set_default_type_templates()
|
219
|
-
super().__init__(*args, **kwargs)
|
220
|
-
|
221
|
-
|
222
|
-
@property
|
223
|
-
def default_type_templates(self) -> dict[str, dict[str, Any]]:
|
224
|
-
return self._default_type_templates
|
225
|
-
|
226
|
-
|
227
|
-
def __set_default_type_templates(self) -> None:
|
228
|
-
self._default_type_templates = {
|
229
|
-
"fixed": {
|
230
|
-
"center_freq": float, # [Hz]
|
231
|
-
"bandwidth": float, # [Hz]
|
232
|
-
"samp_rate": int, # [Hz]
|
233
|
-
"IF_gain": int, # [dB]
|
234
|
-
"RF_gain": int, # [dB]
|
235
|
-
"chunk_size": int, # [s]
|
236
|
-
"joining_time": int, # [s]
|
237
|
-
"time_resolution": float, # [s]
|
238
|
-
"frequency_resolution": float, # [Hz]
|
239
|
-
"window_type": str, # window type for STFFT
|
240
|
-
"window_kwargs": dict, # keyword arguments for window function, must be in order as in scipy documentation.
|
241
|
-
"window_size": int, # number of samples in STFFT window
|
242
|
-
"hop": int, # STFFT window hops by so many samples
|
243
|
-
"chunk_key": str, # maps to the corresponding chunk class
|
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
|
246
|
-
},
|
247
|
-
"sweep": {
|
248
|
-
"min_freq": float, # [Hz]
|
249
|
-
"max_freq": float, # [Hz]
|
250
|
-
"samples_per_step": int,
|
251
|
-
"freq_step": float, # [Hz]
|
252
|
-
"bandwidth": float, # [Hz]
|
253
|
-
"samp_rate": int, # [Hz]
|
254
|
-
"IF_gain": int, # [dB]
|
255
|
-
"RF_gain": int, # [dB]
|
256
|
-
"chunk_size": int, # [s]
|
257
|
-
"joining_time": int, # [s]
|
258
|
-
"time_resolution": float, # [s]
|
259
|
-
"frequency_resolution": float, # [Hz]
|
260
|
-
"window_type": str, # window type for STFFT
|
261
|
-
"window_kwargs": dict, # keyword arguments for window function, must be in order as in scipy documentation.
|
262
|
-
"window_size": int, # number of samples in STFFT window
|
263
|
-
"hop": int, # keyword arguments for the scipy STFFT class
|
264
|
-
"chunk_key": str, # maps to the corresponding chunk class
|
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
|
267
|
-
}
|
268
|
-
}
|
269
|
-
|
270
|
-
|
271
|
-
def _get_default_type_template(self,
|
272
|
-
mode: str) -> dict:
|
273
|
-
default_type_template = self.default_type_templates[mode]
|
274
|
-
if default_type_template is None:
|
275
|
-
raise TemplateNotFoundError(f"No default template found for the mode {mode}")
|
276
|
-
return default_type_template
|
277
|
-
|
278
|
-
|
279
|
-
def _default_sweep_validator(self,
|
280
|
-
capture_config: CaptureConfig) -> None:
|
281
|
-
min_freq = capture_config["min_freq"]
|
282
|
-
max_freq = capture_config["max_freq"]
|
283
|
-
samples_per_step = capture_config["samples_per_step"]
|
284
|
-
freq_step = capture_config["freq_step"]
|
285
|
-
bandwidth = capture_config["bandwidth"]
|
286
|
-
samp_rate = capture_config["samp_rate"]
|
287
|
-
IF_gain = capture_config["IF_gain"]
|
288
|
-
RF_gain = capture_config["RF_gain"]
|
289
|
-
chunk_size = capture_config["chunk_size"]
|
290
|
-
time_resolution = capture_config["time_resolution"]
|
291
|
-
window_type = capture_config["window_type"]
|
292
|
-
window_kwargs = capture_config["window_kwargs"]
|
293
|
-
window_size = capture_config["window_size"]
|
294
|
-
hop = capture_config["hop"]
|
295
|
-
chunk_key = capture_config["chunk_key"]
|
296
|
-
event_handler_key = capture_config["event_handler_key"]
|
297
|
-
watch_extension = capture_config["watch_extension"]
|
298
|
-
|
299
|
-
validators.center_freq_strictly_positive(min_freq)
|
300
|
-
validators.center_freq_strictly_positive(max_freq)
|
301
|
-
validators.samp_rate_strictly_positive(samp_rate)
|
302
|
-
validators.bandwidth_strictly_positive(bandwidth)
|
303
|
-
validators.nyquist_criterion(samp_rate,
|
304
|
-
bandwidth)
|
305
|
-
validators.chunk_size_strictly_positive(chunk_size)
|
306
|
-
validators.time_resolution(time_resolution,
|
307
|
-
chunk_size)
|
308
|
-
validators.window(window_type,
|
309
|
-
window_kwargs,
|
310
|
-
window_size,
|
311
|
-
chunk_size,
|
312
|
-
samp_rate)
|
313
|
-
validators.hop(hop)
|
314
|
-
validators.chunk_key(chunk_key, "sweep")
|
315
|
-
validators.event_handler_key(event_handler_key, "sweep")
|
316
|
-
validators.gain_is_negative(IF_gain)
|
317
|
-
validators.gain_is_negative(RF_gain)
|
318
|
-
validators.num_steps_per_sweep(min_freq,
|
319
|
-
max_freq,
|
320
|
-
samp_rate,
|
321
|
-
freq_step)
|
322
|
-
validators.sweep_interval(min_freq,
|
323
|
-
max_freq,
|
324
|
-
samp_rate,
|
325
|
-
freq_step,
|
326
|
-
samples_per_step,
|
327
|
-
chunk_size)
|
328
|
-
validators.non_overlapping_steps(freq_step,
|
329
|
-
samp_rate)
|
330
|
-
validators.num_samples_per_step(samples_per_step,
|
331
|
-
window_size)
|
332
|
-
validators.watch_extension(watch_extension,
|
333
|
-
"bin")
|
334
|
-
|
335
|
-
# if the api latency is defined, raise a warning if the step interval is of the same order
|
336
|
-
api_latency = self.specifications.get("api_latency")
|
337
|
-
if api_latency:
|
338
|
-
validators.step_interval(samples_per_step,
|
339
|
-
samp_rate,
|
340
|
-
api_latency)
|
341
|
-
|
342
|
-
|
343
|
-
def _default_fixed_validator(self,
|
344
|
-
capture_config: CaptureConfig) -> None:
|
345
|
-
center_freq = capture_config["center_freq"]
|
346
|
-
bandwidth = capture_config["bandwidth"]
|
347
|
-
samp_rate = capture_config["samp_rate"]
|
348
|
-
IF_gain = capture_config["IF_gain"]
|
349
|
-
RF_gain = capture_config["RF_gain"]
|
350
|
-
chunk_size = capture_config["chunk_size"]
|
351
|
-
time_resolution = capture_config["time_resolution"]
|
352
|
-
window_type = capture_config["window_type"]
|
353
|
-
window_kwargs = capture_config["window_kwargs"]
|
354
|
-
window_size = capture_config["window_size"]
|
355
|
-
hop = capture_config["hop"]
|
356
|
-
chunk_key = capture_config["chunk_key"]
|
357
|
-
event_handler_key = capture_config["event_handler_key"]
|
358
|
-
watch_extension = capture_config["watch_extension"]
|
359
|
-
|
360
|
-
validators.center_freq_strictly_positive(center_freq)
|
361
|
-
validators.samp_rate_strictly_positive(samp_rate)
|
362
|
-
validators.bandwidth_strictly_positive(bandwidth)
|
363
|
-
validators.nyquist_criterion(samp_rate, bandwidth)
|
364
|
-
validators.chunk_size_strictly_positive(chunk_size)
|
365
|
-
validators.time_resolution(time_resolution, chunk_size)
|
366
|
-
validators.window(window_type,
|
367
|
-
window_kwargs,
|
368
|
-
window_size,
|
369
|
-
chunk_size,
|
370
|
-
samp_rate)
|
371
|
-
validators.hop(hop)
|
372
|
-
validators.chunk_key(chunk_key,
|
373
|
-
"fixed")
|
374
|
-
validators.event_handler_key(event_handler_key,
|
375
|
-
"fixed")
|
376
|
-
validators.gain_is_negative(IF_gain)
|
377
|
-
validators.gain_is_negative(RF_gain)
|
378
|
-
validators.watch_extension(watch_extension,
|
379
|
-
"bin")
|
380
|
-
|
381
|
-
# parent class for shared methods and attributes of SDRPlay receivers
|
382
|
-
class SDRPlayReceiver(SPECTREReceiver):
|
383
|
-
def __init__(self, *args, **kwargs):
|
384
|
-
super().__init__(*args, **kwargs)
|
385
|
-
|
386
|
-
def _sdrplay_validator(self,
|
387
|
-
capture_config: CaptureConfig) -> None:
|
388
|
-
# RSPduo specific validations in single tuner mode
|
389
|
-
center_freq_lower_bound = self.get_specification("center_freq_lower_bound")
|
390
|
-
center_freq_upper_bound = self.get_specification("center_freq_upper_bound")
|
391
|
-
center_freq = capture_config.get("center_freq")
|
392
|
-
min_freq = capture_config.get("min_freq")
|
393
|
-
max_freq = capture_config.get("max_freq")
|
394
|
-
|
395
|
-
if center_freq:
|
396
|
-
validators.closed_confine_center_freq(center_freq,
|
397
|
-
center_freq_lower_bound,
|
398
|
-
center_freq_upper_bound)
|
399
|
-
|
400
|
-
if min_freq:
|
401
|
-
validators.closed_confine_center_freq(min_freq,
|
402
|
-
center_freq_lower_bound,
|
403
|
-
center_freq_upper_bound)
|
404
|
-
if max_freq:
|
405
|
-
validators.closed_confine_center_freq(max_freq,
|
406
|
-
center_freq_lower_bound,
|
407
|
-
center_freq_upper_bound)
|
408
|
-
|
409
|
-
validators.closed_confine_samp_rate(capture_config["samp_rate"],
|
410
|
-
self.get_specification("samp_rate_lower_bound"),
|
411
|
-
self.get_specification("samp_rate_upper_bound"))
|
412
|
-
|
413
|
-
|
414
|
-
validators.closed_confine_bandwidth(capture_config["bandwidth"],
|
415
|
-
self.get_specification("bandwidth_lower_bound"),
|
416
|
-
self.get_specification("bandwidth_upper_bound"))
|
417
|
-
|
418
|
-
validators.closed_upper_bound_IF_gain(capture_config["IF_gain"],
|
419
|
-
self.get_specification("IF_gain_upper_bound"))
|
420
|
-
|
421
|
-
validators.closed_upper_bound_RF_gain(capture_config["RF_gain"],
|
422
|
-
self.get_specification("RF_gain_upper_bound"))
|
@@ -1,7 +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 spectre_core.dynamic_imports import import_target_modules
|
6
|
-
|
7
|
-
import_target_modules(__file__, __name__, "receiver")
|
File without changes
|
File without changes
|