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.
Files changed (130) hide show
  1. {spectre_core-0.0.12 → spectre_core-0.0.14}/PKG-INFO +14 -7
  2. spectre_core-0.0.14/README.md +28 -0
  3. {spectre_core-0.0.12 → spectre_core-0.0.14}/pyproject.toml +3 -2
  4. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/_file_io/__init__.py +1 -3
  5. spectre_core-0.0.14/src/spectre_core/_file_io/file_handlers.py +233 -0
  6. spectre_core-0.0.14/src/spectre_core/batches/__init__.py +21 -0
  7. spectre_core-0.0.14/src/spectre_core/batches/_base.py +238 -0
  8. spectre_core-0.0.14/src/spectre_core/batches/_batches.py +247 -0
  9. spectre_core-0.0.14/src/spectre_core/batches/_factory.py +69 -0
  10. spectre_core-0.0.14/src/spectre_core/batches/_register.py +30 -0
  11. spectre_core-0.0.14/src/spectre_core/batches/plugins/_batch_keys.py +16 -0
  12. spectre_core-0.0.14/src/spectre_core/batches/plugins/_callisto.py +183 -0
  13. spectre_core-0.0.14/src/spectre_core/batches/plugins/_iq_stream.py +354 -0
  14. spectre_core-0.0.14/src/spectre_core/capture_configs/__init__.py +33 -0
  15. spectre_core-0.0.14/src/spectre_core/capture_configs/_capture_config.py +144 -0
  16. spectre_core-0.0.14/src/spectre_core/capture_configs/_capture_modes.py +22 -0
  17. spectre_core-0.0.14/src/spectre_core/capture_configs/_capture_templates.py +307 -0
  18. spectre_core-0.0.14/src/spectre_core/capture_configs/_parameters.py +180 -0
  19. spectre_core-0.0.14/src/spectre_core/capture_configs/_pconstraints.py +133 -0
  20. spectre_core-0.0.14/src/spectre_core/capture_configs/_pnames.py +49 -0
  21. spectre_core-0.0.14/src/spectre_core/capture_configs/_ptemplates.py +493 -0
  22. spectre_core-0.0.14/src/spectre_core/capture_configs/_pvalidators.py +215 -0
  23. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/config/__init__.py +6 -8
  24. spectre_core-0.0.14/src/spectre_core/config/_paths.py +117 -0
  25. spectre_core-0.0.14/src/spectre_core/config/_time_formats.py +22 -0
  26. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/exceptions.py +2 -4
  27. spectre_core-0.0.14/src/spectre_core/jobs/__init__.py +14 -0
  28. spectre_core-0.0.14/src/spectre_core/jobs/_jobs.py +111 -0
  29. spectre_core-0.0.14/src/spectre_core/jobs/_workers.py +171 -0
  30. spectre_core-0.0.14/src/spectre_core/logs/__init__.py +17 -0
  31. spectre_core-0.0.14/src/spectre_core/logs/_configure.py +67 -0
  32. spectre_core-0.0.14/src/spectre_core/logs/_decorators.py +33 -0
  33. spectre_core-0.0.14/src/spectre_core/logs/_logs.py +228 -0
  34. spectre_core-0.0.14/src/spectre_core/logs/_process_types.py +14 -0
  35. spectre_core-0.0.14/src/spectre_core/plotting/__init__.py +13 -0
  36. spectre_core-0.0.14/src/spectre_core/plotting/_base.py +316 -0
  37. spectre_core-0.0.14/src/spectre_core/plotting/_format.py +31 -0
  38. spectre_core-0.0.14/src/spectre_core/plotting/_panel_names.py +18 -0
  39. spectre_core-0.0.14/src/spectre_core/plotting/_panel_stack.py +261 -0
  40. spectre_core-0.0.14/src/spectre_core/plotting/_panels.py +434 -0
  41. spectre_core-0.0.14/src/spectre_core/post_processing/__init__.py +16 -0
  42. spectre_core-0.0.14/src/spectre_core/post_processing/_base.py +145 -0
  43. spectre_core-0.0.14/src/spectre_core/post_processing/_factory.py +53 -0
  44. spectre_core-0.0.14/src/spectre_core/post_processing/_post_processor.py +38 -0
  45. spectre_core-0.0.14/src/spectre_core/post_processing/_register.py +31 -0
  46. spectre_core-0.0.14/src/spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
  47. spectre_core-0.0.14/src/spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
  48. {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
  49. spectre_core-0.0.14/src/spectre_core/py.typed +0 -0
  50. spectre_core-0.0.14/src/spectre_core/receivers/__init__.py +20 -0
  51. spectre_core-0.0.14/src/spectre_core/receivers/_base.py +331 -0
  52. spectre_core-0.0.14/src/spectre_core/receivers/_factory.py +65 -0
  53. spectre_core-0.0.14/src/spectre_core/receivers/_register.py +43 -0
  54. spectre_core-0.0.14/src/spectre_core/receivers/_spec_names.py +31 -0
  55. spectre_core-0.0.14/src/spectre_core/receivers/plugins/__init__.py +0 -0
  56. spectre_core-0.0.14/src/spectre_core/receivers/plugins/_receiver_names.py +16 -0
  57. spectre_core-0.0.14/src/spectre_core/receivers/plugins/_rsp1a.py +59 -0
  58. spectre_core-0.0.14/src/spectre_core/receivers/plugins/_rspduo.py +67 -0
  59. spectre_core-0.0.14/src/spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
  60. spectre_core-0.0.14/src/spectre_core/receivers/plugins/_test.py +218 -0
  61. spectre_core-0.0.14/src/spectre_core/receivers/plugins/gr/_base.py +80 -0
  62. {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/_rsp1a.py +42 -52
  63. {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/_rspduo.py +61 -74
  64. {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/_test.py +33 -31
  65. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/spectrograms/__init__.py +5 -3
  66. spectre_core-0.0.14/src/spectre_core/spectrograms/_analytical.py +263 -0
  67. spectre_core-0.0.14/src/spectre_core/spectrograms/_array_operations.py +190 -0
  68. spectre_core-0.0.14/src/spectre_core/spectrograms/_spectrogram.py +695 -0
  69. spectre_core-0.0.14/src/spectre_core/spectrograms/_transform.py +265 -0
  70. spectre_core-0.0.14/src/spectre_core/wgetting/__init__.py +11 -0
  71. spectre_core-0.0.14/src/spectre_core/wgetting/_callisto.py +206 -0
  72. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/PKG-INFO +14 -7
  73. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/SOURCES.txt +29 -18
  74. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/requires.txt +1 -0
  75. spectre_core-0.0.12/README.md +0 -22
  76. spectre_core-0.0.12/src/spectre_core/_file_io/file_handlers.py +0 -128
  77. spectre_core-0.0.12/src/spectre_core/batches/__init__.py +0 -22
  78. spectre_core-0.0.12/src/spectre_core/batches/_base.py +0 -146
  79. spectre_core-0.0.12/src/spectre_core/batches/_batches.py +0 -197
  80. spectre_core-0.0.12/src/spectre_core/batches/_factory.py +0 -27
  81. spectre_core-0.0.12/src/spectre_core/batches/_register.py +0 -15
  82. spectre_core-0.0.12/src/spectre_core/batches/library/_callisto.py +0 -96
  83. spectre_core-0.0.12/src/spectre_core/batches/library/_fixed_center_frequency.py +0 -133
  84. spectre_core-0.0.12/src/spectre_core/batches/library/_swept_center_frequency.py +0 -105
  85. spectre_core-0.0.12/src/spectre_core/capture_configs/__init__.py +0 -29
  86. spectre_core-0.0.12/src/spectre_core/capture_configs/_capture_config.py +0 -85
  87. spectre_core-0.0.12/src/spectre_core/capture_configs/_capture_templates.py +0 -222
  88. spectre_core-0.0.12/src/spectre_core/capture_configs/_parameters.py +0 -107
  89. spectre_core-0.0.12/src/spectre_core/capture_configs/_pconstraints.py +0 -82
  90. spectre_core-0.0.12/src/spectre_core/capture_configs/_ptemplates.py +0 -450
  91. spectre_core-0.0.12/src/spectre_core/capture_configs/_pvalidators.py +0 -171
  92. spectre_core-0.0.12/src/spectre_core/config/_paths.py +0 -77
  93. spectre_core-0.0.12/src/spectre_core/config/_time_formats.py +0 -17
  94. spectre_core-0.0.12/src/spectre_core/logging/__init__.py +0 -11
  95. spectre_core-0.0.12/src/spectre_core/logging/_configure.py +0 -35
  96. spectre_core-0.0.12/src/spectre_core/logging/_decorators.py +0 -19
  97. spectre_core-0.0.12/src/spectre_core/logging/_log_handlers.py +0 -176
  98. spectre_core-0.0.12/src/spectre_core/plotting/__init__.py +0 -11
  99. spectre_core-0.0.12/src/spectre_core/plotting/_base.py +0 -214
  100. spectre_core-0.0.12/src/spectre_core/plotting/_format.py +0 -18
  101. spectre_core-0.0.12/src/spectre_core/plotting/_panel_stack.py +0 -147
  102. spectre_core-0.0.12/src/spectre_core/plotting/_panels.py +0 -234
  103. spectre_core-0.0.12/src/spectre_core/post_processing/__init__.py +0 -14
  104. spectre_core-0.0.12/src/spectre_core/post_processing/_base.py +0 -119
  105. spectre_core-0.0.12/src/spectre_core/post_processing/_factory.py +0 -23
  106. spectre_core-0.0.12/src/spectre_core/post_processing/_post_processor.py +0 -40
  107. spectre_core-0.0.12/src/spectre_core/post_processing/_register.py +0 -15
  108. spectre_core-0.0.12/src/spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
  109. spectre_core-0.0.12/src/spectre_core/receivers/__init__.py +0 -17
  110. spectre_core-0.0.12/src/spectre_core/receivers/_base.py +0 -180
  111. spectre_core-0.0.12/src/spectre_core/receivers/_factory.py +0 -19
  112. spectre_core-0.0.12/src/spectre_core/receivers/_register.py +0 -22
  113. spectre_core-0.0.12/src/spectre_core/receivers/_spec_names.py +0 -20
  114. spectre_core-0.0.12/src/spectre_core/receivers/gr/_base.py +0 -33
  115. spectre_core-0.0.12/src/spectre_core/receivers/library/_rsp1a.py +0 -61
  116. spectre_core-0.0.12/src/spectre_core/receivers/library/_rspduo.py +0 -69
  117. spectre_core-0.0.12/src/spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  118. spectre_core-0.0.12/src/spectre_core/receivers/library/_test.py +0 -221
  119. spectre_core-0.0.12/src/spectre_core/spectrograms/_analytical.py +0 -208
  120. spectre_core-0.0.12/src/spectre_core/spectrograms/_array_operations.py +0 -123
  121. spectre_core-0.0.12/src/spectre_core/spectrograms/_spectrogram.py +0 -522
  122. spectre_core-0.0.12/src/spectre_core/spectrograms/_transform.py +0 -237
  123. spectre_core-0.0.12/src/spectre_core/wgetting/__init__.py +0 -9
  124. spectre_core-0.0.12/src/spectre_core/wgetting/_callisto.py +0 -151
  125. {spectre_core-0.0.12 → spectre_core-0.0.14}/LICENSE +0 -0
  126. {spectre_core-0.0.12 → spectre_core-0.0.14}/setup.cfg +0 -0
  127. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core/__init__.py +0 -0
  128. {spectre_core-0.0.12/src/spectre_core/receivers → spectre_core-0.0.14/src/spectre_core/receivers/plugins}/gr/__init__.py +0 -0
  129. {spectre_core-0.0.12 → spectre_core-0.0.14}/src/spectre_core.egg-info/dependency_links.txt +0 -0
  130. {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
1
+ Metadata-Version: 2.2
2
2
  Name: spectre-core
3
- Version: 0.0.12
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 a SDR receiver-agnostic digital signal processing framework. It is the core Python package used by the [`spectre`](https://github.com/jcfitzpatrick12/spectre.git) program.
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
- Typically, installation of ```spectre-core``` is managed in the ```spectre``` back-end. In any case, if you would like, simply call:
706
- ```pip install spectre-core```.
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
- ```git clone https://github.com/jcfitzpatrick12/spectre-core.git && cd spectre-core```
712
+ ```bash
713
+ git clone https://github.com/jcfitzpatrick12/spectre-core.git && cd spectre-core
714
+ ```
710
715
 
711
716
  Then run:
712
- ```pip install -e .```
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.12"
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
 
@@ -2,9 +2,7 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- """
6
- Internal file handling.
7
- """
5
+ """Basic internal file handling capabilities."""
8
6
 
9
7
  from .file_handlers import (
10
8
  BaseFileHandler, JsonHandler, TextHandler,
@@ -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
+