acoular 25.1__py3-none-any.whl → 25.3.post1__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.
acoular/process.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # ------------------------------------------------------------------------------
2
2
  # Copyright (c) Acoular Development Team.
3
3
  # ------------------------------------------------------------------------------
4
- """General purpose blockwise processing methods independent of the domain (time or frequency).
4
+ """
5
+ General purpose blockwise processing methods independent of the domain (time or frequency).
5
6
 
6
7
  .. autosummary::
7
8
  :toctree: generated/
@@ -33,9 +34,25 @@ from .tools.utils import find_basename
33
34
 
34
35
 
35
36
  class LockedGenerator:
36
- """Creates a Thread Safe Iterator.
37
- Takes an iterator/generator and makes it thread-safe by
38
- serializing call to the `next` method of given iterator/generator.
37
+ """
38
+ Thread-safe wrapper for an iterator or generator.
39
+
40
+ The :class:`LockedGenerator` class ensures that calls to the ``__next__`` method of the
41
+ given iterator or generator are thread-safe, preventing race conditions when accessed by
42
+ multiple threads simultaneously.
43
+
44
+ It achieves thread safety by using a lock to serialize access to the underlying
45
+ iterator or generator.
46
+
47
+ Parameters
48
+ ----------
49
+ it : iterator or generator
50
+ The iterator or generator to be made thread-safe.
51
+
52
+ See Also
53
+ --------
54
+ :class:`acoular.process.SampleSplitter` :
55
+ Distribute data from a source to several following objects in a block-wise manner.
39
56
  """
40
57
 
41
58
  def __init__(self, it):
@@ -43,70 +60,78 @@ class LockedGenerator:
43
60
  self.lock = threading.Lock()
44
61
 
45
62
  def __next__(self):
63
+ """Fetch the next item from the iterator or generator in a thread-safe manner."""
46
64
  with self.lock:
47
65
  return self.it.__next__()
48
66
 
49
67
 
50
68
  @deprecated_alias({'naverage': 'num_per_average', 'numsamples': 'num_samples'}, read_only=['numsamples'])
51
69
  class Average(InOut):
52
- """Calculates the average across consecutive time samples or frequency snapshots.
70
+ """
71
+ Calculate the average across consecutive time samples or frequency snapshots.
53
72
 
54
73
  The average operation is performed differently depending on the source type.
55
- If the source is a time domain source
56
- (e.g. derived from :class:`~acoular.base.SamplesGenerator`), the average is
57
- calculated over a certain number of time samples given by :attr:`num_per_average`.
74
+ If the source is a time domain source (e.g. derived from
75
+ :class:`~acoular.base.SamplesGenerator`), the average is calculated
76
+ over a certain number of time samples given by :attr:`num_per_average`.
58
77
  If the source is a frequency domain source (e.g. derived from
59
- :class:`~acoular.base.SpectraGenerator`), the average is calculated over a certain
60
- number of snapshots given by :attr:`num_per_average`.
78
+ :class:`~acoular.base.SpectraGenerator`), the average is calculated
79
+ over a certain number of frequency snapshots given by :attr:`num_per_average`.
80
+
81
+ See Also
82
+ --------
83
+ :class:`acoular.base.InOut` :
84
+ Receive data from any source domain and return signals in the same domain.
61
85
 
62
86
  Examples
63
87
  --------
64
- For estimate the RMS of a white noise (time-domain) signal, the average of the squared
65
- signal can be calculated:
66
-
67
- >>> import acoular as ac
68
- >>> import numpy as np
69
- >>>
70
- >>> signal = ac.WNoiseGenerator(sample_freq=51200, num_samples=51200, rms=2.0).signal()
71
- >>> ts = ac.TimeSamples(data=signal[:, np.newaxis], sample_freq=51200)
72
- >>> tp = ac.TimePower(source=ts)
73
- >>> avg = ac.Average(source=tp, num_per_average=512)
74
- >>> mean_squared_value = next(avg.result(num=1))
75
- >>> rms = np.sqrt(mean_squared_value)[0, 0]
76
- >>> print(rms)
77
- 1.9985200025816718
78
-
79
- Here, each evaluation of the generator created by the :meth:`result` method of the
80
- :class:`Average` object via the :meth:`next` function returns :code:`num=1` average across a
81
- snapshot of 512 samples.
82
-
83
- If the source is a frequency domain source, the average is calculated over a certain number
84
- of snapshots, defined by :attr:`num_per_average`.
85
-
86
- >>> fft = ac.RFFT(source=ts, block_size=64)
87
- >>> ps = ac.AutoPowerSpectra(source=fft)
88
- >>> avg = ac.Average(source=ps, num_per_average=16)
89
- >>> mean_power = next(avg.result(num=1))
90
- >>> print(np.sqrt(mean_power.sum()))
91
- 2.0024960894399295
92
-
93
- Here, the generator created by the :meth:`result` method of the :class:`Average` object
94
- returns the average across 16 snapshots in the frequency domain.
88
+ To estimate the RMS of a white noise (time-domain) signal, the average of the squared
89
+ signal can be calculated:
95
90
 
91
+ >>> import acoular as ac
92
+ >>> import numpy as np
93
+ >>>
94
+ >>> signal = ac.WNoiseGenerator(sample_freq=51200, num_samples=51200, rms=2.0).signal()
95
+ >>> ts = ac.TimeSamples(data=signal[:, np.newaxis], sample_freq=51200)
96
+ >>> tp = ac.TimePower(source=ts)
97
+ >>> avg = ac.Average(source=tp, num_per_average=512)
98
+ >>> mean_squared_value = next(avg.result(num=1))
99
+ >>> rms = np.sqrt(mean_squared_value)[0, 0]
100
+ >>> print(rms)
101
+ 1.9985200025816718
102
+
103
+ Here, each evaluation of the generator created by the :meth:`result` method of the
104
+ :class:`Average` object via the :meth:`next` function returns :code:`num=1` average across a
105
+ snapshot of 512 time samples.
106
+
107
+ If the source is a frequency domain source, the average is calculated over a certain number
108
+ of frequency snapshots, defined by :attr:`num_per_average`.
109
+
110
+ >>> fft = ac.RFFT(source=ts, block_size=64)
111
+ >>> ps = ac.AutoPowerSpectra(source=fft)
112
+ >>> avg = ac.Average(source=ps, num_per_average=16)
113
+ >>> mean_power = next(avg.result(num=1))
114
+ >>> print(np.sqrt(mean_power.sum()))
115
+ 2.0024960894399295
116
+
117
+ Here, the generator created by the :meth:`result` method of the :class:`Average` object
118
+ returns the average across 16 snapshots in the frequency domain.
96
119
  """
