spectre-core 0.0.11__py3-none-any.whl → 0.0.12__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/file_handlers.py +12 -12
- spectre_core/batches/__init__.py +22 -0
- spectre_core/batches/_base.py +146 -0
- spectre_core/batches/_batches.py +197 -0
- spectre_core/batches/_factory.py +27 -0
- spectre_core/{chunks → batches}/_register.py +5 -5
- spectre_core/{chunks → batches}/library/_callisto.py +31 -33
- spectre_core/{chunks → batches}/library/_fixed_center_frequency.py +43 -38
- spectre_core/{chunks → batches}/library/_swept_center_frequency.py +22 -20
- spectre_core/capture_configs/_capture_templates.py +6 -6
- spectre_core/capture_configs/_parameters.py +3 -6
- spectre_core/capture_configs/_ptemplates.py +3 -3
- spectre_core/capture_configs/_pvalidators.py +4 -4
- spectre_core/config/__init__.py +2 -2
- spectre_core/config/_paths.py +5 -5
- spectre_core/config/_time_formats.py +5 -3
- spectre_core/exceptions.py +2 -2
- spectre_core/logging/_configure.py +1 -1
- spectre_core/logging/_log_handlers.py +1 -1
- spectre_core/plotting/_panels.py +1 -1
- spectre_core/post_processing/__init__.py +2 -2
- spectre_core/post_processing/_base.py +5 -5
- spectre_core/post_processing/_factory.py +3 -3
- spectre_core/post_processing/_post_processor.py +5 -5
- spectre_core/post_processing/library/_fixed_center_frequency.py +24 -25
- spectre_core/post_processing/library/_swept_center_frequency.py +68 -83
- spectre_core/receivers/gr/_base.py +1 -1
- spectre_core/receivers/gr/_rsp1a.py +3 -3
- spectre_core/receivers/gr/_rspduo.py +4 -4
- spectre_core/receivers/gr/_test.py +3 -3
- spectre_core/receivers/library/_test.py +3 -3
- spectre_core/spectrograms/_analytical.py +0 -6
- spectre_core/spectrograms/_spectrogram.py +113 -79
- spectre_core/spectrograms/_transform.py +19 -36
- spectre_core/wgetting/_callisto.py +20 -24
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/METADATA +1 -1
- spectre_core-0.0.12.dist-info/RECORD +64 -0
- spectre_core/chunks/__init__.py +0 -22
- spectre_core/chunks/_base.py +0 -116
- spectre_core/chunks/_chunks.py +0 -200
- spectre_core/chunks/_factory.py +0 -25
- spectre_core-0.0.11.dist-info/RECORD +0 -64
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/top_level.txt +0 -0
@@ -10,10 +10,10 @@ from typing import Any, Optional
|
|
10
10
|
|
11
11
|
class BaseFileHandler(ABC):
|
12
12
|
def __init__(self,
|
13
|
-
|
13
|
+
parent_dir_path: str,
|
14
14
|
base_file_name: str,
|
15
15
|
extension: Optional[str] = None):
|
16
|
-
self.
|
16
|
+
self._parent_dir_path = parent_dir_path
|
17
17
|
self._base_file_name = base_file_name
|
18
18
|
self._extension = extension
|
19
19
|
|
@@ -24,8 +24,8 @@ class BaseFileHandler(ABC):
|
|
24
24
|
|
25
25
|
|
26
26
|
@property
|
27
|
-
def
|
28
|
-
return self.
|
27
|
+
def parent_dir_path(self) -> str:
|
28
|
+
return self._parent_dir_path
|
29
29
|
|
30
30
|
|
31
31
|
@property
|
@@ -45,7 +45,7 @@ class BaseFileHandler(ABC):
|
|
45
45
|
|
46
46
|
@property
|
47
47
|
def file_path(self) -> str:
|
48
|
-
return os.path.join(self.
|
48
|
+
return os.path.join(self._parent_dir_path, self.file_name)
|
49
49
|
|
50
50
|
|
51
51
|
@property
|
@@ -53,8 +53,8 @@ class BaseFileHandler(ABC):
|
|
53
53
|
return os.path.exists(self.file_path)
|
54
54
|
|
55
55
|
|
56
|
-
def
|
57
|
-
os.makedirs(self.
|
56
|
+
def make_parent_dir_path(self) -> None:
|
57
|
+
os.makedirs(self.parent_dir_path, exist_ok=True)
|
58
58
|
|
59
59
|
|
60
60
|
def delete(self,
|
@@ -71,13 +71,13 @@ class BaseFileHandler(ABC):
|
|
71
71
|
|
72
72
|
class JsonHandler(BaseFileHandler):
|
73
73
|
def __init__(self,
|
74
|
-
|
74
|
+
parent_dir_path: str,
|
75
75
|
base_file_name: str,
|
76
76
|
extension: str = "json",
|
77
77
|
**kwargs):
|
78
78
|
|
79
79
|
self._dict = None # cache
|
80
|
-
super().__init__(
|
80
|
+
super().__init__(parent_dir_path,
|
81
81
|
base_file_name,
|
82
82
|
extension,
|
83
83
|
**kwargs)
|
@@ -91,7 +91,7 @@ class JsonHandler(BaseFileHandler):
|
|
91
91
|
def save(self,
|
92
92
|
d: dict,
|
93
93
|
force: bool = False) -> None:
|
94
|
-
self.
|
94
|
+
self.make_parent_dir_path()
|
95
95
|
|
96
96
|
if self.exists:
|
97
97
|
if force:
|
@@ -113,11 +113,11 @@ class JsonHandler(BaseFileHandler):
|
|
113
113
|
|
114
114
|
class TextHandler(BaseFileHandler):
|
115
115
|
def __init__(self,
|
116
|
-
|
116
|
+
parent_dir_path: str,
|
117
117
|
base_file_name: str,
|
118
118
|
extension: str = "txt",
|
119
119
|
**kwargs):
|
120
|
-
super().__init__(
|
120
|
+
super().__init__(parent_dir_path,
|
121
121
|
base_file_name,
|
122
122
|
extension,
|
123
123
|
**kwargs)
|
@@ -0,0 +1,22 @@
|
|
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
|
+
# register decorators are actioned on import
|
6
|
+
from .library._fixed_center_frequency import _Batch
|
7
|
+
from .library._swept_center_frequency import _Batch
|
8
|
+
from .library._callisto import _Batch
|
9
|
+
|
10
|
+
from ._base import BaseBatch, BatchFile
|
11
|
+
from ._factory import get_batch_cls_from_tag
|
12
|
+
from ._batches import Batches
|
13
|
+
from .library._swept_center_frequency import SweepMetadata
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"BaseBatch",
|
17
|
+
"BatchFile",
|
18
|
+
"get_batch_cls_from_tag",
|
19
|
+
"Batches",
|
20
|
+
"SweepMetadata"
|
21
|
+
]
|
22
|
+
|
@@ -0,0 +1,146 @@
|
|
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 Optional
|
7
|
+
|
8
|
+
from spectre_core._file_io import BaseFileHandler
|
9
|
+
from spectre_core.config import get_batches_dir_path, TimeFormats
|
10
|
+
from spectre_core.exceptions import BatchFileNotFoundError
|
11
|
+
|
12
|
+
|
13
|
+
class BatchFile(BaseFileHandler):
|
14
|
+
"""A specific file in a given batch, uniquely identified in the batch by the file extension."""
|
15
|
+
def __init__(self,
|
16
|
+
batch_parent_dir_path: str,
|
17
|
+
batch_name: str,
|
18
|
+
extension: str):
|
19
|
+
super().__init__(batch_parent_dir_path,
|
20
|
+
batch_name,
|
21
|
+
extension)
|
22
|
+
self._start_time, self._tag = batch_name.split("_")
|
23
|
+
# computed if required
|
24
|
+
self._start_datetime: Optional[datetime] = None
|
25
|
+
|
26
|
+
|
27
|
+
@property
|
28
|
+
def start_time(self) -> str:
|
29
|
+
"""The start time of the batch file, up to seconds precision."""
|
30
|
+
return self._start_time
|
31
|
+
|
32
|
+
|
33
|
+
@property
|
34
|
+
def start_datetime(self) -> datetime:
|
35
|
+
"""The datetime of the batch file, up to seconds precision."""
|
36
|
+
if self._start_datetime is None:
|
37
|
+
self._start_datetime = datetime.strptime(self.start_time, TimeFormats.DATETIME)
|
38
|
+
return self._start_datetime
|
39
|
+
|
40
|
+
|
41
|
+
@property
|
42
|
+
def tag(self) -> str:
|
43
|
+
"""The tag identifier for the batch file."""
|
44
|
+
return self._tag
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
class BaseBatch:
|
49
|
+
"""A group of one or more files which share a common start time and a tag identifier.
|
50
|
+
|
51
|
+
All files belonging to the same batch will share a batch name, and differ
|
52
|
+
only in their file extension.
|
53
|
+
"""
|
54
|
+
def __init__(self,
|
55
|
+
start_time: str,
|
56
|
+
tag: str):
|
57
|
+
self._start_time = start_time
|
58
|
+
self._tag: str = tag
|
59
|
+
self._batch_files: dict[str, BatchFile] = {}
|
60
|
+
self._start_datetime = datetime.strptime(self.start_time, TimeFormats.DATETIME)
|
61
|
+
self._parent_dir_path = get_batches_dir_path(year = self.start_datetime.year,
|
62
|
+
month = self.start_datetime.month,
|
63
|
+
day = self.start_datetime.day)
|
64
|
+
|
65
|
+
|
66
|
+
@property
|
67
|
+
def start_time(self) -> str:
|
68
|
+
"""The start time of the batch, up to seconds precision."""
|
69
|
+
return self._start_time
|
70
|
+
|
71
|
+
|
72
|
+
@property
|
73
|
+
def tag(self) -> str:
|
74
|
+
"""The tag identifier of for the batch."""
|
75
|
+
return self._tag
|
76
|
+
|
77
|
+
|
78
|
+
@property
|
79
|
+
def start_datetime(self) -> datetime:
|
80
|
+
"""The datetime of the batch file, up to seconds precision."""
|
81
|
+
return self._start_datetime
|
82
|
+
|
83
|
+
|
84
|
+
@property
|
85
|
+
def parent_dir_path(self) -> str:
|
86
|
+
"""The parent directory for the batch."""
|
87
|
+
return self._parent_dir_path
|
88
|
+
|
89
|
+
|
90
|
+
@property
|
91
|
+
def name(self) -> str:
|
92
|
+
"""The name of the batch."""
|
93
|
+
return f"{self._start_time}_{self._tag}"
|
94
|
+
|
95
|
+
|
96
|
+
@property
|
97
|
+
def extensions(self) -> list[str]:
|
98
|
+
"""All defined file extensions for the batch."""
|
99
|
+
return list(self._batch_files.keys())
|
100
|
+
|
101
|
+
@property
|
102
|
+
def batch_files(self) -> dict[str, BatchFile]:
|
103
|
+
"""Map each file extension in the batch to the corresponding batch file instance."""
|
104
|
+
return self._batch_files
|
105
|
+
|
106
|
+
|
107
|
+
def add_file(self, batch_file: BatchFile) -> None:
|
108
|
+
"""Add an instance of a batch file to the batch."""
|
109
|
+
self._batch_files[batch_file.extension] = batch_file
|
110
|
+
|
111
|
+
|
112
|
+
def get_file(self, extension: str) -> BatchFile:
|
113
|
+
"""Get a batch file instance from the batch, according to the file extension."""
|
114
|
+
try:
|
115
|
+
return self._batch_files[extension]
|
116
|
+
except KeyError:
|
117
|
+
raise BatchFileNotFoundError(f"No batch file found with extension '{extension}'")
|
118
|
+
|
119
|
+
|
120
|
+
def read_file(self, extension: str):
|
121
|
+
"""Read a file from the batch, according to the file extension."""
|
122
|
+
batch_file = self.get_file(extension)
|
123
|
+
return batch_file.read()
|
124
|
+
|
125
|
+
|
126
|
+
def delete_file(self, extension: str) -> None:
|
127
|
+
"""Delete a file from the batch, according to the file extension."""
|
128
|
+
batch_file = self.get_file(extension)
|
129
|
+
try:
|
130
|
+
batch_file.delete()
|
131
|
+
except FileNotFoundError as e:
|
132
|
+
raise BatchFileNotFoundError(str(e))
|
133
|
+
|
134
|
+
|
135
|
+
def has_file(self, extension: str) -> bool:
|
136
|
+
"""Return true if a file exists in the batch with the input file extension."""
|
137
|
+
try:
|
138
|
+
# only return true if both
|
139
|
+
# -> the batch has the extension defined
|
140
|
+
# -> the file with that extension exists in the batch parent directory
|
141
|
+
batch_file = self.get_file(extension)
|
142
|
+
return batch_file.exists
|
143
|
+
except BatchFileNotFoundError:
|
144
|
+
return False
|
145
|
+
|
146
|
+
|
@@ -0,0 +1,197 @@
|
|
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_batches_dir_path, TimeFormats
|
16
|
+
from spectre_core.exceptions import (
|
17
|
+
SpectrogramNotFoundError,
|
18
|
+
BatchNotFoundError
|
19
|
+
)
|
20
|
+
from ._base import BaseBatch
|
21
|
+
from ._factory import get_batch_cls_from_tag
|
22
|
+
|
23
|
+
class Batches:
|
24
|
+
"""A collection of batches for a given day of the year."""
|
25
|
+
def __init__(self,
|
26
|
+
tag: str,
|
27
|
+
year: Optional[int] = None,
|
28
|
+
month: Optional[int] = None,
|
29
|
+
day: Optional[int] = None):
|
30
|
+
self._tag = tag
|
31
|
+
self._Batch = get_batch_cls_from_tag(tag)
|
32
|
+
self._batch_map: dict[str, BaseBatch] = OrderedDict()
|
33
|
+
self.set_date(year, month, day)
|
34
|
+
|
35
|
+
|
36
|
+
@property
|
37
|
+
def tag(self) -> str:
|
38
|
+
"""Tag identifier for each batch."""
|
39
|
+
return self._tag
|
40
|
+
|
41
|
+
|
42
|
+
@property
|
43
|
+
def year(self) -> int:
|
44
|
+
"""The numeric year."""
|
45
|
+
return self._year
|
46
|
+
|
47
|
+
|
48
|
+
@property
|
49
|
+
def month(self) -> int:
|
50
|
+
"""The numeric month of the year."""
|
51
|
+
return self._month
|
52
|
+
|
53
|
+
|
54
|
+
@property
|
55
|
+
def day(self) -> int:
|
56
|
+
"""The numeric day of the year."""
|
57
|
+
return self._day
|
58
|
+
|
59
|
+
|
60
|
+
@property
|
61
|
+
def batches_dir_path(self) -> str:
|
62
|
+
"""The parent directory for all the batches."""
|
63
|
+
return get_batches_dir_path(self.year, self.month, self.day)
|
64
|
+
|
65
|
+
|
66
|
+
@property
|
67
|
+
def batch_list(self) -> list[BaseBatch]:
|
68
|
+
"""A list of all the batch instances."""
|
69
|
+
return list(self._batch_map.values())
|
70
|
+
|
71
|
+
|
72
|
+
@property
|
73
|
+
def start_times(self) -> list[str]:
|
74
|
+
"""The start times of each batch."""
|
75
|
+
return list(self._batch_map.keys())
|
76
|
+
|
77
|
+
|
78
|
+
@property
|
79
|
+
def num_batches(self) -> int:
|
80
|
+
"""The number of batches in the batch parent directory."""
|
81
|
+
return len(self.batch_list)
|
82
|
+
|
83
|
+
|
84
|
+
def set_date(self,
|
85
|
+
year: Optional[int],
|
86
|
+
month: Optional[int],
|
87
|
+
day: Optional[int]) -> None:
|
88
|
+
"""Update the parent directory for the batches according to the numeric date."""
|
89
|
+
self._year = year
|
90
|
+
self._month = month
|
91
|
+
self._day = day
|
92
|
+
self._update_batch_map()
|
93
|
+
|
94
|
+
|
95
|
+
def _update_batch_map(self) -> None:
|
96
|
+
# reset cache
|
97
|
+
self._batch_map = OrderedDict()
|
98
|
+
|
99
|
+
# get a list of all batch file names in the batches directory path
|
100
|
+
batch_file_names = [f for (_, _, files) in os.walk(self.batches_dir_path) for f in files]
|
101
|
+
for batch_file_name in batch_file_names:
|
102
|
+
# strip the extension
|
103
|
+
batch_name, _ = os.path.splitext(batch_file_name)
|
104
|
+
start_time, tag = batch_name.split("_", 1)
|
105
|
+
if tag == self._tag:
|
106
|
+
self._batch_map[start_time] = self._Batch(start_time, tag)
|
107
|
+
|
108
|
+
self._batch_map = OrderedDict(sorted(self._batch_map.items()))
|
109
|
+
|
110
|
+
|
111
|
+
def update(self) -> None:
|
112
|
+
"""Public alias for setting batch map"""
|
113
|
+
self._update_batch_map()
|
114
|
+
|
115
|
+
|
116
|
+
def __iter__(self):
|
117
|
+
"""Iterate over the stored batch instances."""
|
118
|
+
yield from self.batch_list
|
119
|
+
|
120
|
+
|
121
|
+
def _get_from_start_time(self,
|
122
|
+
start_time: str) -> BaseBatch:
|
123
|
+
"""Get the batch according to the input start time."""
|
124
|
+
try:
|
125
|
+
return self._batch_map[start_time]
|
126
|
+
except KeyError:
|
127
|
+
raise BatchNotFoundError(f"Batch with start time {start_time} could not be found within {self.batches_dir_path}")
|
128
|
+
|
129
|
+
|
130
|
+
def _get_from_index(self,
|
131
|
+
index: int) -> BaseBatch:
|
132
|
+
"""Get the batch according to its index, where the batches are ordered in time."""
|
133
|
+
num_batches = len(self.batch_list)
|
134
|
+
if num_batches == 0:
|
135
|
+
raise BatchNotFoundError("No batches are available")
|
136
|
+
index = index % num_batches # Use modulo to make the index wrap around. Allows the user to iterate over all the batches via index cyclically.
|
137
|
+
return self.batch_list[index]
|
138
|
+
|
139
|
+
|
140
|
+
def __getitem__(self, subscript: str | int):
|
141
|
+
if isinstance(subscript, str):
|
142
|
+
return self._get_from_start_time(subscript)
|
143
|
+
elif isinstance(subscript, int):
|
144
|
+
return self._get_from_index(subscript)
|
145
|
+
|
146
|
+
|
147
|
+
def num_batch_files(self,
|
148
|
+
extension: str) -> int:
|
149
|
+
"""Get the number of existing batch files with the given extension."""
|
150
|
+
return sum(1 for batch_file in self if batch_file.has_file(extension))
|
151
|
+
|
152
|
+
|
153
|
+
def get_spectrogram_from_range(self,
|
154
|
+
start_time: str,
|
155
|
+
end_time: str) -> Spectrogram:
|
156
|
+
"""Return a spectrogram over the input time range."""
|
157
|
+
# Convert input strings to datetime objects
|
158
|
+
start_datetime = datetime.strptime(start_time, TimeFormats.DATETIME)
|
159
|
+
end_datetime = datetime.strptime(end_time, TimeFormats.DATETIME)
|
160
|
+
|
161
|
+
if start_datetime.day != end_datetime.day:
|
162
|
+
warning_message = "Joining spectrograms across multiple days"
|
163
|
+
_LOGGER.warning(warning_message)
|
164
|
+
warnings.warn(warning_message, RuntimeWarning)
|
165
|
+
|
166
|
+
spectrograms = []
|
167
|
+
num_fits_batch_files = self.num_batch_files("fits")
|
168
|
+
|
169
|
+
for i, batch in enumerate(self):
|
170
|
+
# skip batches without fits files
|
171
|
+
if not batch.has_file("fits"):
|
172
|
+
continue
|
173
|
+
|
174
|
+
# rather than reading all files to evaluate the actual upper bound to their time range (slow)
|
175
|
+
# place an upper bound by using the start datetime for the next batch
|
176
|
+
# this assumes that the batches are non-overlapping (reasonable assumption)
|
177
|
+
lower_bound = batch.start_datetime
|
178
|
+
if i < num_fits_batch_files:
|
179
|
+
next_batch = self[i + 1]
|
180
|
+
upper_bound = next_batch.start_datetime
|
181
|
+
# if there is no "next batch" then we do have to read the file
|
182
|
+
else:
|
183
|
+
fits_batch = batch.get_file("fits")
|
184
|
+
upper_bound = fits_batch.datetimes[-1]
|
185
|
+
|
186
|
+
# if the batch overlaps with the input time range, then read the fits file
|
187
|
+
if start_datetime <= upper_bound and lower_bound <= end_datetime:
|
188
|
+
spectrogram = batch.read_file("fits")
|
189
|
+
spectrogram = time_chop(spectrogram, start_time, end_time)
|
190
|
+
# if we have a non-empty spectrogram, append it to the list of spectrograms
|
191
|
+
if spectrogram:
|
192
|
+
spectrograms.append(spectrogram)
|
193
|
+
|
194
|
+
if spectrograms:
|
195
|
+
return join_spectrograms(spectrograms)
|
196
|
+
else:
|
197
|
+
raise SpectrogramNotFoundError("No spectrogram data found for the given time range")
|
@@ -0,0 +1,27 @@
|
|
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 spectre_core.capture_configs import CaptureConfig, PNames
|
6
|
+
from spectre_core.exceptions import BatchNotFoundError
|
7
|
+
from ._register import batch_map
|
8
|
+
from ._base import BaseBatch
|
9
|
+
|
10
|
+
|
11
|
+
def _get_batch_cls(batch_key: str) -> BaseBatch:
|
12
|
+
Batch = batch_map.get(batch_key)
|
13
|
+
if Batch is None:
|
14
|
+
valid_batch_keys = list(batch_map.keys())
|
15
|
+
raise BatchNotFoundError(f"No batch found for the batch key: {batch_key}. Valid batch keys are: {valid_batch_keys}")
|
16
|
+
return Batch
|
17
|
+
|
18
|
+
|
19
|
+
def get_batch_cls_from_tag(tag: str) -> BaseBatch:
|
20
|
+
# if we are dealing with a callisto batch, the batch key is equal to the tag
|
21
|
+
if "callisto" in tag:
|
22
|
+
batch_key = "callisto"
|
23
|
+
# otherwise, we fetch the batch key from the capture config
|
24
|
+
else:
|
25
|
+
capture_config= CaptureConfig(tag)
|
26
|
+
batch_key = capture_config.get_parameter_value(PNames.BATCH_KEY)
|
27
|
+
return _get_batch_cls(batch_key)
|
@@ -3,13 +3,13 @@
|
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
5
|
# Global dictionaries to hold the mappings
|
6
|
-
|
6
|
+
batch_map = {}
|
7
7
|
|
8
|
-
# classes decorated with @
|
9
|
-
# will be added to
|
10
|
-
def
|
8
|
+
# classes decorated with @register_batch([BATCH_KEY])
|
9
|
+
# will be added to batch_map
|
10
|
+
def register_batch(batch_key: str):
|
11
11
|
def decorator(cls):
|
12
|
-
|
12
|
+
batch_map[batch_key] = cls
|
13
13
|
return cls
|
14
14
|
return decorator
|
15
15
|
|
@@ -11,43 +11,46 @@ 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.spectrograms import Spectrogram
|
15
|
-
from .._register import
|
16
|
-
from .._base import
|
14
|
+
from spectre_core.spectrograms import Spectrogram, SpectrumTypes
|
15
|
+
from .._register import register_batch
|
16
|
+
from .._base import BaseBatch, BatchFile
|
17
17
|
|
18
18
|
|
19
|
-
@
|
20
|
-
class
|
21
|
-
def __init__(self,
|
22
|
-
|
23
|
-
|
19
|
+
@register_batch('callisto')
|
20
|
+
class _Batch(BaseBatch):
|
21
|
+
def __init__(self,
|
22
|
+
start_time: str,
|
23
|
+
tag: str):
|
24
|
+
super().__init__(start_time, tag)
|
25
|
+
self.add_file( FitsFile(self.parent_dir_path, self.name) )
|
24
26
|
|
25
27
|
|
26
|
-
class
|
27
|
-
def __init__(self,
|
28
|
-
|
28
|
+
class FitsFile(BatchFile):
|
29
|
+
def __init__(self,
|
30
|
+
parent_dir_path: str,
|
31
|
+
base_file_name: str):
|
32
|
+
super().__init__(parent_dir_path, base_file_name, "fits")
|
29
33
|
|
30
34
|
|
31
35
|
def read(self) -> Spectrogram:
|
32
36
|
with fits.open(self.file_path, mode='readonly') as hdulist:
|
33
|
-
primary_hdu
|
34
|
-
dynamic_spectra
|
35
|
-
|
36
|
-
bintable_hdu
|
37
|
-
times, frequencies
|
38
|
-
spectrum_type
|
39
|
-
|
40
|
-
if spectrum_type ==
|
37
|
+
primary_hdu = self._get_primary_hdu(hdulist)
|
38
|
+
dynamic_spectra = self._get_dynamic_spectra(primary_hdu)
|
39
|
+
spectrogram_start_datetime = self._get_spectrogram_start_datetime(primary_hdu)
|
40
|
+
bintable_hdu = self._get_bintable_hdu(hdulist)
|
41
|
+
times, frequencies = self._get_time_and_frequency(bintable_hdu)
|
42
|
+
spectrum_type = self._get_spectrum_type(primary_hdu)
|
43
|
+
|
44
|
+
if spectrum_type == SpectrumTypes.DIGITS:
|
41
45
|
dynamic_spectra_linearised = self._convert_units_to_linearised(dynamic_spectra)
|
42
46
|
return Spectrogram(dynamic_spectra_linearised[::-1, :], # reverse the spectra along the frequency axis
|
43
47
|
times,
|
44
48
|
frequencies[::-1], # sort the frequencies in ascending order
|
45
49
|
self.tag,
|
46
|
-
|
47
|
-
|
48
|
-
spectrum_type = spectrum_type)
|
50
|
+
spectrogram_start_datetime,
|
51
|
+
spectrum_type)
|
49
52
|
else:
|
50
|
-
raise NotImplementedError(f"SPECTRE does not currently support spectrum type with BUNITS {spectrum_type}")
|
53
|
+
raise NotImplementedError(f"SPECTRE does not currently support spectrum type with BUNITS '{spectrum_type}'")
|
51
54
|
|
52
55
|
|
53
56
|
@property
|
@@ -55,7 +58,7 @@ class FitsChunk(ChunkFile):
|
|
55
58
|
with fits.open(self.file_path, mode='readonly') as hdulist:
|
56
59
|
bintable_data = hdulist[1].data
|
57
60
|
times = bintable_data['TIME'][0]
|
58
|
-
return [self.
|
61
|
+
return [self.start_datetime + timedelta(seconds=t) for t in times]
|
59
62
|
|
60
63
|
|
61
64
|
def _get_primary_hdu(self, hdulist: HDUList) -> PrimaryHDU:
|
@@ -66,11 +69,10 @@ class FitsChunk(ChunkFile):
|
|
66
69
|
return primary_hdu.data
|
67
70
|
|
68
71
|
|
69
|
-
def
|
70
|
-
date_obs = primary_hdu.header
|
71
|
-
time_obs = primary_hdu.header
|
72
|
-
|
73
|
-
return datetime_obs.microsecond
|
72
|
+
def _get_spectrogram_start_datetime(self, primary_hdu: PrimaryHDU) -> datetime:
|
73
|
+
date_obs = primary_hdu.header['DATE-OBS']
|
74
|
+
time_obs = primary_hdu.header['TIME-OBS']
|
75
|
+
return datetime.strptime(f"{date_obs}T{time_obs}", "%Y-%m-%dT%H:%M:%S.%f")
|
74
76
|
|
75
77
|
|
76
78
|
def _get_bintable_hdu(self, hdulist: HDUList) -> BinTableHDU:
|
@@ -85,10 +87,6 @@ class FitsChunk(ChunkFile):
|
|
85
87
|
return times, frequencies
|
86
88
|
|
87
89
|
|
88
|
-
def _get_spectrum_type(self, primary_hdu: PrimaryHDU) -> str:
|
89
|
-
return primary_hdu.header.get('BUNIT', None)
|
90
|
-
|
91
|
-
|
92
90
|
def _convert_units_to_linearised(self, dynamic_spectra: np.ndarray) -> np.ndarray:
|
93
91
|
digits_floats = np.array(dynamic_spectra, dtype='float')
|
94
92
|
# conversion as per ADC specs [see email from C. Monstein]
|