spectre-core 0.0.12__py3-none-any.whl → 0.0.13__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.13.dist-info}/METADATA +14 -7
  70. spectre_core-0.0.13.dist-info/RECORD +75 -0
  71. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.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.13.dist-info}/LICENSE +0 -0
  88. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -2,107 +2,157 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- from typing import Optional, TypeVar, Any
6
- from copy import deepcopy
5
+ from typing import Optional, TypeVar, Any, Generic, Callable
7
6
  from textwrap import dedent
8
- from dataclasses import dataclass
7
+ from copy import deepcopy
9
8
 
10
- from ._pconstraints import PConstraint, PConstraints
9
+ from ._pnames import PName
10
+ from ._pconstraints import BasePConstraint, EnforceSign, PowerOfTwo
11
11
  from ._parameters import Parameter
12
-
13
- T = TypeVar('T')
14
-
15
- class PTemplate:
16
- """A parameter template.
17
12
 
18
- Constrain the value and type that a parameter can take.
19
- """
20
- def __init__(self,
21
- name: str,
22
- ptype: T,
23
- default: Optional[T] = None,
24
- nullable: bool = False,
25
- enforce_default: Optional[bool] = False,
26
- help: Optional[str] = None,
27
- pconstraints: Optional[list[PConstraint]] = None):
28
- if not callable(ptype):
29
- raise TypeError("ptype must be callable.")
13
+ # value type
14
+ VT = TypeVar('VT')
15
+ class PTemplate(Generic[VT]):
16
+ """A template which constrains the value and type of a capture config parameter."""
17
+ def __init__(
18
+ self,
19
+ name: PName,
20
+ ptype: Callable[[Any], VT],
21
+ default: Optional[VT] = None,
22
+ nullable: bool = False,
23
+ enforce_default: bool = False,
24
+ help: Optional[str] = None,
25
+ pconstraints: Optional[list[BasePConstraint]] = None
26
+ ) -> None:
27
+ """Initialise an instance of `PTemplate`.
30
28
 
29
+ :param name: The name of the parameter.
30
+ :param ptype: The required type of the parameter value.
31
+ :param default: The parameter value if not explicitly specified. Defaults to None.
32
+ :param nullable: Whether the value of the parameter can be `None`. Defaults to False.
33
+ :param enforce_default: Indicates whether the value must match the specified `default`. Defaults to False.
34
+ :param help: A helpful description of what the parameter is and the value it stores. Defaults to None.
35
+ :param pconstraints: Custom constraints to be applied to the value of the parameter. Defaults to None.
36
+ """
31
37
  self._name = name
32
38
  self._ptype = ptype
33
39
  self._default = default
34
40
  self._nullable = nullable
35
41
  self._enforce_default = enforce_default
36
42
  self._help = dedent(help).strip().replace("\n", " ") if help else "No help has been provided."
37
- self._pconstraints: list[PConstraint] = pconstraints or []
43
+ self._pconstraints: list[BasePConstraint] = pconstraints or []
38
44
 
39
45
 
40
46
  @property
41
- def name(self) -> str:
47
+ def name(
48
+ self
49
+ ) -> PName:
42
50
  """The name of the parameter."""
43
51
  return self._name
44
52
 
45
53
 
46
54
  @property
47
- def ptype(self) -> T:
48
- """The parameter type."""
55
+ def ptype(
56
+ self
57
+ ) -> Callable[[object], VT]:
58
+ """The required type of the parameter. The value must be castable as this type."""
49
59
  return self._ptype
50
60
 
51
61
 
52
62
  @property
53
- def default(self) -> Optional[T]:
54
- """The value of the parameter, if the value is unspecified."""
63
+ def default(
64
+ self
65
+ ) -> Optional[VT]:
66
+ """The parameter value if not explictly specified."""
55
67
  return self._default
56
68
 
57
69
 
58
70
  @default.setter
