spectre-core 0.0.9__py3-none-any.whl → 0.0.11__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 +0 -3
- spectre_core/_file_io/__init__.py +15 -0
- spectre_core/_file_io/file_handlers.py +128 -0
- spectre_core/capture_configs/__init__.py +29 -0
- spectre_core/capture_configs/_capture_config.py +85 -0
- spectre_core/capture_configs/_capture_templates.py +222 -0
- spectre_core/capture_configs/_parameters.py +110 -0
- spectre_core/capture_configs/_pconstraints.py +82 -0
- spectre_core/capture_configs/_ptemplates.py +450 -0
- spectre_core/capture_configs/_pvalidators.py +171 -0
- spectre_core/chunks/__init__.py +17 -201
- spectre_core/chunks/{base.py → _base.py} +15 -60
- spectre_core/chunks/_chunks.py +200 -0
- spectre_core/chunks/{factory.py → _factory.py} +6 -7
- spectre_core/chunks/library/{callisto/chunk.py → _callisto.py} +4 -7
- spectre_core/chunks/library/{fixed/chunk.py → _fixed_center_frequency.py} +7 -64
- spectre_core/chunks/library/_swept_center_frequency.py +103 -0
- spectre_core/config/__init__.py +20 -0
- spectre_core/config/_paths.py +77 -0
- spectre_core/config/_time_formats.py +15 -0
- spectre_core/exceptions.py +4 -5
- spectre_core/logging/__init__.py +11 -0
- spectre_core/logging/_configure.py +35 -0
- spectre_core/logging/_decorators.py +19 -0
- spectre_core/{logging.py → logging/_log_handlers.py} +13 -58
- spectre_core/plotting/__init__.py +7 -1
- spectre_core/plotting/{base.py → _base.py} +40 -20
- spectre_core/plotting/_format.py +18 -0
- spectre_core/plotting/{panel_stack.py → _panel_stack.py} +48 -48
- spectre_core/plotting/_panels.py +234 -0
- spectre_core/post_processing/__init__.py +10 -2
- spectre_core/post_processing/_base.py +119 -0
- spectre_core/post_processing/{factory.py → _factory.py} +7 -6
- spectre_core/post_processing/{post_processor.py → _post_processor.py} +3 -3
- spectre_core/post_processing/library/_fixed_center_frequency.py +115 -0
- spectre_core/post_processing/library/_swept_center_frequency.py +382 -0
- spectre_core/receivers/__init__.py +13 -2
- spectre_core/receivers/_base.py +180 -0
- spectre_core/receivers/{factory.py → _factory.py} +2 -2
- spectre_core/receivers/_spec_names.py +20 -0
- spectre_core/receivers/gr/__init__.py +3 -0
- spectre_core/receivers/gr/_base.py +33 -0
- spectre_core/receivers/gr/_rsp1a.py +158 -0
- spectre_core/receivers/gr/_rspduo.py +227 -0
- spectre_core/receivers/gr/_test.py +123 -0
- spectre_core/receivers/library/_rsp1a.py +61 -0
- spectre_core/receivers/library/_rspduo.py +69 -0
- spectre_core/receivers/library/_sdrplay_receiver.py +185 -0
- spectre_core/receivers/library/_test.py +221 -0
- spectre_core/spectrograms/__init__.py +18 -0
- spectre_core/spectrograms/{analytical.py → _analytical.py} +29 -27
- spectre_core/spectrograms/{array_operations.py → _array_operations.py} +47 -1
- spectre_core/spectrograms/{spectrogram.py → _spectrogram.py} +62 -35
- spectre_core/spectrograms/{transform.py → _transform.py} +76 -89
- spectre_core/{post_processing/library → wgetting}/__init__.py +4 -5
- spectre_core/wgetting/_callisto.py +155 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/METADATA +1 -1
- spectre_core-0.0.11.dist-info/RECORD +64 -0
- spectre_core/cfg.py +0 -116
- spectre_core/chunks/library/__init__.py +0 -8
- spectre_core/chunks/library/callisto/__init__.py +0 -0
- spectre_core/chunks/library/fixed/__init__.py +0 -0
- spectre_core/chunks/library/sweep/__init__.py +0 -0
- spectre_core/chunks/library/sweep/chunk.py +0 -400
- spectre_core/dynamic_imports.py +0 -22
- spectre_core/file_handlers/base.py +0 -68
- spectre_core/file_handlers/configs.py +0 -271
- spectre_core/file_handlers/json.py +0 -40
- spectre_core/file_handlers/text.py +0 -21
- spectre_core/plotting/factory.py +0 -26
- spectre_core/plotting/format.py +0 -19
- spectre_core/plotting/library/__init__.py +0 -7
- spectre_core/plotting/library/frequency_cuts/panel.py +0 -74
- spectre_core/plotting/library/integral_over_frequency/panel.py +0 -34
- spectre_core/plotting/library/spectrogram/panel.py +0 -92
- spectre_core/plotting/library/time_cuts/panel.py +0 -77
- spectre_core/plotting/panel_register.py +0 -13
- spectre_core/post_processing/base.py +0 -132
- spectre_core/post_processing/library/fixed/__init__.py +0 -0
- spectre_core/post_processing/library/fixed/event_handler.py +0 -40
- spectre_core/post_processing/library/sweep/event_handler.py +0 -54
- spectre_core/receivers/base.py +0 -422
- spectre_core/receivers/library/__init__.py +0 -7
- spectre_core/receivers/library/rsp1a/__init__.py +0 -0
- spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
- spectre_core/receivers/library/rsp1a/gr/fixed.py +0 -104
- spectre_core/receivers/library/rsp1a/gr/sweep.py +0 -129
- spectre_core/receivers/library/rsp1a/receiver.py +0 -68
- spectre_core/receivers/library/rspduo/__init__.py +0 -0
- spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
- spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +0 -114
- spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +0 -131
- spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py +0 -120
- spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py +0 -119
- spectre_core/receivers/library/rspduo/receiver.py +0 -97
- spectre_core/receivers/library/test/__init__.py +0 -0
- spectre_core/receivers/library/test/gr/__init__.py +0 -0
- spectre_core/receivers/library/test/gr/cosine_signal_1.py +0 -83
- spectre_core/receivers/library/test/gr/tagged_staircase.py +0 -93
- spectre_core/receivers/library/test/receiver.py +0 -203
- spectre_core/receivers/validators.py +0 -231
- spectre_core/web_fetch/callisto.py +0 -101
- spectre_core-0.0.9.dist-info/RECORD +0 -74
- /spectre_core/chunks/{chunk_register.py → _register.py} +0 -0
- /spectre_core/post_processing/{event_handler_register.py → _register.py} +0 -0
- /spectre_core/receivers/{receiver_register.py → _register.py} +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/top_level.txt +0 -0
spectre_core/chunks/__init__.py
CHANGED
@@ -2,205 +2,21 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
# decorators run on import
|
6
|
+
from .library._fixed_center_frequency import _Chunk
|
7
|
+
from .library._swept_center_frequency import _Chunk
|
8
|
+
from .library._callisto import _Chunk
|
9
|
+
|
10
|
+
from ._base import BaseChunk, ChunkFile
|
11
|
+
from ._factory import get_chunk_from_tag
|
12
|
+
from ._chunks import Chunks
|
13
|
+
from .library._swept_center_frequency import SweepMetadata
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"BaseChunk",
|
17
|
+
"ChunkFile",
|
18
|
+
"get_chunk_from_tag",
|
19
|
+
"Chunks",
|
20
|
+
"SweepMetadata"
|
21
|
+
]
|
7
22
|
|
8
|
-
import os
|
9
|
-
from typing import Optional
|
10
|
-
from collections import OrderedDict
|
11
|
-
import warnings
|
12
|
-
from datetime import datetime
|
13
|
-
|
14
|
-
# dynamically import all chunks
|
15
|
-
import spectre_core.chunks.library
|
16
|
-
from spectre_core.chunks.factory import get_chunk_from_tag
|
17
|
-
from spectre_core.chunks.base import BaseChunk
|
18
|
-
from spectre_core.spectrograms.spectrogram import Spectrogram
|
19
|
-
from spectre_core.spectrograms import transform
|
20
|
-
from spectre_core.cfg import (
|
21
|
-
DEFAULT_DATETIME_FORMAT,
|
22
|
-
get_chunks_dir_path
|
23
|
-
)
|
24
|
-
from spectre_core.exceptions import (
|
25
|
-
SpectrogramNotFoundError,
|
26
|
-
ChunkNotFoundError
|
27
|
-
)
|
28
|
-
|
29
|
-
class Chunks:
|
30
|
-
def __init__(self,
|
31
|
-
tag: str,
|
32
|
-
year: Optional[int] = None,
|
33
|
-
month: Optional[int] = None,
|
34
|
-
day: Optional[int] = None):
|
35
|
-
self._tag = tag
|
36
|
-
self._Chunk = get_chunk_from_tag(tag)
|
37
|
-
self._chunk_map: dict[str, BaseChunk] = OrderedDict()
|
38
|
-
self._chunk_list: list[BaseChunk] = []
|
39
|
-
self._chunk_names: list[str] = []
|
40
|
-
self.set_date(year, month, day)
|
41
|
-
|
42
|
-
|
43
|
-
@property
|
44
|
-
def year(self) -> int:
|
45
|
-
return self._year
|
46
|
-
|
47
|
-
|
48
|
-
@property
|
49
|
-
def month(self) -> int:
|
50
|
-
return self._month
|
51
|
-
|
52
|
-
|
53
|
-
@property
|
54
|
-
def day(self) -> int:
|
55
|
-
return self._day
|
56
|
-
|
57
|
-
|
58
|
-
@property
|
59
|
-
def chunks_dir_path(self) -> str:
|
60
|
-
return get_chunks_dir_path(self.year, self.month, self.day)
|
61
|
-
|
62
|
-
|
63
|
-
@property
|
64
|
-
def chunk_map(self) -> dict[str, BaseChunk]:
|
65
|
-
return self._chunk_map
|
66
|
-
|
67
|
-
|
68
|
-
@property
|
69
|
-
def chunk_list(self) -> list[BaseChunk]:
|
70
|
-
return self._chunk_list
|
71
|
-
|
72
|
-
|
73
|
-
@property
|
74
|
-
def chunk_names(self) -> list[str]:
|
75
|
-
return self._chunk_names
|
76
|
-
|
77
|
-
|
78
|
-
@property
|
79
|
-
def num_chunks(self) -> int:
|
80
|
-
return len(self.chunk_list)
|
81
|
-
|
82
|
-
|
83
|
-
def set_date(self,
|
84
|
-
year: Optional[int],
|
85
|
-
month: Optional[int],
|
86
|
-
day: Optional[int]) -> None:
|
87
|
-
self._year = year
|
88
|
-
self._month = month
|
89
|
-
self._day = day
|
90
|
-
self._update_chunk_map()
|
91
|
-
|
92
|
-
|
93
|
-
def _update_chunk_map(self) -> None:
|
94
|
-
self._chunk_map = OrderedDict() # reset cache
|
95
|
-
self._chunk_list = [] # reset cache
|
96
|
-
self._chunk_names = [] # reset cache
|
97
|
-
|
98
|
-
chunk_files = [f for (_, _, files) in os.walk(self.chunks_dir_path) for f in files]
|
99
|
-
|
100
|
-
if len(chunk_files) == 0:
|
101
|
-
warning_message = "No chunks found, setting chunk map with empty dictionary."
|
102
|
-
_LOGGER.warning(warning_message)
|
103
|
-
warnings.warn(warning_message)
|
104
|
-
return
|
105
|
-
|
106
|
-
for chunk_file in chunk_files:
|
107
|
-
file_name, _ = os.path.splitext(chunk_file)
|
108
|
-
chunk_start_time, tag = file_name.split("_", 1)
|
109
|
-
if tag == self._tag:
|
110
|
-
self._chunk_map[chunk_start_time] = self._Chunk(chunk_start_time, tag)
|
111
|
-
|
112
|
-
self._chunk_map = OrderedDict(sorted(self._chunk_map.items()))
|
113
|
-
self._chunk_names = list(self._chunk_map.keys())
|
114
|
-
self._chunk_list = list(self._chunk_map.values())
|
115
|
-
|
116
|
-
|
117
|
-
def update(self) -> None:
|
118
|
-
"""Public alias for setting chunk map"""
|
119
|
-
self._update_chunk_map()
|
120
|
-
|
121
|
-
|
122
|
-
def __iter__(self):
|
123
|
-
yield from self.chunk_list
|
124
|
-
|
125
|
-
|
126
|
-
def _get_chunk_by_chunk_start_time(self,
|
127
|
-
chunk_start_time: str) -> BaseChunk:
|
128
|
-
try:
|
129
|
-
return self.chunk_map[chunk_start_time]
|
130
|
-
except KeyError:
|
131
|
-
raise ChunkNotFoundError(f"Chunk with chunk start time {chunk_start_time} could not be found within {self.chunks_dir_path}")
|
132
|
-
|
133
|
-
|
134
|
-
def _get_chunk_by_index(self,
|
135
|
-
chunk_index: int) -> BaseChunk:
|
136
|
-
num_chunks = len(self.chunk_map)
|
137
|
-
if num_chunks == 0:
|
138
|
-
raise ChunkNotFoundError("No chunks are available")
|
139
|
-
index = chunk_index % num_chunks # Use modulo to make the index wrap around. Allows the user to iterate over all the chunks via index cyclically.
|
140
|
-
return self.chunk_list[index]
|
141
|
-
|
142
|
-
|
143
|
-
def __getitem__(self, subscript: str | int):
|
144
|
-
if isinstance(subscript, str):
|
145
|
-
return self._get_chunk_by_chunk_start_time(subscript)
|
146
|
-
elif isinstance(subscript, int):
|
147
|
-
return self._get_chunk_by_index(subscript)
|
148
|
-
|
149
|
-
|
150
|
-
def get_index_by_chunk(self,
|
151
|
-
chunk_to_match: BaseChunk) -> int:
|
152
|
-
for i, chunk in enumerate(self):
|
153
|
-
if chunk.chunk_start_time == chunk_to_match.chunk_start_time:
|
154
|
-
return i
|
155
|
-
raise ChunkNotFoundError(f"No matching chunk found for chunk {chunk_to_match.chunk_name}")
|
156
|
-
|
157
|
-
|
158
|
-
def count_chunk_files(self,
|
159
|
-
extension: str) -> int:
|
160
|
-
return sum(1 for chunk_file in self if chunk_file.has_file(extension))
|
161
|
-
|
162
|
-
|
163
|
-
def get_spectrogram_from_range(self,
|
164
|
-
start_time: str,
|
165
|
-
end_time: str) -> Spectrogram:
|
166
|
-
# Convert input strings to datetime objects
|
167
|
-
start_datetime = datetime.strptime(start_time, DEFAULT_DATETIME_FORMAT)
|
168
|
-
end_datetime = datetime.strptime(end_time, DEFAULT_DATETIME_FORMAT)
|
169
|
-
|
170
|
-
if start_datetime.day != end_datetime.day:
|
171
|
-
warning_message = "Joining spectrograms across multiple days"
|
172
|
-
_LOGGER.warning(warning_message)
|
173
|
-
warnings.warn(warning_message, RuntimeWarning)
|
174
|
-
|
175
|
-
spectrograms = []
|
176
|
-
num_fits_chunks = self.count_chunk_files("fits")
|
177
|
-
|
178
|
-
for i, chunk in enumerate(self):
|
179
|
-
# skip chunks without fits files
|
180
|
-
if not chunk.has_file("fits"):
|
181
|
-
continue
|
182
|
-
|
183
|
-
# rather than reading all files to evaluate the actual upper bound to their time range (slow)
|
184
|
-
# place an upper bound by using the chunk start datetime for the next chunk
|
185
|
-
# this assumes that the chunks are non-overlapping (reasonable assumption)
|
186
|
-
lower_bound = chunk.chunk_start_datetime
|
187
|
-
if i < num_fits_chunks:
|
188
|
-
next_chunk = self[i + 1]
|
189
|
-
upper_bound = next_chunk.chunk_start_datetime
|
190
|
-
# if there is no "next chunk" then we do have to read the file
|
191
|
-
else:
|
192
|
-
fits_chunk = chunk.get_file("fits")
|
193
|
-
upper_bound = fits_chunk.datetimes[-1]
|
194
|
-
|
195
|
-
# if the chunk overlaps with the input time range, then read the fits file
|
196
|
-
if start_datetime <= upper_bound and lower_bound <= end_datetime:
|
197
|
-
spectrogram = chunk.read_file("fits")
|
198
|
-
spectrogram = transform.time_chop(spectrogram, start_time, end_time)
|
199
|
-
# if we have a non-empty spectrogram, append it to the list of spectrograms
|
200
|
-
if spectrogram:
|
201
|
-
spectrograms.append(spectrogram)
|
202
|
-
|
203
|
-
if spectrograms:
|
204
|
-
return transform.join_spectrograms(spectrograms)
|
205
|
-
else:
|
206
|
-
raise SpectrogramNotFoundError("No spectrogram data found for the given time range")
|
@@ -3,16 +3,10 @@
|
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
5
|
from datetime import datetime
|
6
|
-
from abc import abstractmethod
|
7
6
|
from typing import Optional
|
8
7
|
|
9
|
-
from
|
10
|
-
|
11
|
-
from spectre_core.file_handlers.base import BaseFileHandler
|
12
|
-
from spectre_core.cfg import get_chunks_dir_path
|
13
|
-
from spectre_core.file_handlers.configs import CaptureConfig
|
14
|
-
from spectre_core.spectrograms.spectrogram import Spectrogram
|
15
|
-
from spectre_core.cfg import DEFAULT_DATETIME_FORMAT
|
8
|
+
from spectre_core._file_io import BaseFileHandler
|
9
|
+
from spectre_core.config import get_chunks_dir_path, TimeFormats
|
16
10
|
from spectre_core.exceptions import ChunkFileNotFoundError
|
17
11
|
|
18
12
|
class ChunkFile(BaseFileHandler):
|
@@ -35,15 +29,15 @@ class ChunkFile(BaseFileHandler):
|
|
35
29
|
|
36
30
|
|
37
31
|
@property
|
38
|
-
def
|
39
|
-
|
40
|
-
self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, DEFAULT_DATETIME_FORMAT)
|
41
|
-
return self._chunk_start_datetime
|
32
|
+
def tag(self) -> str:
|
33
|
+
return self._tag
|
42
34
|
|
43
35
|
|
44
36
|
@property
|
45
|
-
def
|
46
|
-
|
37
|
+
def chunk_start_datetime(self) -> datetime:
|
38
|
+
if self._chunk_start_datetime is None:
|
39
|
+
self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, TimeFormats.DATETIME)
|
40
|
+
return self._chunk_start_datetime
|
47
41
|
|
48
42
|
|
49
43
|
|
@@ -64,18 +58,18 @@ class BaseChunk:
|
|
64
58
|
@property
|
65
59
|
def chunk_start_time(self) -> str:
|
66
60
|
return self._chunk_start_time
|
67
|
-
|
68
61
|
|
69
|
-
@property
|
70
|
-
def chunk_start_datetime(self) -> datetime:
|
71
|
-
if self._chunk_start_datetime is None:
|
72
|
-
self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, DEFAULT_DATETIME_FORMAT)
|
73
|
-
return self._chunk_start_datetime
|
74
|
-
|
75
62
|
|
76
63
|
@property
|
77
64
|
def tag(self) -> str:
|
78
65
|
return self._tag
|
66
|
+
|
67
|
+
|
68
|
+
@property
|
69
|
+
def chunk_start_datetime(self) -> datetime:
|
70
|
+
if self._chunk_start_datetime is None:
|
71
|
+
self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, TimeFormats.DATETIME)
|
72
|
+
return self._chunk_start_datetime
|
79
73
|
|
80
74
|
|
81
75
|
@property
|
@@ -120,42 +114,3 @@ class BaseChunk:
|
|
120
114
|
return False
|
121
115
|
|
122
116
|
|
123
|
-
class SPECTREChunk(BaseChunk):
|
124
|
-
def __init__(self, *args, **kwargs):
|
125
|
-
super().__init__(*args, **kwargs)
|
126
|
-
|
127
|
-
self._capture_config = CaptureConfig(self._tag)
|
128
|
-
self._SFT = None # cache
|
129
|
-
|
130
|
-
|
131
|
-
@abstractmethod
|
132
|
-
def build_spectrogram(self) -> Spectrogram:
|
133
|
-
"""Create a spectrogram object derived from chunk files for this chunk."""
|
134
|
-
pass
|
135
|
-
|
136
|
-
|
137
|
-
@property
|
138
|
-
def capture_config(self) -> CaptureConfig:
|
139
|
-
return self._capture_config
|
140
|
-
|
141
|
-
|
142
|
-
@property
|
143
|
-
def SFT(self) -> ShortTimeFFT:
|
144
|
-
if self._SFT is None:
|
145
|
-
self._SFT = self.__get_SFT_instance()
|
146
|
-
return self._SFT
|
147
|
-
|
148
|
-
|
149
|
-
def __get_SFT_instance(self) -> ShortTimeFFT:
|
150
|
-
hop = self.capture_config.get("hop")
|
151
|
-
window_type = self.capture_config.get("window_type")
|
152
|
-
window_params = self.capture_config.get("window_kwargs").values()
|
153
|
-
window_size = self.capture_config.get("window_size")
|
154
|
-
window = get_window((window_type,
|
155
|
-
*window_params),
|
156
|
-
window_size)
|
157
|
-
samp_rate = self.capture_config.get("samp_rate")
|
158
|
-
return ShortTimeFFT(window,
|
159
|
-
hop,
|
160
|
-
samp_rate,
|
161
|
-
fft_mode = "centered")
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
from logging import getLogger
|
6
|
+
_LOGGER = getLogger(__name__)
|
7
|
+
|
8
|
+
import os
|
9
|
+
from typing import Optional
|
10
|
+
from collections import OrderedDict
|
11
|
+
import warnings
|
12
|
+
from datetime import datetime
|
13
|
+
|
14
|
+
from spectre_core.spectrograms import Spectrogram, time_chop, join_spectrograms
|
15
|
+
from spectre_core.config import get_chunks_dir_path, TimeFormats
|
16
|
+
from spectre_core.exceptions import (
|
17
|
+
SpectrogramNotFoundError,
|
18
|
+
ChunkNotFoundError
|
19
|
+
)
|
20
|
+
from ._base import BaseChunk
|
21
|
+
from ._factory import get_chunk_from_tag
|
22
|
+
|
23
|
+
class Chunks:
|
24
|
+
def __init__(self,
|
25
|
+
tag: str,
|
26
|
+
year: Optional[int] = None,
|
27
|
+
month: Optional[int] = None,
|
28
|
+
day: Optional[int] = None):
|
29
|
+
self._tag = tag
|
30
|
+
self._Chunk = get_chunk_from_tag(tag)
|
31
|
+
self._chunk_map: dict[str, BaseChunk] = OrderedDict()
|
32
|
+
self._chunk_list: list[BaseChunk] = []
|
33
|
+
self._chunk_names: list[str] = []
|
34
|
+
self.set_date(year, month, day)
|
35
|
+
|
36
|
+
|
37
|
+
@property
|
38
|
+
def year(self) -> int:
|
39
|
+
return self._year
|
40
|
+
|
41
|
+
|
42
|
+
@property
|
43
|
+
def month(self) -> int:
|
44
|
+
return self._month
|
45
|
+
|
46
|
+
|
47
|
+
@property
|
48
|
+
def day(self) -> int:
|
49
|
+
return self._day
|
50
|
+
|
51
|
+
|
52
|
+
@property
|
53
|
+
def chunks_dir_path(self) -> str:
|
54
|
+
return get_chunks_dir_path(self.year, self.month, self.day)
|
55
|
+
|
56
|
+
|
57
|
+
@property
|
58
|
+
def chunk_map(self) -> dict[str, BaseChunk]:
|
59
|
+
return self._chunk_map
|
60
|
+
|
61
|
+
|
62
|
+
@property
|
63
|
+
def chunk_list(self) -> list[BaseChunk]:
|
64
|
+
return self._chunk_list
|
65
|
+
|
66
|
+
|
67
|
+
@property
|
68
|
+
def chunk_names(self) -> list[str]:
|
69
|
+
return self._chunk_names
|
70
|
+
|
71
|
+
|
72
|
+
@property
|
73
|
+
def num_chunks(self) -> int:
|
74
|
+
return len(self.chunk_list)
|
75
|
+
|
76
|
+
|
77
|
+
def set_date(self,
|
78
|
+
year: Optional[int],
|
79
|
+
month: Optional[int],
|
80
|
+
day: Optional[int]) -> None:
|
81
|
+
self._year = year
|
82
|
+
self._month = month
|
83
|
+
self._day = day
|
84
|
+
self._update_chunk_map()
|
85
|
+
|
86
|
+
|
87
|
+
def _update_chunk_map(self) -> None:
|
88
|
+
self._chunk_map = OrderedDict() # reset cache
|
89
|
+
self._chunk_list = [] # reset cache
|
90
|
+
self._chunk_names = [] # reset cache
|
91
|
+
|
92
|
+
chunk_files = [f for (_, _, files) in os.walk(self.chunks_dir_path) for f in files]
|
93
|
+
|
94
|
+
if len(chunk_files) == 0:
|
95
|
+
warning_message = "No chunks found, setting chunk map with empty dictionary."
|
96
|
+
_LOGGER.warning(warning_message)
|
97
|
+
warnings.warn(warning_message)
|
98
|
+
return
|
99
|
+
|
100
|
+
for chunk_file in chunk_files:
|
101
|
+
file_name, _ = os.path.splitext(chunk_file)
|
102
|
+
chunk_start_time, tag = file_name.split("_", 1)
|
103
|
+
if tag == self._tag:
|
104
|
+
self._chunk_map[chunk_start_time] = self._Chunk(chunk_start_time, tag)
|
105
|
+
|
106
|
+
self._chunk_map = OrderedDict(sorted(self._chunk_map.items()))
|
107
|
+
self._chunk_names = list(self._chunk_map.keys())
|
108
|
+
self._chunk_list = list(self._chunk_map.values())
|
109
|
+
|
110
|
+
|
111
|
+
def update(self) -> None:
|
112
|
+
"""Public alias for setting chunk map"""
|
113
|
+
self._update_chunk_map()
|
114
|
+
|
115
|
+
|
116
|
+
def __iter__(self):
|
117
|
+
yield from self.chunk_list
|
118
|
+
|
119
|
+
|
120
|
+
def _get_chunk_by_chunk_start_time(self,
|
121
|
+
chunk_start_time: str) -> BaseChunk:
|
122
|
+
try:
|
123
|
+
return self.chunk_map[chunk_start_time]
|
124
|
+
except KeyError:
|
125
|
+
raise ChunkNotFoundError(f"Chunk with chunk start time {chunk_start_time} could not be found within {self.chunks_dir_path}")
|
126
|
+
|
127
|
+
|
128
|
+
def _get_chunk_by_index(self,
|
129
|
+
chunk_index: int) -> BaseChunk:
|
130
|
+
num_chunks = len(self.chunk_map)
|
131
|
+
if num_chunks == 0:
|
132
|
+
raise ChunkNotFoundError("No chunks are available")
|
133
|
+
index = chunk_index % num_chunks # Use modulo to make the index wrap around. Allows the user to iterate over all the chunks via index cyclically.
|
134
|
+
return self.chunk_list[index]
|
135
|
+
|
136
|
+
|
137
|
+
def __getitem__(self, subscript: str | int):
|
138
|
+
if isinstance(subscript, str):
|
139
|
+
return self._get_chunk_by_chunk_start_time(subscript)
|
140
|
+
elif isinstance(subscript, int):
|
141
|
+
return self._get_chunk_by_index(subscript)
|
142
|
+
|
143
|
+
|
144
|
+
def get_index_by_chunk(self,
|
145
|
+
chunk_to_match: BaseChunk) -> int:
|
146
|
+
for i, chunk in enumerate(self):
|
147
|
+
if chunk.chunk_start_time == chunk_to_match.chunk_start_time:
|
148
|
+
return i
|
149
|
+
raise ChunkNotFoundError(f"No matching chunk found for chunk {chunk_to_match.chunk_name}")
|
150
|
+
|
151
|
+
|
152
|
+
def count_chunk_files(self,
|
153
|
+
extension: str) -> int:
|
154
|
+
return sum(1 for chunk_file in self if chunk_file.has_file(extension))
|
155
|
+
|
156
|
+
|
157
|
+
def get_spectrogram_from_range(self,
|
158
|
+
start_time: str,
|
159
|
+
end_time: str) -> Spectrogram:
|
160
|
+
# Convert input strings to datetime objects
|
161
|
+
start_datetime = datetime.strptime(start_time, TimeFormats.DATETIME)
|
162
|
+
end_datetime = datetime.strptime(end_time, TimeFormats.DATETIME)
|
163
|
+
|
164
|
+
if start_datetime.day != end_datetime.day:
|
165
|
+
warning_message = "Joining spectrograms across multiple days"
|
166
|
+
_LOGGER.warning(warning_message)
|
167
|
+
warnings.warn(warning_message, RuntimeWarning)
|
168
|
+
|
169
|
+
spectrograms = []
|
170
|
+
num_fits_chunks = self.count_chunk_files("fits")
|
171
|
+
|
172
|
+
for i, chunk in enumerate(self):
|
173
|
+
# skip chunks without fits files
|
174
|
+
if not chunk.has_file("fits"):
|
175
|
+
continue
|
176
|
+
|
177
|
+
# rather than reading all files to evaluate the actual upper bound to their time range (slow)
|
178
|
+
# place an upper bound by using the chunk start datetime for the next chunk
|
179
|
+
# this assumes that the chunks are non-overlapping (reasonable assumption)
|
180
|
+
lower_bound = chunk.chunk_start_datetime
|
181
|
+
if i < num_fits_chunks:
|
182
|
+
next_chunk = self[i + 1]
|
183
|
+
upper_bound = next_chunk.chunk_start_datetime
|
184
|
+
# if there is no "next chunk" then we do have to read the file
|
185
|
+
else:
|
186
|
+
fits_chunk = chunk.get_file("fits")
|
187
|
+
upper_bound = fits_chunk.datetimes[-1]
|
188
|
+
|
189
|
+
# if the chunk overlaps with the input time range, then read the fits file
|
190
|
+
if start_datetime <= upper_bound and lower_bound <= end_datetime:
|
191
|
+
spectrogram = chunk.read_file("fits")
|
192
|
+
spectrogram = time_chop(spectrogram, start_time, end_time)
|
193
|
+
# if we have a non-empty spectrogram, append it to the list of spectrograms
|
194
|
+
if spectrogram:
|
195
|
+
spectrograms.append(spectrogram)
|
196
|
+
|
197
|
+
if spectrograms:
|
198
|
+
return join_spectrograms(spectrograms)
|
199
|
+
else:
|
200
|
+
raise SpectrogramNotFoundError("No spectrogram data found for the given time range")
|
@@ -2,13 +2,12 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
|
6
|
-
from spectre_core.chunks.base import BaseChunk
|
7
|
-
from spectre_core.file_handlers.configs import CaptureConfig
|
5
|
+
from spectre_core.capture_configs import CaptureConfig, PNames
|
8
6
|
from spectre_core.exceptions import ChunkNotFoundError
|
9
|
-
from
|
7
|
+
from ._register import chunk_map
|
8
|
+
from ._base import BaseChunk
|
10
9
|
|
11
|
-
def
|
10
|
+
def _get_chunk(chunk_key: str) -> BaseChunk:
|
12
11
|
Chunk = chunk_map.get(chunk_key)
|
13
12
|
if Chunk is None:
|
14
13
|
valid_chunk_keys = list(chunk_map.keys())
|
@@ -22,5 +21,5 @@ def get_chunk_from_tag(tag: str) -> BaseChunk:
|
|
22
21
|
# otherwise, we fetch the chunk key from the capture config
|
23
22
|
else:
|
24
23
|
capture_config= CaptureConfig(tag)
|
25
|
-
chunk_key = capture_config.
|
26
|
-
return
|
24
|
+
chunk_key = capture_config.get_parameter_value(PNames.CHUNK_KEY)
|
25
|
+
return _get_chunk(chunk_key)
|
@@ -11,16 +11,13 @@ from astropy.io.fits.hdu.image import PrimaryHDU
|
|
11
11
|
from astropy.io.fits.hdu.table import BinTableHDU
|
12
12
|
from astropy.io.fits.hdu.hdulist import HDUList
|
13
13
|
|
14
|
-
from spectre_core.
|
15
|
-
from
|
16
|
-
from
|
17
|
-
BaseChunk,
|
18
|
-
ChunkFile
|
19
|
-
)
|
14
|
+
from spectre_core.spectrograms import Spectrogram
|
15
|
+
from .._register import register_chunk
|
16
|
+
from .._base import BaseChunk, ChunkFile
|
20
17
|
|
21
18
|
|
22
19
|
@register_chunk('callisto')
|
23
|
-
class
|
20
|
+
class _Chunk(BaseChunk):
|
24
21
|
def __init__(self, chunk_start_time: str, tag: str):
|
25
22
|
super().__init__(chunk_start_time, tag)
|
26
23
|
self.add_file(FitsChunk(self.chunk_parent_path, self.chunk_name))
|
@@ -11,15 +11,13 @@ from astropy.io.fits.hdu.image import PrimaryHDU
|
|
11
11
|
from astropy.io.fits.hdu.table import BinTableHDU
|
12
12
|
from astropy.io.fits.hdu.hdulist import HDUList
|
13
13
|
|
14
|
-
from spectre_core.
|
15
|
-
from spectre_core.
|
16
|
-
from
|
17
|
-
|
18
|
-
|
19
|
-
)
|
20
|
-
|
21
|
-
@register_chunk("fixed")
|
22
|
-
class Chunk(SPECTREChunk):
|
14
|
+
from spectre_core.spectrograms import Spectrogram
|
15
|
+
from spectre_core.capture_configs import CaptureModes
|
16
|
+
from .._register import register_chunk
|
17
|
+
from .._base import BaseChunk, ChunkFile
|
18
|
+
|
19
|
+
@register_chunk(CaptureModes.FIXED_CENTER_FREQUENCY)
|
20
|
+
class _Chunk(BaseChunk):
|
23
21
|
def __init__(self, *args, **kwargs):
|
24
22
|
super().__init__(*args, **kwargs)
|
25
23
|
|
@@ -28,61 +26,6 @@ class Chunk(SPECTREChunk):
|
|
28
26
|
self.add_file(HdrChunk(self.chunk_parent_path, self.chunk_name))
|
29
27
|
|
30
28
|
|
31
|
-
def build_spectrogram(self) -> Spectrogram:
|
32
|
-
"""Create a spectrogram by performing a Short Time FFT on the IQ samples for this chunk."""
|
33
|
-
iq_data = self.read_file("bin")
|
34
|
-
millisecond_correction = self.read_file("hdr")
|
35
|
-
|
36
|
-
# units conversion
|
37
|
-
microsecond_correction = millisecond_correction * 1e3
|
38
|
-
|
39
|
-
times, frequencies, dynamic_spectra = self.__do_STFFT(iq_data)
|
40
|
-
|
41
|
-
# explicitly type cast data arrays to 32-bit floats
|
42
|
-
times = np.array(times, dtype = 'float32')
|
43
|
-
frequencies = np.array(frequencies, dtype = 'float32')
|
44
|
-
dynamic_spectra = np.array(dynamic_spectra, dtype = 'float32')
|
45
|
-
|
46
|
-
return Spectrogram(dynamic_spectra,
|
47
|
-
times,
|
48
|
-
frequencies,
|
49
|
-
self.tag,
|
50
|
-
chunk_start_time = self.chunk_start_time,
|
51
|
-
microsecond_correction = microsecond_correction,
|
52
|
-
spectrum_type = "amplitude")
|
53
|
-
|
54
|
-
|
55
|
-
def __do_STFFT(self,
|
56
|
-
iq_data: np.array) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
57
|
-
"""For reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.ShortTimeFFT.html"""
|
58
|
-
|
59
|
-
# set p0=0, since by convention in the STFFT docs, p=0 corresponds to the slice centred at t=0
|
60
|
-
p0=0
|
61
|
-
|
62
|
-
# set p1 to the index of the first slice where the "midpoint" of the window is still inside the signal
|
63
|
-
num_samples = len(iq_data)
|
64
|
-
p1 = self.SFT.upper_border_begin(num_samples)[1]
|
65
|
-
|
66
|
-
# compute a ShortTimeFFT on the IQ samples
|
67
|
-
complex_spectra = self.SFT.stft(iq_data,
|
68
|
-
p0 = p0,
|
69
|
-
p1 = p1)
|
70
|
-
|
71
|
-
# compute the magnitude of each spectral component
|
72
|
-
dynamic_spectra = np.abs(complex_spectra)
|
73
|
-
|
74
|
-
|
75
|
-
# assign a physical time to each spectrum
|
76
|
-
times = self.SFT.t(num_samples,
|
77
|
-
p0 = 0,
|
78
|
-
p1 = p1)
|
79
|
-
|
80
|
-
# assign physical frequencies to each spectral component
|
81
|
-
frequencies = self.SFT.f + self.capture_config.get('center_freq', 0.0)
|
82
|
-
|
83
|
-
return times, frequencies, dynamic_spectra
|
84
|
-
|
85
|
-
|
86
29
|
class BinChunk(ChunkFile):
|
87
30
|
def __init__(self, chunk_parent_path: str, chunk_name: str):
|
88
31
|
super().__init__(chunk_parent_path, chunk_name, "bin")
|