spectre-core 0.0.12__py3-none-any.whl → 0.0.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spectre_core/_file_io/__init__.py +1 -3
- spectre_core/_file_io/file_handlers.py +163 -58
- spectre_core/batches/__init__.py +10 -11
- spectre_core/batches/_base.py +170 -78
- spectre_core/batches/_batches.py +149 -99
- spectre_core/batches/_factory.py +56 -14
- spectre_core/batches/_register.py +23 -8
- spectre_core/batches/plugins/_batch_keys.py +16 -0
- spectre_core/batches/plugins/_callisto.py +183 -0
- spectre_core/batches/plugins/_iq_stream.py +354 -0
- spectre_core/capture_configs/__init__.py +17 -13
- spectre_core/capture_configs/_capture_config.py +93 -34
- spectre_core/capture_configs/_capture_modes.py +22 -0
- spectre_core/capture_configs/_capture_templates.py +207 -122
- spectre_core/capture_configs/_parameters.py +115 -42
- spectre_core/capture_configs/_pconstraints.py +86 -35
- spectre_core/capture_configs/_pnames.py +49 -0
- spectre_core/capture_configs/_ptemplates.py +389 -346
- spectre_core/capture_configs/_pvalidators.py +117 -73
- spectre_core/config/__init__.py +6 -8
- spectre_core/config/_paths.py +65 -25
- spectre_core/config/_time_formats.py +15 -10
- spectre_core/exceptions.py +2 -4
- spectre_core/jobs/__init__.py +14 -0
- spectre_core/jobs/_jobs.py +111 -0
- spectre_core/jobs/_workers.py +171 -0
- spectre_core/logs/__init__.py +17 -0
- spectre_core/logs/_configure.py +67 -0
- spectre_core/logs/_decorators.py +33 -0
- spectre_core/logs/_logs.py +228 -0
- spectre_core/logs/_process_types.py +14 -0
- spectre_core/plotting/__init__.py +4 -2
- spectre_core/plotting/_base.py +204 -102
- spectre_core/plotting/_format.py +17 -4
- spectre_core/plotting/_panel_names.py +18 -0
- spectre_core/plotting/_panel_stack.py +167 -53
- spectre_core/plotting/_panels.py +341 -141
- spectre_core/post_processing/__init__.py +8 -6
- spectre_core/post_processing/_base.py +70 -44
- spectre_core/post_processing/_factory.py +42 -12
- spectre_core/post_processing/_post_processor.py +24 -26
- spectre_core/post_processing/_register.py +22 -6
- spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
- spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
- spectre_core/py.typed +0 -0
- spectre_core/receivers/__init__.py +10 -7
- spectre_core/receivers/_base.py +220 -69
- spectre_core/receivers/_factory.py +53 -7
- spectre_core/receivers/_register.py +30 -9
- spectre_core/receivers/_spec_names.py +26 -15
- spectre_core/receivers/plugins/__init__.py +0 -0
- spectre_core/receivers/plugins/_receiver_names.py +16 -0
- spectre_core/receivers/plugins/_rsp1a.py +59 -0
- spectre_core/receivers/plugins/_rspduo.py +67 -0
- spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
- spectre_core/receivers/plugins/_test.py +218 -0
- spectre_core/receivers/plugins/gr/_base.py +80 -0
- spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
- spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
- spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
- spectre_core/spectrograms/__init__.py +5 -3
- spectre_core/spectrograms/_analytical.py +121 -66
- spectre_core/spectrograms/_array_operations.py +103 -36
- spectre_core/spectrograms/_spectrogram.py +380 -207
- spectre_core/spectrograms/_transform.py +197 -169
- spectre_core/wgetting/__init__.py +4 -2
- spectre_core/wgetting/_callisto.py +173 -118
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
- spectre_core-0.0.13.dist-info/RECORD +75 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
- spectre_core/batches/library/_callisto.py +0 -96
- spectre_core/batches/library/_fixed_center_frequency.py +0 -133
- spectre_core/batches/library/_swept_center_frequency.py +0 -105
- spectre_core/logging/__init__.py +0 -11
- spectre_core/logging/_configure.py +0 -35
- spectre_core/logging/_decorators.py +0 -19
- spectre_core/logging/_log_handlers.py +0 -176
- spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
- spectre_core/receivers/gr/_base.py +0 -33
- spectre_core/receivers/library/_rsp1a.py +0 -61
- spectre_core/receivers/library/_rspduo.py +0 -69
- spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
- spectre_core/receivers/library/_test.py +0 -221
- spectre_core-0.0.12.dist-info/RECORD +0 -64
- /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -1,133 +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 datetime import datetime, timedelta
|
6
|
-
from typing import Tuple
|
7
|
-
import numpy as np
|
8
|
-
|
9
|
-
from astropy.io import fits
|
10
|
-
from astropy.io.fits.hdu.image import PrimaryHDU
|
11
|
-
from astropy.io.fits.hdu.table import BinTableHDU
|
12
|
-
from astropy.io.fits.hdu.hdulist import HDUList
|
13
|
-
|
14
|
-
from spectre_core.config import TimeFormats
|
15
|
-
from spectre_core.spectrograms import Spectrogram
|
16
|
-
from spectre_core.capture_configs import CaptureModes
|
17
|
-
from .._register import register_batch
|
18
|
-
from .._base import BaseBatch, BatchFile
|
19
|
-
|
20
|
-
|
21
|
-
@register_batch(CaptureModes.FIXED_CENTER_FREQUENCY)
|
22
|
-
class _Batch(BaseBatch):
|
23
|
-
def __init__(self,
|
24
|
-
start_time: str,
|
25
|
-
tag: str):
|
26
|
-
super().__init__(start_time, tag)
|
27
|
-
self.add_file( BinFile(self.parent_dir_path, self.name) )
|
28
|
-
self.add_file( HdrFile(self.parent_dir_path, self.name) )
|
29
|
-
self.add_file( FitsFile(self.parent_dir_path, self.name))
|
30
|
-
|
31
|
-
|
32
|
-
class BinFile(BatchFile):
|
33
|
-
def __init__(self,
|
34
|
-
parent_dir_path: str,
|
35
|
-
base_file_name: str):
|
36
|
-
super().__init__(parent_dir_path, base_file_name, "bin")
|
37
|
-
|
38
|
-
def read(self) -> np.ndarray:
|
39
|
-
with open(self.file_path, "rb") as fh:
|
40
|
-
return np.fromfile(fh, dtype=np.complex64)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
class HdrFile(BatchFile):
|
45
|
-
def __init__(self,
|
46
|
-
parent_dir_path: str,
|
47
|
-
base_file_name: str):
|
48
|
-
super().__init__(parent_dir_path, base_file_name, "hdr")
|
49
|
-
|
50
|
-
|
51
|
-
def read(self) -> int:
|
52
|
-
hdr_contents = self._extract_contents()
|
53
|
-
return self._get_millisecond_correction(hdr_contents)
|
54
|
-
|
55
|
-
|
56
|
-
def _extract_contents(self) -> np.ndarray:
|
57
|
-
with open(self.file_path, "rb") as fh:
|
58
|
-
return np.fromfile(fh, dtype=np.float32)
|
59
|
-
|
60
|
-
|
61
|
-
def _get_millisecond_correction(self, hdr_contents: np.ndarray) -> int:
|
62
|
-
if len(hdr_contents) != 1:
|
63
|
-
raise ValueError(f"Expected exactly one integer in the header, but received header contents: {hdr_contents}")
|
64
|
-
|
65
|
-
millisecond_correction_as_float = float(hdr_contents[0])
|
66
|
-
|
67
|
-
if not millisecond_correction_as_float.is_integer():
|
68
|
-
raise TypeError(f"Expected integer value for millisecond correction, but got {millisecond_correction_as_float}")
|
69
|
-
|
70
|
-
return int(millisecond_correction_as_float)
|
71
|
-
|
72
|
-
|
73
|
-
class FitsFile(BatchFile):
|
74
|
-
def __init__(self,
|
75
|
-
parent_dir_path: str,
|
76
|
-
base_file_name: str):
|
77
|
-
super().__init__(parent_dir_path, base_file_name, "fits")
|
78
|
-
|
79
|
-
|
80
|
-
@property
|
81
|
-
def datetimes(self) -> np.ndarray:
|
82
|
-
with fits.open(self.file_path, mode='readonly') as hdulist:
|
83
|
-
bintable_data = hdulist[1].data
|
84
|
-
times = bintable_data['TIME'][0]
|
85
|
-
return [self.start_datetime + timedelta(seconds=t) for t in times]
|
86
|
-
|
87
|
-
|
88
|
-
def read(self) -> Spectrogram:
|
89
|
-
with fits.open(self.file_path, mode='readonly') as hdulist:
|
90
|
-
primary_hdu = self._get_primary_hdu(hdulist)
|
91
|
-
dynamic_spectra = self._get_dynamic_spectra(primary_hdu)
|
92
|
-
spectrum_type = self._get_spectrum_type(primary_hdu)
|
93
|
-
spectrogram_start_datetime = self._get_spectrogram_start_datetime(primary_hdu)
|
94
|
-
bintable_hdu = self._get_bintable_hdu(hdulist)
|
95
|
-
times, frequencies = self._get_time_and_frequency(bintable_hdu)
|
96
|
-
|
97
|
-
return Spectrogram(dynamic_spectra,
|
98
|
-
times,
|
99
|
-
frequencies,
|
100
|
-
self.tag,
|
101
|
-
spectrogram_start_datetime,
|
102
|
-
spectrum_type)
|
103
|
-
|
104
|
-
|
105
|
-
def _get_primary_hdu(self, hdulist: HDUList) -> PrimaryHDU:
|
106
|
-
return hdulist['PRIMARY']
|
107
|
-
|
108
|
-
|
109
|
-
def _get_dynamic_spectra(self, primary_hdu: PrimaryHDU) -> np.ndarray:
|
110
|
-
return primary_hdu.data
|
111
|
-
|
112
|
-
|
113
|
-
def _get_spectrum_type(self, primary_hdu: PrimaryHDU) -> str:
|
114
|
-
return primary_hdu.header['BUNIT']
|
115
|
-
|
116
|
-
|
117
|
-
def _get_spectrogram_start_datetime(self, primary_hdu: PrimaryHDU) -> datetime:
|
118
|
-
date_obs = primary_hdu.header['DATE-OBS']
|
119
|
-
time_obs = primary_hdu.header['TIME-OBS']
|
120
|
-
return datetime.strptime(f"{date_obs}T{time_obs}", TimeFormats.PRECISE_DATETIME)
|
121
|
-
|
122
|
-
|
123
|
-
def _get_bintable_hdu(self, hdulist: HDUList) -> BinTableHDU:
|
124
|
-
return hdulist[1]
|
125
|
-
|
126
|
-
|
127
|
-
def _get_time_and_frequency(self, bintable_hdu: BinTableHDU) -> Tuple[np.ndarray, np.ndarray]:
|
128
|
-
data = bintable_hdu.data
|
129
|
-
times = data['TIME'][0]
|
130
|
-
frequencies_MHz = data['FREQUENCY'][0]
|
131
|
-
frequencies = frequencies_MHz * 1e6 # convert to Hz
|
132
|
-
return times, frequencies
|
133
|
-
|
@@ -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 typing import Tuple
|
6
|
-
from dataclasses import dataclass
|
7
|
-
|
8
|
-
import numpy as np
|
9
|
-
|
10
|
-
from spectre_core.exceptions import InvalidSweepMetadataError
|
11
|
-
from spectre_core.capture_configs import CaptureModes
|
12
|
-
from ._fixed_center_frequency import BinFile, FitsFile
|
13
|
-
from .._register import register_batch
|
14
|
-
from .._base import BaseBatch, BatchFile
|
15
|
-
|
16
|
-
|
17
|
-
@dataclass
|
18
|
-
class SweepMetadata:
|
19
|
-
"""Wrapper for metadata required to assign center frequencies to each IQ sample in the batch.
|
20
|
-
|
21
|
-
center_frequencies is an ordered list containing all the center frequencies that the IQ samples
|
22
|
-
were collected at. Typically, these will be ordered in "steps", where each step corresponds to
|
23
|
-
IQ samples collected at a constant center frequency:
|
24
|
-
|
25
|
-
(freq_0, freq_1, ..., freq_M, freq_0, freq_1, ..., freq_M, ...), freq_0 < freq_1 < ... < freq_M
|
26
|
-
|
27
|
-
The n'th element of the num_samples list, tells us how many samples were collected at the n'th
|
28
|
-
element of center_frequencies.
|
29
|
-
|
30
|
-
Number of samples: (num_samples_at_freq_0, num_samples_at_freq_1, ...)
|
31
|
-
|
32
|
-
Both these lists together allow us to map for each IQ sample, the center frequency it was collected at.
|
33
|
-
"""
|
34
|
-
center_frequencies: np.ndarray
|
35
|
-
num_samples: np.ndarray
|
36
|
-
|
37
|
-
|
38
|
-
@register_batch(CaptureModes.SWEPT_CENTER_FREQUENCY)
|
39
|
-
class _Batch(BaseBatch):
|
40
|
-
def __init__(self,
|
41
|
-
start_time: str,
|
42
|
-
tag: str):
|
43
|
-
super().__init__(start_time, tag)
|
44
|
-
self.add_file( HdrFile(self.parent_dir_path, self.name) )
|
45
|
-
# reuse the binary and fits batch from the fixed center frequency case.
|
46
|
-
self.add_file( BinFile(self.parent_dir_path, self.name) )
|
47
|
-
self.add_file( FitsFile(self.parent_dir_path, self.name))
|
48
|
-
|
49
|
-
|
50
|
-
class HdrFile(BatchFile):
|
51
|
-
def __init__(self,
|
52
|
-
parent_dir_path: str,
|
53
|
-
base_file_name: str):
|
54
|
-
super().__init__(parent_dir_path, base_file_name, "hdr")
|
55
|
-
|
56
|
-
def read(self) -> Tuple[int, SweepMetadata]:
|
57
|
-
hdr_contents = self._read_file_contents()
|
58
|
-
millisecond_correction = self._get_millisecond_correction(hdr_contents)
|
59
|
-
center_frequencies = self._get_center_frequencies(hdr_contents)
|
60
|
-
num_samples = self._get_num_samples(hdr_contents)
|
61
|
-
self._validate_frequencies_and_samples(center_frequencies,
|
62
|
-
num_samples)
|
63
|
-
return millisecond_correction, SweepMetadata(center_frequencies, num_samples)
|
64
|
-
|
65
|
-
|
66
|
-
def _read_file_contents(self) -> np.ndarray:
|
67
|
-
with open(self.file_path, "rb") as fh:
|
68
|
-
return np.fromfile(fh, dtype=np.float32)
|
69
|
-
|
70
|
-
|
71
|
-
def _get_millisecond_correction(self, hdr_contents: np.ndarray) -> int:
|
72
|
-
''' Millisecond correction is an integral quantity, but stored in the detached header as a 32-bit float.'''
|
73
|
-
millisecond_correction_as_float = float(hdr_contents[0])
|
74
|
-
|
75
|
-
if not millisecond_correction_as_float.is_integer():
|
76
|
-
raise TypeError(f"Expected integer value for millisecond correction, but got {millisecond_correction_as_float}")
|
77
|
-
|
78
|
-
return int(millisecond_correction_as_float)
|
79
|
-
|
80
|
-
|
81
|
-
def _get_center_frequencies(self, hdr_contents: np.ndarray) -> np.ndarray:
|
82
|
-
'''
|
83
|
-
Detached header contents are stored in (center_freq_i, num_samples_at_center_freq_i) pairs
|
84
|
-
Return only a list of center frequencies, by skipping over file contents in twos.
|
85
|
-
'''
|
86
|
-
return hdr_contents[1::2]
|
87
|
-
|
88
|
-
|
89
|
-
def _get_num_samples(self, hdr_contents: np.ndarray) -> np.ndarray:
|
90
|
-
'''
|
91
|
-
Detached header contents are stored in (center_freq_i, num_samples_at_center_freq_i) pairs
|
92
|
-
Return only the number of samples at each center frequency, by skipping over file contents in twos.
|
93
|
-
Number of samples is an integral quantity, but stored in the detached header as a 32-bit float.
|
94
|
-
Types are checked before return.
|
95
|
-
'''
|
96
|
-
num_samples_as_float = hdr_contents[2::2]
|
97
|
-
if not all(num_samples_as_float == num_samples_as_float.astype(int)):
|
98
|
-
raise InvalidSweepMetadataError("Number of samples per frequency is expected to describe an integer")
|
99
|
-
return num_samples_as_float.astype(int)
|
100
|
-
|
101
|
-
|
102
|
-
def _validate_frequencies_and_samples(self, center_frequencies: np.ndarray, num_samples: np.ndarray) -> None:
|
103
|
-
"""Validates that the center frequencies and the number of samples arrays have the same length."""
|
104
|
-
if len(center_frequencies) != len(num_samples):
|
105
|
-
raise InvalidSweepMetadataError("Center frequencies and number of samples arrays are not the same length")
|
spectre_core/logging/__init__.py
DELETED
@@ -1,11 +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 ._decorators import log_call
|
6
|
-
from ._configure import configure_root_logger
|
7
|
-
from ._log_handlers import LogHandler, LogHandlers
|
8
|
-
|
9
|
-
__all__ = [
|
10
|
-
"log_call", "configure_root_logger", "LogHandler", "LogHandlers"
|
11
|
-
]
|
@@ -1,35 +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
|
-
import os
|
6
|
-
import logging
|
7
|
-
from datetime import datetime
|
8
|
-
|
9
|
-
from spectre_core.config import TimeFormats
|
10
|
-
from ._log_handlers import LogHandler
|
11
|
-
|
12
|
-
def configure_root_logger(process_type: str,
|
13
|
-
level: int = logging.INFO
|
14
|
-
) -> LogHandler:
|
15
|
-
system_datetime = datetime.now()
|
16
|
-
datetime_stamp = system_datetime.strftime(TimeFormats.DATETIME)
|
17
|
-
pid = os.getpid()
|
18
|
-
log_handler = LogHandler(datetime_stamp, pid, process_type)
|
19
|
-
log_handler.make_parent_dir_path()
|
20
|
-
|
21
|
-
# configure the root logger
|
22
|
-
logger = logging.getLogger()
|
23
|
-
logger.setLevel(level)
|
24
|
-
# Remove any existing handlers to avoid duplicate logs
|
25
|
-
for handler in logger.handlers:
|
26
|
-
logger.removeHandler(handler)
|
27
|
-
# Set up file handler with specific filename
|
28
|
-
file_handler = logging.FileHandler(log_handler.file_path)
|
29
|
-
file_handler.setLevel(level)
|
30
|
-
formatter = logging.Formatter("[%(asctime)s] [%(levelname)8s] --- %(message)s (%(name)s:%(lineno)s)")
|
31
|
-
file_handler.setFormatter(formatter)
|
32
|
-
# and add it to the root logger
|
33
|
-
logger.addHandler(file_handler)
|
34
|
-
|
35
|
-
return log_handler
|
@@ -1,19 +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
|
-
import logging
|
6
|
-
from typing import Callable
|
7
|
-
from functools import wraps
|
8
|
-
|
9
|
-
def log_call(func: Callable) -> Callable:
|
10
|
-
@wraps(func)
|
11
|
-
def wrapper(*args, **kwargs):
|
12
|
-
logger = logging.getLogger(func.__module__)
|
13
|
-
try:
|
14
|
-
logger.info(f"Calling the function: {func.__name__}")
|
15
|
-
return func(*args, **kwargs)
|
16
|
-
except Exception as e:
|
17
|
-
logger.error(f"Error in function: {func.__name__}", exc_info=True)
|
18
|
-
raise
|
19
|
-
return wrapper
|
@@ -1,176 +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
|
-
|
6
|
-
from logging import getLogger
|
7
|
-
_LOGGER = getLogger(__name__)
|
8
|
-
|
9
|
-
import os
|
10
|
-
import logging
|
11
|
-
from dataclasses import dataclass
|
12
|
-
from typing import Optional
|
13
|
-
import warnings
|
14
|
-
from collections import OrderedDict
|
15
|
-
from datetime import datetime
|
16
|
-
|
17
|
-
from spectre_core._file_io import TextHandler
|
18
|
-
from spectre_core.config import get_logs_dir_path, TimeFormats
|
19
|
-
|
20
|
-
PROCESS_TYPES = [
|
21
|
-
"user",
|
22
|
-
"worker"
|
23
|
-
]
|
24
|
-
|
25
|
-
def _validate_process_type(process_type: str
|
26
|
-
) -> None:
|
27
|
-
if process_type not in PROCESS_TYPES:
|
28
|
-
raise ValueError(f"Invalid process type: {process_type}. Expected one of {PROCESS_TYPES}")
|
29
|
-
|
30
|
-
|
31
|
-
class LogHandler(TextHandler):
|
32
|
-
def __init__(self,
|
33
|
-
datetime_stamp: str,
|
34
|
-
pid: str,
|
35
|
-
process_type: str):
|
36
|
-
self._datetime_stamp = datetime_stamp
|
37
|
-
self._pid = pid
|
38
|
-
_validate_process_type(process_type)
|
39
|
-
self._process_type = process_type
|
40
|
-
|
41
|
-
dt = datetime.strptime(datetime_stamp, TimeFormats.DATETIME)
|
42
|
-
parent_path = get_logs_dir_path(dt.year, dt.month, dt.day)
|
43
|
-
base_file_name = f"{datetime_stamp}_{pid}_{process_type}"
|
44
|
-
|
45
|
-
super().__init__(parent_path, base_file_name, extension = "log")
|
46
|
-
|
47
|
-
|
48
|
-
@property
|
49
|
-
def datetime_stamp(self) -> str:
|
50
|
-
return self._datetime_stamp
|
51
|
-
|
52
|
-
|
53
|
-
@property
|
54
|
-
def pid(self) -> str:
|
55
|
-
return self._pid
|
56
|
-
|
57
|
-
|
58
|
-
@property
|
59
|
-
def process_type(self) -> str:
|
60
|
-
return self._process_type
|
61
|
-
|
62
|
-
|
63
|
-
class LogHandlers:
|
64
|
-
def __init__(self,
|
65
|
-
process_type: Optional[str] = None,
|
66
|
-
year: Optional[int] = None,
|
67
|
-
month: Optional[int] = None,
|
68
|
-
day: Optional[int] = None):
|
69
|
-
self._process_type = process_type
|
70
|
-
self._log_handler_map: dict[str, LogHandler] = OrderedDict()
|
71
|
-
self.set_date(year, month, day)
|
72
|
-
|
73
|
-
|
74
|
-
@property
|
75
|
-
def process_type(self) -> str:
|
76
|
-
return self._process_type
|
77
|
-
|
78
|
-
|
79
|
-
@property
|
80
|
-
def year(self) -> Optional[int]:
|
81
|
-
return self._year
|
82
|
-
|
83
|
-
|
84
|
-
@property
|
85
|
-
def month(self) -> Optional[int]:
|
86
|
-
return self._month
|
87
|
-
|
88
|
-
|
89
|
-
@property
|
90
|
-
def day(self) -> Optional[int]:
|
91
|
-
return self._day
|
92
|
-
|
93
|
-
|
94
|
-
@property
|
95
|
-
def logs_dir_path(self) -> str:
|
96
|
-
return get_logs_dir_path(self.year, self.month, self.day)
|
97
|
-
|
98
|
-
|
99
|
-
@property
|
100
|
-
def log_handler_map(self) -> dict[str, LogHandler]:
|
101
|
-
if not self._log_handler_map: # check for empty dictionary
|
102
|
-
self._update_log_handler_map()
|
103
|
-
return self._log_handler_map
|
104
|
-
|
105
|
-
|
106
|
-
@property
|
107
|
-
def log_handler_list(self) -> list[LogHandler]:
|
108
|
-
return list(self.log_handler_map.values())
|
109
|
-
|
110
|
-
|
111
|
-
@property
|
112
|
-
def num_logs(self) -> int:
|
113
|
-
return len(self.log_handler_list)
|
114
|
-
|
115
|
-
|
116
|
-
@property
|
117
|
-
def file_names(self) -> list[str]:
|
118
|
-
return list(self.log_handler_map.keys())
|
119
|
-
|
120
|
-
|
121
|
-
def set_date(self,
|
122
|
-
year: Optional[int],
|
123
|
-
month: Optional[int],
|
124
|
-
day: Optional[int]) -> None:
|
125
|
-
self._year = year
|
126
|
-
self._month = month
|
127
|
-
self._day = day
|
128
|
-
self._update_log_handler_map()
|
129
|
-
|
130
|
-
|
131
|
-
def _update_log_handler_map(self) -> None:
|
132
|
-
log_files = [f for (_, _, files) in os.walk(self.logs_dir_path) for f in files]
|
133
|
-
|
134
|
-
if not log_files:
|
135
|
-
warning_message = "No logs found, setting log map to an empty dictionary"
|
136
|
-
_LOGGER.warning(warning_message)
|
137
|
-
warnings.warn(warning_message)
|
138
|
-
return
|
139
|
-
|
140
|
-
for log_file in log_files:
|
141
|
-
file_name, _ = os.path.splitext(log_file)
|
142
|
-
log_start_time, pid, process_type = file_name.split("_")
|
143
|
-
|
144
|
-
if self.process_type and process_type != self.process_type:
|
145
|
-
continue
|
146
|
-
|
147
|
-
self._log_handler_map[file_name] = LogHandler(log_start_time, pid, process_type)
|
148
|
-
|
149
|
-
self._log_handler_map = OrderedDict(sorted(self._log_handler_map.items()))
|
150
|
-
|
151
|
-
|
152
|
-
def update(self) -> None:
|
153
|
-
"""Public alias for setting log handler map"""
|
154
|
-
self._update_log_handler_map()
|
155
|
-
|
156
|
-
|
157
|
-
def __iter__(self):
|
158
|
-
yield from self.log_handler_list
|
159
|
-
|
160
|
-
|
161
|
-
def get_from_file_name(self,
|
162
|
-
file_name: str) -> LogHandler:
|
163
|
-
# auto strip the extension if present
|
164
|
-
file_name, _ = os.path.splitext(file_name)
|
165
|
-
try:
|
166
|
-
return self.log_handler_map[file_name]
|
167
|
-
except KeyError:
|
168
|
-
raise FileNotFoundError(f"Log handler for file name '{file_name}' not found in log map")
|
169
|
-
|
170
|
-
|
171
|
-
def get_from_pid(self,
|
172
|
-
pid: str) -> LogHandler:
|
173
|
-
for log_handler in self.log_handler_list:
|
174
|
-
if log_handler.pid == pid:
|
175
|
-
return log_handler
|
176
|
-
raise FileNotFoundError(f"Log handler for PID '{pid}' not found in log map")
|
@@ -1,114 +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 numpy as np
|
9
|
-
from typing import Tuple
|
10
|
-
from datetime import timedelta
|
11
|
-
|
12
|
-
import os
|
13
|
-
|
14
|
-
from spectre_core.capture_configs import CaptureConfig, PNames, CaptureModes
|
15
|
-
from spectre_core.batches import BaseBatch
|
16
|
-
from spectre_core.spectrograms import Spectrogram, time_average, frequency_average
|
17
|
-
from .._base import BaseEventHandler, make_sft_instance
|
18
|
-
from .._register import register_event_handler
|
19
|
-
|
20
|
-
|
21
|
-
def _do_stfft(iq_data: np.array,
|
22
|
-
capture_config: CaptureConfig,
|
23
|
-
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
24
|
-
"""For reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.ShortTimeFFT.html"""
|
25
|
-
|
26
|
-
sft = make_sft_instance(capture_config)
|
27
|
-
|
28
|
-
# set p0=0, since by convention in the STFFT docs, p=0 corresponds to the slice centred at t=0
|
29
|
-
p0=0
|
30
|
-
|
31
|
-
# set p1 to the index of the first slice where the "midpoint" of the window is still inside the signal
|
32
|
-
num_samples = len(iq_data)
|
33
|
-
p1 = sft.upper_border_begin(num_samples)[1]
|
34
|
-
|
35
|
-
# compute a ShortTimeFFT on the IQ samples
|
36
|
-
complex_spectra = sft.stft(iq_data,
|
37
|
-
p0 = p0,
|
38
|
-
p1 = p1)
|
39
|
-
|
40
|
-
# compute the magnitude of each spectral component
|
41
|
-
dynamic_spectra = np.abs(complex_spectra)
|
42
|
-
|
43
|
-
|
44
|
-
# assign a physical time to each spectrum
|
45
|
-
# p0 is defined to correspond with the first sample, at t=0 [s]
|
46
|
-
times = sft.t(num_samples,
|
47
|
-
p0 = p0,
|
48
|
-
p1 = p1)
|
49
|
-
# assign physical frequencies to each spectral component
|
50
|
-
frequencies = sft.f + capture_config.get_parameter_value(PNames.CENTER_FREQUENCY)
|
51
|
-
|
52
|
-
return times, frequencies, dynamic_spectra
|
53
|
-
|
54
|
-
|
55
|
-
def _build_spectrogram(batch: BaseBatch,
|
56
|
-
capture_config: CaptureConfig) -> Spectrogram:
|
57
|
-
"""Create a spectrogram by performing a Short Time FFT on the IQ samples for this batch."""
|
58
|
-
|
59
|
-
# read the data from the batch
|
60
|
-
millisecond_correction = batch.read_file("hdr")
|
61
|
-
iq_data = batch.read_file("bin")
|
62
|
-
|
63
|
-
times, frequencies, dynamic_spectra = _do_stfft(iq_data,
|
64
|
-
capture_config)
|
65
|
-
|
66
|
-
# explicitly type cast data arrays to 32-bit floats
|
67
|
-
times = np.array(times, dtype = 'float32')
|
68
|
-
frequencies = np.array(frequencies, dtype = 'float32')
|
69
|
-
dynamic_spectra = np.array(dynamic_spectra, dtype = 'float32')
|
70
|
-
|
71
|
-
# compute the start datetime for the spectrogram by adding the millisecond component to the batch start time
|
72
|
-
spectrogram_start_datetime = batch.start_datetime + timedelta(milliseconds=millisecond_correction)
|
73
|
-
return Spectrogram(dynamic_spectra,
|
74
|
-
times,
|
75
|
-
frequencies,
|
76
|
-
batch.tag,
|
77
|
-
spectrogram_start_datetime,
|
78
|
-
spectrum_type = "amplitude")
|
79
|
-
|
80
|
-
|
81
|
-
@register_event_handler(CaptureModes.FIXED_CENTER_FREQUENCY)
|
82
|
-
class _EventHandler(BaseEventHandler):
|
83
|
-
def __init__(self, *args, **kwargs):
|
84
|
-
super().__init__(*args, **kwargs)
|
85
|
-
|
86
|
-
def process(self,
|
87
|
-
absolute_file_path: str):
|
88
|
-
_LOGGER.info(f"Processing: {absolute_file_path}")
|
89
|
-
file_name = os.path.basename(absolute_file_path)
|
90
|
-
base_file_name, _ = os.path.splitext(file_name)
|
91
|
-
batch_start_time, tag = base_file_name.split('_')
|
92
|
-
|
93
|
-
# create an instance of the current batch being processed
|
94
|
-
batch = self._Batch(batch_start_time, tag)
|
95
|
-
|
96
|
-
_LOGGER.info("Creating spectrogram")
|
97
|
-
spectrogram = _build_spectrogram(batch,
|
98
|
-
self._capture_config)
|
99
|
-
|
100
|
-
spectrogram = time_average(spectrogram,
|
101
|
-
resolution = self._capture_config.get_parameter_value(PNames.TIME_RESOLUTION))
|
102
|
-
|
103
|
-
spectrogram = frequency_average(spectrogram,
|
104
|
-
resolution = self._capture_config.get_parameter_value(PNames.FREQUENCY_RESOLUTION))
|
105
|
-
|
106
|
-
self._cache_spectrogram(spectrogram)
|
107
|
-
|
108
|
-
bin_file = batch.get_file('bin')
|
109
|
-
_LOGGER.info(f"Deleting {bin_file.file_path}")
|
110
|
-
bin_file.delete()
|
111
|
-
|
112
|
-
hdr_file = batch.get_file('hdr')
|
113
|
-
_LOGGER.info(f"Deleting {hdr_file.file_path}")
|
114
|
-
hdr_file.delete()
|
@@ -1,33 +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
|
-
import sys
|
6
|
-
import signal
|
7
|
-
|
8
|
-
from gnuradio import gr
|
9
|
-
from gnuradio import spectre
|
10
|
-
|
11
|
-
from spectre_core.capture_configs import Parameters, PNames
|
12
|
-
from spectre_core.config import get_batches_dir_path
|
13
|
-
|
14
|
-
from spectre_core.capture_configs import Parameters
|
15
|
-
|
16
|
-
|
17
|
-
def capture(tag: str,
|
18
|
-
parameters: Parameters,
|
19
|
-
top_block_cls: gr.top_block,
|
20
|
-
max_noutput_items: int = 10000000):
|
21
|
-
tb: gr.top_block = top_block_cls(tag,
|
22
|
-
parameters)
|
23
|
-
|
24
|
-
def sig_handler(sig=None, frame=None):
|
25
|
-
tb.stop()
|
26
|
-
tb.wait()
|
27
|
-
sys.exit(0)
|
28
|
-
|
29
|
-
signal.signal(signal.SIGINT, sig_handler)
|
30
|
-
signal.signal(signal.SIGTERM, sig_handler)
|
31
|
-
|
32
|
-
tb.start(max_noutput_items)
|
33
|
-
tb.wait()
|
@@ -1,61 +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 typing import Optional
|
6
|
-
from dataclasses import dataclass
|
7
|
-
from functools import partial
|
8
|
-
|
9
|
-
from spectre_core.capture_configs import (
|
10
|
-
CaptureModes
|
11
|
-
)
|
12
|
-
from ..gr._rsp1a import CaptureMethods
|
13
|
-
from .._spec_names import SpecNames
|
14
|
-
from ._sdrplay_receiver import SDRPlayReceiver
|
15
|
-
from .._register import register_receiver
|
16
|
-
|
17
|
-
@dataclass
|
18
|
-
class Modes:
|
19
|
-
FIXED_CENTER_FREQUENCY = CaptureModes.FIXED_CENTER_FREQUENCY
|
20
|
-
SWEPT_CENTER_FREQUENCY = CaptureModes.SWEPT_CENTER_FREQUENCY
|
21
|
-
|
22
|
-
@register_receiver("rsp1a")
|
23
|
-
class _Receiver(SDRPlayReceiver):
|
24
|
-
def __init__(self,
|
25
|
-
name: str,
|
26
|
-
mode: Optional[str]):
|
27
|
-
super().__init__(name,
|
28
|
-
mode)
|
29
|
-
|
30
|
-
|
31
|
-
def _add_specs(self) -> None:
|
32
|
-
self.add_spec( SpecNames.SAMPLE_RATE_LOWER_BOUND, 200e3 ) # Hz
|
33
|
-
self.add_spec( SpecNames.SAMPLE_RATE_UPPER_BOUND, 10e6 ) # Hz
|
34
|
-
self.add_spec( SpecNames.FREQUENCY_LOWER_BOUND , 1e3 ) # Hz
|
35
|
-
self.add_spec( SpecNames.FREQUENCY_UPPER_BOUND , 2e9 ) # Hz
|
36
|
-
self.add_spec( SpecNames.IF_GAIN_UPPER_BOUND , -20 ) # dB
|
37
|
-
self.add_spec( SpecNames.RF_GAIN_UPPER_BOUND , 0 ) # dB
|
38
|
-
self.add_spec( SpecNames.API_RETUNING_LATENCY , 50 * 1e-3 ) # s
|
39
|
-
self.add_spec( SpecNames.BANDWIDTH_OPTIONS,
|
40
|
-
[200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000])
|
41
|
-
|
42
|
-
|
43
|
-
def _add_capture_methods(self) -> None:
|
44
|
-
self.add_capture_method(Modes.FIXED_CENTER_FREQUENCY,
|
45
|
-
CaptureMethods.fixed_center_frequency)
|
46
|
-
self.add_capture_method(Modes.SWEPT_CENTER_FREQUENCY,
|
47
|
-
CaptureMethods.swept_center_frequency)
|
48
|
-
|
49
|
-
|
50
|
-
def _add_capture_templates(self):
|
51
|
-
self.add_capture_template(Modes.FIXED_CENTER_FREQUENCY,
|
52
|
-
self._get_capture_template_fixed_center_frequency())
|
53
|
-
self.add_capture_template(Modes.SWEPT_CENTER_FREQUENCY,
|
54
|
-
self._get_capture_template_swept_center_frequency())
|
55
|
-
|
56
|
-
|
57
|
-
def _add_pvalidators(self):
|
58
|
-
self.add_pvalidator(Modes.FIXED_CENTER_FREQUENCY,
|
59
|
-
self._get_pvalidator_fixed_center_frequency())
|
60
|
-
self.add_pvalidator(Modes.SWEPT_CENTER_FREQUENCY,
|
61
|
-
self._get_pvalidator_swept_center_frequency())
|