59
- def default(self, value: T) -> None:
60
- """Update the default of a ptemplate"""
61
- self._default = value
71
+ def default(
72
+ self,
73
+ value: VT
74
+ ) -> None:
75
+ """Update the `default` of this parameter template.
76
+
77
+ :param value: The new default value to set.
78
+ """
79
+ self._default = self._cast(value)
62
80
 
63
81
 
64
82
  @property
65
- def nullable(self) -> bool:
66
- """Whether the value of the parameter is allowed to be of None type."""
83
+ def nullable(
84
+ self
85
+ ) -> bool:
86
+ """Whether the value of the parameter can be `None`."""
67
87
  return self._nullable
68
88
 
69
89
 
70
90
  @property
71
- def enforce_default(self) -> bool:
72
- """Whether the provided default value is enforced."""
91
+ def enforce_default(
92
+ self
93
+ ) -> bool:
94
+ """Indicates whether the value must match the specified `default`."""
73
95
  return self._enforce_default
74
96
 
75
97
 
76
98
  @enforce_default.setter
77
- def enforce_default(self, value: bool) -> None:
78
- """Set whether the provided default value is enforced."""
99
+ def enforce_default(
100
+ self,
101
+ value: bool
102
+ ) -> None:
103
+ """Update whether to `enforce_default` for this parameter template.
104
+
105
+ :param value: Whether to enforce the default value.
106
+ """
79
107
  self._enforce_default = value
80
108
 
81
109
 
82
110
  @property
83
- def help(self) -> str:
84
- """A description of what the parameter is, and the value it stores."""
111
+ def help(
112
+ self
113
+ ) -> str:
114
+ """A helpful description of what the parameter is, and the value it stores."""
85
115
  return self._help
86
116
 
87
117
 
88
- def add_pconstraint(self,
89
- pconstraint: PConstraint) -> None:
118
+ def add_pconstraint(
119
+ self,
120
+ pconstraint: BasePConstraint
121
+ ) -> None:
122
+ """Add a parameter constraint to this template.
123
+
124
+ :param pconstraint: A `PConstraint` instance compatible with the `ptype`.
125
+ """
90
126
  self._pconstraints.append(pconstraint)
91
127
 
92
128
 
93
- def _cast(self,
94
- value: Any) -> T:
95
- """Cast the input value to the ptype of this template"""
129
+ def _cast(
130
+ self,
131
+ value: Any
132
+ ) -> VT:
133
+ """Cast the input value to the `ptype` for this parameter template.
134
+
135
+ :param value: The value to be type casted.
136
+ :raises ValueError: If there is any trouble casting `value` as the `ptype` for this parameter template.
137
+ :return: The input value cast as `ptype` for this parameter template.
138
+ """
96
139
  try:
97
140
  return self._ptype(value)
98
141
  except (TypeError, ValueError) as e:
99
142
  raise ValueError(f"Could not cast '{value}' to '{self._ptype.__name__}': {e}")
100
143
 
101
144
 
102
- def _constrain(self,
103
- value: T) -> T:
104
- """Constrain the input value according to constraints of the template."""
145
+ def _constrain(
146
+ self,
147
+ value: VT
148
+ ) -> VT:
149
+ """Constrain the input value according to constraints of the template.
105
150
 
151
+ :param value: The value to be constrained.
152
+ :raises ValueError: If a custom `PConstraint` fails for the input value.
153
+ :raises RuntimeError: If an unexpected error occurs during constraint validation.
154
+ :return: The input value unchanged if it passes validation.
155
+ """
106
156
  if self._enforce_default and value != self._default:
107
157
  raise ValueError(f"The default value of '{self._default}' "
108
158
  f"is required for the parameter '{self._name}'.")
@@ -119,9 +169,16 @@ class PTemplate:
119
169
  return value
120
170
 
121
171
 