97
120
 
98
- #: Number of samples (time domain source) or snapshots (frequency domain source)
99
- #: to average over, defaults to 64.
121
+ #: The number of samples (time domain source) or snapshots (frequency domain source)
122
+ #: to average over. Default is ``64``.
100
123
  num_per_average = Int(64, desc='number of samples/snapshots to average over')
101
124
 
102
- #: Sampling frequency of the output signal, is set automatically.
125
+ #: The sampling frequency of the output signal. It is set automatically as
126
+ #: (:attr:`~acoular.base.Generator.sample_freq` ``/`` :attr:`num_per_average`).
103
127
  sample_freq = Property(depends_on=['source.sample_freq', 'num_per_average'])
104
128
 
105
- #: Number of samples (time domain) or snapshots (frequency domain) of the output signal.
106
- #: Is set automatically.
129
+ #: The number of samples (time domain) or snapshots (frequency domain) of the output signal.
130
+ #: It is set automatically as
131
+ #: (:attr:`~acoular.base.Generator.num_samples` ``/`` :attr:`num_per_average`).
107
132
  num_samples = Property(depends_on=['source.num_samples', 'num_per_average'])
108
133
 
109
- # internal identifier
134
+ #: A unique identifier based on the class properties.
110
135
  digest = Property(depends_on=['source.digest', 'num_per_average'])
111
136
 
112
137
  @cached_property
@@ -126,20 +151,40 @@ class Average(InOut):
126
151
  return None
127
152
 
128
153
  def result(self, num):
129
- """Python generator that yields the output block-wise.
154
+ """
155
+ Generate averaged output blocks from the source data.
156
+
157
+ This method implements a Python generator that yields blocks of averaged data
158
+ from the source. The averaging is performed over :attr:`num_per_average` samples
159
+ (for time-domain sources) or snapshots (for frequency-domain sources).
160
+ The size of the blocks yielded is defined by the ``num`` parameter.
130
161
 
131
162
  Parameters
132
163
  ----------
133
- num : integer
134
- This parameter defines the size of the blocks to be yielded
135
- (i.e. the number of samples per block).
136
-
137
- Returns
138
- -------
139
- Average of the output of source.
140
- Yields samples in blocks of shape (num, num_channels).
141
- The last block may be shorter than num.
164
+ num : :class:`int`
165
+ The number of averaged blocks to yield at a time. Each block contains the average over
166
+ :attr:`num_per_average` time samples or frequency snapshots. The last block may be
167
+ shorter than the specified size if the remaining data is insufficient.
142
168
 
