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