122
- def apply_template(self,
123
- value: Optional[Any]) -> T:
124
- """Cast the value and constrain it according to this ptemplate."""
172
+ def apply_template(
173
+ self,
174
+ value: Optional[Any]
175
+ ) -> Optional[VT]:
176
+ """Cast a value and validate it according to this parameter template.
177
+
178
+ :param value: The input value.
179
+ :raises ValueError: If the value is `None`, no `default` is specified, and the parameter is not nullable.
180
+ :return: The input value type cast and validated according to the parameter template.
181
+ """
125
182
  if value is None:
126
183
  if self._default is not None:
127
184
  value = self._default
@@ -132,319 +189,305 @@ class PTemplate:
132
189
  else:
133
190
  return None
134
191
 
135
- value = self._cast(value)
136
- value = self._constrain(value)
137
- return value
192
+ return self._constrain( self._cast(value) )
138
193
 
139
194
 
140
- def make_parameter(self,
141
- value: Optional[Any] = None) -> Parameter:
195
+ def make_parameter(
196
+ self,
197
+ value: Optional[Any] = None
198
+ ) -> Parameter:
199
+ """Create a `Parameter` compliant with this template.
200
+
201
+ :param value: The provided value for the parameter. Defaults to None.
202
+ :return: A `Parameter` object validated according to this template.
203
+ """
142
204
  value = self.apply_template(value)
143
205
  return Parameter(self._name, value)
144
206
 
145
207
 