169
+ Yields
170
+ ------
171
+ :class:`numpy.ndarray`
172
+ A 2D NumPy array of shape ``(num, num_channels)``, where ``num`` is the number
173
+ of averaged blocks requested, and ``num_channels`` corresponds to the number of channels
174
+ in the source, as specified by :attr:`~acoular.base.Generator.num_channels`.
175
+ Each entry in the array is the average over :attr:`num_per_average` samples/snapshots.
176
+
177
+ Notes
178
+ -----
179
+ - The averaging operation depends on the source type:
180
+ - For time-domain sources (e.g., derived from :class:`~acoular.base.SamplesGenerator`),
181
+ the average is calculated over :attr:`num_per_average` time samples.
182
+ - For frequency-domain sources (e.g., derived from
183
+ :class:`~acoular.base.SpectraGenerator`), the average is calculated over
184
+ :attr:`num_per_average` frequency snapshots.
185
+ - The generator will stop yielding when the source data is exhausted.
186
+ - If the source provides fewer than ``num * num_per_average`` samples,
187
+ the final block may be smaller than the requested ``num`` size.
143
188
  """
144
189
  nav = self.num_per_average
145
190
  for temp in self.source.result(num * nav):
@@ -150,14 +195,32 @@ class Average(InOut):
150
195
 
151
196
 
152
197
  class Cache(InOut):
153
- """Caches source output in cache file.
198
+ """
199
+ Cache the output of a source in a file to avoid redundant computations.
200
+
201
+ The :class:`Cache` class stores the output of a source (derived from
202
+ :class:`~acoular.base.Generator`) in a cache file within the Acoular cache directory.
203
+ This enables faster reuse of precomputed data by avoiding time-consuming recalculations.
204
+ The cache behavior is managed through the :class:`~acoular.configuration.Config` class by
205
+ setting the :attr:`~acoular.configuration.Config.global_caching` attribute.
154
206
 
155
- This class is used to cache the output of a :class:`acoular.base.Generator` derived source
156
- object in a cache file to circumvent time-consuming re-calculation.
157
- The cache file is created in the Acoular cache directory.
207
+ The class intelligently determines whether to use the cached data, update it,
208
+ or bypass caching based on the global caching configuration and the state of the cache file.
209
+ The caching mechanism supports scenarios such as:
210
+
211
+ - Reading from a complete or incomplete cache.
212
+ - Overwriting an existing cache.
213
+ - Operating in a read-only or no-cache mode.
214
+
215
+ See Also
216
+ --------
217
+ :class:`acoular.base.InOut` : Receive data from any source domain and return signals in the same
218
+ domain.
158
219
 
159
220
  Examples
160
221
  --------
222
+ Caching the output of an FFT computation:
223
+
161
224
  >>> import acoular as ac
162
225
  >>> import numpy as np
163
226
  >>>
@@ -171,23 +234,24 @@ class Cache(InOut):
171
234
  [('void_cache.h5', 1)]
172
235
  (1, 513)
173
236
 
174
- The caching behaviour can be controlled by the :class:`~acoular.configuration.Config` instance
175
- via the :attr:`~acoular.configuration.Config.global_caching` attribute.
176
- To turn off caching, set :attr:`~acoular.configuration.Config.global_caching` to 'none' before
177
- running the code. The cache file directory can be obtained (and set) via the
178
- :attr:`~acoular.configuration.Config.cache_dir`
237
+ Disabling caching globally:
179
238
 
180
239
  >>> ac.config.global_caching = 'none'
181
240
 
241
+ Changing the cache directory:
242
+
243
+ >>> ac.config.cache_dir = '/path/to/cache_dir' # doctest: +SKIP
182
244
  """
183
245
 
184
- # basename for cache
246
+ # The basename for the cache file.
247
+ # Derived from the :attr:`digest` property and used to uniquely identify the cache file.
185
248
  basename = Property(depends_on=['digest'])
186
249
 
187
- # hdf5 cache file
250
+ # The HDF5 cache file instance.
251
+ # This is used to store or retrieve cached data in the Acoular cache directory.
188
252
  h5f = Instance(H5CacheFileBase, transient=True)
189
253
 
190
- # internal identifier
254
+ #: A unique identifier based on the cache properties.
191
255
  digest = Property(depends_on=['source.digest'])
192
256
 
193
257
  @cached_property
@@ -246,21 +310,40 @@ class Cache(InOut):
246
310
 
247
311
  # result generator: delivers input, possibly from cache
248
312
  def result(self, num):
249
- """Python generator that yields the output from cache block-wise.
313
+ """
314
+ Generate data blocks from the source, using cache when available.
315
+
316
+ This method acts as a Python generator that yields blocks of output data from the source,
317
+ reading from the cache file when possible. The size of the data blocks is determined by the
318
+ ``num`` parameter. The caching mechanism helps prevent redundant calculations by storing and
319
+ reusing the source's output.
250
320
 
251
321
  Parameters
252
322
  ----------
253
- num : integer
254
- This parameter defines the size of the blocks to be yielded
255
- (i.e. the number of samples per block).
256
-
257
- Returns
258
- -------
259
- Samples in blocks of shape (num, num_channels).
260
- The last block may be shorter than num.
261
- Echos the source output, but reads it from cache
262
- when available and prevents unnecessary recalculation.
323
+ num : :class:`int`
324
+ The number of time samples or frequency snapshots per block to yield.
325
+ The final block may be smaller if there is insufficient data.
263
326
 
327
+ Yields
328
+ ------
329
+ :class:`numpy.ndarray`
330
+ A 2D NumPy array of shape ``(num, num_channels)`` representing the output data.
331
+ Each block is either retrieved from the cache file or generated by the source
332
+ and cached dynamically during processing.
333
+
334
+ Notes
335
+ -----
336
+ - The behavior of the caching mechanism depends on the
337
+ :attr:`~acoular.configuration.Config.global_caching` setting:
338
+
339
+ - ``'none'``: Bypasses caching and directly retrieves data from the source.
340
+ - ``'readonly'``: Reads data from the cache if available; otherwise,
341
+ retrieves data from the source without caching.
342
+ - ``'overwrite'``: Replaces any existing cache with newly computed data.
343
+
344
+ - If the cache file is incomplete or corrupted, the method may generate new data
345
+ from the source to update the cache unless the caching mode is ``'readonly'``.
346
+ - The cache node name is based on the source's :attr:`digest` attribute.
264
347
  """
265
348
  if config.global_caching == 'none':
266
349
  generator = self._pass_data
@@ -299,30 +382,45 @@ class Cache(InOut):
299
382
 
300
383
  class SampleSplitter(InOut):
301
384
  """
