spectre-core 0.0.22__py3-none-any.whl → 0.0.24__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 +5 -0
- spectre_core/_file_io/__init__.py +4 -4
- spectre_core/_file_io/file_handlers.py +60 -106
- spectre_core/batches/__init__.py +20 -3
- spectre_core/batches/_base.py +85 -134
- spectre_core/batches/_batches.py +55 -99
- spectre_core/batches/_factory.py +21 -20
- spectre_core/batches/_register.py +8 -8
- spectre_core/batches/plugins/_batch_keys.py +7 -6
- spectre_core/batches/plugins/_callisto.py +65 -97
- spectre_core/batches/plugins/_iq_stream.py +105 -169
- spectre_core/capture_configs/__init__.py +46 -17
- spectre_core/capture_configs/_capture_config.py +25 -52
- spectre_core/capture_configs/_capture_modes.py +8 -6
- spectre_core/capture_configs/_capture_templates.py +50 -110
- spectre_core/capture_configs/_parameters.py +37 -74
- spectre_core/capture_configs/_pconstraints.py +40 -40
- spectre_core/capture_configs/_pnames.py +36 -34
- spectre_core/capture_configs/_ptemplates.py +260 -347
- spectre_core/capture_configs/_pvalidators.py +99 -102
- spectre_core/config/__init__.py +19 -8
- spectre_core/config/_paths.py +25 -47
- spectre_core/config/_time_formats.py +6 -5
- spectre_core/exceptions.py +38 -0
- spectre_core/jobs/__init__.py +3 -6
- spectre_core/jobs/_duration.py +12 -0
- spectre_core/jobs/_jobs.py +72 -43
- spectre_core/jobs/_workers.py +55 -105
- spectre_core/logs/__init__.py +7 -2
- spectre_core/logs/_configure.py +13 -17
- spectre_core/logs/_decorators.py +6 -4
- spectre_core/logs/_logs.py +37 -89
- spectre_core/logs/_process_types.py +5 -3
- spectre_core/plotting/__init__.py +19 -3
- spectre_core/plotting/_base.py +112 -177
- spectre_core/plotting/_format.py +10 -8
- spectre_core/plotting/_panel_names.py +7 -5
- spectre_core/plotting/_panel_stack.py +138 -130
- spectre_core/plotting/_panels.py +152 -162
- spectre_core/post_processing/__init__.py +6 -3
- spectre_core/post_processing/_base.py +41 -55
- spectre_core/post_processing/_factory.py +14 -11
- spectre_core/post_processing/_post_processor.py +16 -12
- spectre_core/post_processing/_register.py +10 -7
- spectre_core/post_processing/plugins/_event_handler_keys.py +4 -3
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +54 -47
- spectre_core/post_processing/plugins/_swept_center_frequency.py +199 -174
- spectre_core/receivers/__init__.py +9 -2
- spectre_core/receivers/_base.py +82 -148
- spectre_core/receivers/_factory.py +20 -30
- spectre_core/receivers/_register.py +7 -10
- spectre_core/receivers/_spec_names.py +17 -15
- spectre_core/receivers/plugins/_b200mini.py +47 -60
- spectre_core/receivers/plugins/_receiver_names.py +8 -6
- spectre_core/receivers/plugins/_rsp1a.py +44 -40
- spectre_core/receivers/plugins/_rspduo.py +59 -44
- spectre_core/receivers/plugins/_sdrplay_receiver.py +67 -83
- spectre_core/receivers/plugins/_test.py +136 -129
- spectre_core/receivers/plugins/_usrp.py +93 -85
- spectre_core/receivers/plugins/gr/__init__.py +1 -1
- spectre_core/receivers/plugins/gr/_base.py +14 -22
- spectre_core/receivers/plugins/gr/_rsp1a.py +53 -60
- spectre_core/receivers/plugins/gr/_rspduo.py +77 -89
- spectre_core/receivers/plugins/gr/_test.py +49 -57
- spectre_core/receivers/plugins/gr/_usrp.py +61 -59
- spectre_core/spectrograms/__init__.py +21 -13
- spectre_core/spectrograms/_analytical.py +108 -99
- spectre_core/spectrograms/_array_operations.py +39 -46
- spectre_core/spectrograms/_spectrogram.py +293 -324
- spectre_core/spectrograms/_transform.py +106 -73
- spectre_core/wgetting/__init__.py +1 -3
- spectre_core/wgetting/_callisto.py +87 -93
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/METADATA +9 -23
- spectre_core-0.0.24.dist-info/RECORD +79 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/WHEEL +1 -1
- spectre_core-0.0.22.dist-info/RECORD +0 -78
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/licenses/LICENSE +0 -0
- {spectre_core-0.0.22.dist-info → spectre_core-0.0.24.dist-info}/top_level.txt +0 -0
@@ -24,25 +24,23 @@ from .._register import register_batch
|
|
24
24
|
@dataclass(frozen=True)
|
25
25
|
class _BatchExtension:
|
26
26
|
"""Supported extensions for a `IQStreamBatch`.
|
27
|
-
|
27
|
+
|
28
28
|
:ivar FITS: Corresponds to the `.fits` file extension.
|
29
29
|
:ivar BIN: Corresponds to the `.bin` file extension.
|
30
30
|
:ivar HDR: Corresponds to the `.hdr` file extension.
|
31
31
|
"""
|
32
|
+
|
32
33
|
FITS: str = "fits"
|
33
|
-
BIN
|
34
|
-
HDR
|
35
|
-
|
36
|
-
|
34
|
+
BIN: str = "bin"
|
35
|
+
HDR: str = "hdr"
|
36
|
+
|
37
|
+
|
37
38
|
class _BinFile(BatchFile[npt.NDArray[np.complex64]]):
|
38
|
-
"""Stores complex IQ samples in the binary format, as produced by the `gr-spectre`
|
39
|
+
"""Stores complex IQ samples in the binary format, as produced by the `gr-spectre`
|
39
40
|
OOT module block `batched_file_sink`.
|
40
41
|
"""
|
41
|
-
|
42
|
-
|
43
|
-
batch_parent_dir_path: str,
|
44
|
-
batch_name: str
|
45
|
-
) -> None:
|
42
|
+
|
43
|
+
def __init__(self, batch_parent_dir_path: str, batch_name: str) -> None:
|
46
44
|
"""Initialise a `_BinFile` instance.
|
47
45
|
|
48
46
|
:param batch_parent_dir_path: The parent directory for the batch.
|
@@ -50,48 +48,46 @@ class _BinFile(BatchFile[npt.NDArray[np.complex64]]):
|
|
50
48
|
"""
|
51
49
|
super().__init__(batch_parent_dir_path, batch_name, _BatchExtension.BIN)
|
52
50
|
|
53
|
-
|
54
|
-
def _read(
|
55
|
-
self
|
56
|
-
) -> npt.NDArray[np.complex64]:
|
51
|
+
def _read(self) -> npt.NDArray[np.complex64]:
|
57
52
|
"""Reads the binary file and returns the stored complex IQ samples.
|
58
53
|
|
59
54
|
:return: The raw 32-bit floats in the binary file, interpreted as 64-bit complex IQ samples.
|
60
55
|
"""
|
61
56
|
with open(self.file_path, "rb") as fh:
|
62
57
|
return np.fromfile(fh, dtype=np.complex64)
|
63
|
-
|
58
|
+
|
64
59
|
|
65
60
|
@dataclass
|
66
61
|
class IQMetadata:
|
67
62
|
"""Represents metadata for IQ samples produced by the `gr-spectre` OOT module block `batched_file_sink`.
|
68
63
|
|
69
64
|
:ivar millisecond_correction: The millisecond component of the batch start time.
|
70
|
-
:ivar center_frequencies: Center frequencies for each IQ sample, if the stream was frequency tagged.
|
65
|
+
:ivar center_frequencies: Center frequencies for each IQ sample, if the stream was frequency tagged.
|
71
66
|
None otherwise.
|
72
|
-
:ivar num_samples: Number of samples collected at each center frequency, if the stream was frequency
|
67
|
+
:ivar num_samples: Number of samples collected at each center frequency, if the stream was frequency
|
73
68
|
tagged. None otherwise.
|
74
69
|
"""
|
70
|
+
|
75
71
|
millisecond_correction: int
|
76
72
|
center_frequencies: Optional[npt.NDArray[np.float32]] = None
|
77
73
|
num_samples: Optional[npt.NDArray[np.int32]] = None
|
78
|
-
|
74
|
+
|
79
75
|
def is_frequency_tagged(self) -> bool:
|
80
76
|
"""Check if the IQ metadata contains frequency tagging information.
|
81
77
|
|
82
78
|
:return: True if frequency tagging information is present; False otherwise.
|
83
79
|
"""
|
84
80
|
return (self.center_frequencies is not None) and (self.num_samples is not None)
|
85
|
-
|
81
|
+
|
86
82
|
|
87
83
|
class _HdrFile(BatchFile[IQMetadata]):
|
88
|
-
"""Stores IQ sample metadata produced by the `gr-spectre` OOT module block `batched_file_sink`, used
|
84
|
+
"""Stores IQ sample metadata produced by the `gr-spectre` OOT module block `batched_file_sink`, used
|
89
85
|
to help parse the corresponding `.bin` file.
|
90
86
|
|
91
87
|
File Structure:
|
92
88
|
- If frequency tagged:
|
93
89
|
(`<millisecond_correction>`, `<freq_0>`, `<num_samples_0>`, `<freq_1>`, `<num_samples_1>`, ...)
|
94
|
-
All values are stored as 32-bit floats.
|
90
|
+
All values are stored as 32-bit floats.
|
95
91
|
- The first value is the millisecond component for the batch start time.
|
96
92
|
- Subsequent tuples (`<freq_n>`, `<num_samples_n>`) indicate that `<num_samples_n>` samples were collected at `<freq_n>`.
|
97
93
|
- If not frequency tagged:
|
@@ -100,11 +96,8 @@ class _HdrFile(BatchFile[IQMetadata]):
|
|
100
96
|
|
101
97
|
This format enables mapping IQ samples in the binary file to their corresponding center frequencies, if applicable.
|
102
98
|
"""
|
103
|
-
|
104
|
-
|
105
|
-
parent_dir_path: str,
|
106
|
-
base_file_name: str
|
107
|
-
) -> None:
|
99
|
+
|
100
|
+
def __init__(self, parent_dir_path: str, base_file_name: str) -> None:
|
108
101
|
"""Initialise a `_HdrFile` instance.
|
109
102
|
|
110
103
|
:param parent_dir_path: The parent directory for the batch.
|
@@ -112,44 +105,30 @@ class _HdrFile(BatchFile[IQMetadata]):
|
|
112
105
|
"""
|
113
106
|
super().__init__(parent_dir_path, base_file_name, _BatchExtension.HDR)
|
114
107
|
|
115
|
-
|
116
|
-
def _read(
|
117
|
-
self
|
118
|
-
) -> IQMetadata:
|
108
|
+
def _read(self) -> IQMetadata:
|
119
109
|
"""Parses the binary contents of the `.hdr` file to extract IQ sample metadata.
|
120
110
|
|
121
|
-
:return: An instance of `IQMetadata` containing the parsed metadata, including the millisecond correction
|
111
|
+
:return: An instance of `IQMetadata` containing the parsed metadata, including the millisecond correction
|
122
112
|
and, if applicable, frequency tagging details.
|
123
113
|
"""
|
124
|
-
hdr_contents
|
114
|
+
hdr_contents = self._extract_raw_contents()
|
125
115
|
millisecond_correction = self._get_millisecond_correction(hdr_contents)
|
126
116
|
if hdr_contents.size == 1:
|
127
117
|
return IQMetadata(millisecond_correction)
|
128
118
|
else:
|
129
|
-
center_frequencies
|
130
|
-
num_samples
|
131
|
-
self._validate_frequencies_and_samples(center_frequencies,
|
132
|
-
|
133
|
-
return IQMetadata(millisecond_correction,
|
134
|
-
center_frequencies,
|
135
|
-
num_samples)
|
136
|
-
|
119
|
+
center_frequencies = self._get_center_frequencies(hdr_contents)
|
120
|
+
num_samples = self._get_num_samples(hdr_contents)
|
121
|
+
self._validate_frequencies_and_samples(center_frequencies, num_samples)
|
122
|
+
return IQMetadata(millisecond_correction, center_frequencies, num_samples)
|
137
123
|
|
138
|
-
|
139
|
-
def _extract_raw_contents(
|
140
|
-
self
|
141
|
-
) -> npt.NDArray[np.float32]:
|
124
|
+
def _extract_raw_contents(self) -> npt.NDArray[np.float32]:
|
142
125
|
"""Reads the raw contents of the `.hdr` file."""
|
143
126
|
with open(self.file_path, "rb") as fh:
|
144
|
-
# the `batched_file_sink` block in the `gr-spectre` GNU Radio OOT module stores
|
127
|
+
# the `batched_file_sink` block in the `gr-spectre` GNU Radio OOT module stores
|
145
128
|
# the integral millisecond component as a 32-bit float.
|
146
129
|
return np.fromfile(fh, dtype=np.float32)
|
147
130
|
|
148
|
-
|
149
|
-
def _get_millisecond_correction(
|
150
|
-
self,
|
151
|
-
hdr_contents: npt.NDArray[np.float32]
|
152
|
-
) -> int:
|
131
|
+
def _get_millisecond_correction(self, hdr_contents: npt.NDArray[np.float32]) -> int:
|
153
132
|
"""Extracts and validates the millisecond component of the batch start time.
|
154
133
|
|
155
134
|
The value is stored as a 32-bit float but interpreted as an integer.
|
@@ -157,14 +136,14 @@ class _HdrFile(BatchFile[IQMetadata]):
|
|
157
136
|
millisecond_correction = float(hdr_contents[0])
|
158
137
|
|
159
138
|
if not millisecond_correction.is_integer():
|
160
|
-
raise TypeError(
|
161
|
-
|
139
|
+
raise TypeError(
|
140
|
+
f"Expected integer value for millisecond correction, but got {millisecond_correction}"
|
141
|
+
)
|
142
|
+
|
162
143
|
return int(millisecond_correction)
|
163
|
-
|
164
144
|
|
165
145
|
def _get_center_frequencies(
|
166
|
-
self,
|
167
|
-
hdr_contents: npt.NDArray[np.float32]
|
146
|
+
self, hdr_contents: npt.NDArray[np.float32]
|
168
147
|
) -> npt.NDArray[np.float32]:
|
169
148
|
"""Extracts the center frequencies from the `.hdr` file contents.
|
170
149
|
|
@@ -172,182 +151,139 @@ class _HdrFile(BatchFile[IQMetadata]):
|
|
172
151
|
"""
|
173
152
|
return hdr_contents[1::2]
|
174
153
|
|
175
|
-
|
176
154
|
def _get_num_samples(
|
177
|
-
self,
|
178
|
-
hdr_contents: npt.NDArray[np.float32]
|
155
|
+
self, hdr_contents: npt.NDArray[np.float32]
|
179
156
|
) -> npt.NDArray[np.int32]:
|
180
157
|
"""Extracts the number of samples per frequency from the `.hdr` file contents.
|
181
158
|
|
182
|
-
The values are stored as 32-bit floats but are interpreted as integers.
|
183
|
-
Sample counts are located at every second entry, starting from the second index.
|
159
|
+
The values are stored as 32-bit floats but are interpreted as integers.
|
160
|
+
Sample counts are located at every second entry, starting from the second index.
|
184
161
|
"""
|
185
162
|
num_samples_as_float = hdr_contents[2::2]
|
186
163
|
if not all(num_samples_as_float == num_samples_as_float.astype(int)):
|
187
|
-
raise InvalidSweepMetadataError(
|
164
|
+
raise InvalidSweepMetadataError(
|
165
|
+
"Number of samples per frequency is expected to describe an integer"
|
166
|
+
)
|
188
167
|
return num_samples_as_float.astype(np.int32)
|
189
168
|
|
190
|
-
|
191
169
|
def _validate_frequencies_and_samples(
|
192
|
-
self,
|
193
|
-
center_frequencies: npt.NDArray[np.float32],
|
194
|
-
num_samples: npt.NDArray[np.int32]
|
170
|
+
self,
|
171
|
+
center_frequencies: npt.NDArray[np.float32],
|
172
|
+
num_samples: npt.NDArray[np.int32],
|
195
173
|
) -> None:
|
196
174
|
"""Ensures that each center frequency has a corresponding sample count."""
|
197
175
|
if len(center_frequencies) != len(num_samples):
|
198
|
-
raise InvalidSweepMetadataError(
|
176
|
+
raise InvalidSweepMetadataError(
|
177
|
+
"Center frequencies and number of samples arrays are not the same length"
|
178
|
+
)
|
199
179
|
|
200
180
|
|
201
181
|
class _FitsFile(BatchFile[Spectrogram]):
|
202
182
|
"""Stores spectrogram data in the FITS file format, as generated by `spectre` from a stream of IQ samples."""
|
203
|
-
|
204
|
-
|
205
|
-
parent_dir_path: str,
|
206
|
-
base_file_name: str
|
207
|
-
) -> None:
|
183
|
+
|
184
|
+
def __init__(self, parent_dir_path: str, base_file_name: str) -> None:
|
208
185
|
"""Initialise a `_FitsFile` instance.
|
209
186
|
|
210
187
|
:param parent_dir_path: The parent directory for the batch.
|
211
188
|
:param base_file_name: The batch name.
|
212
189
|
"""
|
213
190
|
super().__init__(parent_dir_path, base_file_name, _BatchExtension.FITS)
|
214
|
-
|
215
191
|
|
216
|
-
def _read(
|
217
|
-
self
|
218
|
-
) -> Spectrogram:
|
192
|
+
def _read(self) -> Spectrogram:
|
219
193
|
"""Read the FITS file and create a spectrogram.
|
220
194
|
|
221
195
|
:return: A `Spectrogram` instance containing the parsed FITS file data.
|
222
196
|
"""
|
223
|
-
with fits.open(self.file_path, mode=
|
224
|
-
primary_hdu
|
225
|
-
dynamic_spectra
|
226
|
-
bunit
|
227
|
-
spectrogram_start_datetime = self._get_spectrogram_start_datetime(
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
197
|
+
with fits.open(self.file_path, mode="readonly") as hdulist:
|
198
|
+
primary_hdu = self._get_primary_hdu(hdulist)
|
199
|
+
dynamic_spectra = self._get_dynamic_spectra(primary_hdu)
|
200
|
+
bunit = self._get_bunit(primary_hdu)
|
201
|
+
spectrogram_start_datetime = self._get_spectrogram_start_datetime(
|
202
|
+
primary_hdu
|
203
|
+
)
|
204
|
+
bintable_hdu = self._get_bintable_hdu(hdulist)
|
205
|
+
times = self._get_times(bintable_hdu)
|
206
|
+
frequencies = self._get_frequencies(bintable_hdu)
|
207
|
+
|
232
208
|
# bunit is interpreted as a SpectrumUnit.
|
233
209
|
spectrum_unit = SpectrumUnit(bunit)
|
234
|
-
return Spectrogram(
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
hdulist
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
def _get_bintable_hdu(
|
250
|
-
self,
|
251
|
-
hdulist: HDUList
|
252
|
-
) -> BinTableHDU:
|
210
|
+
return Spectrogram(
|
211
|
+
dynamic_spectra,
|
212
|
+
times,
|
213
|
+
frequencies,
|
214
|
+
self.tag,
|
215
|
+
spectrum_unit,
|
216
|
+
spectrogram_start_datetime,
|
217
|
+
)
|
218
|
+
|
219
|
+
def _get_primary_hdu(self, hdulist: HDUList) -> PrimaryHDU:
|
220
|
+
return hdulist["PRIMARY"]
|
221
|
+
|
222
|
+
def _get_bintable_hdu(self, hdulist: HDUList) -> BinTableHDU:
|
253
223
|
return hdulist[1]
|
254
|
-
|
255
|
-
|
256
|
-
def _get_bunit(
|
257
|
-
self,
|
258
|
-
primary_hdu: PrimaryHDU
|
259
|
-
) -> str:
|
224
|
+
|
225
|
+
def _get_bunit(self, primary_hdu: PrimaryHDU) -> str:
|
260
226
|
"""Get the units corresponding to the elements of the dynamic spectra."""
|
261
|
-
return primary_hdu.header[
|
262
|
-
|
263
|
-
|
264
|
-
def _get_dynamic_spectra(
|
265
|
-
self,
|
266
|
-
primary_hdu: PrimaryHDU
|
267
|
-
) -> npt.NDArray[np.float32]:
|
268
|
-
return primary_hdu.data
|
227
|
+
return primary_hdu.header["BUNIT"]
|
269
228
|
|
229
|
+
def _get_dynamic_spectra(self, primary_hdu: PrimaryHDU) -> npt.NDArray[np.float32]:
|
230
|
+
return primary_hdu.data
|
270
231
|
|
271
|
-
def _get_spectrogram_start_datetime(
|
272
|
-
self,
|
273
|
-
primary_hdu: PrimaryHDU
|
274
|
-
) -> datetime:
|
232
|
+
def _get_spectrogram_start_datetime(self, primary_hdu: PrimaryHDU) -> datetime:
|
275
233
|
"""Get the start time of the spectrogram, up to the full precision available."""
|
276
|
-
date_obs = primary_hdu.header[
|
277
|
-
time_obs = primary_hdu.header[
|
234
|
+
date_obs = primary_hdu.header["DATE-OBS"]
|
235
|
+
time_obs = primary_hdu.header["TIME-OBS"]
|
278
236
|
return datetime.strptime(f"{date_obs}T{time_obs}", TimeFormat.PRECISE_DATETIME)
|
279
237
|
|
280
|
-
|
281
|
-
def _get_times(
|
282
|
-
self,
|
283
|
-
bintable_hdu: BinTableHDU
|
284
|
-
) -> npt.NDArray[np.float32]:
|
238
|
+
def _get_times(self, bintable_hdu: BinTableHDU) -> npt.NDArray[np.float32]:
|
285
239
|
"""Extracts the elapsed times for each spectrum in seconds, with the first spectrum set to t=0
|
286
240
|
by convention.
|
287
241
|
"""
|
288
|
-
return bintable_hdu.data[
|
289
|
-
|
242
|
+
return bintable_hdu.data["TIME"][0] # already in seconds
|
290
243
|
|
291
|
-
def _get_frequencies(
|
292
|
-
self,
|
293
|
-
bintable_hdu: BinTableHDU
|
294
|
-
) -> npt.NDArray[np.float32]:
|
244
|
+
def _get_frequencies(self, bintable_hdu: BinTableHDU) -> npt.NDArray[np.float32]:
|
295
245
|
"""Extracts the frequencies for each spectral component."""
|
296
|
-
frequencies_MHz = bintable_hdu.data[
|
297
|
-
return frequencies_MHz * 1e6
|
298
|
-
|
299
|
-
|
246
|
+
frequencies_MHz = bintable_hdu.data["FREQUENCY"][0]
|
247
|
+
return frequencies_MHz * 1e6 # convert to Hz
|
248
|
+
|
249
|
+
|
300
250
|
@register_batch(BatchKey.IQ_STREAM)
|
301
251
|
class IQStreamBatch(BaseBatch):
|
302
252
|
"""A batch of data derived from a stream of IQ samples from some receiver.
|
303
|
-
|
253
|
+
|
304
254
|
Supports the following extensions:
|
305
255
|
- `.fits` (via the `spectrogram_file` attribute)
|
306
256
|
- `.bin` (via the `bin_file` attribute)
|
307
257
|
- `.hdr` (via the `hdr_file` attribute)
|
308
258
|
"""
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
tag: str
|
313
|
-
) -> None:
|
314
|
-
"""Initialise a `IQStreamBatch` instance.
|
259
|
+
|
260
|
+
def __init__(self, start_time: str, tag: str) -> None:
|
261
|
+
"""Initialise a `IQStreamBatch` instance.
|
315
262
|
|
316
263
|
:param start_time: The start time of the batch.
|
317
264
|
:param tag: The batch name tag.
|
318
265
|
"""
|
319
|
-
super().__init__(start_time, tag)
|
266
|
+
super().__init__(start_time, tag)
|
320
267
|
self._fits_file = _FitsFile(self.parent_dir_path, self.name)
|
321
|
-
self._bin_file
|
322
|
-
self._hdr_file
|
323
|
-
|
268
|
+
self._bin_file = _BinFile(self.parent_dir_path, self.name)
|
269
|
+
self._hdr_file = _HdrFile(self.parent_dir_path, self.name)
|
270
|
+
|
324
271
|
# add files formally to the batch
|
325
|
-
self.add_file(
|
326
|
-
self.add_file(
|
327
|
-
self.add_file(
|
328
|
-
|
272
|
+
self.add_file(self.spectrogram_file)
|
273
|
+
self.add_file(self.bin_file)
|
274
|
+
self.add_file(self.hdr_file)
|
329
275
|
|
330
276
|
@property
|
331
|
-
def spectrogram_file(
|
332
|
-
self
|
333
|
-
) -> _FitsFile:
|
277
|
+
def spectrogram_file(self) -> _FitsFile:
|
334
278
|
"""The batch file corresponding to the `.fits` extension."""
|
335
279
|
return self._fits_file
|
336
|
-
|
337
|
-
|
280
|
+
|
338
281
|
@property
|
339
|
-
def bin_file(
|
340
|
-
self
|
341
|
-
) -> _BinFile:
|
282
|
+
def bin_file(self) -> _BinFile:
|
342
283
|
"""The batch file corresponding to the `.bin` extension."""
|
343
284
|
return self._bin_file
|
344
|
-
|
345
|
-
|
285
|
+
|
346
286
|
@property
|
347
|
-
def hdr_file(
|
348
|
-
self
|
349
|
-
) -> _HdrFile:
|
287
|
+
def hdr_file(self) -> _HdrFile:
|
350
288
|
"""The batch file corresponding to the `.hdr` extension."""
|
351
289
|
return self._hdr_file
|
352
|
-
|
353
|
-
|
@@ -6,28 +6,57 @@
|
|
6
6
|
|
7
7
|
from ._pnames import PName
|
8
8
|
from ._capture_modes import CaptureMode
|
9
|
-
from ._pvalidators
|
10
|
-
validate_fixed_center_frequency,
|
11
|
-
|
12
|
-
|
9
|
+
from ._pvalidators import (
|
10
|
+
validate_fixed_center_frequency,
|
11
|
+
validate_non_overlapping_steps,
|
12
|
+
validate_num_samples_per_step,
|
13
|
+
validate_num_steps_per_sweep,
|
14
|
+
validate_nyquist_criterion,
|
15
|
+
validate_step_interval,
|
16
|
+
validate_sweep_interval,
|
17
|
+
validate_swept_center_frequency,
|
18
|
+
validate_window,
|
19
|
+
validate_sample_rate_with_master_clock_rate,
|
13
20
|
)
|
14
21
|
from ._capture_config import CaptureConfig
|
15
22
|
from ._ptemplates import PTemplate, get_base_ptemplate
|
16
|
-
from ._parameters
|
17
|
-
Parameter, Parameters, parse_string_parameters, make_parameters
|
18
|
-
)
|
23
|
+
from ._parameters import Parameter, Parameters, parse_string_parameters, make_parameters
|
19
24
|
from ._capture_templates import (
|
20
|
-
CaptureTemplate,
|
21
|
-
|
22
|
-
|
23
|
-
BasePConstraint, EnforceSign, Bound, OneOf, PowerOfTwo
|
25
|
+
CaptureTemplate,
|
26
|
+
get_base_capture_template,
|
27
|
+
make_base_capture_template,
|
24
28
|
)
|
29
|
+
from ._pconstraints import BasePConstraint, EnforceSign, Bound, OneOf, PowerOfTwo
|
25
30
|
|
26
31
|
__all__ = [
|
27
|
-
"PTemplate",
|
28
|
-
"
|
29
|
-
"
|
30
|
-
"
|
31
|
-
"
|
32
|
-
"
|
32
|
+
"PTemplate",
|
33
|
+
"PValidator",
|
34
|
+
"CaptureConfig",
|
35
|
+
"Parameter",
|
36
|
+
"Parameters",
|
37
|
+
"parse_string_parameters",
|
38
|
+
"make_parameters",
|
39
|
+
"CaptureTemplate",
|
40
|
+
"CaptureMode",
|
41
|
+
"get_base_capture_template",
|
42
|
+
"make_base_capture_template" "PConstraint",
|
43
|
+
"PConstraint",
|
44
|
+
"Bound",
|
45
|
+
"OneOf",
|
46
|
+
"EnforceSign",
|
47
|
+
"PowerOfTwo",
|
48
|
+
"make_base_capture_template",
|
49
|
+
"PName",
|
50
|
+
"get_base_ptemplate",
|
51
|
+
"BasePConstraint",
|
52
|
+
"validate_fixed_center_frequency",
|
53
|
+
"validate_non_overlapping_steps",
|
54
|
+
"validate_num_samples_per_step",
|
55
|
+
"validate_num_steps_per_sweep",
|
56
|
+
"validate_nyquist_criterion",
|
57
|
+
"validate_step_interval",
|
58
|
+
"validate_sweep_interval",
|
59
|
+
"validate_swept_center_frequency",
|
60
|
+
"validate_window",
|
61
|
+
"validate_sample_rate_with_master_clock_rate",
|
33
62
|
]
|
@@ -10,91 +10,69 @@ from spectre_core._file_io import JsonHandler
|
|
10
10
|
from spectre_core.config import get_configs_dir_path
|
11
11
|
from spectre_core.exceptions import InvalidTagError
|
12
12
|
from ._ptemplates import PName
|
13
|
-
from ._parameters import
|
14
|
-
|
15
|
-
Parameters,
|
16
|
-
make_parameters
|
17
|
-
)
|
13
|
+
from ._parameters import Parameter, Parameters, make_parameters
|
14
|
+
|
18
15
|
|
19
16
|
@dataclass(frozen=True)
|
20
17
|
class _CaptureConfigKey:
|
21
18
|
"""Defined JSON keys for capture configs.
|
22
|
-
|
19
|
+
|
23
20
|
:ivar RECEIVER_NAME: The name of the receiver used for capture.
|
24
21
|
:ivar RECEIVER_MODE: The operating mode for the receiver to be used for capture.
|
25
22
|
:ivar PARAMETERS: The user configured parameters given to the receiver at the time of capture.
|
26
23
|
"""
|
24
|
+
|
27
25
|
RECEIVER_NAME = "receiver_name"
|
28
26
|
RECEIVER_MODE = "receiver_mode"
|
29
|
-
PARAMETERS
|
27
|
+
PARAMETERS = "parameters"
|
30
28
|
|
31
29
|
|
32
30
|
class CaptureConfig(JsonHandler):
|
33
31
|
"""Simple IO interface for a capture configs under a particular tag."""
|
34
|
-
|
35
|
-
|
36
|
-
tag: str
|
37
|
-
) -> None:
|
32
|
+
|
33
|
+
def __init__(self, tag: str) -> None:
|
38
34
|
"""Initialise an instance of `CaptureConfig`.
|
39
35
|
|
40
36
|
:param tag: The tag identifier for the capture config.
|
41
37
|
"""
|
42
38
|
self._validate_tag(tag)
|
43
39
|
self._tag = tag
|
44
|
-
super().__init__(get_configs_dir_path(),
|
45
|
-
|
46
|
-
|
40
|
+
super().__init__(get_configs_dir_path(), tag)
|
41
|
+
|
47
42
|
@property
|
48
|
-
def tag(
|
49
|
-
self
|
50
|
-
) -> str:
|
43
|
+
def tag(self) -> str:
|
51
44
|
"""The tag identifier for the capture config."""
|
52
45
|
return self._tag
|
53
46
|
|
54
|
-
|
55
|
-
def _validate_tag(
|
56
|
-
self,
|
57
|
-
tag: str
|
58
|
-
) -> None:
|
47
|
+
def _validate_tag(self, tag: str) -> None:
|
59
48
|
"""Validate the tag of the capture config.
|
60
|
-
|
49
|
+
|
61
50
|
Some substrings are reserved for third-party spectrogram data.
|
62
51
|
"""
|
63
52
|
if "callisto" in tag:
|
64
|
-
raise ValueError(
|
65
|
-
|
53
|
+
raise ValueError(
|
54
|
+
f"The substring `callisto` is reserved, and is not allowed in a capture config tag."
|
55
|
+
)
|
66
56
|
|
67
57
|
@property
|
68
|
-
def receiver_name(
|
69
|
-
self
|
70
|
-
) -> str:
|
58
|
+
def receiver_name(self) -> str:
|
71
59
|
"""The name of the receiver to be used for capture."""
|
72
60
|
d = self.read(cache=True)
|
73
61
|
return d[_CaptureConfigKey.RECEIVER_NAME]
|
74
|
-
|
75
62
|
|
76
63
|
@property
|
77
|
-
def receiver_mode(
|
78
|
-
self
|
79
|
-
) -> str:
|
64
|
+
def receiver_mode(self) -> str:
|
80
65
|
"""The operating mode for the receiver to be used for capture."""
|
81
66
|
d = self.read(cache=True)
|
82
67
|
return d[_CaptureConfigKey.RECEIVER_MODE]
|
83
|
-
|
84
68
|
|
85
69
|
@cached_property
|
86
|
-
def parameters(
|
87
|
-
self
|
88
|
-
) -> Parameters:
|
70
|
+
def parameters(self) -> Parameters:
|
89
71
|
"""The user-configured parameters provided to the receiver at the time of capture."""
|
90
72
|
d = self.read(cache=True)
|
91
|
-
return make_parameters(
|
92
|
-
|
73
|
+
return make_parameters(d[_CaptureConfigKey.PARAMETERS])
|
93
74
|
|
94
|
-
def get_parameter(
|
95
|
-
self,
|
96
|
-
name: PName
|
97
|
-
) -> Parameter:
|
75
|
+
def get_parameter(self, name: PName) -> Parameter:
|
98
76
|
"""Get a parameter stored by the capture config.
|
99
77
|
|
100
78
|
:param name: The name of the parameter.
|
@@ -102,14 +80,10 @@ class CaptureConfig(JsonHandler):
|
|
102
80
|
configuration file.
|
103
81
|
"""
|
104
82
|
return self.parameters.get_parameter(name)
|
105
|
-
|
106
83
|
|
107
|
-
def get_parameter_value(
|
108
|
-
self,
|
109
|
-
name: PName
|
110
|
-
) -> Optional[Any]:
|
84
|
+
def get_parameter_value(self, name: PName) -> Optional[Any]:
|
111
85
|
"""Get the value of a parameter stored by the capture config.
|
112
|
-
|
86
|
+
|
113
87
|
For static typing, should be `cast` after return according to the corresponding `PTemplate`.
|
114
88
|
|
115
89
|
:param name: The name of the parameter.
|
@@ -118,13 +92,12 @@ class CaptureConfig(JsonHandler):
|
|
118
92
|
"""
|
119
93
|
return self.parameters.get_parameter_value(name)
|
120
94
|
|
121
|
-
|
122
95
|
def save_parameters(
|
123
96
|
self,
|
124
97
|
receiver_name: str,
|
125
98
|
receiver_mode: str,
|
126
99
|
parameters: Parameters,
|
127
|
-
force: bool = False
|
100
|
+
force: bool = False,
|
128
101
|
):
|
129
102
|
"""Write the input parameters to a capture config.
|
130
103
|
|
@@ -136,6 +109,6 @@ class CaptureConfig(JsonHandler):
|
|
136
109
|
d = {
|
137
110
|
_CaptureConfigKey.RECEIVER_MODE: receiver_mode,
|
138
111
|
_CaptureConfigKey.RECEIVER_NAME: receiver_name,
|
139
|
-
_CaptureConfigKey.PARAMETERS
|
112
|
+
_CaptureConfigKey.PARAMETERS: parameters.to_dict(),
|
140
113
|
}
|
141
|
-
self.save(d, force=force)
|
114
|
+
self.save(d, force=force)
|