spectre-core 0.0.12__tar.gz → 0.0.14__tar.gz
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-0.0.12 → spectre_core-0.0.14}/PKG-INFO +14 -7
- spectre_core-0.0.14/README.md +28 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/pyproject.toml +3 -2
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/_file_io/__init__.py +1 -3
- spectre_core-0.0.14/src/spectre_core/_file_io/file_handlers.py +233 -0
- spectre_core-0.0.14/src/spectre_core/batches/__init__.py +21 -0
- spectre_core-0.0.14/src/spectre_core/batches/_base.py +238 -0
- spectre_core-0.0.14/src/spectre_core/batches/_batches.py +247 -0
- spectre_core-0.0.14/src/spectre_core/batches/_factory.py +69 -0
- spectre_core-0.0.14/src/spectre_core/batches/_register.py +30 -0
- spectre_core-0.0.14/src/spectre_core/batches/plugins/_batch_keys.py +16 -0
- spectre_core-0.0.14/src/spectre_core/batches/plugins/_callisto.py +183 -0
- spectre_core-0.0.14/src/spectre_core/batches/plugins/_iq_stream.py +354 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/__init__.py +33 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_capture_config.py +144 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_capture_modes.py +22 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_capture_templates.py +307 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_parameters.py +180 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_pconstraints.py +133 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_pnames.py +49 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_ptemplates.py +493 -0
- spectre_core-0.0.14/src/spectre_core/capture_configs/_pvalidators.py +215 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/config/__init__.py +6 -8
- spectre_core-0.0.14/src/spectre_core/config/_paths.py +117 -0
- spectre_core-0.0.14/src/spectre_core/config/_time_formats.py +22 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/exceptions.py +2 -4
- spectre_core-0.0.14/src/spectre_core/jobs/__init__.py +14 -0
- spectre_core-0.0.14/src/spectre_core/jobs/_jobs.py +111 -0
- spectre_core-0.0.14/src/spectre_core/jobs/_workers.py +171 -0
- spectre_core-0.0.14/src/spectre_core/logs/__init__.py +17 -0
- spectre_core-0.0.14/src/spectre_core/logs/_configure.py +67 -0
- spectre_core-0.0.14/src/spectre_core/logs/_decorators.py +33 -0
- spectre_core-0.0.14/src/spectre_core/logs/_logs.py +228 -0
- spectre_core-0.0.14/src/spectre_core/logs/_process_types.py +14 -0
- spectre_core-0.0.14/src/spectre_core/plotting/__init__.py +13 -0
- spectre_core-0.0.14/src/spectre_core/plotting/_base.py +316 -0
- spectre_core-0.0.14/src/spectre_core/plotting/_format.py +31 -0
- spectre_core-0.0.14/src/spectre_core/plotting/_panel_names.py +18 -0
- spectre_core-0.0.14/src/spectre_core/plotting/_panel_stack.py +261 -0
- spectre_core-0.0.14/src/spectre_core/plotting/_panels.py +434 -0
- spectre_core-0.0.14/src/spectre_core/post_processing/__init__.py +16 -0
- spectre_core-0.0.14/src/spectre_core/post_processing/_base.py +145 -0
- spectre_core-0.0.14/src/spectre_core/post_processing/_factory.py +53 -0
- spectre_core-0.0.14/src/spectre_core/post_processing/_post_processor.py +38 -0
- spectre_core-0.0.14/src/spectre_core/post_processing/_register.py +31 -0
- spectre_core-0.0.14/src/spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
- spectre_core-0.0.14/src/spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
- {spectre_core-0.0.12/src/spectre_core/post_processing/library → spectre_core-0.0.14/src/spectre_core/post_processing/plugins}/_swept_center_frequency.py +215 -143
- spectre_core-0.0.14/src/spectre_core/py.typed +0 -0
- spectre_core-0.0.14/src/spectre_core/receivers/__init__.py +20 -0
- spectre_core-0.0.14/src/spectre_core/receivers/_base.py +331 -0
- spectre_core-0.0.14/src/spectre_core/receivers/_factory.py +65 -0
- spectre_core-0.0.14/src/spectre_core/receivers/_register.py +43 -0
- spectre_core-0.0.14/src/spectre_core/receivers/_spec_names.py +31 -0
- spectre_core-0.0.14/src/spectre_core/receivers/plugins/__init__.py +0 -0
- spectre_core-0.0.14/src/spectre_core/receivers/plugins/_receiver_names.py +16 -0
- spectre_core-0.0.14/src/spectre_core/receivers/plugins/_rsp1a.py +59 -0
- spectre_core-0.0.14/src/spectre_core/receivers/plugins/_rspduo.py +67 -0
- spectre_core-0.0.14/src/spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
- spectre_core-0.0.14/src/spectre_core/receivers/plugins/_test.py +218 -0
- spectre_core-0.0.14/src/spectre_core/receivers/plugins/gr/_base.py +80 -0
- {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/_rsp1a.py +42 -52
- {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/_rspduo.py +61 -74
- {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/_test.py +33 -31
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/spectrograms/__init__.py +5 -3
- spectre_core-0.0.14/src/spectre_core/spectrograms/_analytical.py +263 -0
- spectre_core-0.0.14/src/spectre_core/spectrograms/_array_operations.py +190 -0
- spectre_core-0.0.14/src/spectre_core/spectrograms/_spectrogram.py +695 -0
- spectre_core-0.0.14/src/spectre_core/spectrograms/_transform.py +265 -0
- spectre_core-0.0.14/src/spectre_core/wgetting/__init__.py +11 -0
- spectre_core-0.0.14/src/spectre_core/wgetting/_callisto.py +206 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/PKG-INFO +14 -7
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/SOURCES.txt +29 -18
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/requires.txt +1 -0
- spectre_core-0.0.12/README.md +0 -22
- spectre_core-0.0.12/src/spectre_core/_file_io/file_handlers.py +0 -128
- spectre_core-0.0.12/src/spectre_core/batches/__init__.py +0 -22
- spectre_core-0.0.12/src/spectre_core/batches/_base.py +0 -146
- spectre_core-0.0.12/src/spectre_core/batches/_batches.py +0 -197
- spectre_core-0.0.12/src/spectre_core/batches/_factory.py +0 -27
- spectre_core-0.0.12/src/spectre_core/batches/_register.py +0 -15
- spectre_core-0.0.12/src/spectre_core/batches/library/_callisto.py +0 -96
- spectre_core-0.0.12/src/spectre_core/batches/library/_fixed_center_frequency.py +0 -133
- spectre_core-0.0.12/src/spectre_core/batches/library/_swept_center_frequency.py +0 -105
- spectre_core-0.0.12/src/spectre_core/capture_configs/__init__.py +0 -29
- spectre_core-0.0.12/src/spectre_core/capture_configs/_capture_config.py +0 -85
- spectre_core-0.0.12/src/spectre_core/capture_configs/_capture_templates.py +0 -222
- spectre_core-0.0.12/src/spectre_core/capture_configs/_parameters.py +0 -107
- spectre_core-0.0.12/src/spectre_core/capture_configs/_pconstraints.py +0 -82
- spectre_core-0.0.12/src/spectre_core/capture_configs/_ptemplates.py +0 -450
- spectre_core-0.0.12/src/spectre_core/capture_configs/_pvalidators.py +0 -171
- spectre_core-0.0.12/src/spectre_core/config/_paths.py +0 -77
- spectre_core-0.0.12/src/spectre_core/config/_time_formats.py +0 -17
- spectre_core-0.0.12/src/spectre_core/logging/__init__.py +0 -11
- spectre_core-0.0.12/src/spectre_core/logging/_configure.py +0 -35
- spectre_core-0.0.12/src/spectre_core/logging/_decorators.py +0 -19
- spectre_core-0.0.12/src/spectre_core/logging/_log_handlers.py +0 -176
- spectre_core-0.0.12/src/spectre_core/plotting/__init__.py +0 -11
- spectre_core-0.0.12/src/spectre_core/plotting/_base.py +0 -214
- spectre_core-0.0.12/src/spectre_core/plotting/_format.py +0 -18
- spectre_core-0.0.12/src/spectre_core/plotting/_panel_stack.py +0 -147
- spectre_core-0.0.12/src/spectre_core/plotting/_panels.py +0 -234
- spectre_core-0.0.12/src/spectre_core/post_processing/__init__.py +0 -14
- spectre_core-0.0.12/src/spectre_core/post_processing/_base.py +0 -119
- spectre_core-0.0.12/src/spectre_core/post_processing/_factory.py +0 -23
- spectre_core-0.0.12/src/spectre_core/post_processing/_post_processor.py +0 -40
- spectre_core-0.0.12/src/spectre_core/post_processing/_register.py +0 -15
- spectre_core-0.0.12/src/spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
- spectre_core-0.0.12/src/spectre_core/receivers/__init__.py +0 -17
- spectre_core-0.0.12/src/spectre_core/receivers/_base.py +0 -180
- spectre_core-0.0.12/src/spectre_core/receivers/_factory.py +0 -19
- spectre_core-0.0.12/src/spectre_core/receivers/_register.py +0 -22
- spectre_core-0.0.12/src/spectre_core/receivers/_spec_names.py +0 -20
- spectre_core-0.0.12/src/spectre_core/receivers/gr/_base.py +0 -33
- spectre_core-0.0.12/src/spectre_core/receivers/library/_rsp1a.py +0 -61
- spectre_core-0.0.12/src/spectre_core/receivers/library/_rspduo.py +0 -69
- spectre_core-0.0.12/src/spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
- spectre_core-0.0.12/src/spectre_core/receivers/library/_test.py +0 -221
- spectre_core-0.0.12/src/spectre_core/spectrograms/_analytical.py +0 -208
- spectre_core-0.0.12/src/spectre_core/spectrograms/_array_operations.py +0 -123
- spectre_core-0.0.12/src/spectre_core/spectrograms/_spectrogram.py +0 -522
- spectre_core-0.0.12/src/spectre_core/spectrograms/_transform.py +0 -237
- spectre_core-0.0.12/src/spectre_core/wgetting/__init__.py +0 -9
- spectre_core-0.0.12/src/spectre_core/wgetting/_callisto.py +0 -151
- {spectre_core-0.0.12 → spectre_core-0.0.14}/LICENSE +0 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/setup.cfg +0 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/__init__.py +0 -0
- {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/__init__.py +0 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/dependency_links.txt +0 -0
- {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: spectre-core
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.14
|
4
4
|
Summary: The core Python package used by the spectre program.
|
5
5
|
Maintainer-email: Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
@@ -691,6 +691,7 @@ Requires-Dist: scipy==1.12.0
|
|
691
691
|
Requires-Dist: astropy==6.0.1
|
692
692
|
Requires-Dist: matplotlib==3.5.0
|
693
693
|
Requires-Dist: watchdog==4.0.0
|
694
|
+
Requires-Dist: mypy==1.14.1
|
694
695
|
|
695
696
|
# spectre-core
|
696
697
|
|
@@ -698,18 +699,24 @@ Requires-Dist: watchdog==4.0.0
|
|
698
699
|
|
699
700
|
:loudspeaker: **This project is under active development. Contributors welcome.** :loudspeaker:
|
700
701
|
|
701
|
-
```spectre-core``` provides
|
702
|
+
```spectre-core``` provides an extensible, receiver-agnostic toolkit for generating radio spectrograms in real-time. It is the core Python package used by the [`spectre`](https://github.com/jcfitzpatrick12/spectre.git) program.
|
702
703
|
|
703
704
|
|
704
705
|
## Installation
|
705
|
-
|
706
|
-
```
|
706
|
+
Simply call:
|
707
|
+
```bash
|
708
|
+
pip install spectre-core
|
709
|
+
```
|
707
710
|
|
708
711
|
Alternatively, you can clone the repository in your preferred directory:
|
709
|
-
```
|
712
|
+
```bash
|
713
|
+
git clone https://github.com/jcfitzpatrick12/spectre-core.git && cd spectre-core
|
714
|
+
```
|
710
715
|
|
711
716
|
Then run:
|
712
|
-
```
|
717
|
+
```
|
718
|
+
pip install -e .
|
719
|
+
```
|
713
720
|
|
714
721
|
|
715
722
|
## Contributing
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# spectre-core
|
2
|
+
|
3
|
+
## Description
|
4
|
+
|
5
|
+
:loudspeaker: **This project is under active development. Contributors welcome.** :loudspeaker:
|
6
|
+
|
7
|
+
```spectre-core``` provides an extensible, receiver-agnostic toolkit for generating radio spectrograms in real-time. It is the core Python package used by the [`spectre`](https://github.com/jcfitzpatrick12/spectre.git) program.
|
8
|
+
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
Simply call:
|
12
|
+
```bash
|
13
|
+
pip install spectre-core
|
14
|
+
```
|
15
|
+
|
16
|
+
Alternatively, you can clone the repository in your preferred directory:
|
17
|
+
```bash
|
18
|
+
git clone https://github.com/jcfitzpatrick12/spectre-core.git && cd spectre-core
|
19
|
+
```
|
20
|
+
|
21
|
+
Then run:
|
22
|
+
```
|
23
|
+
pip install -e .
|
24
|
+
```
|
25
|
+
|
26
|
+
|
27
|
+
## Contributing
|
28
|
+
This repository is in active development. If you are interested, feel free to contact jcfitzpatrick12@gmail.com :)
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "spectre-core"
|
7
|
-
version = "0.0.
|
7
|
+
version = "0.0.14"
|
8
8
|
maintainers = [
|
9
9
|
{ name="Jimmy Fitzpatrick", email="jcfitzpatrick12@gmail.com" },
|
10
10
|
]
|
@@ -21,7 +21,8 @@ dependencies = [
|
|
21
21
|
'scipy==1.12.0',
|
22
22
|
'astropy==6.0.1',
|
23
23
|
'matplotlib==3.5.0',
|
24
|
-
'watchdog==4.0.0'
|
24
|
+
'watchdog==4.0.0',
|
25
|
+
'mypy==1.14.1',
|
25
26
|
]
|
26
27
|
license = { file = "LICENSE" }
|
27
28
|
|
@@ -0,0 +1,233 @@
|
|
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 json
|
7
|
+
from abc import ABC, abstractmethod
|
8
|
+
from typing import Any, Optional, TypeVar, Generic
|
9
|
+
|
10
|
+
T = TypeVar('T')
|
11
|
+
|
12
|
+
class BaseFileHandler(ABC, Generic[T]):
|
13
|
+
"""
|
14
|
+
Base class for handling file operations.
|
15
|
+
|
16
|
+
When subclassing, specify the return type of `_read`
|
17
|
+
using `Generic[T]`.
|
18
|
+
|
19
|
+
Example:
|
20
|
+
.. code-block:: python
|
21
|
+
|
22
|
+
from typing import Any, Generic
|
23
|
+
|
24
|
+
class JsonHandler(BaseFileHandler[dict[str, Any]]):
|
25
|
+
def _read(self) -> dict[str, Any]:
|
26
|
+
# Implementation here
|
27
|
+
...
|
28
|
+
"""
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
parent_dir_path: str,
|
32
|
+
base_file_name: str,
|
33
|
+
extension: Optional[str] = None
|
34
|
+
) -> None:
|
35
|
+
"""Initialise a `BaseFileHandler` instance.
|
36
|
+
|
37
|
+
:param parent_dir_path: The directory path where the file is located (absolute or relative).
|
38
|
+
:param base_file_name: The name of the file without its extension.
|
39
|
+
:param extension: The file extension (without the dot), defaults to None
|
40
|
+
"""
|
41
|
+
self._data_cache: Optional[T] = None
|
42
|
+
|
43
|
+
self._parent_dir_path = parent_dir_path
|
44
|
+
self._base_file_name = base_file_name
|
45
|
+
|
46
|
+
if extension == "":
|
47
|
+
extension = None
|
48
|
+
self._extension = extension
|
49
|
+
|
50
|
+
|
51
|
+
@abstractmethod
|
52
|
+
def _read(
|
53
|
+
self
|
54
|
+
) -> T:
|
55
|
+
"""The grunt work to return the file contents.
|
56
|
+
|
57
|
+
:return: The file contents.
|
58
|
+
"""
|
59
|
+
|
60
|
+
|
61
|
+
@property
|
62
|
+
def parent_dir_path(
|
63
|
+
self
|
64
|
+
) -> str:
|
65
|
+
"""Return the parent directory path for the file."""
|
66
|
+
return self._parent_dir_path
|
67
|
+
|
68
|
+
|
69
|
+
@property
|
70
|
+
def base_file_name(
|
71
|
+
self
|
72
|
+
) -> str:
|
73
|
+
"""Return the file name, stripped of the file extension."""
|
74
|
+
return self._base_file_name
|
75
|
+
|
76
|
+
|
77
|
+
@property
|
78
|
+
def extension(
|
79
|
+
self
|
80
|
+
) -> Optional[str]:
|
81
|
+
"""Return the file path suffix, excluding the dot."""
|
82
|
+
return self._extension
|
83
|
+
|
84
|
+
|
85
|
+
@property
|
86
|
+
def file_name(
|
87
|
+
self
|
88
|
+
) -> str:
|
89
|
+
"""Generate the file name based on the base name and extension.
|
90
|
+
|
91
|
+
:return: The file name with the extension (including the dot), or the base name if no extension is set.
|
92
|
+
"""
|
93
|
+
return self._base_file_name if (self._extension is None) else f"{self._base_file_name}.{self._extension}"
|
94
|
+
|
95
|
+
|
96
|
+
@property
|
97
|
+
def file_path(
|
98
|
+
self
|
99
|
+
) -> str:
|
100
|
+
"""The absolute or relative file path as defined by the parent directory path,
|
101
|
+
base file name and extension."""
|
102
|
+
return os.path.join(self._parent_dir_path, self.file_name)
|
103
|
+
|
104
|
+
|
105
|
+
@property
|
106
|
+
def exists(
|
107
|
+
self
|
108
|
+
) -> bool:
|
109
|
+
"""Check if the file exists in the filesystem."""
|
110
|
+
return os.path.exists(self.file_path)
|
111
|
+
|
112
|
+
|
113
|
+
def read(
|
114
|
+
self,
|
115
|
+
cache: bool = True
|
116
|
+
) -> T:
|
117
|
+
"""Read the file contents.
|
118
|
+
|
119
|
+
:param cache: If False, bypasses the cache and reads the file directly on each `read` call, defaults to True
|
120
|
+
:return: The file contents.
|
121
|
+
"""
|
122
|
+
# if the user has specified to ignore the cache, simply read the file.
|
123
|
+
if not cache:
|
124
|
+
return self._read()
|
125
|
+
|
126
|
+
# otherwise make use of the cache
|
127
|
+
if self._data_cache is None:
|
128
|
+
self._data_cache = self._read()
|
129
|
+
return self._data_cache
|
130
|
+
|
131
|
+
|
132
|
+
def make_parent_dir_path(
|
133
|
+
self
|
134
|
+
) -> None:
|
135
|
+
"""Make the parent directory path of the file. No error is raised if the target
|
136
|
+
directory already exists.
|
137
|
+
"""
|
138
|
+
os.makedirs(self.parent_dir_path, exist_ok=True)
|
139
|
+
|
140
|
+
|
141
|
+
def delete(
|
142
|
+
self,
|
143
|
+
ignore_if_missing: bool = False
|
144
|
+
) -> None:
|
145
|
+
"""Delete the file from the filesystem.
|
146
|
+
|
147
|
+
:param ignore_if_missing: If True, skips deletion if the file does not exist, defaults to False
|
148
|
+
:raises FileNotFoundError: If the file is missing and `ignore_if_missing` is False.
|
149
|
+
"""
|
150
|
+
if not self.exists and not ignore_if_missing:
|
151
|
+
raise FileNotFoundError(f"{self.file_name} does not exist, and so cannot be deleted")
|
152
|
+
else:
|
153
|
+
os.remove(self.file_path)
|
154
|
+
|
155
|
+
|
156
|
+
def cat(
|
157
|
+
self
|
158
|
+
) -> None:
|
159
|
+
"""Display the file contents on the standard output."""
|
160
|
+
print(self.read())
|
161
|
+
|
162
|
+
|
163
|
+
class JsonHandler(BaseFileHandler[dict[str, Any]]):
|
164
|
+
"""File handler for JSON formatted files.
|
165
|
+
|
166
|
+
We assume that the files are of the form
|
167
|
+
{
|
168
|
+
"foo": <JSON compatable structure>
|
169
|
+
... and so on.
|
170
|
+
}
|
171
|
+
|
172
|
+
"""
|
173
|
+
def __init__(
|
174
|
+
self,
|
175
|
+
parent_dir_path: str,
|
176
|
+
base_file_name: str,
|
177
|
+
extension: str = "json"
|
178
|
+
) -> None:
|
179
|
+
super().__init__(parent_dir_path,
|
180
|
+
base_file_name,
|
181
|
+
extension)
|
182
|
+
|
183
|
+
|
184
|
+
def _read(
|
185
|
+
self
|
186
|
+
) -> dict[str, Any]:
|
187
|
+
with open(self.file_path, 'r') as f:
|
188
|
+
return json.load(f)
|
189
|
+
|
190
|
+
|
191
|
+
def save(
|
192
|
+
self,
|
193
|
+
d: dict[str, Any],
|
194
|
+
force: bool = False
|
195
|
+
) -> None:
|
196
|
+
"""Save the input dictionary to file in the JSON file format.
|
197
|
+
|
198
|
+
:param d: The dictionary to save.
|
199
|
+
:param force: If True, overwrites the file if it already exists, defaults to False
|
200
|
+
:raises FileExistsError: If the file exists and `force` is False.
|
201
|
+
"""
|
202
|
+
self.make_parent_dir_path()
|
203
|
+
|
204
|
+
if self.exists:
|
205
|
+
if force:
|
206
|
+
pass
|
207
|
+
else:
|
208
|
+
raise FileExistsError((f"{self.file_name} already exists, write has been abandoned. "
|
209
|
+
f"You can override this functionality with `force`"))
|
210
|
+
|
211
|
+
with open(self.file_path, 'w') as file:
|
212
|
+
json.dump(d, file, indent=4)
|
213
|
+
|
214
|
+
|
215
|
+
|
216
|
+
class TextHandler(BaseFileHandler[str]):
|
217
|
+
"""File handler for text formatted files."""
|
218
|
+
def __init__(
|
219
|
+
self,
|
220
|
+
parent_dir_path: str,
|
221
|
+
base_file_name: str,
|
222
|
+
extension: str = "txt"
|
223
|
+
) -> None:
|
224
|
+
super().__init__(parent_dir_path,
|
225
|
+
base_file_name,
|
226
|
+
extension)
|
227
|
+
|
228
|
+
|
229
|
+
def _read(
|
230
|
+
self
|
231
|
+
) -> str:
|
232
|
+
with open(self.file_path, 'r') as f:
|
233
|
+
return f.read()
|
@@ -0,0 +1,21 @@
|
|
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
|
+
"""IO operations on batched data files."""
|
6
|
+
|
7
|
+
from .plugins._batch_keys import BatchKey
|
8
|
+
|
9
|
+
# register decorators take effect on import
|
10
|
+
from .plugins._iq_stream import IQStreamBatch, IQMetadata
|
11
|
+
from .plugins._callisto import CallistoBatch
|
12
|
+
|
13
|
+
from ._base import BaseBatch, BatchFile
|
14
|
+
from ._batches import Batches
|
15
|
+
from ._factory import get_batch_cls, get_batch_cls_from_tag
|
16
|
+
|
17
|
+
__all__ = [
|
18
|
+
"IQStreamBatch", "IQMetadata", "CallistoBatch", "BaseBatch", "BatchFile",
|
19
|
+
"Batches", "get_batch_cls", "BatchKey", "get_batch_cls_from_tag"
|
20
|
+
]
|
21
|
+
|
@@ -0,0 +1,238 @@
|
|
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
|
6
|
+
from typing import TypeVar
|
7
|
+
from functools import cached_property
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
|
10
|
+
from spectre_core._file_io import BaseFileHandler
|
11
|
+
from spectre_core.config import get_batches_dir_path, TimeFormat
|
12
|
+
from spectre_core.spectrograms import Spectrogram
|
13
|
+
|
14
|
+
|
15
|
+
T = TypeVar('T')
|
16
|
+
|
17
|
+
class BatchFile(BaseFileHandler[T]):
|
18
|
+
"""Abstract base class for files belonging to a batch, identified by their file extension.
|
19
|
+
|
20
|
+
Batch file names must conform to the following structure:
|
21
|
+
|
22
|
+
`<start time>_<tag>.<extension>`
|
23
|
+
|
24
|
+
The substring `<start time>_<tag>` is referred to as the batch name. Files with the same batch name
|
25
|
+
belong to the same batch.
|
26
|
+
"""
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
batch_parent_dir_path: str,
|
30
|
+
batch_name: str,
|
31
|
+
extension: str
|
32
|
+
) -> None:
|
33
|
+
"""Initialise a `BatchFile` instance.
|
34
|
+
|
35
|
+
:param batch_parent_dir_path: Parent directory of the batch.
|
36
|
+
:param batch_name: Base file name, composed of the batch start time and tag.
|
37
|
+
:param extension: File extension.
|
38
|
+
"""
|
39
|
+
super().__init__(batch_parent_dir_path,
|
40
|
+
batch_name,
|
41
|
+
extension)
|
42
|
+
self._start_time, self._tag = batch_name.split("_")
|
43
|
+
|
44
|
+
|
45
|
+
@property
|
46
|
+
def start_time(
|
47
|
+
self
|
48
|
+
) -> str:
|
49
|
+
"""The start time of the batch, formatted as a string up to seconds precision."""
|
50
|
+
return self._start_time
|
51
|
+
|
52
|
+
|
53
|
+
@cached_property
|
54
|
+
def start_datetime(
|
55
|
+
self
|
56
|
+
) -> datetime:
|
57
|
+
"""The start time of the batch, parsed as a datetime up to seconds precision."""
|
58
|
+
return datetime.strptime(self.start_time, TimeFormat.DATETIME)
|
59
|
+
|
60
|
+
|
61
|
+
@property
|
62
|
+
def tag(
|
63
|
+
self
|
64
|
+
) -> str:
|
65
|
+
"""The batch name tag."""
|
66
|
+
return self._tag
|
67
|
+
|
68
|
+
|
69
|
+
class BaseBatch(ABC):
|
70
|
+
"""
|
71
|
+
An abstract base class representing a group of data files over a common time interval.
|
72
|
+
|
73
|
+
All files in a batch share a base file name and differ only by their extension.
|
74
|
+
Subclasses of `BaseBatch` define the expected data for each file extension and
|
75
|
+
provide an API for accessing their contents using `BatchFile` subclasses.
|
76
|
+
|
77
|
+
Subclasses should expose `BatchFile` instances directly as attributes, which
|
78
|
+
simplifies static typing. Additionally, they should call `add_file` in the constructor
|
79
|
+
to formally register each `BatchFile`.
|
80
|
+
"""
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
start_time: str,
|
84
|
+
tag: str
|
85
|
+
) -> None:
|
86
|
+
"""Initialise a `BaseBatch` instance.
|
87
|
+
|
88
|
+
:param start_time: Start time of the batch as a string with seconds precision.
|
89
|
+
:param tag: The batch name tag.
|
90
|
+
"""
|
91
|
+
self._start_time = start_time
|
92
|
+
self._tag: str = tag
|
93
|
+
self._start_datetime = datetime.strptime(self.start_time, TimeFormat.DATETIME)
|
94
|
+
self._parent_dir_path = get_batches_dir_path(year = self.start_datetime.year,
|
95
|
+
month = self.start_datetime.month,
|
96
|
+
day = self.start_datetime.day)
|
97
|
+
|
98
|
+
# internal register of batch files
|
99
|
+
self._batch_files: dict[str, BatchFile] = {}
|
100
|
+
|
101
|
+
|
102
|
+
@property
|
103
|
+
@abstractmethod
|
104
|
+
def spectrogram_file(
|
105
|
+
self
|
106
|
+
) -> BatchFile:
|
107
|
+
"""The batch file which contains spectrogram data."""
|
108
|
+
|
109
|
+
|
110
|
+
@property
|
111
|
+
def start_time(
|
112
|
+
self
|
113
|
+
) -> str:
|
114
|
+
"""The start time of the batch, formatted as a string up to seconds precision."""
|
115
|
+
return self._start_time
|
116
|
+
|
117
|
+
|
118
|
+
@property
|
119
|
+
def start_datetime(
|
120
|
+
self
|
121
|
+
) -> datetime:
|
122
|
+
"""The start time of the batch, parsed as a datetime up to seconds precision."""
|
123
|
+
return self._start_datetime
|
124
|
+
|
125
|
+
|
126
|
+
@property
|
127
|
+
def tag(
|
128
|
+
self
|
129
|
+
) -> str:
|
130
|
+
"""The batch name tag."""
|
131
|
+
return self._tag
|
132
|
+
|
133
|
+
|
134
|
+
@property
|
135
|
+
def parent_dir_path(
|
136
|
+
self
|
137
|
+
) -> str:
|
138
|
+
"""The parent directory for the batch."""
|
139
|
+
return self._parent_dir_path
|
140
|
+
|
141
|
+
|
142
|
+
@property
|
143
|
+
def name(
|
144
|
+
self
|
145
|
+
) -> str:
|
146
|
+
"""Return the base file name shared by all files in the batch,
|
147
|
+
composed of the start time and the batch tag."""
|
148
|
+
return f"{self._start_time}_{self._tag}"
|
149
|
+
|
150
|
+
|
151
|
+
@property
|
152
|
+
def extensions(
|
153
|
+
self
|
154
|
+
) -> list[str]:
|
155
|
+
"""All defined file extensions for the batch."""
|
156
|
+
return list(self._batch_files.keys())
|
157
|
+
|
158
|
+
|
159
|
+
@property
|
160
|
+
def batch_files(
|
161
|
+
self
|
162
|
+
) -> dict[str, BatchFile]:
|
163
|
+
"""Map each file extension in the batch to the corresponding batch file instance.
|
164
|
+
|
165
|
+
Use `add_file` to add a file to the batch.
|
166
|
+
"""
|
167
|
+
return self._batch_files
|
168
|
+
|
169
|
+
|
170
|
+
def add_file(
|
171
|
+
self,
|
172
|
+
batch_file: BatchFile
|
173
|
+
) -> None:
|
174
|
+
"""Add an instance of a batch file to the batch.
|
175
|
+
|
176
|
+
:param batch_file: The `BatchFile` instance to add to the batch.
|
177
|
+
:raises ValueError: If the `BatchFile` instance does not have a defined file extension.
|
178
|
+
"""
|
179
|
+
if batch_file.extension is None:
|
180
|
+
raise ValueError(f"The `BatchFile` must have a defined file extension. "
|
181
|
+
f"Received '{batch_file.extension}.")
|
182
|
+
self._batch_files[batch_file.extension] = batch_file
|
183
|
+
|
184
|
+
|
185
|
+
def get_file(
|
186
|
+
self,
|
187
|
+
extension: str
|
188
|
+
) -> BatchFile:
|
189
|
+
"""Get a batch file instance from the batch, according to the file extension.
|
190
|
+
|
191
|
+
:param extension: The file extension of the batch file.
|
192
|
+
:raises NotImplementedError: If the extension is undefined for the batch.
|
193
|
+
:return: The batch file instance registered under the input file extension.
|
194
|
+
"""
|
195
|
+
try:
|
196
|
+
return self._batch_files[extension]
|
197
|
+
except KeyError:
|
198
|
+
raise NotImplementedError(f"A batch file with extension '{extension}' is not implemented for this batch.")
|
199
|
+
|
200
|
+
|
201
|
+
def delete_file(
|
202
|
+
self,
|
203
|
+
extension: str
|
204
|
+
) -> None:
|
205
|
+
"""Delete a file from the batch, according to the file extension.
|
206
|
+
|
207
|
+
:param extension: The file extension of the batch file.
|
208
|
+
:raises FileNotFoundError: If the batch file does not exist in the file system.
|
209
|
+
"""
|
210
|
+
batch_file = self.get_file(extension)
|
211
|
+
batch_file.delete()
|
212
|
+
|
213
|
+
|
214
|
+
def has_file(
|
215
|
+
self,
|
216
|
+
extension: str
|
217
|
+
) -> bool:
|
218
|
+
"""Determine the existance of a batch file in the file system.
|
219
|
+
|
220
|
+
:param extension: The file extension of the batch file.
|
221
|
+
:return: True if the batch file exists in the file system, False otherwise.
|
222
|
+
"""
|
223
|
+
try:
|
224
|
+
batch_file = self.get_file(extension)
|
225
|
+
return batch_file.exists
|
226
|
+
except FileNotFoundError:
|
227
|
+
return False
|
228
|
+
|
229
|
+
|
230
|
+
def read_spectrogram(
|
231
|
+
self
|
232
|
+
) -> Spectrogram:
|
233
|
+
"""Read and return the spectrogram data stored in the batch.
|
234
|
+
|
235
|
+
:return: The spectrogram stored by the batch `spectrogram_file`.
|
236
|
+
"""
|
237
|
+
return self.spectrogram_file.read()
|
238
|
+
|