302
- Distributes data from a source to several following objects in a block-wise manner.
385
+ Distribute data from a source to multiple connected objects in a block-wise manner.
386
+
387
+ The :class:`SampleSplitter` class is designed to manage the distribution of data blocks from a
388
+ single source object, derived from :class:`~acoular.base.Generator`, to multiple target
389
+ objects, also derived from :class:`~acoular.base.Generator`. Each connected target object
390
+ is assigned a dedicated buffer to hold incoming data blocks. These buffers operate in a
391
+ first-in-first-out (FIFO) manner, ensuring efficient and parallelized data handling.
392
+
393
+ This class is particularly useful when distributing data blocks from a streaming source
394
+ to multiple downstream processing objects.
395
+
396
+ Each registered target object maintains its own dedicated block buffer, allowing for independent
397
+ data management. The buffer size can be customized per object, and different overflow handling
398
+ strategies can be configured, such as raising an error, issuing a warning, or discarding old
399
+ data. This ensures efficient parallel data processing, making it well-suited for complex
400
+ workflows.
303
401
 
304
- The `SampleSplitter` class is designed to take data from a single
305
- :class:`~acoular.base.Generator` derived source object and distribute it to multiple
306
- :class:`~acoular.base.Generator` derived objects. For each object, the :class:`SampleSplitter`
307
- holds a virtual block buffer from which the subsequently connected objects receive data in a
308
- first-in-first-out (FIFO) manner. This allows for efficient data handling and processing in
309
- parallel.
402
+ Notes
403
+ -----
404
+ - Buffers are dynamically created and managed for each registered object.
405
+ - Buffer overflow behavior can be set individually for each target object.
310
406
 
311
407
  Examples
312
408
  --------
313
- Consider a time domain source signal stream from which the FFT spectra and the signal power
314
- are calculated block-wise and in parallel by using the :class:`~acoular.fprocess.RFFT` as well
315
- as the :class:`~acoular.tprocess.TimePower` and :class:`~acoular.process.Average`
316
- objects. The `SampleSplitter` object is used to distribute the incoming blocks of data to the
317
- `RFFT` and `TimePower` object buffers whenever one of these objects calls the :meth:`result`
318
- generator.
319
- For the `TimePower` object, the buffer size is set to 10 blocks. If the buffer is full, an error
320
- is raised since the buffer overflow treatment is set to 'error'. For the `RFFT` object, the
321
- block buffer size is set to 1 block, and the buffer overflow treatment is set to 'none'. This
322
- is done to reduce latency in the FFT calculation, as the FFT calculation may take longer than
323
- the signal power calculation. If new data is available and the block buffer for the `RFFT`
324
- object is full, the `SampleSplitter` will drop the oldest block of data in the buffer. Thus, the
325
- `RFFT` object will always receive the most recent block of data.
409
+ Consider a time-domain signal stream where the FFT spectra and signal power are calculated
410
+ block-by-block and in parallel using the :class:`~acoular.fprocess.RFFT`,
411
+ :class:`~acoular.tprocess.TimePower`, and :class:`~acoular.process.Average` objects.
412
+ The :class:`SampleSplitter` is responsible for distributing incoming data blocks to the buffers
413
+ of the :class:`~acoular.fprocess.RFFT` and :class:`~acoular.tprocess.TimePower` objects whenever
414
+ either object requests data via the :meth:`result` generator.
415
+
416
+ For the :class:`~acoular.tprocess.TimePower` object, the buffer size is set to 10 blocks.
417
+ If the buffer is full, an error is raised, as the buffer overflow treatment is set to
418
+ ``'error'``. For the :class:`~acoular.fprocess.RFFT` object, the buffer size is limited to 1
419
+ block, and the overflow treatment is set to ``'none'``. This setup helps reduce latency in FFT
420
+ calculations, which may take longer than signal power calculations. If new data arrives and the
421
+ :class:`~acoular.fprocess.RFFT` buffer is full, the :class:`SampleSplitter` will discard the
422
+ oldest block, ensuring that the :class:`~acoular.fprocess.RFFT`
423
+ object always receives the most recent block of data.
326
424
 
327
425
  >>> import acoular as ac
328
426
  >>> import numpy as np
@@ -342,9 +440,9 @@ class SampleSplitter(InOut):
342
440
  >>> ss.register_object(fft, buffer_size=1, buffer_overflow_treatment='none')
343
441
  >>> ss.register_object(pow, buffer_size=10, buffer_overflow_treatment='error')
344
442
 
345
- After object registration, the `SampleSplitter` object is ready to distribute the data to the
346
- object buffers. The block buffers can be accessed via the `block_buffer` attribute of the
347
- `SampleSplitter` object.
443
+ After object registration, the ``SampleSplitter`` object is ready to distribute the data to the
444
+ object buffers. The block buffers can be accessed via the :attr:`block_buffer` attribute of the
445
+ ``SampleSplitter`` object.
348
446
 
349
447
  >>> ss.block_buffer.values()
350
448
  dict_values([deque([], maxlen=1), deque([], maxlen=10)])
@@ -361,42 +459,37 @@ class SampleSplitter(InOut):
361
459
  >>> print(len(ss.block_buffer[pow]))
362
460
  1
363
461
 
364
- To remove registered objects from the `SampleSplitter`, use the :meth:`remove_object` method.
462
+ To remove registered objects from the :class:`SampleSplitter`, use the :meth:`remove_object`
463
+ method.
365
464
 
366
465
  >>> ss.remove_object(pow)
367
466
  >>> print(len(ss.block_buffer))
368
467
  1
