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.
Files changed (88) hide show
  1. spectre_core/_file_io/__init__.py +1 -3
  2. spectre_core/_file_io/file_handlers.py +163 -58
  3. spectre_core/batches/__init__.py +10 -11
  4. spectre_core/batches/_base.py +170 -78
  5. spectre_core/batches/_batches.py +149 -99
  6. spectre_core/batches/_factory.py +56 -14
  7. spectre_core/batches/_register.py +23 -8
  8. spectre_core/batches/plugins/_batch_keys.py +16 -0
  9. spectre_core/batches/plugins/_callisto.py +183 -0
  10. spectre_core/batches/plugins/_iq_stream.py +354 -0
  11. spectre_core/capture_configs/__init__.py +17 -13
  12. spectre_core/capture_configs/_capture_config.py +93 -34
  13. spectre_core/capture_configs/_capture_modes.py +22 -0
  14. spectre_core/capture_configs/_capture_templates.py +207 -122
  15. spectre_core/capture_configs/_parameters.py +115 -42
  16. spectre_core/capture_configs/_pconstraints.py +86 -35
  17. spectre_core/capture_configs/_pnames.py +49 -0
  18. spectre_core/capture_configs/_ptemplates.py +389 -346
  19. spectre_core/capture_configs/_pvalidators.py +117 -73
  20. spectre_core/config/__init__.py +6 -8
  21. spectre_core/config/_paths.py +65 -25
  22. spectre_core/config/_time_formats.py +15 -10
  23. spectre_core/exceptions.py +2 -4
  24. spectre_core/jobs/__init__.py +14 -0
  25. spectre_core/jobs/_jobs.py +111 -0
  26. spectre_core/jobs/_workers.py +171 -0
  27. spectre_core/logs/__init__.py +17 -0
  28. spectre_core/logs/_configure.py +67 -0
  29. spectre_core/logs/_decorators.py +33 -0
  30. spectre_core/logs/_logs.py +228 -0
  31. spectre_core/logs/_process_types.py +14 -0
  32. spectre_core/plotting/__init__.py +4 -2
  33. spectre_core/plotting/_base.py +204 -102
  34. spectre_core/plotting/_format.py +17 -4
  35. spectre_core/plotting/_panel_names.py +18 -0
  36. spectre_core/plotting/_panel_stack.py +167 -53
  37. spectre_core/plotting/_panels.py +341 -141
  38. spectre_core/post_processing/__init__.py +8 -6
  39. spectre_core/post_processing/_base.py +70 -44
  40. spectre_core/post_processing/_factory.py +42 -12
  41. spectre_core/post_processing/_post_processor.py +24 -26
  42. spectre_core/post_processing/_register.py +22 -6
  43. spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
  44. spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
  45. spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
  46. spectre_core/py.typed +0 -0
  47. spectre_core/receivers/__init__.py +10 -7
  48. spectre_core/receivers/_base.py +220 -69
  49. spectre_core/receivers/_factory.py +53 -7
  50. spectre_core/receivers/_register.py +30 -9
  51. spectre_core/receivers/_spec_names.py +26 -15
  52. spectre_core/receivers/plugins/__init__.py +0 -0
  53. spectre_core/receivers/plugins/_receiver_names.py +16 -0
  54. spectre_core/receivers/plugins/_rsp1a.py +59 -0
  55. spectre_core/receivers/plugins/_rspduo.py +67 -0
  56. spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
  57. spectre_core/receivers/plugins/_test.py +218 -0
  58. spectre_core/receivers/plugins/gr/_base.py +80 -0
  59. spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
  60. spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
  61. spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
  62. spectre_core/spectrograms/__init__.py +5 -3
  63. spectre_core/spectrograms/_analytical.py +121 -66
  64. spectre_core/spectrograms/_array_operations.py +103 -36
  65. spectre_core/spectrograms/_spectrogram.py +380 -207
  66. spectre_core/spectrograms/_transform.py +197 -169
  67. spectre_core/wgetting/__init__.py +4 -2
  68. spectre_core/wgetting/_callisto.py +173 -118
  69. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
  70. spectre_core-0.0.13.dist-info/RECORD +75 -0
  71. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
  72. spectre_core/batches/library/_callisto.py +0 -96
  73. spectre_core/batches/library/_fixed_center_frequency.py +0 -133
  74. spectre_core/batches/library/_swept_center_frequency.py +0 -105
  75. spectre_core/logging/__init__.py +0 -11
  76. spectre_core/logging/_configure.py +0 -35
  77. spectre_core/logging/_decorators.py +0 -19
  78. spectre_core/logging/_log_handlers.py +0 -176
  79. spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
  80. spectre_core/receivers/gr/_base.py +0 -33
  81. spectre_core/receivers/library/_rsp1a.py +0 -61
  82. spectre_core/receivers/library/_rspduo.py +0 -69
  83. spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  84. spectre_core/receivers/library/_test.py +0 -221
  85. spectre_core-0.0.12.dist-info/RECORD +0 -64
  86. /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
  87. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
  88. {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")
@@ -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())