146
- def to_dict(self) -> dict:
147
- """Convert the template to a dictionary representation."""
148
- return {
149
- "name": self._name,
150
- "type": str(self._ptype),
208
+ def to_dict(
209
+ self
210
+ ) -> dict[str, str]:
211
+ """Convert this parameter template to a serialisable dictionary.
212
+
213
+ :return: A dictionary representation of this parameter template with string formatted values.
214
+ """
215
+ d = {
216
+ "name": self._name.value,
217
+ "type": self._ptype.__name__,
151
218
  "default": self._default,
152
219
  "enforce_default": self._enforce_default,
153
220
  "help": self._help,
154
- "constraints": [f"{constraint}" for constraint in self._pconstraints]
221
+ "pconstraints": [f"{constraint}" for constraint in self._pconstraints]
155
222
  }
156
-
223
+ return {k: f"{v}" for k,v in d.items()}
224
+
225
+
226
+ # ------------------------------------------------------------------------------------------ #
227
+ # `_base_ptemplates` holds all shared base parameter templates. They are 'base' templates,
228
+ # in the sense that they should be configured according to specific use-cases. For example,
229
+ # `default` values should be set, and `pconstraints` added according to specific SDR specs.
230
+ # ------------------------------------------------------------------------------------------ #
157
231
 
158
- @dataclass(frozen=True)
159
- class PNames:
160
- """A centralised store of default parameter template names"""
161
- CENTER_FREQUENCY : str = "center_frequency"
162
- MIN_FREQUENCY : str = "min_frequency"
163
- MAX_FREQUENCY : str = "max_frequency"
164
- FREQUENCY_STEP : str = "frequency_step"
165
- FREQUENCY : str = "frequency"
166
- BANDWIDTH : str = "bandwidth"
167
- SAMPLE_RATE : str = "sample_rate"
168
- IF_GAIN : str = "if_gain"
169
- RF_GAIN : str = "rf_gain"
170
- AMPLITUDE : str = "amplitude"
171
- FREQUENCY : str = "frequency"
172
- TIME_RESOLUTION : str = "time_resolution"
173
- FREQUENCY_RESOLUTION : str = "frequency_resolution"
174
- TIME_RANGE : str = "time_range"
175
- BATCH_SIZE : str = "batch_size"
176
- WINDOW_TYPE : str = "window_type"
177
- WINDOW_HOP : str = "window_hop"
178
- WINDOW_SIZE : str = "window_size"
179
- EVENT_HANDLER_KEY : str = "event_handler_key"
180
- WATCH_EXTENSION : str = "watch_extension"
181
- BATCH_KEY : str = "batch_key"
182
- SAMPLES_PER_STEP : str = "samples_per_step"
183
- MIN_SAMPLES_PER_STEP : str = "min_samples_per_step"
184
- MAX_SAMPLES_PER_STEP : str = "max_samples_per_step"
185
- STEP_INCREMENT : str = "step_increment"
186
- ORIGIN : str = "origin"
187
- TELESCOPE : str = "telescope"
188
- INSTRUMENT : str = "instrument"
189
- OBJECT : str = "object"
190
- OBS_LAT : str = "obs_lat"
191
- OBS_LON : str = "obs_lon"
192
- OBS_ALT : str = "obs_alt"
193
-
194
- #
195
- # All stored base ptemplates
196
- #
197
- _base_ptemplates = {
198
- PNames.CENTER_FREQUENCY: PTemplate(PNames.CENTER_FREQUENCY,
199
- float,
200
- help = """
201
- The center frequency of the SDR in Hz.
202
- This value determines the midpoint of the frequency range
203
- being processed.
204
- """,
205
- pconstraints=[
206
- PConstraints.enforce_positive
207
- ]),
208
- PNames.MIN_FREQUENCY: PTemplate(PNames.MIN_FREQUENCY,
209
- float,
210
- help = """
211
- The minimum center frequency, in Hz, for the frequency sweep.
212
- """,
213
- pconstraints=[
214
- PConstraints.enforce_positive
215
- ]),
216
- PNames.MAX_FREQUENCY: PTemplate(PNames.MAX_FREQUENCY,
217
- float,
218
- help = """
219
- The maximum center frequency, in Hz, for the frequency sweep.
220
- """,
221
- pconstraints=[
222
- PConstraints.enforce_positive
223
- ]),
224
- PNames.FREQUENCY_STEP: PTemplate(PNames.FREQUENCY_STEP,
225
- float,
226
- help = """
227
- The amount, in Hz, by which the center frequency is incremented
228
- for each step in the frequency sweep.
229
- """,
230
- pconstraints=[
231
- PConstraints.enforce_positive
232
- ]),
233
- PNames.BANDWIDTH: PTemplate(PNames.BANDWIDTH,
234
- float,
235
- help = """
236
- The frequency range in Hz the signal will occupy without
237
- significant attenutation.
238
- """,
239
- pconstraints=[
240
- PConstraints.enforce_positive
241
- ]),
242
- PNames.SAMPLE_RATE: PTemplate(PNames.SAMPLE_RATE,
243
- int,
244
- help = """
245
- The number of samples per second in Hz.
246
- """,
247
- pconstraints=[
248
- PConstraints.enforce_positive
249
- ]),
250
- PNames.IF_GAIN: PTemplate(PNames.IF_GAIN,
251
- float,
252
- help = """
253
- The intermediate frequency gain, in dB.
254
- Negative value indicates attenuation.
255
- """,
256
- pconstraints=[
257
- PConstraints.enforce_negative
258
- ]),
259
- PNames.RF_GAIN: PTemplate(PNames.RF_GAIN,
260
- float,
261
- help = """
262
- The radio frequency gain, in dB.
263
- Negative value indicates attenuation.
264
- """,
265
- pconstraints=[
266
- PConstraints.enforce_non_positive
267
- ]),
268
- PNames.EVENT_HANDLER_KEY: PTemplate(PNames.EVENT_HANDLER_KEY,
269
- str,
270
- help = """
271
- Identifies which post-processing functions to invoke
272
- on newly created files.
273
- """),
274
- PNames.BATCH_KEY: PTemplate(PNames.BATCH_KEY,
275
- str,
276
- help = """
277
- Identifies the type of data is stored in each batch.
278
- """,
279
- ),
280
- PNames.WINDOW_SIZE: PTemplate(PNames.WINDOW_SIZE,
281
- int,
282
- help = """
283
- The size of the window, in samples, when performing the
284
- Short Time FFT.
285
- """,
286
- pconstraints=[
287
- PConstraints.enforce_positive,
288
- PConstraints.power_of_two
289
- ]),
290
- PNames.WINDOW_HOP: PTemplate(PNames.WINDOW_HOP,
291
- int,
292
- help = """
293
- How much the window is shifted, in samples,
294
- when performing the Short Time FFT.
295
- """,
296
- pconstraints=[
297
- PConstraints.enforce_positive
298
- ]),
299
- PNames.WINDOW_TYPE: PTemplate(PNames.WINDOW_TYPE,
300
- str,
301
- help = """
302
- The type of window applied when performing the Short
303
- Time FFT.
304
- """,
305
- ),
306
- PNames.WATCH_EXTENSION: PTemplate(PNames.WATCH_EXTENSION,
307
- str,
308
- help = """
309
- Post-processing is triggered by newly created files with this extension.
310
- Extensions are specified without the '.' character.
311
- """,
312
- ),
313
- PNames.TIME_RESOLUTION: PTemplate(PNames.TIME_RESOLUTION,
314
- float,
315
- nullable=True,
316
- help = """
317
- Batched spectrograms are smoothed by averaging up to the time resolution,
318
- specified in seconds.
319
- """,
320
- pconstraints=[
321
- PConstraints.enforce_non_negative
322
- ]),
323
- PNames.FREQUENCY_RESOLUTION: PTemplate(PNames.FREQUENCY_RESOLUTION,
324
- float,
325
- nullable=True,
326
- help = """
327
- Batched spectrograms are smoothed by averaging up to the frequency resolution,
328
- specified in Hz.
329
- """,
330
- pconstraints=[
331
- PConstraints.enforce_non_negative
332
- ]),
333
- PNames.TIME_RANGE: PTemplate(PNames.TIME_RANGE,
334
- float,
335
- nullable=True,
336
- help = """
337
- Batched spectrograms are stitched together until
338
- the time range, in seconds, is surpassed.
339
- """,
340
- pconstraints=[
341
- PConstraints.enforce_non_negative
342
- ]),
343
- PNames.BATCH_SIZE: PTemplate(PNames.BATCH_SIZE,
344
- int,
345
- help = """
346
- SDR data is collected in batches of this size, specified
347
- in seconds.
348
- """,
349
- pconstraints=[
350
- PConstraints.enforce_positive
351
- ]),
352
- PNames.SAMPLES_PER_STEP: PTemplate(PNames.SAMPLES_PER_STEP,
353
- int,
354
- help = """
355
- The number of samples taken at each center frequency in the sweep.
356
- This may vary slightly from what is specified due to the nature of
357
- GNU Radio runtime.
358
- """,
359
- pconstraints=[
360
- PConstraints.enforce_positive
361
- ]),
362
- PNames.ORIGIN: PTemplate(PNames.ORIGIN,
363
- str,
364
- nullable=True,
365
- help="""
366
- Corresponds to the FITS keyword ORIGIN.
367
- """),
368
- PNames.TELESCOPE: PTemplate(PNames.TELESCOPE,
369
- str,
370
- nullable=True,
371
- help="""
372
- Corresponds to the FITS keyword TELESCOP.
373
- """),
374
- PNames.INSTRUMENT: PTemplate(PNames.INSTRUMENT,
375
- str,
376
- nullable=True,
377
- help="""
378
- Corresponds to the FITS keyword INSTRUME.
379
- """),
380
- PNames.OBJECT: PTemplate(PNames.OBJECT,
381
- str,
382
- nullable=True,
383
- help="""
384
- Corresponds to the FITS keyword OBJECT.
385
- """),
386
- PNames.OBS_LAT: PTemplate(PNames.OBS_LAT,
387
- float,
388
- nullable=True,
389
- help="""
390
- Corresponds to the FITS keyword OBS_LAT.
391
- """),
392
- PNames.OBS_LON: PTemplate(PNames.OBS_LON,
393
- float,
394
- nullable=True,
395
- help="""
396
- Corresponds to the FITS keyword OBS_LONG.
397
- """),
398
- PNames.OBS_ALT: PTemplate(PNames.OBS_ALT,
399
- float,
400
- nullable=True,
401
- help="""
402
- Corresponds to the FITS keyword OBS_ALT.
403
- """),
404
- PNames.AMPLITUDE: PTemplate(PNames.AMPLITUDE,
405
- float,
406
- help="""
407
- The amplitude of the signal.
408
- """),
409
- PNames.FREQUENCY: PTemplate(PNames.FREQUENCY,
410
- float,
411
- help="""
412
- The frequency of the signal, in Hz.
413
- """),
414
- PNames.MIN_SAMPLES_PER_STEP: PTemplate(PNames.MIN_SAMPLES_PER_STEP,
415
- int,
416
- help="""
417
- The number of samples in the shortest step of the staircase.
418
- """,
419
- pconstraints=[
420
- PConstraints.enforce_positive
421
- ]),
422
- PNames.MAX_SAMPLES_PER_STEP: PTemplate(PNames.MAX_SAMPLES_PER_STEP,
423
- int,
424
- help="""
425
- The number of samples in the longest step of the staircase.
426
- """,
232
+ _base_ptemplates: dict[PName, PTemplate] = {
233
+ PName.CENTER_FREQUENCY: PTemplate(PName.CENTER_FREQUENCY,
234
+ float,
235
+ help = """
236
+ The center frequency of the SDR in Hz.
237
+ This value determines the midpoint of the frequency range
238
+ being processed.
239
+ """,
240
+ pconstraints=[
241
+ EnforceSign.positive
242
+ ]),
243
+ PName.MIN_FREQUENCY: PTemplate(PName.MIN_FREQUENCY,
244
+ float,
245
+ help = """
246
+ The minimum center frequency, in Hz, for the frequency sweep.
247
+ """,
248
+ pconstraints=[
249
+ EnforceSign.positive
250
+ ]),
251
+ PName.MAX_FREQUENCY: PTemplate(PName.MAX_FREQUENCY,
252
+ float,
253
+ help = """
254
+ The maximum center frequency, in Hz, for the frequency sweep.
255
+ """,
427
256
  pconstraints=[
428
- PConstraints.enforce_positive
429
- ]),
430
- PNames.STEP_INCREMENT: PTemplate(PNames.STEP_INCREMENT,
431
- int,
432
- help="""
433
- The length by which each step in the staircase is incremented.
434
- """,
435
- pconstraints=[
436
- PConstraints.enforce_positive,
437
- ])
257
+ EnforceSign.positive
258
+ ]),
259
+ PName.FREQUENCY_STEP: PTemplate(PName.FREQUENCY_STEP,
260
+ float,
261
+ help = """
262
+ The amount, in Hz, by which the center frequency is incremented
263
+ for each step in the frequency sweep.
264
+ """,
265
+ pconstraints=[
266
+ EnforceSign.positive
267
+ ]),
268
+ PName.BANDWIDTH: PTemplate(PName.BANDWIDTH,
269
+ float,
270
+ help = """
271
+ The frequency range in Hz the signal will occupy without
272
+ significant attenutation.
273
+ """,
274
+ pconstraints=[
275
+ EnforceSign.positive
276
+ ]),
277
+ PName.SAMPLE_RATE: PTemplate(PName.SAMPLE_RATE,
278
+ int,
279
+ help = """
280
+ The number of samples per second in Hz.
281
+ """,
282
+ pconstraints=[
283
+ EnforceSign.positive
284
+ ]),
285
+ PName.IF_GAIN: PTemplate(PName.IF_GAIN,
286
+ float,
287
+ help = """
288
+ The intermediate frequency gain, in dB.
289
+ Negative value indicates attenuation.
290
+ """,
291
+ pconstraints=[
292
+ EnforceSign.negative
293
+ ]),
294
+ PName.RF_GAIN: PTemplate(PName.RF_GAIN,
295
+ float,
296
+ help = """
297
+ The radio frequency gain, in dB.
298
+ Negative value indicates attenuation.
299
+ """,
300
+ pconstraints=[
301
+ EnforceSign.non_positive
302
+ ]),
303
+ PName.EVENT_HANDLER_KEY: PTemplate(PName.EVENT_HANDLER_KEY,
304
+ str,
305
+ help = """
306
+ Identifies which post-processing functions to invoke
307
+ on newly created files.
308
+ """),
309
+ PName.BATCH_KEY: PTemplate(PName.BATCH_KEY,
310
+ str,
311
+ help = """
312
+ Identifies the type of data is stored in each batch.
313
+ """,
314
+ ),
315
+ PName.WINDOW_SIZE: PTemplate(PName.WINDOW_SIZE,
316
+ int,
317
+ help = """
318
+ The size of the window, in samples, when performing the
319
+ Short Time FFT.
320
+ """,
321
+ pconstraints=[
322
+ EnforceSign.positive,
323
+ PowerOfTwo(),
324
+ ]),
325
+ PName.WINDOW_HOP: PTemplate(PName.WINDOW_HOP,
326
+ int,
327
+ help = """
328
+ How much the window is shifted, in samples,
329
+ when performing the Short Time FFT.
330
+ """,
331
+ pconstraints=[
332
+ EnforceSign.positive
333
+ ]),
334
+ PName.WINDOW_TYPE: PTemplate(PName.WINDOW_TYPE,
335
+ str,
336
+ help = """
337
+ The type of window applied when performing the Short
338
+ Time FFT.
339
+ """,
340
+ ),
341
+ PName.WATCH_EXTENSION: PTemplate(PName.WATCH_EXTENSION,
342
+ str,
343
+ help = """
344
+ Post-processing is triggered by newly created files with this extension.
345
+ Extensions are specified without the '.' character.
346
+ """,
347
+ ),
348
+ PName.TIME_RESOLUTION: PTemplate(PName.TIME_RESOLUTION,
349
+ float,
350
+ nullable=True,
351
+ help = """
352
+ Batched spectrograms are smoothed by averaging up to the time resolution,
353
+ specified in seconds.
354
+ """,
355
+ pconstraints=[
356
+ EnforceSign.non_negative
357
+ ]),
358
+ PName.FREQUENCY_RESOLUTION: PTemplate(PName.FREQUENCY_RESOLUTION,
359
+ float,
360
+ nullable=True,
361
+ help = """
362
+ Batched spectrograms are smoothed by averaging up to the frequency resolution,
363
+ specified in Hz.
364
+ """,
365
+ pconstraints=[
366
+ EnforceSign.non_negative
367
+ ]),
368
+ PName.TIME_RANGE: PTemplate(PName.TIME_RANGE,
369
+ float,
370
+ nullable=True,
371
+ help = """
372
+ Batched spectrograms are stitched together until
373
+ the time range, in seconds, is surpassed.
374
+ """,
375
+ pconstraints=[
376
+ EnforceSign.non_negative
377
+ ]),
378
+ PName.BATCH_SIZE: PTemplate(PName.BATCH_SIZE,
379
+ int,
380
+ help = """
381
+ SDR data is collected in batches of this size, specified
382
+ in seconds.
383
+ """,
384
+ pconstraints=[
385
+ EnforceSign.positive
386
+ ]),
387
+ PName.SAMPLES_PER_STEP: PTemplate(PName.SAMPLES_PER_STEP,
388
+ int,
389
+ help = """
390
+ The number of samples taken at each center frequency in the sweep.
391
+ This may vary slightly from what is specified due to the nature of
392
+ GNU Radio runtime.
393
+ """,
394
+ pconstraints=[
395
+ EnforceSign.positive
396
+ ]),
397
+ PName.ORIGIN: PTemplate(PName.ORIGIN,
398
+ str,
399
+ nullable=True,
400
+ help="""
401
+ Corresponds to the FITS keyword ORIGIN.
402
+ """),
403
+ PName.TELESCOPE: PTemplate(PName.TELESCOPE,
404
+ str,
405
+ nullable=True,
406
+ help="""
407
+ Corresponds to the FITS keyword TELESCOP.
408
+ """),
409
+ PName.INSTRUMENT: PTemplate(PName.INSTRUMENT,
410
+ str,
411
+ nullable=True,
412
+ help="""
413
+ Corresponds to the FITS keyword INSTRUME.
414
+ """),
415
+ PName.OBJECT: PTemplate(PName.OBJECT,
416
+ str,
417
+ nullable=True,
418
+ help="""
419
+ Corresponds to the FITS keyword OBJECT.
420
+ """),
421
+ PName.OBS_LAT: PTemplate(PName.OBS_LAT,
422
+ float,
423
+ nullable=True,
424
+ help="""
425
+ Corresponds to the FITS keyword OBS_LAT.
426
+ """),
427
+ PName.OBS_LON: PTemplate(PName.OBS_LON,
428
+ float,
429
+ nullable=True,
430
+ help="""
431
+ Corresponds to the FITS keyword OBS_LONG.
432
+ """),
433
+ PName.OBS_ALT: PTemplate(PName.OBS_ALT,
434
+ float,
435
+ nullable=True,
436
+ help="""
437
+ Corresponds to the FITS keyword OBS_ALT.
438
+ """),
439
+ PName.AMPLITUDE: PTemplate(PName.AMPLITUDE,
440
+ float,
441
+ help="""
442
+ The amplitude of the signal.
443
+ """),
444
+ PName.FREQUENCY: PTemplate(PName.FREQUENCY,
445
+ float,
446
+ help="""
447
+ The frequency of the signal, in Hz.
448
+ """),
449
+ PName.MIN_SAMPLES_PER_STEP: PTemplate(PName.MIN_SAMPLES_PER_STEP,
450
+ int,
451
+ help="""
452
+ The number of samples in the shortest step of the staircase.
453
+ """,
454
+ pconstraints=[
455
+ EnforceSign.positive
456
+ ]),
457
+ PName.MAX_SAMPLES_PER_STEP: PTemplate(PName.MAX_SAMPLES_PER_STEP,
458
+ int,
459
+ help="""
460
+ The number of samples in the longest step of the staircase.
461
+ """,
462
+ pconstraints=[
463
+ EnforceSign.positive
464
+ ]),
465
+ PName.STEP_INCREMENT: PTemplate(PName.STEP_INCREMENT,
466
+ int,
467
+ help="""
468
+ The length by which each step in the staircase is incremented.
469
+ """,
470
+ pconstraints=[
471
+ EnforceSign.positive,
472
+ ])
438
473
 
439
474
 
440
475
  }
441
476
 
442
- T = TypeVar('T')
477
+
443
478
  def get_base_ptemplate(
444
- parameter_name: str,
479
+ pname: PName,
445
480
  ) -> PTemplate:
446
- """Create a fresh deep copy of a pre-defined ptemplate"""
447
- if parameter_name not in _base_ptemplates:
448
- raise KeyError(f"No ptemplate found for the parameter name '{parameter_name}'. "
481
+ """Get a pre-defined base parameter template, to be configured according to the specific use case.
482
+
483
+ :param pname: The parameter name for the template.
484
+ :raises KeyError: If there is no base parameter template corresponding to the input name.
485
+ :return: A deep copy of the corresponding base parameter template, if it exists.
486
+ """
487
+ if pname not in _base_ptemplates:
488
+ raise KeyError(f"No ptemplate found for the parameter name '{pname}'. "
449
489
  f"Expected one of {list(_base_ptemplates.keys())}")
450
- return deepcopy( _base_ptemplates[parameter_name] )
490
+ # A deep copy is required as each receiver instance may mutate the original instance
491
+ # according to its particular use case. Copying preserves the original instance,
492
+ # enabling reuse.
493
+ return deepcopy( _base_ptemplates[pname] )