369
468
  """
370
469
 
371
- #: dictionary with block buffers (dict values) of registered objects (dict
372
- #: keys).
470
+ #: A dictionary containing block buffers for registered objects.
471
+ #: Keys are the registered objects, and values are deque structures holding data blocks.
373
472
  block_buffer = Dict(key_trait=Instance(Generator))
374
473
 
375
- #: max elements/blocks in block buffers.
376
- #: Can be set individually for each registered object.
377
- #: Default is 100 blocks for each registered object.
474
+ #: The maximum number of blocks each buffer can hold.
475
+ #: Can be set globally for all objects or individually using a dictionary.
378
476
  buffer_size = Union(
379
477
  Int,
380
478
  Dict(key_trait=Instance(Generator), value_trait=Int),
381
479
  default_value=100,
382
480
  )
383
481
 
384
- #: defines behaviour in case of block_buffer overflow. Can be set individually
385
- #: for each registered object.
386
- #:
387
- #: * 'error': an IOError is thrown by the class
388
- #: * 'warning': a warning is displayed. Possibly leads to lost blocks of data
389
- #: * 'none': nothing happens. Possibly leads to lost blocks of data
482
+ #: Defines behavior when a buffer exceeds its maximum size.
390
483
  buffer_overflow_treatment = Dict(
391
484
  key_trait=Instance(Generator),
392
485
  value_trait=Enum('error', 'warning', 'none'),
393
486
  desc='defines buffer overflow behaviour.',
394
487
  )
395
488
 
396
- # shadow trait to monitor if source deliver samples or is empty
489
+ # A shadow trait to monitor if source deliver samples or is empty.
397
490
  _source_generator_exist = Bool(False)
398
491
 
399
- # shadow trait to monitor if buffer of objects with overflow treatment = 'error'
492
+ # A shadow trait to monitor if buffer of objects with overflow treatment = 'error'
400
493
  # or warning is overfilled. Error will be raised in all threads.
401
494
  _buffer_overflow = Bool(False)
402
495
 
@@ -456,23 +549,35 @@ class SampleSplitter(InOut):
456
549
  self._create_block_buffer(obj)
457
550
 
458
551
  def register_object(self, *objects_to_register, buffer_size=None, buffer_overflow_treatment=None):
459
- """Register one or multiple :class:`~acoular.base.Generator` objects to the SampleSplitter.
552
+ """
553
+ Register one or more target objects to the :class:`SampleSplitter` object.
460
554
 
461
- Creates a block buffer for each object and sets the buffer size and buffer
462
- overflow treatment.
555
+ This method creates and configures block buffers for the specified target objects, enabling
556
+ them to receive data blocks from the :class:`SampleSplitter`. Each registered object is
557
+ assigned a dedicated buffer with customizable size and overflow behavior.
463
558
 
464
559
  Parameters
465
560
  ----------
466
- objects_to_register : Generator
467
- One or multiple :class:`~acoular.base.Generator` derived objects to be registered.
468
- buffer_size : int, optional
469
- Maximum number of elements/blocks in block buffer. If not set, the default buffer size
470
- of 100 blocks is used.
471
- buffer_overflow_treatment : str, optional
472
- Defines the behaviour in case of reaching the buffer size.
473
- Can be set individually for each object. Possible values are 'error', 'warning', and
474
- 'none'. If not set, the default value is 'error'.
475
- """
561
+ objects_to_register : :class:`~acoular.base.Generator` or list of :class:`~acoular.base.Generator`
562
+ A single object or a list of objects derived from :class:`~acoular.base.Generator` to be
563
+ registered as targets for data distribution.
564
+ buffer_size : :class:`int`, optional
565
+ The maximum number of data blocks each object's buffer can hold. If not specified,
566
+ the default buffer size (100 blocks) is used, or a globally defined size if
567
+ ``buffer_size`` is a dictionary.
568
+ buffer_overflow_treatment : :attr:`str`, optional
569
+ Defines the behavior when a buffer exceeds its maximum size. Options are:
570
+
571
+ - ``'error'``: Raises an :obj:`IOError` when the buffer overflows.
572
+ - ``'warning'``: Issues a warning and may result in data loss.
573
+ - ``'none'``: Silently discards the oldest data blocks to make room for new ones.
574
+ If not specified, the default behavior is ``'error'``.
575
+
576
+ Raises
577
+ ------
578
+ :obj:`OSError`
579
+ If any of the specified objects is already registered.
580
+ """ # noqa: W505
476
581
  for obj in objects_to_register:
477
582
  if obj not in self.block_buffer:
478
583
  self._create_block_buffer(obj, buffer_size)
@@ -482,16 +587,31 @@ class SampleSplitter(InOut):
482
587
  raise OSError(msg)
483
588
 
484
589
  def remove_object(self, *objects_to_remove):
485
- """Function that can be used to remove registered objects.
590
+ """
591
+ Unregister one or more objects from the :class:`SampleSplitter`.
486
592
 
487
- If no objects are given, all registered objects are removed.
593
+ This method removes the specified objects and their associated block buffers from the
594
+ :class:`SampleSplitter`. If no objects are specified, all currently registered objects
595
+ are unregistered, effectively clearing all buffers.
488
596
 
489
597
  Parameters
490
598
  ----------
491
- objects_to_remove : list
492
- One or multiple :class:`~acoular.base.Generator` derived objects to be removed.
493
- If not set, all registered objects are removed.
494
- """
599
+ objects_to_remove : :class:`~acoular.base.Generator` or list of :class:`~acoular.base.Generator`, optional
600
+ A single object or a list of objects derived from :class:`~acoular.base.Generator` to be
601
+ removed from the :class:`SampleSplitter`.
602
+ If no objects are provided, all registered objects will be removed.
603
+
604
+ Raises
605
+ ------
606
+ :obj:`KeyError`
607
+ If any of the specified objects are not currently registered.
608
+
609
+ Notes
610
+ -----
611
+ - Once an object is removed, it will no longer receive data from the
612
+ :class:`SampleSplitter`.
613
+ - Removing an object also clears its associated buffer.
614
+ """ # noqa: W505
495
615
  if not objects_to_remove:
496
616
  objects_to_remove = list(self.block_buffer.keys())
497
617
  for obj in objects_to_remove:
@@ -499,20 +619,39 @@ class SampleSplitter(InOut):
499
619
  self._remove_buffer_overflow_treatment(obj)
500
620
 
501
621
  def result(self, num):
502
- """Python generator that yields the output block-wise from block-buffer.
622
+ """
623
+ Yield data blocks from the buffer to the calling object.
624
+
625
+ This generator method retrieves blocks of data for the calling object, either
626
+ from its dedicated block buffer or by processing new data from the source.
627
+ If the buffer is empty, new data blocks are generated and distributed to
628
+ all registered objects in a block-wise manner.
503
629
 
504
630
  Parameters
505
631
  ----------
506
- num : integer
507
- This parameter defines the size of the blocks to be yielded
508
- (i.e. the number of samples per block).
632
+ num : :class:`int`
633
+ The size of each block to be yielded, defined as the number of samples per block.
509
634
 
510
- Returns
511
- -------
512
- Samples in blocks of shape (num, num_channels).
513
- Delivers a block of samples to the calling object.
514
- The last block may be shorter than num.
635
+ Yields
636
+ ------
637
+ :class:`numpy.ndarray`
638
+ Blocks of data with shape ``(num, num_channels)``.
639
+ The last block may be shorter than ``num`` if the source data is exhausted.
515
640
 
641
+ Raises
642
+ ------
643
+ :obj:`OSError`
644
+ If the calling object is not registered with the :class:`SampleSplitter`.
645
+ :obj:`OSError`
646
+ If the block buffer reaches its maximum size and the overflow handling
647
+ policy is set to ``'error'``.
648
+
649
+ Notes
650
+ -----
651
+ - If the block buffer is empty, new data is fetched from the source and distributed to all
652
+ registered objects.
653
+ - Buffer overflow behavior is controlled by the :attr:`buffer_overflow_treatment` attribute,
654
+ which can be set to ``'error'``, ``'warning'``, or ``'none'``.
516
655
  """
