spectre-core 0.0.22__py3-none-any.whl → 0.0.23__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 (77) hide show
  1. spectre_core/_file_io/__init__.py +4 -4
  2. spectre_core/_file_io/file_handlers.py +60 -106
  3. spectre_core/batches/__init__.py +20 -3
  4. spectre_core/batches/_base.py +85 -134
  5. spectre_core/batches/_batches.py +55 -99
  6. spectre_core/batches/_factory.py +21 -20
  7. spectre_core/batches/_register.py +8 -8
  8. spectre_core/batches/plugins/_batch_keys.py +7 -6
  9. spectre_core/batches/plugins/_callisto.py +65 -97
  10. spectre_core/batches/plugins/_iq_stream.py +105 -169
  11. spectre_core/capture_configs/__init__.py +46 -17
  12. spectre_core/capture_configs/_capture_config.py +25 -52
  13. spectre_core/capture_configs/_capture_modes.py +8 -6
  14. spectre_core/capture_configs/_capture_templates.py +50 -110
  15. spectre_core/capture_configs/_parameters.py +37 -74
  16. spectre_core/capture_configs/_pconstraints.py +40 -40
  17. spectre_core/capture_configs/_pnames.py +36 -34
  18. spectre_core/capture_configs/_ptemplates.py +260 -347
  19. spectre_core/capture_configs/_pvalidators.py +99 -102
  20. spectre_core/config/__init__.py +13 -8
  21. spectre_core/config/_paths.py +18 -35
  22. spectre_core/config/_time_formats.py +6 -5
  23. spectre_core/exceptions.py +38 -0
  24. spectre_core/jobs/__init__.py +3 -6
  25. spectre_core/jobs/_duration.py +12 -0
  26. spectre_core/jobs/_jobs.py +72 -43
  27. spectre_core/jobs/_workers.py +55 -105
  28. spectre_core/logs/__init__.py +7 -2
  29. spectre_core/logs/_configure.py +13 -17
  30. spectre_core/logs/_decorators.py +6 -4
  31. spectre_core/logs/_logs.py +37 -89
  32. spectre_core/logs/_process_types.py +5 -3
  33. spectre_core/plotting/__init__.py +13 -3
  34. spectre_core/plotting/_base.py +64 -138
  35. spectre_core/plotting/_format.py +10 -8
  36. spectre_core/plotting/_panel_names.py +7 -5
  37. spectre_core/plotting/_panel_stack.py +82 -115
  38. spectre_core/plotting/_panels.py +120 -155
  39. spectre_core/post_processing/__init__.py +6 -3
  40. spectre_core/post_processing/_base.py +41 -55
  41. spectre_core/post_processing/_factory.py +14 -11
  42. spectre_core/post_processing/_post_processor.py +16 -12
  43. spectre_core/post_processing/_register.py +10 -7
  44. spectre_core/post_processing/plugins/_event_handler_keys.py +4 -3
  45. spectre_core/post_processing/plugins/_fixed_center_frequency.py +54 -47
  46. spectre_core/post_processing/plugins/_swept_center_frequency.py +199 -174
  47. spectre_core/receivers/__init__.py +9 -2
  48. spectre_core/receivers/_base.py +82 -148
  49. spectre_core/receivers/_factory.py +20 -30
  50. spectre_core/receivers/_register.py +7 -10
  51. spectre_core/receivers/_spec_names.py +17 -15
  52. spectre_core/receivers/plugins/_b200mini.py +47 -60
  53. spectre_core/receivers/plugins/_receiver_names.py +8 -6
  54. spectre_core/receivers/plugins/_rsp1a.py +44 -40
  55. spectre_core/receivers/plugins/_rspduo.py +59 -44
  56. spectre_core/receivers/plugins/_sdrplay_receiver.py +67 -83
  57. spectre_core/receivers/plugins/_test.py +136 -129
  58. spectre_core/receivers/plugins/_usrp.py +93 -85
  59. spectre_core/receivers/plugins/gr/__init__.py +1 -1
  60. spectre_core/receivers/plugins/gr/_base.py +14 -22
  61. spectre_core/receivers/plugins/gr/_rsp1a.py +53 -60
  62. spectre_core/receivers/plugins/gr/_rspduo.py +77 -89
  63. spectre_core/receivers/plugins/gr/_test.py +49 -57
  64. spectre_core/receivers/plugins/gr/_usrp.py +61 -59
  65. spectre_core/spectrograms/__init__.py +21 -13
  66. spectre_core/spectrograms/_analytical.py +108 -99
  67. spectre_core/spectrograms/_array_operations.py +39 -46
  68. spectre_core/spectrograms/_spectrogram.py +289 -322
  69. spectre_core/spectrograms/_transform.py +106 -73
  70. spectre_core/wgetting/__init__.py +1 -3
  71. spectre_core/wgetting/_callisto.py +87 -93
  72. {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.dist-info}/METADATA +9 -23
  73. spectre_core-0.0.23.dist-info/RECORD +79 -0
  74. {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.dist-info}/WHEEL +1 -1
  75. spectre_core-0.0.22.dist-info/RECORD +0 -78
  76. {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.dist-info}/licenses/LICENSE +0 -0
  77. {spectre_core-0.0.22.dist-info → spectre_core-0.0.23.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  from logging import getLogger
6
+
6
7
  _LOGGER = getLogger(__name__)
7
8
 
8
9
  from typing import Optional, cast
@@ -15,34 +16,26 @@ from spectre_core.capture_configs import CaptureConfig, PName
15
16
  from spectre_core.spectrograms import Spectrogram, join_spectrograms
16
17
 
17
18
 
18
- def make_sft_instance(
19
- capture_config: CaptureConfig
20
- ) -> ShortTimeFFT:
19
+ def make_sft_instance(capture_config: CaptureConfig) -> ShortTimeFFT:
21
20
  """Extract window parameters from the input capture config and create an instance
22
21
  of `ShortTimeFFT` from `scipy.signal`.
23
22
 
24
23
  :param capture_config: The capture config storing the parameters.
25
- :return: An instance of `ShortTimeFFT` consistent with the window parameters
24
+ :return: An instance of `ShortTimeFFT` consistent with the window parameters
26
25
  in the capture config.
27
26
  """
28
- window_type = cast(str, capture_config.get_parameter_value(PName.WINDOW_TYPE))
29
- sample_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
30
- window_hop = cast(int, capture_config.get_parameter_value(PName.WINDOW_HOP))
31
- window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
32
- window = get_window(window_type,
33
- window_size)
34
- return ShortTimeFFT(window,
35
- window_hop,
36
- sample_rate,
37
- fft_mode = "centered")
27
+ window_type = cast(str, capture_config.get_parameter_value(PName.WINDOW_TYPE))
28
+ sample_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
29
+ window_hop = cast(int, capture_config.get_parameter_value(PName.WINDOW_HOP))
30
+ window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
31
+ window = get_window(window_type, window_size)
32
+ return ShortTimeFFT(window, window_hop, sample_rate, fft_mode="centered")
38
33
 
39
34
 
40
35
  class BaseEventHandler(ABC, FileSystemEventHandler):
41
36
  """An abstract base class for event-driven file post-processing."""
42
- def __init__(
43
- self,
44
- tag: str
45
- ) -> None:
37
+
38
+ def __init__(self, tag: str) -> None:
46
39
  """Initialise a `BaseEventHandler` instance.
47
40
 
48
41
  :param tag: The tag of the capture config used to capture the data.
@@ -51,7 +44,7 @@ class BaseEventHandler(ABC, FileSystemEventHandler):
51
44
 
52
45
  # load the capture config corresponding to the input tag
53
46
  self._capture_config = CaptureConfig(tag)
54
-
47
+
55
48
  # store the next file to be processed (specifically, the absolute file path of the file)
56
49
  self._queued_file: Optional[str] = None
57
50
 
@@ -59,25 +52,17 @@ class BaseEventHandler(ABC, FileSystemEventHandler):
59
52
  # this can be flushed periodically to file as required.
60
53
  self._cached_spectrogram: Optional[Spectrogram] = None
61
54
 
62
-
63
55
  @abstractmethod
64
- def process(
65
- self,
66
- absolute_file_path: str
67
- ) -> None:
56
+ def process(self, absolute_file_path: str) -> None:
68
57
  """
69
58
  Process a batch file at the given file path.
70
59
 
71
60
  :param absolute_file_path: The absolute path to the batch file to be processed.
72
61
  """
73
62
 
74
-
75
- def on_created(
76
- self,
77
- event: FileSystemEvent
78
- ) -> None:
63
+ def on_created(self, event: FileSystemEvent) -> None:
79
64
  """Process a newly created batch file, only once the next batch is created.
80
-
65
+
81
66
  Since we assume that the batches are non-overlapping in time, this guarantees
82
67
  we avoid post processing a file while it is being written to. Files are processed
83
68
  sequentially, in the order they are created.
@@ -86,49 +71,49 @@ class BaseEventHandler(ABC, FileSystemEventHandler):
86
71
  """
87
72
  # The `src_path`` attribute holds the absolute path of the freshly closed file
88
73
  absolute_file_path = event.src_path
89
-
74
+
90
75
  # Only process a file if:
91
76
  #
92
77
  # - It's extension matches the `watch_extension` as defined in the capture config.
93
78
  # - It's tag matches the current sessions tag.
94
79
  #
95
- # This is important for two reasons.
96
- #
97
- # In the case of one session, the capture worker may write to two batch files simultaneously
98
- # (e.g., raw data file + seperate metadata file). We want to process them together - but this method will get called
80
+ # This is important for two reasons.
81
+ #
82
+ # In the case of one session, the capture worker may write to two batch files simultaneously
83
+ # (e.g., raw data file + seperate metadata file). We want to process them together - but this method will get called
99
84
  # seperately for both file creation events. So, we filter by extension to account for this.
100
85
  #
101
86
  # Additionally in the case of multiple sessions, the capture workers will create batch files in the same directory concurrently.
102
87
  # This method is triggered for all file creation events, so we ensure the batch file tag matches the session tag and early return
103
88
  # otherwise. This way, each post processor worker picks up the right files to process.
104
- watch_extension = cast(str, self._capture_config.get_parameter_value(PName.WATCH_EXTENSION))
89
+ watch_extension = cast(
90
+ str, self._capture_config.get_parameter_value(PName.WATCH_EXTENSION)
91
+ )
105
92
  if not absolute_file_path.endswith(f"{self._tag}.{watch_extension}"):
106
93
  return
107
-
94
+
108
95
  _LOGGER.info(f"Noticed {absolute_file_path}")
109
96
  # If there exists a queued file, try and process it
110
97
  if self._queued_file is not None:
111
98
  try:
112
99
  self.process(self._queued_file)
113
100
  except Exception:
114
- _LOGGER.error(f"An error has occured while processing {self._queued_file}",
115
- exc_info=True)
101
+ _LOGGER.error(
102
+ f"An error has occured while processing {self._queued_file}",
103
+ exc_info=True,
104
+ )
116
105
  # Flush any internally stored spectrogram on error to avoid lost data
117
106
  self._flush_cache()
118
107
  # re-raise the exception to the main thread
119
108
  raise
120
-
109
+
121
110
  # Queue the current file for processing next
122
111
  _LOGGER.info(f"Queueing {absolute_file_path} for post processing")
123
112
  self._queued_file = absolute_file_path
124
113
 
125
-
126
- def _cache_spectrogram(
127
- self,
128
- spectrogram: Spectrogram
129
- ) -> None:
114
+ def _cache_spectrogram(self, spectrogram: Spectrogram) -> None:
130
115
  """Cache the input spectrogram by storing it in the `_cached_spectrogram` attribute.
131
-
116
+
132
117
  If the time range of the cached spectrogram exceeds that as specified in the capture config
133
118
  `PName.TIME_RANGE` parameter, the spectrogram in the cache is flushed to file. If `PName.TIME_RANGE`
134
119
  is nulled, the cache is flushed immediately.
@@ -140,20 +125,21 @@ class BaseEventHandler(ABC, FileSystemEventHandler):
140
125
  if self._cached_spectrogram is None:
141
126
  self._cached_spectrogram = spectrogram
142
127
  else:
143
- self._cached_spectrogram = join_spectrograms([self._cached_spectrogram, spectrogram])
144
-
128
+ self._cached_spectrogram = join_spectrograms(
129
+ [self._cached_spectrogram, spectrogram]
130
+ )
131
+
145
132
  time_range = self._capture_config.get_parameter_value(PName.TIME_RANGE) or 0.0
146
133
  if self._cached_spectrogram.time_range >= cast(float, time_range):
147
134
  self._flush_cache()
148
-
149
135
 
150
- def _flush_cache(
151
- self
152
- ) -> None:
136
+ def _flush_cache(self) -> None:
153
137
  """Flush the cached spectrogram to file."""
154
138
  if self._cached_spectrogram:
155
- _LOGGER.info(f"Flushing spectrogram to file with start time "
156
- f"'{self._cached_spectrogram.format_start_time()}'")
139
+ _LOGGER.info(
140
+ f"Flushing spectrogram to file with start time "
141
+ f"'{self._cached_spectrogram.format_start_time()}'"
142
+ )
157
143
  self._cached_spectrogram.save()
158
144
  _LOGGER.info("Flush successful, resetting spectrogram cache")
159
- self._cached_spectrogram = None # reset the cache
145
+ self._cached_spectrogram = None # reset the cache
@@ -10,8 +10,9 @@ from spectre_core.exceptions import EventHandlerNotFoundError
10
10
  from .plugins._event_handler_keys import EventHandlerKey
11
11
  from ._register import event_handler_map
12
12
 
13
+
13
14
  def _get_event_handler_cls_from_key(
14
- event_handler_key: EventHandlerKey
15
+ event_handler_key: EventHandlerKey,
15
16
  ) -> Type[BaseEventHandler]:
16
17
  """Get a registered `BaseEventHandler` subclass.
17
18
 
@@ -22,14 +23,16 @@ def _get_event_handler_cls_from_key(
22
23
  event_handler_cls = event_handler_map.get(event_handler_key)
23
24
  if event_handler_cls is None:
24
25
  event_handler_keys = list(event_handler_map.keys())
25
- raise EventHandlerNotFoundError((f"No event handler found for the capture mode '{event_handler_key}'. "
26
- f"Please specify one of the following capture modes: {event_handler_keys}"))
26
+ raise EventHandlerNotFoundError(
27
+ (
28
+ f"No event handler found for the capture mode '{event_handler_key}'. "
29
+ f"Please specify one of the following capture modes: {event_handler_keys}"
30
+ )
31
+ )
27
32
  return event_handler_cls
28
33
 
29
34
 
30
- def get_event_handler_cls_from_tag(
31
- tag: str
32
- ) -> Type[BaseEventHandler]:
35
+ def get_event_handler_cls_from_tag(tag: str) -> Type[BaseEventHandler]:
33
36
  """
34
37
  Retrieve the event handler class, using the event handler key stored in a capture config.
35
38
 
@@ -37,13 +40,13 @@ def get_event_handler_cls_from_tag(
37
40
  :return: The event handler class specified in the capture config.
38
41
  """
39
42
  capture_config = CaptureConfig(tag)
40
- event_handler_key = cast(str, capture_config.get_parameter_value(PName.EVENT_HANDLER_KEY))
41
- return _get_event_handler_cls_from_key( EventHandlerKey(event_handler_key) )
43
+ event_handler_key = cast(
44
+ str, capture_config.get_parameter_value(PName.EVENT_HANDLER_KEY)
45
+ )
46
+ return _get_event_handler_cls_from_key(EventHandlerKey(event_handler_key))
42
47
 
43
48
 
44
- def get_event_handler(
45
- tag: str
46
- ) -> BaseEventHandler:
49
+ def get_event_handler(tag: str) -> BaseEventHandler:
47
50
  """Create an event handler class instance, using the event handler key stored in a capture config.
48
51
 
49
52
  :param tag: The capture config tag.
@@ -3,6 +3,7 @@
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  from logging import getLogger
6
+
6
7
  _LOGGER = getLogger(__name__)
7
8
 
8
9
  from watchdog.observers import Observer
@@ -12,27 +13,30 @@ from ._factory import get_event_handler
12
13
  from spectre_core.config import get_batches_dir_path
13
14
 
14
15
 
15
- def start_post_processor(
16
- tag: str
17
- ) -> None:
16
+ def start_post_processor(tag: str) -> None:
18
17
  """Start a thread to process newly created files in the `batches` directory.
19
18
 
20
19
  :param tag: The tag of the capture config used for data capture.
21
20
  """
22
21
  post_processor = Observer()
23
22
  event_handler = get_event_handler(tag)
24
- post_processor.schedule(event_handler,
25
- get_batches_dir_path(),
26
- recursive=True,
27
- event_filter=[ FileCreatedEvent ])
28
-
23
+ post_processor.schedule(
24
+ event_handler,
25
+ get_batches_dir_path(),
26
+ recursive=True,
27
+ event_filter=[FileCreatedEvent],
28
+ )
29
+
29
30
  try:
30
- _LOGGER.info("Starting the post processing thread...")
31
+ _LOGGER.info("Starting the post processing thread...")
31
32
  post_processor.start()
32
33
  post_processor.join()
33
34
  except KeyboardInterrupt:
34
- _LOGGER.warning(("Keyboard interrupt detected. Signalling "
35
- "the post processing thread to stop"))
35
+ _LOGGER.warning(
36
+ (
37
+ "Keyboard interrupt detected. Signalling "
38
+ "the post processing thread to stop"
39
+ )
40
+ )
36
41
  post_processor.stop()
37
42
  _LOGGER.warning(("Post processing thread has been successfully stopped"))
38
-
@@ -10,9 +10,11 @@ from ._base import BaseEventHandler
10
10
  # Map populated at runtime via the `register_event_handler` decorator.
11
11
  event_handler_map: dict[EventHandlerKey, Type[BaseEventHandler]] = {}
12
12
 
13
- T = TypeVar('T', bound=BaseEventHandler)
13
+ T = TypeVar("T", bound=BaseEventHandler)
14
+
15
+
14
16
  def register_event_handler(
15
- event_handler_key: EventHandlerKey
17
+ event_handler_key: EventHandlerKey,
16
18
  ) -> Callable[[Type[T]], Type[T]]:
17
19
  """Decorator to register a `BaseEventHandler` subclass under a specified `EventHandlerKey`.
18
20
 
@@ -20,12 +22,13 @@ def register_event_handler(
20
22
  :raises ValueError: If the provided `event_handler_key` is already registered.
21
23
  :return: A decorator that registers the `BaseBatch` subclass under the given `batch_key`.
22
24
  """
23
- def decorator(
24
- cls: Type[T]
25
- ) -> Type[T]:
25
+
26
+ def decorator(cls: Type[T]) -> Type[T]:
26
27
  if event_handler_key in event_handler_map:
27
- raise ValueError(f"An event handler with key '{event_handler_key}' is already registered!")
28
+ raise ValueError(
29
+ f"An event handler with key '{event_handler_key}' is already registered!"
30
+ )
28
31
  event_handler_map[event_handler_key] = cls
29
32
  return cls
30
- return decorator
31
33
 
34
+ return decorator
@@ -4,13 +4,14 @@
4
4
 
5
5
  from enum import Enum
6
6
 
7
- class EventHandlerKey(Enum):
7
+
8
+ class EventHandlerKey(Enum):
8
9
  """Key bound to a `BaseEventHandler` plugin class.
9
-
10
+
10
11
  :ivar FIXED_CENTER_FREQUENCY: Postprocess data capture at a fixed center frequency.
11
12
  :ivar SWEPT_CENTER_FREQUENCY: Postprocess data capture where the center frequency is continually sweeping
12
13
  in fixed increments.
13
14
  """
15
+
14
16
  FIXED_CENTER_FREQUENCY = "fixed_center_frequency"
15
17
  SWEPT_CENTER_FREQUENCY = "swept_center_frequency"
16
-
@@ -3,6 +3,7 @@
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  from logging import getLogger
6
+
6
7
  _LOGGER = getLogger(__name__)
7
8
 
8
9
  import numpy as np
@@ -15,7 +16,12 @@ import numpy as np
15
16
 
16
17
  from spectre_core.capture_configs import CaptureConfig, PName
17
18
  from spectre_core.batches import IQStreamBatch
18
- from spectre_core.spectrograms import Spectrogram, SpectrumUnit, time_average, frequency_average
19
+ from spectre_core.spectrograms import (
20
+ Spectrogram,
21
+ SpectrumUnit,
22
+ time_average,
23
+ frequency_average,
24
+ )
19
25
  from ._event_handler_keys import EventHandlerKey
20
26
  from .._base import BaseEventHandler, make_sft_instance
21
27
  from .._register import register_event_handler
@@ -26,11 +32,11 @@ def _do_stfft(
26
32
  capture_config: CaptureConfig,
27
33
  ) -> Tuple[npt.NDArray[np.float32], npt.NDArray[np.float32], npt.NDArray[np.float32]]:
28
34
  """Do a Short-time Fast Fourier Transform on an array of complex IQ samples.
29
-
30
- The computation requires extra metadata, which is extracted from the detached header in the batch
35
+
36
+ The computation requires extra metadata, which is extracted from the detached header in the batch
31
37
  and the capture config used to capture the data.
32
-
33
- The current implementation relies heavily on the `ShortTimeFFT` implementation from
38
+
39
+ The current implementation relies heavily on the `ShortTimeFFT` implementation from
34
40
  `scipy.signal` (https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.ShortTimeFFT.html)
35
41
  which takes up a lot of the compute time.
36
42
  """
@@ -38,64 +44,64 @@ def _do_stfft(
38
44
  sft = make_sft_instance(capture_config)
39
45
 
40
46
  # set p0=0, since by convention in the STFFT docs, p=0 corresponds to the slice centred at t=0
41
- p0=0
47
+ p0 = 0
42
48
 
43
49
  # set p1 to the index of the first slice where the "midpoint" of the window is still inside the signal
44
50
  num_samples = len(iq_data)
45
51
  p1 = sft.upper_border_begin(num_samples)[1]
46
-
52
+
47
53
  # compute a ShortTimeFFT on the IQ samples
48
- complex_spectra = sft.stft(iq_data,
49
- p0 = p0,
50
- p1 = p1)
51
-
54
+ complex_spectra = sft.stft(iq_data, p0=p0, p1=p1)
55
+
52
56
  # compute the magnitude of each spectral component
53
57
  dynamic_spectra = np.abs(complex_spectra)
54
58
 
55
-
56
59
  # assign a physical time to each spectrum
57
60
  # p0 is defined to correspond with the first sample, at t=0 [s]
58
- times = sft.t(num_samples,
59
- p0 = p0,
60
- p1 = p1)
61
+ times = sft.t(num_samples, p0=p0, p1=p1)
61
62
  # assign physical frequencies to each spectral component
62
- frequencies = sft.f + cast(float, capture_config.get_parameter_value(PName.CENTER_FREQUENCY))
63
+ frequencies = sft.f + cast(
64
+ float, capture_config.get_parameter_value(PName.CENTER_FREQUENCY)
65
+ )
63
66
 
64
- return times.astype(np.float32), frequencies.astype(np.float32), dynamic_spectra.astype(np.float32)
67
+ return (
68
+ times.astype(np.float32),
69
+ frequencies.astype(np.float32),
70
+ dynamic_spectra.astype(np.float32),
71
+ )
65
72
 
66
73
 
67
74
  def _build_spectrogram(
68
- batch: IQStreamBatch,
69
- capture_config: CaptureConfig
75
+ batch: IQStreamBatch, capture_config: CaptureConfig
70
76
  ) -> Spectrogram:
71
77
  """Generate a spectrogram using `IQStreamBatch` IQ samples."""
72
78
  # read the data from the batch
73
79
  iq_metadata = batch.hdr_file.read()
74
- iq_samples = batch.bin_file.read()
80
+ iq_samples = batch.bin_file.read()
75
81
 
76
- times, frequencies, dynamic_spectra = _do_stfft(iq_samples,
77
- capture_config)
82
+ times, frequencies, dynamic_spectra = _do_stfft(iq_samples, capture_config)
78
83
 
79
84
  # compute the start datetime for the spectrogram by adding the millisecond component to the batch start time
80
- spectrogram_start_datetime = batch.start_datetime + timedelta(milliseconds=iq_metadata.millisecond_correction)
81
-
82
- return Spectrogram(dynamic_spectra,
83
- times,
84
- frequencies,
85
- batch.tag,
86
- SpectrumUnit.AMPLITUDE,
87
- spectrogram_start_datetime)
85
+ spectrogram_start_datetime = batch.start_datetime + timedelta(
86
+ milliseconds=iq_metadata.millisecond_correction
87
+ )
88
+
89
+ return Spectrogram(
90
+ dynamic_spectra,
91
+ times,
92
+ frequencies,
93
+ batch.tag,
94
+ SpectrumUnit.AMPLITUDE,
95
+ spectrogram_start_datetime,
96
+ )
88
97
 
89
98
 
90
99
  @register_event_handler(EventHandlerKey.FIXED_CENTER_FREQUENCY)
91
100
  class FixedEventHandler(BaseEventHandler):
92
- def process(
93
- self,
94
- absolute_file_path: str
95
- ) -> None:
101
+ def process(self, absolute_file_path: str) -> None:
96
102
  """Compute a spectrogram using `IQStreamBatch` IQ samples, cache it, then save it to file in the FITS
97
103
  format. The IQ samples are assumed to have been collected at a fixed center frequency.
98
-
104
+
99
105
  The computed spectrogram is averaged in time and frequency as per the user-configured capture config.
100
106
  Once the spectrogram has been computed successfully, the `.bin` and `.hdr` files are removed.
101
107
 
@@ -104,22 +110,23 @@ class FixedEventHandler(BaseEventHandler):
104
110
  _LOGGER.info(f"Processing: {absolute_file_path}")
105
111
  file_name = os.path.basename(absolute_file_path)
106
112
  base_file_name, _ = os.path.splitext(file_name)
107
- batch_start_time, tag = base_file_name.split('_')
113
+ batch_start_time, tag = base_file_name.split("_")
108
114
 
109
115
  batch = IQStreamBatch(batch_start_time, tag)
110
116
 
111
117
  _LOGGER.info("Creating spectrogram")
112
- spectrogram = _build_spectrogram(batch,
113
- self._capture_config)
114
-
115
- time_resolution = cast(float, self._capture_config.get_parameter_value(PName.TIME_RESOLUTION))
116
- spectrogram = time_average(spectrogram,
117
- resolution=time_resolution)
118
-
119
- frequency_resolution = cast(float, self._capture_config.get_parameter_value(PName.FREQUENCY_RESOLUTION))
120
- spectrogram = frequency_average(spectrogram,
121
- resolution=frequency_resolution)
122
-
118
+ spectrogram = _build_spectrogram(batch, self._capture_config)
119
+
120
+ time_resolution = cast(
121
+ float, self._capture_config.get_parameter_value(PName.TIME_RESOLUTION)
122
+ )
123
+ spectrogram = time_average(spectrogram, resolution=time_resolution)
124
+
125
+ frequency_resolution = cast(
126
+ float, self._capture_config.get_parameter_value(PName.FREQUENCY_RESOLUTION)
127
+ )
128
+ spectrogram = frequency_average(spectrogram, resolution=frequency_resolution)
129
+
123
130
  self._cache_spectrogram(spectrogram)
124
131
 
125
132
  _LOGGER.info(f"Deleting {batch.bin_file.file_path}")