spectre-core 0.0.22__py3-none-any.whl → 0.0.23__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 +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 +13 -8
- spectre_core/config/_paths.py +18 -35
- 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 +13 -3
- spectre_core/plotting/_base.py +64 -138
- spectre_core/plotting/_format.py +10 -8
- spectre_core/plotting/_panel_names.py +7 -5
- spectre_core/plotting/_panel_stack.py +82 -115
- spectre_core/plotting/_panels.py +120 -155
- 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 +289 -322
- 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.23.dist-info}/METADATA +9 -23
- spectre_core-0.0.23.dist-info/RECORD +79 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.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.23.dist-info}/licenses/LICENSE +0 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.dist-info}/top_level.txt +0 -0
@@ -5,9 +5,9 @@
|
|
5
5
|
"""Basic internal file handling capabilities."""
|
6
6
|
|
7
7
|
from .file_handlers import (
|
8
|
-
BaseFileHandler,
|
8
|
+
BaseFileHandler,
|
9
|
+
JsonHandler,
|
10
|
+
TextHandler,
|
9
11
|
)
|
10
12
|
|
11
|
-
__all__ = [
|
12
|
-
"BaseFileHandler", "JsonHandler", "TextHandler"
|
13
|
-
]
|
13
|
+
__all__ = ["BaseFileHandler", "JsonHandler", "TextHandler"]
|
@@ -7,14 +7,15 @@ import json
|
|
7
7
|
from abc import ABC, abstractmethod
|
8
8
|
from typing import Any, Optional, TypeVar, Generic
|
9
9
|
|
10
|
-
T = TypeVar(
|
10
|
+
T = TypeVar("T")
|
11
|
+
|
11
12
|
|
12
13
|
class BaseFileHandler(ABC, Generic[T]):
|
13
14
|
"""
|
14
15
|
Base class for handling file operations.
|
15
16
|
|
16
|
-
When subclassing, specify the return type of `_read`
|
17
|
-
using `Generic[T]`.
|
17
|
+
When subclassing, specify the return type of `_read`
|
18
|
+
using `Generic[T]`.
|
18
19
|
|
19
20
|
Example:
|
20
21
|
.. code-block:: python
|
@@ -26,11 +27,9 @@ class BaseFileHandler(ABC, Generic[T]):
|
|
26
27
|
# Implementation here
|
27
28
|
...
|
28
29
|
"""
|
30
|
+
|
29
31
|
def __init__(
|
30
|
-
self,
|
31
|
-
parent_dir_path: str,
|
32
|
-
base_file_name: str,
|
33
|
-
extension: Optional[str] = None
|
32
|
+
self, parent_dir_path: str, base_file_name: str, extension: Optional[str] = None
|
34
33
|
) -> None:
|
35
34
|
"""Initialise a `BaseFileHandler` instance.
|
36
35
|
|
@@ -39,130 +38,101 @@ class BaseFileHandler(ABC, Generic[T]):
|
|
39
38
|
:param extension: The file extension (without the dot), defaults to None
|
40
39
|
"""
|
41
40
|
self._data_cache: Optional[T] = None
|
42
|
-
|
41
|
+
|
43
42
|
self._parent_dir_path = parent_dir_path
|
44
|
-
self._base_file_name
|
45
|
-
|
43
|
+
self._base_file_name = base_file_name
|
44
|
+
|
46
45
|
if extension == "":
|
47
46
|
extension = None
|
48
47
|
self._extension = extension
|
49
48
|
|
50
|
-
|
51
49
|
@abstractmethod
|
52
|
-
def _read(
|
53
|
-
self
|
54
|
-
) -> T:
|
50
|
+
def _read(self) -> T:
|
55
51
|
"""The grunt work to return the file contents.
|
56
52
|
|
57
53
|
:return: The file contents.
|
58
54
|
"""
|
59
|
-
|
60
|
-
|
55
|
+
|
61
56
|
@property
|
62
|
-
def parent_dir_path(
|
63
|
-
self
|
64
|
-
) -> str:
|
57
|
+
def parent_dir_path(self) -> str:
|
65
58
|
"""Return the parent directory path for the file."""
|
66
59
|
return self._parent_dir_path
|
67
|
-
|
68
60
|
|
69
61
|
@property
|
70
|
-
def base_file_name(
|
71
|
-
self
|
72
|
-
) -> str:
|
62
|
+
def base_file_name(self) -> str:
|
73
63
|
"""Return the file name, stripped of the file extension."""
|
74
64
|
return self._base_file_name
|
75
|
-
|
76
65
|
|
77
66
|
@property
|
78
|
-
def extension(
|
79
|
-
self
|
80
|
-
) -> Optional[str]:
|
67
|
+
def extension(self) -> Optional[str]:
|
81
68
|
"""Return the file path suffix, excluding the dot."""
|
82
69
|
return self._extension
|
83
|
-
|
84
70
|
|
85
71
|
@property
|
86
|
-
def file_name(
|
87
|
-
self
|
88
|
-
) -> str:
|
72
|
+
def file_name(self) -> str:
|
89
73
|
"""Generate the file name based on the base name and extension.
|
90
74
|
|
91
75
|
:return: The file name with the extension (including the dot), or the base name if no extension is set.
|
92
76
|
"""
|
93
|
-
return
|
94
|
-
|
77
|
+
return (
|
78
|
+
self._base_file_name
|
79
|
+
if (self._extension is None)
|
80
|
+
else f"{self._base_file_name}.{self._extension}"
|
81
|
+
)
|
95
82
|
|
96
83
|
@property
|
97
|
-
def file_path(
|
98
|
-
self
|
99
|
-
) -> str:
|
84
|
+
def file_path(self) -> str:
|
100
85
|
"""The absolute or relative file path as defined by the parent directory path,
|
101
86
|
base file name and extension."""
|
102
87
|
return os.path.join(self._parent_dir_path, self.file_name)
|
103
|
-
|
104
|
-
|
88
|
+
|
105
89
|
@property
|
106
|
-
def exists(
|
107
|
-
self
|
108
|
-
) -> bool:
|
90
|
+
def exists(self) -> bool:
|
109
91
|
"""Check if the file exists in the filesystem."""
|
110
|
-
return os.path.exists(self.file_path)
|
111
|
-
|
92
|
+
return os.path.exists(self.file_path)
|
112
93
|
|
113
|
-
def read(
|
114
|
-
self,
|
115
|
-
cache: bool = True
|
116
|
-
) -> T:
|
94
|
+
def read(self, cache: bool = True) -> T:
|
117
95
|
"""Read the file contents.
|
118
|
-
|
96
|
+
|
119
97
|
:param cache: If False, bypasses the cache and reads the file directly on each `read` call, defaults to True
|
120
98
|
:return: The file contents.
|
121
99
|
"""
|
122
100
|
# if the user has specified to ignore the cache, simply read the file.
|
123
101
|
if not cache:
|
124
102
|
return self._read()
|
125
|
-
|
103
|
+
|
126
104
|
# otherwise make use of the cache
|
127
105
|
if self._data_cache is None:
|
128
106
|
self._data_cache = self._read()
|
129
107
|
return self._data_cache
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
self
|
134
|
-
) -> None:
|
135
|
-
"""Make the parent directory path of the file. No error is raised if the target
|
108
|
+
|
109
|
+
def make_parent_dir_path(self) -> None:
|
110
|
+
"""Make the parent directory path of the file. No error is raised if the target
|
136
111
|
directory already exists.
|
137
112
|
"""
|
138
|
-
os.makedirs(self.parent_dir_path, exist_ok=True)
|
139
|
-
|
113
|
+
os.makedirs(self.parent_dir_path, exist_ok=True)
|
140
114
|
|
141
|
-
def delete(
|
142
|
-
self,
|
143
|
-
ignore_if_missing: bool = False
|
144
|
-
) -> None:
|
115
|
+
def delete(self, ignore_if_missing: bool = False) -> None:
|
145
116
|
"""Delete the file from the filesystem.
|
146
117
|
|
147
118
|
:param ignore_if_missing: If True, skips deletion if the file does not exist, defaults to False
|
148
119
|
:raises FileNotFoundError: If the file is missing and `ignore_if_missing` is False.
|
149
120
|
"""
|
150
121
|
if not self.exists and not ignore_if_missing:
|
151
|
-
raise FileNotFoundError(
|
122
|
+
raise FileNotFoundError(
|
123
|
+
f"{self.file_name} does not exist, and so cannot be deleted"
|
124
|
+
)
|
152
125
|
else:
|
153
126
|
os.remove(self.file_path)
|
154
|
-
|
155
127
|
|
156
|
-
def cat(
|
157
|
-
self
|
158
|
-
) -> None:
|
128
|
+
def cat(self) -> None:
|
159
129
|
"""Display the file contents on the standard output."""
|
160
130
|
print(self.read())
|
161
131
|
|
162
132
|
|
163
133
|
class JsonHandler(BaseFileHandler[dict[str, Any]]):
|
164
134
|
"""File handler for JSON formatted files.
|
165
|
-
|
135
|
+
|
166
136
|
We assume that the files are of the form
|
167
137
|
{
|
168
138
|
"foo": <JSON compatable structure>
|
@@ -170,29 +140,17 @@ class JsonHandler(BaseFileHandler[dict[str, Any]]):
|
|
170
140
|
}
|
171
141
|
|
172
142
|
"""
|
143
|
+
|
173
144
|
def __init__(
|
174
|
-
self,
|
175
|
-
parent_dir_path: str,
|
176
|
-
base_file_name: str,
|
177
|
-
extension: str = "json"
|
145
|
+
self, parent_dir_path: str, base_file_name: str, extension: str = "json"
|
178
146
|
) -> None:
|
179
|
-
super().__init__(parent_dir_path,
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
def _read(
|
185
|
-
self
|
186
|
-
) -> dict[str, Any]:
|
187
|
-
with open(self.file_path, 'r') as f:
|
147
|
+
super().__init__(parent_dir_path, base_file_name, extension)
|
148
|
+
|
149
|
+
def _read(self) -> dict[str, Any]:
|
150
|
+
with open(self.file_path, "r") as f:
|
188
151
|
return json.load(f)
|
189
|
-
|
190
|
-
|
191
|
-
def save(
|
192
|
-
self,
|
193
|
-
d: dict[str, Any],
|
194
|
-
force: bool = False
|
195
|
-
) -> None:
|
152
|
+
|
153
|
+
def save(self, d: dict[str, Any], force: bool = False) -> None:
|
196
154
|
"""Save the input dictionary to file in the JSON file format.
|
197
155
|
|
198
156
|
:param d: The dictionary to save.
|
@@ -205,29 +163,25 @@ class JsonHandler(BaseFileHandler[dict[str, Any]]):
|
|
205
163
|
if force:
|
206
164
|
pass
|
207
165
|
else:
|
208
|
-
raise FileExistsError(
|
209
|
-
|
166
|
+
raise FileExistsError(
|
167
|
+
(
|
168
|
+
f"{self.file_name} already exists, write has been abandoned. "
|
169
|
+
f"You can override this functionality with `force`"
|
170
|
+
)
|
171
|
+
)
|
210
172
|
|
211
|
-
with open(self.file_path,
|
212
|
-
|
173
|
+
with open(self.file_path, "w") as file:
|
174
|
+
json.dump(d, file, indent=4)
|
213
175
|
|
214
|
-
|
215
176
|
|
216
177
|
class TextHandler(BaseFileHandler[str]):
|
217
178
|
"""File handler for text formatted files."""
|
179
|
+
|
218
180
|
def __init__(
|
219
|
-
self,
|
220
|
-
parent_dir_path: str,
|
221
|
-
base_file_name: str,
|
222
|
-
extension: str = "txt"
|
181
|
+
self, parent_dir_path: str, base_file_name: str, extension: str = "txt"
|
223
182
|
) -> None:
|
224
|
-
super().__init__(parent_dir_path,
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
def _read(
|
230
|
-
self
|
231
|
-
) -> str:
|
232
|
-
with open(self.file_path, 'r') as f:
|
233
|
-
return f.read()
|
183
|
+
super().__init__(parent_dir_path, base_file_name, extension)
|
184
|
+
|
185
|
+
def _read(self) -> str:
|
186
|
+
with open(self.file_path, "r") as f:
|
187
|
+
return f.read()
|
spectre_core/batches/__init__.py
CHANGED
@@ -10,12 +10,29 @@ from .plugins._batch_keys import BatchKey
|
|
10
10
|
from .plugins._iq_stream import IQStreamBatch, IQMetadata
|
11
11
|
from .plugins._callisto import CallistoBatch
|
12
12
|
|
13
|
-
from ._base import
|
13
|
+
from ._base import (
|
14
|
+
BaseBatch,
|
15
|
+
BatchFile,
|
16
|
+
parse_batch_file_name,
|
17
|
+
parse_batch_base_file_name,
|
18
|
+
)
|
14
19
|
from ._batches import Batches
|
15
20
|
from ._factory import get_batch_cls, get_batch_cls_from_tag
|
16
21
|
|
17
22
|
__all__ = [
|
18
|
-
"IQStreamBatch",
|
19
|
-
"
|
23
|
+
"IQStreamBatch",
|
24
|
+
"IQMetadata",
|
25
|
+
"CallistoBatch",
|
26
|
+
"BaseBatch",
|
27
|
+
"BatchFile",
|
28
|
+
"Batches",
|
29
|
+
"get_batch_cls",
|
30
|
+
"BatchKey",
|
31
|
+
"get_batch_cls_from_tag",
|
32
|
+
"parse_batch_file_name",
|
20
33
|
]
|
21
34
|
|
35
|
+
# To be deprecated.
|
36
|
+
__all__ += [
|
37
|
+
"parse_batch_base_file_name",
|
38
|
+
]
|
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
|
-
|