517
656
  calling_obj = currentframe().f_back.f_locals['self']
518
657
  self._assert_obj_registered(calling_obj)
@@ -537,11 +676,15 @@ class SampleSplitter(InOut):
537
676
 
538
677
 
539
678
  class TimeAverage(Average):
540
- """Calculates average of the signal (Alias for :class:`acoular.process.Average`).
679
+ """
680
+ Calculate the average of the signal.
541
681
 
542
682
  .. deprecated:: 24.10
543
- Using :class:`~acoular.process.TimeAverage` is deprecated and will be removed in Acoular
544
- version 25.07. Use :class:`~acoular.process.Average` instead.
683
+ The use of :class:`~acoular.process.TimeAverage` is deprecated
684
+ and will be removed in Acoular version 25.07.
685
+ Please use :class:`~acoular.process.Average` instead for future compatibility.
686
+
687
+ Alias for :class:`~acoular.process.Average`.
545
688
  """
546
689
 
547
690
  def __init__(self, *args, **kwargs):
@@ -554,11 +697,15 @@ class TimeAverage(Average):
554
697
 
555
698
 
556
699
  class TimeCache(Cache):
557
- """Caches source signals in cache file (Alias for :class:`acoular.process.Cache`).
700
+ """
701
+ Cache source signals in cache file.
558
702
 
559
703
  .. deprecated:: 24.10
560
- Using :class:`~acoular.process.TimeCache` is deprecated and will be removed in Acoular
561
- version 25.07. Use :class:`~acoular.process.Cache` instead.
704
+ The use of :class:`~acoular.process.TimeCache` is deprecated
705
+ and will be removed in Acoular version 25.07.
706
+ Please use :class:`~acoular.process.Cache` instead for future compatibility.
707
+
708
+ Alias for :class:`~acoular.process.Cache`.
562
709
  """
563
710
 
564
711
  def __init__(self, *args, **kwargs):
@@ -571,17 +718,17 @@ class TimeCache(Cache):
571
718
 
572
719
 
573
720
  class SamplesBuffer(InOut):
574
- """Handles buffering of samples from a source.
721
+ """
722
+ Handle buffering of samples from a source.
575
723
 
576
- This class is used to buffer samples from a source and provide them in blocks
577
- of a specified size. There are several usecases for this class, as demonstrated in
578
- the following.
724
+ The :class:`SamplesBuffer` class buffers samples from a source and provides them in blocks of a
725
+ specified size. It supports various use cases for efficient handling of sample data.
726
+ Below is an example demonstrating its functionality.
579
727
 
580
728
  Examples
581
729
  --------
582
- Let us assume we want to draw blocks of 16 samples from our source, but we want to make sure
583
- that we always have twice the number of samples in the buffer. We can achieve this simple
584
- behaviour by using the following code:
730
+ Suppose we want to draw blocks of 16 samples from the source, while ensuring that the buffer
731
+ always holds twice that number (32 samples). The following code achieves this behavior:
585
732
 
586
733
  >>> import acoular as ac
587
734
  >>> import numpy as np
