spectre-core 0.0.22__py3-none-any.whl → 0.0.24__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 +5 -0
- spectre_core/_file_io/__init__.py +4 -4
- spectre_core/_file_io/file_handlers.py +60 -106
- spectre_core/batches/__init__.py +20 -3
- spectre_core/batches/_base.py +85 -134
- spectre_core/batches/_batches.py +55 -99
- spectre_core/batches/_factory.py +21 -20
- spectre_core/batches/_register.py +8 -8
- spectre_core/batches/plugins/_batch_keys.py +7 -6
- spectre_core/batches/plugins/_callisto.py +65 -97
- spectre_core/batches/plugins/_iq_stream.py +105 -169
- spectre_core/capture_configs/__init__.py +46 -17
- spectre_core/capture_configs/_capture_config.py +25 -52
- spectre_core/capture_configs/_capture_modes.py +8 -6
- spectre_core/capture_configs/_capture_templates.py +50 -110
- spectre_core/capture_configs/_parameters.py +37 -74
- spectre_core/capture_configs/_pconstraints.py +40 -40
- spectre_core/capture_configs/_pnames.py +36 -34
- spectre_core/capture_configs/_ptemplates.py +260 -347
- spectre_core/capture_configs/_pvalidators.py +99 -102
- spectre_core/config/__init__.py +19 -8
- spectre_core/config/_paths.py +25 -47
- spectre_core/config/_time_formats.py +6 -5
- spectre_core/exceptions.py +38 -0
- spectre_core/jobs/__init__.py +3 -6
- spectre_core/jobs/_duration.py +12 -0
- spectre_core/jobs/_jobs.py +72 -43
- spectre_core/jobs/_workers.py +55 -105
- spectre_core/logs/__init__.py +7 -2
- spectre_core/logs/_configure.py +13 -17
- spectre_core/logs/_decorators.py +6 -4
- spectre_core/logs/_logs.py +37 -89
- spectre_core/logs/_process_types.py +5 -3
- spectre_core/plotting/__init__.py +19 -3
- spectre_core/plotting/_base.py +112 -177
- spectre_core/plotting/_format.py +10 -8
- spectre_core/plotting/_panel_names.py +7 -5
- spectre_core/plotting/_panel_stack.py +138 -130
- spectre_core/plotting/_panels.py +152 -162
- spectre_core/post_processing/__init__.py +6 -3
- spectre_core/post_processing/_base.py +41 -55
- spectre_core/post_processing/_factory.py +14 -11
- spectre_core/post_processing/_post_processor.py +16 -12
- spectre_core/post_processing/_register.py +10 -7
- spectre_core/post_processing/plugins/_event_handler_keys.py +4 -3
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +54 -47
- spectre_core/post_processing/plugins/_swept_center_frequency.py +199 -174
- spectre_core/receivers/__init__.py +9 -2
- spectre_core/receivers/_base.py +82 -148
- spectre_core/receivers/_factory.py +20 -30
- spectre_core/receivers/_register.py +7 -10
- spectre_core/receivers/_spec_names.py +17 -15
- spectre_core/receivers/plugins/_b200mini.py +47 -60
- spectre_core/receivers/plugins/_receiver_names.py +8 -6
- spectre_core/receivers/plugins/_rsp1a.py +44 -40
- spectre_core/receivers/plugins/_rspduo.py +59 -44
- spectre_core/receivers/plugins/_sdrplay_receiver.py +67 -83
- spectre_core/receivers/plugins/_test.py +136 -129
- spectre_core/receivers/plugins/_usrp.py +93 -85
- spectre_core/receivers/plugins/gr/__init__.py +1 -1
- spectre_core/receivers/plugins/gr/_base.py +14 -22
- spectre_core/receivers/plugins/gr/_rsp1a.py +53 -60
- spectre_core/receivers/plugins/gr/_rspduo.py +77 -89
- spectre_core/receivers/plugins/gr/_test.py +49 -57
- spectre_core/receivers/plugins/gr/_usrp.py +61 -59
- spectre_core/spectrograms/__init__.py +21 -13
- spectre_core/spectrograms/_analytical.py +108 -99
- spectre_core/spectrograms/_array_operations.py +39 -46
- spectre_core/spectrograms/_spectrogram.py +293 -324
- spectre_core/spectrograms/_transform.py +106 -73
- spectre_core/wgetting/__init__.py +1 -3
- spectre_core/wgetting/_callisto.py +87 -93
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/METADATA +9 -23
- spectre_core-0.0.24.dist-info/RECORD +79 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/WHEEL +1 -1
- spectre_core-0.0.22.dist-info/RECORD +0 -78
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/licenses/LICENSE +0 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/top_level.txt +0 -0
spectre_core/batches/_base.py
CHANGED
@@ -13,21 +13,40 @@ from os.path import splitext
|
|
13
13
|
from spectre_core._file_io import BaseFileHandler
|
14
14
|
from spectre_core.config import get_batches_dir_path, TimeFormat
|
15
15
|
from spectre_core.spectrograms import Spectrogram
|
16
|
+
from spectre_core.exceptions import deprecated
|
16
17
|
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
@deprecated(
|
20
|
+
"The terminology (base file name) is inconsistent with the `_file_io` submodule. "
|
21
|
+
f"Please use `parse_batch_file_name` instead."
|
22
|
+
)
|
23
|
+
def parse_batch_base_file_name(base_file_name: str) -> Tuple[str, str, str]:
|
24
|
+
"""Included for backwards compatability - please use `parse_batch_file_name` instead."""
|
25
|
+
return parse_batch_file_name(base_file_name)
|
26
|
+
|
27
|
+
|
28
|
+
def parse_batch_file_name(file_name: str) -> Tuple[str, str, str]:
|
21
29
|
"""Parse the base file name of a batch file into a start time, tag and extension.
|
30
|
+
|
31
|
+
:param file_name: The file name of the batch file, with optional extension.
|
32
|
+
:return: The file name decomposed into its start time, tag and extension. If no extension is present,
|
33
|
+
the final element of the tuple will be an empty string.
|
22
34
|
"""
|
23
|
-
batch_name, extension = splitext(
|
35
|
+
batch_name, extension = splitext(file_name)
|
36
|
+
|
37
|
+
num_underscores = batch_name.count("_")
|
38
|
+
if num_underscores != 1:
|
39
|
+
raise ValueError(
|
40
|
+
f"Expected exactly one underscore in the batch name '{batch_name}'. Found {num_underscores}"
|
41
|
+
)
|
42
|
+
|
24
43
|
# strip the dot from the extension
|
25
|
-
extension = extension.lstrip(
|
44
|
+
extension = extension.lstrip(".")
|
26
45
|
start_time, tag = batch_name.split("_", 1)
|
27
46
|
return start_time, tag, extension
|
28
47
|
|
29
48
|
|
30
|
-
T = TypeVar(
|
49
|
+
T = TypeVar("T")
|
31
50
|
|
32
51
|
|
33
52
|
class BatchFile(BaseFileHandler[T]):
|
@@ -37,14 +56,12 @@ class BatchFile(BaseFileHandler[T]):
|
|
37
56
|
|
38
57
|
`<start time>_<tag>.<extension>`
|
39
58
|
|
40
|
-
The substring `<start time>_<tag>` is referred to as the batch name. Files with the same batch name
|
59
|
+
The substring `<start time>_<tag>` is referred to as the batch name. Files with the same batch name
|
41
60
|
belong to the same batch.
|
42
61
|
"""
|
62
|
+
|
43
63
|
def __init__(
|
44
|
-
self,
|
45
|
-
batch_parent_dir_path: str,
|
46
|
-
batch_name: str,
|
47
|
-
extension: str
|
64
|
+
self, batch_parent_dir_path: str, batch_name: str, extension: str
|
48
65
|
) -> None:
|
49
66
|
"""Initialise a `BatchFile` instance.
|
50
67
|
|
@@ -52,52 +69,39 @@ class BatchFile(BaseFileHandler[T]):
|
|
52
69
|
:param batch_name: Base file name, composed of the batch start time and tag.
|
53
70
|
:param extension: File extension.
|
54
71
|
"""
|
55
|
-
super().__init__(batch_parent_dir_path,
|
56
|
-
batch_name,
|
57
|
-
extension)
|
72
|
+
super().__init__(batch_parent_dir_path, batch_name, extension)
|
58
73
|
self._start_time, self._tag = batch_name.split("_")
|
59
|
-
|
60
|
-
|
74
|
+
|
61
75
|
@property
|
62
|
-
def start_time(
|
63
|
-
self
|
64
|
-
) -> str:
|
76
|
+
def start_time(self) -> str:
|
65
77
|
"""The start time of the batch, formatted as a string up to seconds precision."""
|
66
78
|
return self._start_time
|
67
79
|
|
68
|
-
|
69
80
|
@cached_property
|
70
|
-
def start_datetime(
|
71
|
-
self
|
72
|
-
) -> datetime:
|
81
|
+
def start_datetime(self) -> datetime:
|
73
82
|
"""The start time of the batch, parsed as a datetime up to seconds precision."""
|
74
83
|
return datetime.strptime(self.start_time, TimeFormat.DATETIME)
|
75
84
|
|
76
|
-
|
77
85
|
@property
|
78
|
-
def tag(
|
79
|
-
self
|
80
|
-
) -> str:
|
86
|
+
def tag(self) -> str:
|
81
87
|
"""The batch name tag."""
|
82
88
|
return self._tag
|
83
|
-
|
84
|
-
|
89
|
+
|
90
|
+
|
85
91
|
@dataclass(frozen=True)
|
86
92
|
class _BatchExtension:
|
87
93
|
"""Supported extensions for a `BaseBatch`, inherited by all derived classes.
|
88
|
-
|
94
|
+
|
89
95
|
:ivar PNG: Corresponds to the `.png` file extension.
|
90
96
|
"""
|
97
|
+
|
91
98
|
PNG: str = "png"
|
92
|
-
|
93
|
-
|
99
|
+
|
100
|
+
|
94
101
|
class _PngFile(BatchFile[str]):
|
95
102
|
"""Stores an image visualising the data for the batch."""
|
96
|
-
|
97
|
-
|
98
|
-
batch_parent_dir_path: str,
|
99
|
-
batch_name: str
|
100
|
-
) -> None:
|
103
|
+
|
104
|
+
def __init__(self, batch_parent_dir_path: str, batch_name: str) -> None:
|
101
105
|
"""Initialise a `_PngFile` instance.
|
102
106
|
|
103
107
|
:param batch_parent_dir_path: The parent directory for the batch.
|
@@ -105,7 +109,6 @@ class _PngFile(BatchFile[str]):
|
|
105
109
|
"""
|
106
110
|
super().__init__(batch_parent_dir_path, batch_name, _BatchExtension.PNG)
|
107
111
|
|
108
|
-
|
109
112
|
def _read(self) -> str:
|
110
113
|
"""Reads the PNG file and returns it base64-encoded.
|
111
114
|
|
@@ -114,25 +117,22 @@ class _PngFile(BatchFile[str]):
|
|
114
117
|
with open(self.file_path, "rb") as f:
|
115
118
|
encoded = b64encode(f.read())
|
116
119
|
return encoded.decode("ascii")
|
117
|
-
|
118
|
-
|
120
|
+
|
121
|
+
|
119
122
|
class BaseBatch(ABC):
|
120
123
|
"""
|
121
124
|
An abstract base class representing a group of data files over a common time interval.
|
122
125
|
|
123
126
|
All files in a batch share a base file name and differ only by their extension.
|
124
|
-
Subclasses of `BaseBatch` define the expected data for each file extension and
|
127
|
+
Subclasses of `BaseBatch` define the expected data for each file extension and
|
125
128
|
provide an API for accessing their contents using `BatchFile` subclasses.
|
126
129
|
|
127
130
|
Subclasses should expose `BatchFile` instances directly as attributes, which
|
128
131
|
simplifies static typing. Additionally, they should call `add_file` in the constructor
|
129
132
|
to formally register each `BatchFile`.
|
130
133
|
"""
|
131
|
-
|
132
|
-
|
133
|
-
start_time: str,
|
134
|
-
tag: str
|
135
|
-
) -> None:
|
134
|
+
|
135
|
+
def __init__(self, start_time: str, tag: str) -> None:
|
136
136
|
"""Initialise a `BaseBatch` instance.
|
137
137
|
|
138
138
|
:param start_time: Start time of the batch as a string with seconds precision.
|
@@ -141,113 +141,82 @@ class BaseBatch(ABC):
|
|
141
141
|
self._start_time = start_time
|
142
142
|
self._tag: str = tag
|
143
143
|
self._start_datetime = datetime.strptime(self.start_time, TimeFormat.DATETIME)
|
144
|
-
self._parent_dir_path = get_batches_dir_path(
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
self._parent_dir_path = get_batches_dir_path(
|
145
|
+
year=self.start_datetime.year,
|
146
|
+
month=self.start_datetime.month,
|
147
|
+
day=self.start_datetime.day,
|
148
|
+
)
|
149
|
+
|
148
150
|
# internal register of batch files
|
149
151
|
self._batch_files: dict[str, BatchFile] = {}
|
150
|
-
|
152
|
+
|
151
153
|
# Add the files shared by all derived classes
|
152
|
-
self._png_file
|
153
|
-
self.add_file(
|
154
|
-
|
155
|
-
|
154
|
+
self._png_file = _PngFile(self.parent_dir_path, self.name)
|
155
|
+
self.add_file(self._png_file)
|
156
|
+
|
156
157
|
@property
|
157
158
|
@abstractmethod
|
158
|
-
def spectrogram_file(
|
159
|
-
self
|
160
|
-
) -> BatchFile:
|
159
|
+
def spectrogram_file(self) -> BatchFile:
|
161
160
|
"""The batch file which contains spectrogram data."""
|
162
|
-
|
163
|
-
|
161
|
+
|
164
162
|
@property
|
165
|
-
def start_time(
|
166
|
-
self
|
167
|
-
) -> str:
|
163
|
+
def start_time(self) -> str:
|
168
164
|
"""The start time of the batch, formatted as a string up to seconds precision."""
|
169
165
|
return self._start_time
|
170
166
|
|
171
|
-
|
172
167
|
@property
|
173
|
-
def start_datetime(
|
174
|
-
self
|
175
|
-
) -> datetime:
|
168
|
+
def start_datetime(self) -> datetime:
|
176
169
|
"""The start time of the batch, parsed as a datetime up to seconds precision."""
|
177
170
|
return self._start_datetime
|
178
|
-
|
179
|
-
|
171
|
+
|
180
172
|
@property
|
181
|
-
def tag(
|
182
|
-
self
|
183
|
-
) -> str:
|
173
|
+
def tag(self) -> str:
|
184
174
|
"""The batch name tag."""
|
185
175
|
return self._tag
|
186
|
-
|
187
176
|
|
188
177
|
@property
|
189
|
-
def parent_dir_path(
|
190
|
-
self
|
191
|
-
) -> str:
|
178
|
+
def parent_dir_path(self) -> str:
|
192
179
|
"""The parent directory for the batch."""
|
193
180
|
return self._parent_dir_path
|
194
181
|
|
195
|
-
|
196
182
|
@property
|
197
|
-
def name(
|
198
|
-
|
199
|
-
) -> str:
|
200
|
-
"""Return the base file name shared by all files in the batch,
|
183
|
+
def name(self) -> str:
|
184
|
+
"""Return the base file name shared by all files in the batch,
|
201
185
|
composed of the start time and the batch tag."""
|
202
186
|
return f"{self._start_time}_{self._tag}"
|
203
|
-
|
204
|
-
|
187
|
+
|
205
188
|
@property
|
206
|
-
def extensions(
|
207
|
-
self
|
208
|
-
) -> list[str]:
|
189
|
+
def extensions(self) -> list[str]:
|
209
190
|
"""All defined file extensions for the batch."""
|
210
191
|
return list(self._batch_files.keys())
|
211
|
-
|
212
|
-
|
192
|
+
|
213
193
|
@property
|
214
|
-
def batch_files(
|
215
|
-
self
|
216
|
-
) -> dict[str, BatchFile]:
|
194
|
+
def batch_files(self) -> dict[str, BatchFile]:
|
217
195
|
"""Map each file extension in the batch to the corresponding batch file instance.
|
218
|
-
|
196
|
+
|
219
197
|
Use `add_file` to add a file to the batch.
|
220
198
|
"""
|
221
199
|
return self._batch_files
|
222
|
-
|
223
|
-
|
200
|
+
|
224
201
|
@property
|
225
|
-
def png_file(
|
226
|
-
self
|
227
|
-
) -> _PngFile:
|
202
|
+
def png_file(self) -> _PngFile:
|
228
203
|
"""The batch file corresponding to the `.png` extension."""
|
229
204
|
return self._png_file
|
230
|
-
|
231
205
|
|
232
|
-
def add_file(
|
233
|
-
self,
|
234
|
-
batch_file: BatchFile
|
235
|
-
) -> None:
|
206
|
+
def add_file(self, batch_file: BatchFile) -> None:
|
236
207
|
"""Add an instance of a batch file to the batch.
|
237
208
|
|
238
209
|
:param batch_file: The `BatchFile` instance to add to the batch.
|
239
210
|
:raises ValueError: If the `BatchFile` instance does not have a defined file extension.
|
240
211
|
"""
|
241
212
|
if batch_file.extension is None:
|
242
|
-
raise ValueError(
|
243
|
-
|
213
|
+
raise ValueError(
|
214
|
+
f"The `BatchFile` must have a defined file extension. "
|
215
|
+
f"Received '{batch_file.extension}."
|
216
|
+
)
|
244
217
|
self._batch_files[batch_file.extension] = batch_file
|
245
|
-
|
246
218
|
|
247
|
-
def get_file(
|
248
|
-
self,
|
249
|
-
extension: str
|
250
|
-
) -> BatchFile:
|
219
|
+
def get_file(self, extension: str) -> BatchFile:
|
251
220
|
"""Get a batch file instance from the batch, according to the file extension.
|
252
221
|
|
253
222
|
:param extension: The file extension of the batch file.
|
@@ -257,13 +226,11 @@ class BaseBatch(ABC):
|
|
257
226
|
try:
|
258
227
|
return self._batch_files[extension]
|
259
228
|
except KeyError:
|
260
|
-
raise NotImplementedError(
|
261
|
-
|
229
|
+
raise NotImplementedError(
|
230
|
+
f"A batch file with extension '{extension}' is not implemented for this batch."
|
231
|
+
)
|
262
232
|
|
263
|
-
def delete_file(
|
264
|
-
self,
|
265
|
-
extension: str
|
266
|
-
) -> None:
|
233
|
+
def delete_file(self, extension: str) -> None:
|
267
234
|
"""Delete a file from the batch, according to the file extension.
|
268
235
|
|
269
236
|
:param extension: The file extension of the batch file.
|
@@ -272,11 +239,7 @@ class BaseBatch(ABC):
|
|
272
239
|
batch_file = self.get_file(extension)
|
273
240
|
batch_file.delete()
|
274
241
|
|
275
|
-
|
276
|
-
def has_file(
|
277
|
-
self,
|
278
|
-
extension: str
|
279
|
-
) -> bool:
|
242
|
+
def has_file(self, extension: str) -> bool:
|
280
243
|
"""Determine the existance of a batch file in the file system.
|
281
244
|
|
282
245
|
:param extension: The file extension of the batch file.
|
@@ -287,22 +250,10 @@ class BaseBatch(ABC):
|
|
287
250
|
return batch_file.exists
|
288
251
|
except FileNotFoundError:
|
289
252
|
return False
|
290
|
-
|
291
|
-
|
292
|
-
def read_spectrogram(
|
293
|
-
self
|
294
|
-
) -> Spectrogram:
|
253
|
+
|
254
|
+
def read_spectrogram(self) -> Spectrogram:
|
295
255
|
"""Read and return the spectrogram data stored in the batch.
|
296
256
|
|
297
257
|
:return: The spectrogram stored by the batch `spectrogram_file`.
|
298
258
|
"""
|
299
259
|
return self.spectrogram_file.read()
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
spectre_core/batches/_batches.py
CHANGED
@@ -10,22 +10,23 @@ from datetime import datetime
|
|
10
10
|
from spectre_core.config import TimeFormat
|
11
11
|
from spectre_core.spectrograms import Spectrogram, time_chop, join_spectrograms
|
12
12
|
from spectre_core.config import get_batches_dir_path
|
13
|
-
from spectre_core.exceptions import
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
from spectre_core.exceptions import BatchNotFoundError
|
14
|
+
from ._base import BaseBatch, parse_batch_file_name
|
15
|
+
|
16
|
+
T = TypeVar("T", bound=BaseBatch)
|
17
|
+
|
17
18
|
|
18
|
-
T = TypeVar('T', bound=BaseBatch)
|
19
19
|
class Batches(Generic[T]):
|
20
20
|
"""Managed collection of `Batch` instances for a given tag. Provides a simple
|
21
21
|
interface for read operations on batched data files."""
|
22
|
+
|
22
23
|
def __init__(
|
23
|
-
self,
|
24
|
+
self,
|
24
25
|
tag: str,
|
25
26
|
batch_cls: Type[T],
|
26
|
-
year: Optional[int] = None,
|
27
|
-
month: Optional[int] = None,
|
28
|
-
day: Optional[int] = None
|
27
|
+
year: Optional[int] = None,
|
28
|
+
month: Optional[int] = None,
|
29
|
+
day: Optional[int] = None,
|
29
30
|
) -> None:
|
30
31
|
"""Initialise a `Batches` instance.
|
31
32
|
|
@@ -40,85 +41,54 @@ class Batches(Generic[T]):
|
|
40
41
|
self._batch_map: dict[str, T] = OrderedDict()
|
41
42
|
self.set_date(year, month, day)
|
42
43
|
|
43
|
-
|
44
44
|
@property
|
45
|
-
def tag(
|
46
|
-
self
|
47
|
-
) -> str:
|
45
|
+
def tag(self) -> str:
|
48
46
|
"""The batch name tag."""
|
49
47
|
return self._tag
|
50
48
|
|
51
|
-
|
52
49
|
@property
|
53
|
-
def batch_cls(
|
54
|
-
self
|
55
|
-
) -> Type[T]:
|
50
|
+
def batch_cls(self) -> Type[T]:
|
56
51
|
"""The `Batch` class used to read the batched files."""
|
57
52
|
return self._batch_cls
|
58
53
|
|
59
|
-
|
60
54
|
@property
|
61
|
-
def year(
|
62
|
-
self
|
63
|
-
) -> Optional[int]:
|
55
|
+
def year(self) -> Optional[int]:
|
64
56
|
"""The numeric year, to filter batch files."""
|
65
57
|
return self._year
|
66
58
|
|
67
|
-
|
68
|
-
|
69
|
-
def month(
|
70
|
-
self
|
71
|
-
) -> Optional[int]:
|
59
|
+
@property
|
60
|
+
def month(self) -> Optional[int]:
|
72
61
|
"""The numeric month of the year, to filter batch files."""
|
73
62
|
return self._month
|
74
|
-
|
75
63
|
|
76
64
|
@property
|
77
|
-
def day(
|
78
|
-
self
|
79
|
-
) -> Optional[int]:
|
65
|
+
def day(self) -> Optional[int]:
|
80
66
|
"""The numeric day of the year, to filter batch files."""
|
81
67
|
return self._day
|
82
|
-
|
83
68
|
|
84
69
|
@property
|
85
|
-
def batches_dir_path(
|
86
|
-
self
|
87
|
-
) -> str:
|
70
|
+
def batches_dir_path(self) -> str:
|
88
71
|
"""The shared ancestral path for all the batches. `Batches` recursively searches
|
89
72
|
this directory to find all batches whose batch name contains `tag`."""
|
90
73
|
return get_batches_dir_path(self.year, self.month, self.day)
|
91
|
-
|
92
74
|
|
93
75
|
@property
|
94
|
-
def batch_list(
|
95
|
-
self
|
96
|
-
) -> list[T]:
|
76
|
+
def batch_list(self) -> list[T]:
|
97
77
|
"""A list of all batches found within `batches_dir_path`."""
|
98
|
-
return
|
99
|
-
|
78
|
+
return list(self._batch_map.values())
|
100
79
|
|
101
80
|
@property
|
102
|
-
def start_times(
|
103
|
-
self
|
104
|
-
) -> list[str]:
|
81
|
+
def start_times(self) -> list[str]:
|
105
82
|
"""The start times of each batch found within `batches_dir_path`."""
|
106
83
|
return list(self._batch_map.keys())
|
107
84
|
|
108
|
-
|
109
85
|
@property
|
110
|
-
def num_batches(
|
111
|
-
self
|
112
|
-
) -> int:
|
86
|
+
def num_batches(self) -> int:
|
113
87
|
"""The total number of batches found within `batches_dir_path`."""
|
114
88
|
return len(self.batch_list)
|
115
89
|
|
116
|
-
|
117
90
|
def set_date(
|
118
|
-
self,
|
119
|
-
year: Optional[int],
|
120
|
-
month: Optional[int],
|
121
|
-
day: Optional[int]
|
91
|
+
self, year: Optional[int], month: Optional[int], day: Optional[int]
|
122
92
|
) -> None:
|
123
93
|
"""Reset `batches_dir_path` according to the numeric date, and refresh the list
|
124
94
|
of available batches.
|
@@ -132,72 +102,57 @@ class Batches(Generic[T]):
|
|
132
102
|
self._day = day
|
133
103
|
self.update()
|
134
104
|
|
135
|
-
|
136
|
-
|
137
|
-
self
|
138
|
-
) -> None:
|
139
|
-
"""Perform a fresh search all files in `batches_dir_path` for batches
|
105
|
+
def update(self) -> None:
|
106
|
+
"""Perform a fresh search all files in `batches_dir_path` for batches
|
140
107
|
with `tag` in the batch name."""
|
141
108
|
# reset cache
|
142
|
-
self._batch_map = OrderedDict()
|
143
|
-
|
109
|
+
self._batch_map = OrderedDict()
|
110
|
+
|
144
111
|
# get a list of all batch file names in the batches directory path
|
145
|
-
batch_file_names = [
|
112
|
+
batch_file_names = [
|
113
|
+
f for (_, _, files) in os.walk(self.batches_dir_path) for f in files
|
114
|
+
]
|
146
115
|
for batch_file_name in batch_file_names:
|
147
|
-
start_time, tag, _ =
|
116
|
+
start_time, tag, _ = parse_batch_file_name(batch_file_name)
|
148
117
|
if tag == self._tag:
|
149
118
|
self._batch_map[start_time] = self.batch_cls(start_time, tag)
|
150
|
-
|
119
|
+
|
151
120
|
self._batch_map = OrderedDict(sorted(self._batch_map.items()))
|
152
|
-
|
153
121
|
|
154
|
-
def __iter__(
|
155
|
-
self
|
156
|
-
) -> Iterator[T]:
|
122
|
+
def __iter__(self) -> Iterator[T]:
|
157
123
|
"""Iterate over the stored batch instances."""
|
158
124
|
yield from self.batch_list
|
159
|
-
|
160
|
-
|
161
|
-
def __len__(
|
162
|
-
self
|
163
|
-
):
|
164
|
-
return self.num_batches
|
165
125
|
|
126
|
+
def __len__(self):
|
127
|
+
return self.num_batches
|
166
128
|
|
167
|
-
def _get_from_start_time(
|
168
|
-
self,
|
169
|
-
start_time: str
|
170
|
-
) -> T:
|
129
|
+
def _get_from_start_time(self, start_time: str) -> T:
|
171
130
|
"""Find and return the `Batch` instance based on the string formatted start time."""
|
172
131
|
try:
|
173
132
|
return self._batch_map[start_time]
|
174
133
|
except KeyError:
|
175
|
-
raise BatchNotFoundError(
|
134
|
+
raise BatchNotFoundError(
|
135
|
+
f"Batch with start time {start_time} could not be found within {self.batches_dir_path}"
|
136
|
+
)
|
176
137
|
|
177
|
-
|
178
|
-
def _get_from_index(
|
179
|
-
self,
|
180
|
-
index: int
|
181
|
-
) -> T:
|
138
|
+
def _get_from_index(self, index: int) -> T:
|
182
139
|
"""Find and return the `Batch` instance based on its numeric index.
|
183
|
-
|
140
|
+
|
184
141
|
Batches are ordered sequentially in time, so index `0` corresponds to the oldest
|
185
142
|
`Batch` with respect to the start time.
|
186
143
|
"""
|
187
144
|
if self.num_batches == 0:
|
188
145
|
raise BatchNotFoundError("No batches are available")
|
189
146
|
elif index > self.num_batches:
|
190
|
-
raise IndexError(
|
147
|
+
raise IndexError(
|
148
|
+
f"Index '{index}' is greater than the number of batches '{self.num_batches}'"
|
149
|
+
)
|
191
150
|
return self.batch_list[index]
|
192
151
|
|
193
|
-
|
194
|
-
def __getitem__(
|
195
|
-
self,
|
196
|
-
subscript: str | int
|
197
|
-
) -> T:
|
152
|
+
def __getitem__(self, subscript: str | int) -> T:
|
198
153
|
"""Get a `Batch` instanced based on either the start time or chronological index.
|
199
154
|
|
200
|
-
:param subscript: If the subscript is a string, interpreted as a formatted start time.
|
155
|
+
:param subscript: If the subscript is a string, interpreted as a formatted start time.
|
201
156
|
If the subscript is an integer, it is interpreted as a chronological index.
|
202
157
|
:return: The corresponding `BaseBatch` subclass.
|
203
158
|
"""
|
@@ -205,22 +160,19 @@ class Batches(Generic[T]):
|
|
205
160
|
return self._get_from_start_time(subscript)
|
206
161
|
elif isinstance(subscript, int):
|
207
162
|
return self._get_from_index(subscript)
|
208
|
-
|
209
163
|
|
210
164
|
def get_spectrogram(
|
211
|
-
self,
|
212
|
-
start_datetime: datetime,
|
213
|
-
end_datetime: datetime
|
165
|
+
self, start_datetime: datetime, end_datetime: datetime
|
214
166
|
) -> Spectrogram:
|
215
167
|
"""
|
216
|
-
Retrieve a spectrogram spanning the specified time range.
|
168
|
+
Retrieve a spectrogram spanning the specified time range.
|
217
169
|
|
218
170
|
:param start_datetime: The start time of the range (inclusive).
|
219
171
|
:param end_datetime: The end time of the range (inclusive).
|
220
172
|
:raises FileNotFoundError: If no spectrogram data is available within the specified time range.
|
221
173
|
:return: A spectrogram created by stitching together data from all matching batches.
|
222
174
|
"""
|
223
|
-
|
175
|
+
|
224
176
|
spectrograms = []
|
225
177
|
for batch in self:
|
226
178
|
# skip batches without spectrogram data
|
@@ -233,10 +185,14 @@ class Batches(Generic[T]):
|
|
233
185
|
|
234
186
|
# Check if the batch overlaps with the input time range
|
235
187
|
if start_datetime <= upper_bound and lower_bound <= end_datetime:
|
236
|
-
spectrograms.append(
|
188
|
+
spectrograms.append(
|
189
|
+
time_chop(spectrogram, start_datetime, end_datetime)
|
190
|
+
)
|
237
191
|
|
238
192
|
if spectrograms:
|
239
193
|
return join_spectrograms(spectrograms)
|
240
194
|
else:
|
241
|
-
raise FileNotFoundError(
|
242
|
-
|
195
|
+
raise FileNotFoundError(
|
196
|
+
f"No spectrogram data found for the time range "
|
197
|
+
f"{start_datetime} to {end_datetime}."
|
198
|
+
)
|