@@ -599,20 +746,20 @@ class SamplesBuffer(InOut):
599
746
  >>> block = next(buffer.result(num=16))
600
747
  >>> np.testing.assert_array_equal(block, source.data[:16])
601
748
 
602
- Here, on the first call to the result method, the buffer will fill up by collecting blocks with
603
- same size from the source. The buffer will then return the first block of 16 samples. On the
604
- next call to the result method, the buffer will be filled again and returns the next block of 16
605
- samples.
606
-
607
- In some cases, we might want to draw a different number of samples from the source than we want
608
- to return. This can be achieved by setting the `source_num` trait of the buffer. A special case
609
- is the return of a variable number of samples. This is the case, for example, in the class
610
- :class:`~acoular.tbeamform.BeamformerTimeTraj`, in which a different number of time samples is
611
- required from the buffer for further delay-and-sum processing depending on the expected delay,
612
- which can be vary for moving sources. At the same time, however, only 'num' samples should be
613
- written to and removed from the buffer. This behavior can be achieved by setting the
614
- `shift_index_by` trait to 'num' and by setting the `result_num` trait to the number of samples
615
- that should be returned by the result function.
749
+ In the example above, the buffer initially collects blocks of the specified size from the
750
+ source. It then returns the first block of 16 samples. With subsequent calls to the
751
+ :meth:`result` method, the buffer refills and returns additional blocks of 16 samples.
752
+
753
+ In some cases, you may wish to retrieve a different number of samples from the source than you
754
+ want to return. This can be achieved by setting the :attr:`source_num` attribute. For example,
755
+ in the :class:`~acoular.tbeamform.BeamformerTimeTraj` class, the number of time samples varies
756
+ based on the expected delay for moving sources, while still adhering to the desired block size
757
+ for the buffer.
758
+
759
+ The :attr:`shift_index_by` attribute controls how the buffer updates its index when retrieving
760
+ data. If set to ``'num'``, the buffer returns :attr:`result_num` samples but forgets ``'num'``
761
+ samples from the buffer.
762
+ If set to :attr:`result_num`, the buffer will return and forget the same number of samples.
616
763
 
617
764
  >>> buffer = ac.process.SamplesBuffer(source=source, length=32, result_num=20, shift_index_by='num')
618
765
  >>> block_sizes = []
@@ -626,11 +773,11 @@ class SamplesBuffer(InOut):
626
773
  >>> np.testing.assert_array_equal(block_sizes, [20, 24])
627
774
  """ # noqa: W505
628
775
 
629
- #: number of samples that fit in the buffer
776
+ #: The number of samples that the buffer can hold.
630
777
  length = Int(desc='number of samples that fit in the buffer')
631
778
 
632
- #: number of samples per block to obtain from the source. If 'None', use 'num' argument of
633
- #: result method
779
+ #: The number of samples per block to obtain from the source. If set to ``None``, the number of
780
+ #: samples will be determined by the ``num`` argument of the :meth:`result` method.
634
781
  source_num = Union(
635
782
  None,
636
783
  Int(),
@@ -638,7 +785,8 @@ class SamplesBuffer(InOut):
638
785
  desc='number of samples to return from the source. If "None", use "num" argument of result method',
639
786
  )
640
787
 
641
- #: number of samples to return from the buffer. If 'None', use 'num' argument of result method
788
+ #: The number of samples to return from the buffer. If set to ``None``, the number of
789
+ #: samples will be determined by the ``num`` argument of the :meth:`result` method.
642
790
  result_num = Union(
643
791
  None,
644
792
  Int(),
@@ -646,8 +794,11 @@ class SamplesBuffer(InOut):
646
794
  desc="number of samples to return from the buffer. If 'None', use 'num' argument of result method",
647
795
  )
648
796
 
649
- #: index shift value for the buffer. If "result_num", buffer will return and forget 'result_num'
650
- #: samples. If "num", buffer will return 'result_num' samples but will forget 'num' samples
797
+ #: Index shift value for the buffer.
798
+ #:
799
+ #: - If set to ``'result_num'``, the buffer will return and forget :attr:`result_num` samples.
800
+ #: - If set to ``'num'``, the buffer will return :attr:`result_num` samples but forget ``num``
801
+ #: samples.
651
802
  shift_index_by = Enum(
652
803
  ('result_num', 'num'),
653
804
  desc=(
@@ -656,19 +807,19 @@ class SamplesBuffer(InOut):
656
807
  ),
657
808
  )
658
809
 
659
- #: current filling level of buffer
810
+ #: The current filling level of the buffer, i.e., how many samples are currently available.
660
811
  level = Property(desc='current filling level of buffer')
661
812
 
662
- #: data type of the buffer elements
813
+ #: The data type of the elements in the buffer.
663
814
  dtype = Any(desc='data type of the buffer')
664
815
 
665
- # flag to indicate that the source is empty, for internal use
816
+ # Flag indicating if the source is empty (for internal use).
666
817
  _empty_source = Bool(False, desc='flag to indicate that the source is empty')
667
818
 
668
- # the buffer for processing
819
+ # The actual buffer holding the samples for processing.
669
820
  _buffer = Array(shape=(None, None), desc='buffer for block processing')
670
821
 
671
- # current index in buffer
822
+ # The current index position in the buffer.
672
823
  _index = Int(desc='current index in buffer')
673
824
 
674
825
  def _get_level(self):
@@ -686,11 +837,17 @@ class SamplesBuffer(InOut):
686
837
  self._index -= ns
687
838
 
688
839
  def increase_buffer(self, num):
689
- """Increase the buffer by 'num' samples.
840
+ """
841
+ Increase the size of the buffer by a specified number of samples.
690
842
 
691
- Returns
692
- -------
693
- None
843
+ This method expands the buffer by appending additional samples, effectively increasing
844
+ its capacity. The new samples are initialized to zero. The index of the buffer is adjusted
845
+ accordingly to accommodate the increase.
846
+
847
+ Parameters
848
+ ----------
849
+ num : :class:`int`
850
+ The number of samples by which to increase the buffer size.
694
851
  """
695
852
  ar = np.zeros((num, self.num_channels), dtype=self._buffer.dtype)
696
853
  self._buffer = np.concatenate((ar, self._buffer), axis=0)
@@ -698,18 +855,32 @@ class SamplesBuffer(InOut):
698
855
  self.length += num
699
856
 
700
857
  def read_from_buffer(self, num):
701
- """Read samples from the buffer.
858
+ """
859
+ Read a specified number of samples from the buffer.
860
+
861
+ This method retrieves samples from the buffer, ensuring that the requested number of samples
862
+ is returned. If the buffer contains fewer samples than requested, the method will return all
863
+ available samples. The index of the buffer is updated based on the :attr:`shift_index_by`
864
+ setting.
702
865
 
703
866
  Parameters
704
867
  ----------
705
- num : int
706
- number of samples to read from the buffer.
868
+ num : :class:`int`
869
+ The number of samples to read from the buffer.
707
870
 
708
871
  Returns
709
872
  -------
710
- numpy.ndarray
711
- block of samples from the buffer
873
+ :class:`numpy.ndarray`
874
+ A block of samples (array) from the buffer.
875
+
876
+ Notes
877
+ -----
878
+ - If the :attr:`result_num` attribute is set, it determines the number of samples to return.
879
+ - The method ensures the buffer index is adjusted according to the :attr:`shift_index_by`
880
+ setting. Options are:
712
881
 
882
+ - ``'result_num'``: The index will shift by the number of samples returned.
883
+ - ``'num'``: The index will shift by the number of samples requested (``num``).
713
884
  """
714
885
  rnum = num if self.result_num is None else self.result_num
715
886
  rnum = rnum if self.level >= rnum else self.level
@@ -721,16 +892,32 @@ class SamplesBuffer(InOut):
721
892
  return data
722
893
 
723
894
  def fill_buffer(self, snum):
724
- """Fill the buffer with samples from the source.
895
+ """
896
+ Fill the buffer with samples from the source.
897
+
898
+ The :meth:`fill_buffer` method collects samples from the source and writes them to the
899
+ buffer. It continues to fill the buffer until there are enough samples available, or the
900
+ source runs out of data. If the buffer reaches its maximum capacity, additional samples are
901
+ discarded. The buffer will only contain the most recent data, and its index will be updated
902
+ accordingly.
725
903
 
726
904
  Parameters
727
905
  ----------
728
- snum : int
729
- number of samples to return from the source.
906
+ snum : :class:`int`
907
+ The number of samples to retrieve from the source in each iteration.
730
908
 
731
909
  Yields
732
910
  ------
733
- None
911
+ :obj:`None`
912
+ This method is a generator and yields control back after filling the buffer.
913
+
914
+ Notes
915
+ -----
916
+ - The method ensures that the buffer is filled with the required number of samples,
917
+ adjusting the buffer size if necessary (via the :meth:`increase_buffer` method) when more
918
+ space is needed.
919
+ - Once the buffer is filled, it yields control and resumes only when the buffer is ready for
920
+ more data.
734
921
  """
735
922
  source_generator = self.source.result(snum)
736
923
  while not self._empty_source:
@@ -746,17 +933,34 @@ class SamplesBuffer(InOut):
746
933
  yield
747
934
 
748
935
  def result(self, num):
749
- """Return blocks of samples from the buffer.
936
+ """
937
+ Return blocks of samples from the buffer.
938
+
939
+ The :meth:`result` method retrieves blocks of samples from the buffer and yields them to the
940
+ calling process. The number of samples per block is determined by the ``num`` argument, but
941
+ can also be influenced by other attributes like `result_num` (if set). If the buffer is not
942
+ yet filled, it will continue to collect samples from the source until the buffer contains
943
+ enough data. Once the buffer is full, it will return the requested blocks of samples.
750
944
 
751
945
  Parameters
752
946
  ----------
753
- num : int
754
- number of samples to return.
947
+ num : :class:`int`
948
+ The number of samples to return in each block.
949
+ This value specifies the size of the blocks to be yielded from the buffer.
755
950
 
756
951
  Yields
757
952
  ------
758
- numpy.ndarray
759
- block of samples from the buffer
953
+ :class:`numpy.ndarray`
954
+ A block of samples from the buffer. The size of the block is determined by the ``num``
955
+ parameter or the :attr:`result_num` attribute, depending on the buffer's configuration.
956
+
957
+ Notes
958
+ -----
959
+ - If :attr:`result_num` is set, the method will use it to determine the number of samples
960
+ returned instead of the ``num`` parameter.
961
+ - If the buffer is empty or does not have enough samples, it will attempt to fill the buffer
962
+ by collecting data from the source. If there are not enough samples available from the
963
+ source, the method will yield whatever samples are left in the buffer.
760
964
  """
761
965
  self._create_new_buffer()
762
966
  snum = num