acoular 24.7__py3-none-any.whl → 25.1__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/tprocess.py CHANGED
@@ -1,14 +1,12 @@
1
1
  # ------------------------------------------------------------------------------
2
2
  # Copyright (c) Acoular Development Team.
3
3
  # ------------------------------------------------------------------------------
4
- """Implements processing in the time domain.
4
+ """Implements blockwise processing in the time domain.
5
5
 
6
6
  .. autosummary::
7
7
  :toctree: generated/
8
8
 
9
- SamplesGenerator
10
- TimeInOut
11
- MaskedTimeInOut
9
+ MaskedTimeOut
12
10
  Trigger
13
11
  AngleTracker
14
12
  ChannelMixer
@@ -17,7 +15,7 @@
17
15
  SpatialInterpolatorConstantRotation
18
16
  Mixer
19
17
  TimePower
20
- TimeAverage
18
+ TimeCumAverage
21
19
  TimeReverse
22
20
  Filter
23
21
  FilterBank
@@ -26,20 +24,16 @@
26
24
  TimeExpAverage
27
25
  FiltFreqWeight
28
26
  OctaveFilterBank
29
- TimeCache
30
- TimeCumAverage
31
27
  WriteWAV
32
28
  WriteH5
33
- SampleSplitter
34
29
  TimeConvolve
30
+ MaskedTimeInOut
35
31
  """
36
32
 
37
33
  # imports from other packages
38
- import threading
39
34
  import wave
40
- from collections import deque
35
+ from abc import abstractmethod
41
36
  from datetime import datetime, timezone
42
- from inspect import currentframe
43
37
  from os import path
44
38
  from warnings import warn
45
39
 
@@ -77,158 +71,93 @@ from numpy import (
77
71
  split,
78
72
  sqrt,
79
73
  stack,
80
- sum,
74
+ sum, # noqa: A004
75
+ tile,
81
76
  unique,
82
77
  zeros,
83
78
  )
84
- from numpy.linalg import norm
85
- from numpy.matlib import repmat
86
79
  from scipy.fft import irfft, rfft
87
80
  from scipy.interpolate import CloughTocher2DInterpolator, CubicSpline, LinearNDInterpolator, Rbf, splev, splrep
81
+ from scipy.linalg import norm
88
82
  from scipy.signal import bilinear, butter, sosfilt, sosfiltfilt, tf2sos
89
83
  from scipy.spatial import Delaunay
90
84
  from traits.api import (
91
85
  Bool,
92
86
  CArray,
93
- CLong,
87
+ CInt,
94
88
  Constant,
95
89
  Delegate,
96
90
  Dict,
91
+ Enum,
97
92
  File,
98
93
  Float,
99
- HasPrivateTraits,
100
94
  Instance,
101
95
  Int,
102
96
  List,
103
- ListInt,
97
+ Map,
104
98
  Property,
105
99
  Str,
106
- Trait,
100
+ Union,
107
101
  cached_property,
108
102
  observe,
109
103
  on_trait_change,
110
104
  )
111
105
 
106
+ # acoular imports
107
+ from .base import SamplesGenerator, TimeOut
112
108
  from .configuration import config
109
+ from .deprecation import deprecated_alias
113
110
  from .environments import cartToCyl, cylToCart
114
- from .h5cache import H5cache
115
- from .h5files import H5CacheFileBase, _get_h5file_class
116
-
117
- # acoular imports
111
+ from .h5files import _get_h5file_class
118
112
  from .internal import digest, ldigest
119
113
  from .microphones import MicGeom
114
+ from .tools.utils import find_basename
120
115
 
121
116
 
122
- class SamplesGenerator(HasPrivateTraits):
123
- """Base class for any generating signal processing block.
124
-
125
- It provides a common interface for all SamplesGenerator classes, which
126
- generate an output via the generator :meth:`result`.
127
- This class has no real functionality on its own and should not be
128
- used directly.
129
- """
130
-
131
- #: Sampling frequency of the signal, defaults to 1.0
132
- sample_freq = Float(1.0, desc='sampling frequency')
133
-
134
- #: Number of channels
135
- numchannels = CLong
136
-
137
- #: Number of samples
138
- numsamples = CLong
139
-
140
- # internal identifier
141
- digest = Property(depends_on=['sample_freq', 'numchannels', 'numsamples'])
142
-
143
- def _get_digest(self):
144
- return digest(self)
145
-
146
- def result(self, num):
147
- """Python generator that yields the output block-wise.
148
-
149
- Parameters
150
- ----------
151
- num : integer
152
- This parameter defines the size of the blocks to be yielded
153
- (i.e. the number of samples per block)
154
-
155
- Returns
156
- -------
157
- No output since `SamplesGenerator` only represents a base class to derive
158
- other classes from.
159
-
160
- """
161
-
162
-
163
- class TimeInOut(SamplesGenerator):
164
- """Base class for any time domain signal processing block,
165
- gets samples from :attr:`source` and generates output via the
166
- generator :meth:`result`.
167
- """
168
-
169
- #: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
170
- source = Trait(SamplesGenerator)
171
-
172
- #: Sampling frequency of output signal, as given by :attr:`source`.
173
- sample_freq = Delegate('source')
174
-
175
- #: Number of channels in output, as given by :attr:`source`.
176
- numchannels = Delegate('source')
177
-
178
- #: Number of samples in output, as given by :attr:`source`.
179
- numsamples = Delegate('source')
180
-
181
- # internal identifier
182
- digest = Property(depends_on=['source.digest'])
183
-
184
- @cached_property
185
- def _get_digest(self):
186
- return digest(self)
187
-
188
- def result(self, num):
189
- """Python generator: dummy function, just echoes the output of source,
190
- yields samples in blocks of shape (num, :attr:`numchannels`), the last block
191
- may be shorter than num.
192
- """
193
- yield from self.source.result(num)
194
-
195
-
196
- class MaskedTimeInOut(TimeInOut):
117
+ @deprecated_alias({'numchannels_total': 'num_channels_total', 'numsamples_total': 'num_samples_total'})
118
+ class MaskedTimeOut(TimeOut):
197
119
  """Signal processing block for channel and sample selection.
198
120
 
199
121
  This class serves as intermediary to define (in)valid
200
122
  channels and samples for any
201
123
  :class:`~acoular.sources.SamplesGenerator` (or derived) object.
202
- It gets samples from :attr:`~acoular.tprocess.TimeInOut.source`
124
+ It gets samples from :attr:`~acoular.base.TimeOut.source`
203
125
  and generates output via the generator :meth:`result`.
204
126
  """
205
127
 
206
- #: Index of the first sample to be considered valid.
207
- start = CLong(0, desc='start of valid samples')
128
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
129
+ source = Instance(SamplesGenerator)
130
+
131
+ # Index of the first sample to be considered valid.
132
+ start = CInt(0, desc='start of valid samples')
208
133
 
209
- #: Index of the last sample to be considered valid.
210
- stop = Trait(None, None, CLong, desc='stop of valid samples')
134
+ # Index of the last sample to be considered valid.
135
+ stop = Union(None, CInt, desc='stop of valid samples')
211
136
 
212
- #: Channels that are to be treated as invalid.
213
- invalid_channels = ListInt(desc='list of invalid channels')
137
+ # Channels that are to be treated as invalid.
138
+ invalid_channels = List(int, desc='list of invalid channels')
214
139
 
215
- #: Channel mask to serve as an index for all valid channels, is set automatically.
216
- channels = Property(depends_on=['invalid_channels', 'source.numchannels'], desc='channel mask')
140
+ # Channel mask to serve as an index for all valid channels, is set automatically.
141
+ channels = Property(depends_on=['invalid_channels', 'source.num_channels'], desc='channel mask')
217
142
 
218
- #: Number of channels in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
219
- numchannels_total = Delegate('source', 'numchannels')
143
+ # Number of channels in input, as given by :attr:`~acoular.base.TimeOut.source`.
144
+ num_channels_total = Delegate('source', 'num_channels')
220
145
 
221
- #: Number of samples in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
222
- numsamples_total = Delegate('source', 'numsamples')
146
+ # Number of samples in input, as given by :attr:`~acoular.base.TimeOut.source`.
147
+ num_samples_total = Delegate('source', 'num_samples')
223
148
 
224
- #: Number of valid channels, is set automatically.
225
- numchannels = Property(depends_on=['invalid_channels', 'source.numchannels'], desc='number of valid input channels')
149
+ # Number of valid channels, is set automatically.
150
+ num_channels = Property(
151
+ depends_on=['invalid_channels', 'source.num_channels'], desc='number of valid input channels'
152
+ )
226
153
 
227
- #: Number of valid time samples, is set automatically.
228
- numsamples = Property(depends_on=['start', 'stop', 'source.numsamples'], desc='number of valid samples per channel')
154
+ # Number of valid time samples, is set automatically.
155
+ num_samples = Property(
156
+ depends_on=['start', 'stop', 'source.num_samples'], desc='number of valid samples per channel'
157
+ )
229
158
 
230
- #: Name of the cache file without extension, readonly.
231
- basename = Property(depends_on='source.digest', desc='basename for cache file')
159
+ # Name of the cache file without extension, readonly.
160
+ basename = Property(depends_on=['source.digest'], desc='basename for cache file')
232
161
 
233
162
  # internal identifier
234
163
  digest = Property(depends_on=['source.digest', 'start', 'stop', 'invalid_channels'])
@@ -239,26 +168,32 @@ class MaskedTimeInOut(TimeInOut):
239
168
 
240
169
  @cached_property
241
170
  def _get_basename(self):
242
- if 'basename' in self.source.all_trait_names():
243
- return self.source.basename
244
- return self.source.__class__.__name__ + self.source.digest
171
+ warn(
172
+ (
173
+ f'The basename attribute of a {self.__class__.__name__} object is deprecated'
174
+ ' and will be removed in a future release!'
175
+ ),
176
+ DeprecationWarning,
177
+ stacklevel=2,
178
+ )
179
+ return find_basename(self.source, alternative_basename=self.source.__class__.__name__ + self.source.digest)
245
180
 
246
181
  @cached_property
247
182
  def _get_channels(self):
248
183
  if len(self.invalid_channels) == 0:
249
184
  return slice(0, None, None)
250
- allr = [i for i in range(self.numchannels_total) if i not in self.invalid_channels]
185
+ allr = [i for i in range(self.num_channels_total) if i not in self.invalid_channels]
251
186
  return array(allr)
252
187
 
253
188
  @cached_property
254
- def _get_numchannels(self):
189
+ def _get_num_channels(self):
255
190
  if len(self.invalid_channels) == 0:
256
- return self.numchannels_total
191
+ return self.num_channels_total
257
192
  return len(self.channels)
258
193
 
259
194
  @cached_property
260
- def _get_numsamples(self):
261
- sli = slice(self.start, self.stop).indices(self.numsamples_total)
195
+ def _get_num_samples(self):
196
+ sli = slice(self.start, self.stop).indices(self.num_samples_total)
262
197
  return sli[1] - sli[0]
263
198
 
264
199
  def result(self, num):
@@ -272,22 +207,22 @@ class MaskedTimeInOut(TimeInOut):
272
207
 
273
208
  Returns
274
209
  -------
275
- Samples in blocks of shape (num, :attr:`numchannels`).
210
+ Samples in blocks of shape (num, :attr:`num_channels`).
276
211
  The last block may be shorter than num.
277
212
 
278
213
  """
279
- sli = slice(self.start, self.stop).indices(self.numsamples_total)
214
+ sli = slice(self.start, self.stop).indices(self.num_samples_total)
280
215
  start = sli[0]
281
216
  stop = sli[1]
282
217
  if start >= stop:
283
218
  msg = 'no samples available'
284
219
  raise OSError(msg)
285
220
 
286
- if start != 0 or stop != self.numsamples_total:
221
+ if start != 0 or stop != self.num_samples_total:
287
222
  offset = -start % num
288
223
  if offset == 0:
289
224
  offset = num
290
- buf = empty((num + offset, self.numchannels), dtype=float)
225
+ buf = empty((num + offset, self.num_channels), dtype=float)
291
226
  bsize = 0
292
227
  i = 0
293
228
  fblock = True
@@ -323,16 +258,19 @@ class MaskedTimeInOut(TimeInOut):
323
258
  yield block[:, self.channels]
324
259
 
325
260
 
326
- class ChannelMixer(TimeInOut):
261
+ class ChannelMixer(TimeOut):
327
262
  """Class for directly mixing the channels of a multi-channel source.
328
263
  Outputs a single channel.
329
264
  """
330
265
 
331
- #: Amplitude weight(s) for the channels as array. If not set, all channels are equally weighted.
266
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
267
+ source = Instance(SamplesGenerator)
268
+
269
+ # Amplitude weight(s) for the channels as array. If not set, all channels are equally weighted.
332
270
  weights = CArray(desc='channel weights')
333
271
 
334
272
  # Number of channels is always one here.
335
- numchannels = Constant(1)
273
+ num_channels = Constant(1)
336
274
 
337
275
  # internal identifier
338
276
  digest = Property(depends_on=['source.digest', 'weights'])
@@ -357,10 +295,10 @@ class ChannelMixer(TimeInOut):
357
295
 
358
296
  """
359
297
  if self.weights.size:
360
- if self.weights.shape in {(self.source.numchannels,), (1,)}:
298
+ if self.weights.shape in {(self.source.num_channels,), (1,)}:
361
299
  weights = self.weights
362
300
  else:
363
- msg = f'Weight factors can not be broadcasted: {self.weights.shape}, {(self.source.numchannels,)}'
301
+ msg = f'Weight factors can not be broadcasted: {self.weights.shape}, {(self.source.num_channels,)}'
364
302
  raise ValueError(msg)
365
303
  else:
366
304
  weights = 1
@@ -369,7 +307,7 @@ class ChannelMixer(TimeInOut):
369
307
  yield sum(weights * block, 1, keepdims=True)
370
308
 
371
309
 
372
- class Trigger(TimeInOut):
310
+ class Trigger(TimeOut): # pragma: no cover
373
311
  """Class for identifying trigger signals.
374
312
  Gets samples from :attr:`source` and stores the trigger samples in :meth:`trigger_data`.
375
313
 
@@ -383,59 +321,56 @@ class Trigger(TimeInOut):
383
321
  vary too much.
384
322
  """
385
323
 
386
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
324
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
387
325
  source = Instance(SamplesGenerator)
388
326
 
389
- #: Threshold of trigger. Has different meanings for different
390
- #: :attr:`~acoular.tprocess.Trigger.trigger_type`. The sign is relevant.
391
- #: If a sample of the signal is above/below the positive/negative threshold,
392
- #: it is assumed to be a peak.
393
- #: Default is None, in which case a first estimate is used: The threshold
394
- #: is assumed to be 75% of the max/min difference between all extremums and the
395
- #: mean value of the trigger signal. E.g: the mean value is 0 and there are positive
396
- #: extremums at 400 and negative extremums at -800. Then the estimated threshold would be
397
- #: 0.75 * -800 = -600.
327
+ # Threshold of trigger. Has different meanings for different
328
+ # :attr:`~acoular.tprocess.Trigger.trigger_type`. The sign is relevant.
329
+ # If a sample of the signal is above/below the positive/negative threshold,
330
+ # it is assumed to be a peak.
331
+ # Default is None, in which case a first estimate is used: The threshold
332
+ # is assumed to be 75% of the max/min difference between all extremums and the
333
+ # mean value of the trigger signal. E.g: the mean value is 0 and there are positive
334
+ # extremums at 400 and negative extremums at -800. Then the estimated threshold would be
335
+ # 0.75 * -800 = -600.
398
336
  threshold = Float(None)
399
337
 
400
- #: Maximum allowable variation of length of each revolution duration. Default is
401
- #: 2%. A warning is thrown, if any revolution length surpasses this value:
402
- #: abs(durationEachRev - meanDuration) > 0.02 * meanDuration
338
+ # Maximum allowable variation of length of each revolution duration. Default is
339
+ # 2%. A warning is thrown, if any revolution length surpasses this value:
340
+ # abs(durationEachRev - meanDuration) > 0.02 * meanDuration
403
341
  max_variation_of_duration = Float(0.02)
404
342
 
405
- #: Defines the length of hunks via lenHunk = hunk_length * maxOncePerRevDuration.
406
- #: If there are multiple peaks within lenHunk, then the algorithm will
407
- #: cancel all but one out (see :attr:`~acoular.tprocess.Trigger.multiple_peaks_in_hunk`).
408
- #: Default is to 0.1.
343
+ # Defines the length of hunks via lenHunk = hunk_length * maxOncePerRevDuration.
344
+ # If there are multiple peaks within lenHunk, then the algorithm will
345
+ # cancel all but one out (see :attr:`~acoular.tprocess.Trigger.multiple_peaks_in_hunk`).
346
+ # Default is to 0.1.
409
347
  hunk_length = Float(0.1)
410
348
 
411
- #: Type of trigger.
412
- #:
413
- #: 'dirac': a single puls is assumed (sign of
414
- #: :attr:`~acoular.tprocess.Trigger.trigger_type` is important).
415
- #: Sample will trigger if its value is above/below the pos/neg threshold.
416
- #:
417
- #: 'rect' : repeating rectangular functions. Only every second
418
- #: edge is assumed to be a trigger. The sign of
419
- #: :attr:`~acoular.tprocess.Trigger.trigger_type` gives information
420
- #: on which edge should be used (+ for rising edge, - for falling edge).
421
- #: Sample will trigger if the difference between its value and its predecessors value
422
- #: is above/below the pos/neg threshold.
423
- #:
424
- #: Default is 'dirac'.
425
- trigger_type = Trait('dirac', 'rect')
426
-
427
- #: Identifier which peak to consider, if there are multiple peaks in one hunk
428
- #: (see :attr:`~acoular.tprocess.Trigger.hunk_length`). Default is to 'extremum',
429
- #: in which case the extremal peak (maximum if threshold > 0, minimum if threshold < 0) is considered.
430
- multiple_peaks_in_hunk = Trait('extremum', 'first')
431
-
432
- #: Tuple consisting of 3 entries:
433
- #:
434
- #: 1.: -Vector with the sample indices of the 1/Rev trigger samples
435
- #:
436
- #: 2.: -maximum of number of samples between adjacent trigger samples
437
- #:
438
- #: 3.: -minimum of number of samples between adjacent trigger samples
349
+ # Type of trigger.
350
+ #
351
+ # 'dirac': a single pulse is assumed (sign of :attr:`~acoular.tprocess.Trigger.trigger_type` is
352
+ # important). Sample will trigger if its value is above/below the pos/neg threshold.
353
+ #
354
+ # 'rect' : repeating rectangular functions. Only every second edge is assumed to be a trigger.
355
+ # The sign of :attr:`~acoular.tprocess.Trigger.trigger_type` gives information on which edge
356
+ # should be used (+ for rising edge, - for falling edge). Sample will trigger if the difference
357
+ # between its value and its predecessors value is above/below the pos/neg threshold.
358
+ #
359
+ # Default is 'dirac'.
360
+ trigger_type = Enum('dirac', 'rect')
361
+
362
+ # Identifier which peak to consider, if there are multiple peaks in one hunk : (see
363
+ # :attr:`~acoular.tprocess.Trigger.hunk_length`). Default is to 'extremum', : in which case the
364
+ # extremal peak (maximum if threshold > 0, minimum if threshold < 0) is considered.
365
+ multiple_peaks_in_hunk = Enum('extremum', 'first')
366
+
367
+ # Tuple consisting of 3 entries:
368
+ #
369
+ # 1.: -Vector with the sample indices of the 1/Rev trigger samples
370
+ #
371
+ # 2.: -maximum of number of samples between adjacent trigger samples
372
+ #
373
+ # 3.: -minimum of number of samples between adjacent trigger samples
439
374
  trigger_data = Property(
440
375
  depends_on=[
441
376
  'source.digest',
@@ -513,8 +448,8 @@ class Trigger(TimeInOut):
513
448
  faultyInd = flatnonzero(diffDist > self.max_variation_of_duration * meanDist)
514
449
  if faultyInd.size != 0:
515
450
  warn(
516
- 'In Trigger-Identification: The distances between the peaks (and therefor the lengths of the revolutions) vary too much (check samples %s).'
517
- % str(peakLoc[faultyInd] + self.source.start),
451
+ f'In Trigger-Identification: The distances between the peaks (and therefore the lengths of the \
452
+ revolutions) vary too much (check samples {peakLoc[faultyInd] + self.source.start}).',
518
453
  Warning,
519
454
  stacklevel=2,
520
455
  )
@@ -527,7 +462,8 @@ class Trigger(TimeInOut):
527
462
  def _trigger_rect(self, x0, x, threshold):
528
463
  # x0 stores the last value of the the last generator cycle
529
464
  xNew = append(x0, x)
530
- # indPeakHunk = abs(xNew[1:] - xNew[:-1]) > abs(threshold) # with this line: every edge would be located
465
+ # indPeakHunk = abs(xNew[1:] - xNew[:-1]) > abs(threshold)
466
+ # with above line, every edge would be located
531
467
  return self._trigger_value_comp(xNew[1:] - xNew[:-1], threshold)
532
468
 
533
469
  def _trigger_value_comp(self, trigger_data, threshold):
@@ -551,30 +487,33 @@ class Trigger(TimeInOut):
551
487
  maxTriggerHelp = [minVal, maxVal] - meanVal
552
488
  argInd = argmax(abs(maxTriggerHelp))
553
489
  thresh = maxTriggerHelp[argInd] * 0.75 # 0.75 for 75% of max trigger signal
554
- warn('No threshold was passed. An estimated threshold of %s is assumed.' % thresh, Warning, stacklevel=2)
490
+ warn(f'No threshold was passed. An estimated threshold of {thresh} is assumed.', Warning, stacklevel=2)
555
491
  else: # take user defined threshold
556
492
  thresh = self.threshold
557
493
  return thresh
558
494
 
559
495
  def _check_trigger_existence(self):
560
- nChannels = self.source.numchannels
561
- if not nChannels == 1:
562
- raise Exception('Trigger signal must consist of ONE channel, instead %s channels are given!' % nChannels)
496
+ nChannels = self.source.num_channels
497
+ if nChannels != 1:
498
+ msg = f'Trigger signal must consist of ONE channel, instead {nChannels} channels are given!'
499
+ raise Exception(msg)
563
500
  return 0
564
501
 
502
+ def result(self, num):
503
+ msg = 'result method not implemented yet! Data from source will be passed without transformation.'
504
+ warn(msg, Warning, stacklevel=2)
505
+ yield from self.source.result(num)
506
+
565
507
 
566
- class AngleTracker(MaskedTimeInOut):
508
+ class AngleTracker(MaskedTimeOut):
567
509
  """Calculates rotation angle and rpm per sample from a trigger signal
568
510
  using spline interpolation in the time domain.
569
511
 
570
- Gets samples from :attr:`trigger` and stores the angle and rpm samples in :meth:`angle` and :meth:`rpm`.
571
-
512
+ Gets samples from :attr:`trigger` and stores the angle and rpm samples in :meth:`angle` and
513
+ :meth:`rpm`.
572
514
  """
573
515
 
574
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
575
- source = Instance(SamplesGenerator)
576
-
577
- #: Trigger data from :class:`acoular.tprocess.Trigger`.
516
+ # Trigger data from :class:`acoular.tprocess.Trigger`.
578
517
  trigger = Instance(Trigger)
579
518
 
580
519
  # internal identifier
@@ -589,29 +528,29 @@ class AngleTracker(MaskedTimeInOut):
589
528
  ],
590
529
  )
591
530
 
592
- #: Trigger signals per revolution,
593
- #: defaults to 1.
531
+ # Trigger signals per revolution,
532
+ # defaults to 1.
594
533
  trigger_per_revo = Int(1, desc='trigger signals per revolution')
595
534
 
596
- #: Flag to set counter-clockwise (1) or clockwise (-1) rotation,
597
- #: defaults to -1.
535
+ # Flag to set counter-clockwise (1) or clockwise (-1) rotation,
536
+ # defaults to -1.
598
537
  rot_direction = Int(-1, desc='mathematical direction of rotation')
599
538
 
600
- #: Points of interpolation used for spline,
601
- #: defaults to 4.
539
+ # Points of interpolation used for spline,
540
+ # defaults to 4.
602
541
  interp_points = Int(4, desc='Points of interpolation used for spline')
603
542
 
604
- #: rotation angle in radians for first trigger position
543
+ # rotation angle in radians for first trigger position
605
544
  start_angle = Float(0, desc='rotation angle for trigger position')
606
545
 
607
- #: revolutions per minute for each sample, read-only
608
- rpm = Property(depends_on='digest', desc='revolutions per minute for each sample')
546
+ # revolutions per minute for each sample, read-only
547
+ rpm = Property(depends_on=['digest'], desc='revolutions per minute for each sample')
609
548
 
610
- #: average revolutions per minute, read-only
611
- average_rpm = Property(depends_on='digest', desc='average revolutions per minute')
549
+ # average revolutions per minute, read-only
550
+ average_rpm = Property(depends_on=['digest'], desc='average revolutions per minute')
612
551
 
613
- #: rotation angle in radians for each sample, read-only
614
- angle = Property(depends_on='digest', desc='rotation angle for each sample')
552
+ # rotation angle in radians for each sample, read-only
553
+ angle = Property(depends_on=['digest'], desc='rotation angle for each sample')
615
554
 
616
555
  # Internal flag to determine whether rpm and angle calculation has been processed,
617
556
  # prevents recalculation
@@ -642,17 +581,17 @@ class AngleTracker(MaskedTimeInOut):
642
581
  # init
643
582
  ind = 0
644
583
  # trigger data
645
- peakloc, maxdist, mindist = self.trigger.trigger_data()
584
+ peakloc, maxdist, mindist = self.trigger.trigger_data
646
585
  TriggerPerRevo = self.trigger_per_revo
647
586
  rotDirection = self.rot_direction
648
- num = self.source.numsamples
587
+ num = self.source.num_samples
649
588
  samplerate = self.source.sample_freq
650
589
  self._rpm = zeros(num)
651
590
  self._angle = zeros(num)
652
591
  # number of spline points
653
592
  InterpPoints = self.interp_points
654
593
 
655
- # loop over alle timesamples
594
+ # loop over all timesamples
656
595
  while ind < num:
657
596
  # when starting spline forward
658
597
  if ind < peakloc[InterpPoints]:
@@ -716,21 +655,24 @@ class AngleTracker(MaskedTimeInOut):
716
655
 
717
656
  """
718
657
  # trigger indices data
719
- peakloc = self.trigger.trigger_data()[0]
658
+ peakloc = self.trigger.trigger_data[0]
720
659
  # calculation of average rpm in 1/min
721
660
  return (len(peakloc) - 1) / (peakloc[-1] - peakloc[0]) / self.trigger_per_revo * self.source.sample_freq * 60
722
661
 
723
662
 
724
- class SpatialInterpolator(TimeInOut):
663
+ class SpatialInterpolator(TimeOut): # pragma: no cover
725
664
  """Base class for spatial interpolation of microphone data.
726
665
  Gets samples from :attr:`source` and generates output via the
727
666
  generator :meth:`result`.
728
667
  """
729
668
 
730
- #: :class:`~acoular.microphones.MicGeom` object that provides the real microphone locations.
669
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
670
+ source = Instance(SamplesGenerator)
671
+
672
+ # :class:`~acoular.microphones.MicGeom` object that provides the real microphone locations.
731
673
  mics = Instance(MicGeom(), desc='microphone geometry')
732
674
 
733
- #: :class:`~acoular.microphones.MicGeom` object that provides the virtual microphone locations.
675
+ # :class:`~acoular.microphones.MicGeom` object that provides the virtual microphone locations.
734
676
  mics_virtual = Property(desc='microphone geometry')
735
677
 
736
678
  _mics_virtual = Instance(MicGeom, desc='internal microphone geometry;internal usage, read only')
@@ -743,15 +685,12 @@ class SpatialInterpolator(TimeInOut):
743
685
  def _set_mics_virtual(self, mics_virtual):
744
686
  self._mics_virtual = mics_virtual
745
687
 
746
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
747
- source = Instance(SamplesGenerator)
748
-
749
- #: Interpolation method in spacial domain, defaults to linear
750
- #: linear uses numpy linear interpolation
751
- #: spline uses scipy CloughTocher algorithm
752
- #: rbf is scipy radial basis function with multiquadric, cubic and sinc functions
753
- #: idw refers to the inverse distance weighting algorithm
754
- method = Trait(
688
+ # Interpolation method in spatial domain, defaults to linear
689
+ # linear uses numpy linear interpolation
690
+ # spline uses scipy CloughTocher algorithm
691
+ # rbf is scipy radial basis function with multiquadric, cubic and sinc functions
692
+ # idw refers to the inverse distance weighting algorithm
693
+ method = Enum(
755
694
  'linear',
756
695
  'spline',
757
696
  'rbf-multiquadric',
@@ -762,42 +701,41 @@ class SpatialInterpolator(TimeInOut):
762
701
  desc='method for interpolation used',
763
702
  )
764
703
 
765
- #: spacial dimensionality of the array geometry
766
- array_dimension = Trait('1D', '2D', 'ring', '3D', 'custom', desc='spacial dimensionality of the array geometry')
704
+ # spatial dimensionality of the array geometry
705
+ array_dimension = Enum('1D', '2D', 'ring', '3D', 'custom', desc='spatial dimensionality of the array geometry')
767
706
 
768
- #: Sampling frequency of output signal, as given by :attr:`source`.
707
+ # Sampling frequency of output signal, as given by :attr:`source`.
769
708
  sample_freq = Delegate('source', 'sample_freq')
770
709
 
771
- #: Number of channels in output.
772
- numchannels = Property()
710
+ # Number of channels in output.
711
+ num_channels = Property()
773
712
 
774
- #: Number of samples in output, as given by :attr:`source`.
775
- numsamples = Delegate('source', 'numsamples')
713
+ # Number of samples in output, as given by :attr:`source`.
714
+ num_samples = Delegate('source', 'num_samples')
776
715
 
777
- #:Interpolate a point at the origin of the Array geometry
716
+ # Interpolate a point at the origin of the Array geometry
778
717
  interp_at_zero = Bool(False)
779
718
 
780
- #: The rotation must be around the z-axis, which means from x to y axis.
781
- #: If the coordinates are not build like that, than this 3x3 orthogonal
782
- #: transformation matrix Q can be used to modify the coordinates.
783
- #: It is assumed that with the modified coordinates the rotation is around the z-axis.
784
- #: The transformation is done via [x,y,z]_mod = Q * [x,y,z]. (default is Identity).
719
+ # The rotation must be around the z-axis, which means from x to y axis.
720
+ # If the coordinates are not build like that, than this 3x3 orthogonal
721
+ # transformation matrix Q can be used to modify the coordinates.
722
+ # It is assumed that with the modified coordinates the rotation is around the z-axis.
723
+ # The transformation is done via [x,y,z]_mod = Q * [x,y,z]. (default is Identity).
785
724
  Q = CArray(dtype=float64, shape=(3, 3), value=identity(3))
786
725
 
787
- num_IDW = Trait(3, dtype=int, desc='number of neighboring microphones, DEFAULT=3') # noqa: N815
726
+ num_IDW = Int(3, desc='number of neighboring microphones, DEFAULT=3') # noqa: N815
788
727
 
789
- p_weight = Trait(
728
+ p_weight = Float(
790
729
  2,
791
- dtype=float,
792
730
  desc='used in interpolation for virtual microphone, weighting power exponent for IDW',
793
731
  )
794
732
 
795
- #: Stores the output of :meth:`_virtNewCoord_func`; Read-Only
733
+ # Stores the output of :meth:`_virtNewCoord_func`; Read-Only
796
734
  _virtNewCoord_func = Property( # noqa: N815
797
735
  depends_on=['mics.digest', 'mics_virtual.digest', 'method', 'array_dimension', 'interp_at_zero'],
798
736
  )
799
737
 
800
- #: internal identifier
738
+ # internal identifier
801
739
  digest = Property(
802
740
  depends_on=[
803
741
  'mics.digest',
@@ -810,7 +748,7 @@ class SpatialInterpolator(TimeInOut):
810
748
  ],
811
749
  )
812
750
 
813
- def _get_numchannels(self):
751
+ def _get_num_channels(self):
814
752
  return self.mics_virtual.num_mics
815
753
 
816
754
  @cached_property
@@ -842,26 +780,30 @@ class SpatialInterpolator(TimeInOut):
842
780
  Returns
843
781
  -------
844
782
  mesh : List[]
845
- The items of these lists are dependent of the reduced interpolation dimension of each subarray.
783
+ The items of these lists depend on the reduced interpolation dimension of each subarray.
846
784
  If the Array is 1D the list items are:
847
785
  1. item : float64[nMicsInSpecificSubarray]
848
- Ordered positions of the real mics on the new 1d axis, to be used as inputs for numpys interp.
786
+ Ordered positions of the real mics on the new 1d axis,
787
+ to be used as inputs for numpys interp.
849
788
  2. item : int64[nMicsInArray]
850
- Indices identifying how the measured pressures must be evaluated, s.t. the entries of the previous item (see last line)
851
- correspond to their initial pressure values
789
+ Indices identifying how the measured pressures must be evaluated, s.t. the
790
+ entries of the previous item (see last line) correspond to their initial
791
+ pressure values.
852
792
  If the Array is 2D or 3d the list items are:
853
793
  1. item : Delaunay mesh object
854
- Delauney mesh (see scipy.spatial.Delaunay) for the specific Array
794
+ Delaunay mesh (see scipy.spatial.Delaunay) for the specific Array
855
795
  2. item : int64[nMicsInArray]
856
- same as 1d case, BUT with the difference, that here the rotational periodicy is handled, when constructing the mesh.
857
- Therefor the mesh could have more vertices than the actual Array mics.
796
+ same as 1d case, BUT with the difference, that here the rotational periodicity
797
+ is handled, when constructing the mesh. Therefore, the mesh could have more
798
+ vertices than the actual Array mics.
858
799
 
859
800
  virtNewCoord : float64[3, nVirtualMics]
860
- Projection of each virtual mic onto its new coordinates. The columns of virtNewCoord correspond to [phi, rho, z]
801
+ Projection of each virtual mic onto its new coordinates. The columns of virtNewCoord
802
+ correspond to [phi, rho, z].
861
803
 
862
804
  newCoord : float64[3, nMics]
863
- Projection of each mic onto its new coordinates. The columns of newCoordinates correspond to [phi, rho, z]
864
-
805
+ Projection of each mic onto its new coordinates. The columns of newCoordinates
806
+ correspond to [phi, rho, z].
865
807
  """
866
808
  # init positions of virtual mics in cyl coordinates
867
809
  nVirtMics = mpos_virt.shape[1]
@@ -1017,12 +959,12 @@ class SpatialInterpolator(TimeInOut):
1017
959
  if self.array_dimension == '1D' or self.array_dimension == 'ring':
1018
960
  # for rotation add phi_delay
1019
961
  if not array_equal(phi_delay, []):
1020
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
962
+ xInterpHelp = tile(virtNewCoord[0, :], (nTime, 1)) + tile(phi_delay, (virtNewCoord.shape[1], 1)).T
1021
963
  xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
1022
964
  # if no rotation given
1023
965
  else:
1024
- xInterp = repmat(virtNewCoord[0, :], nTime, 1)
1025
- # get ordered microphone posions in radiant
966
+ xInterp = tile(virtNewCoord[0, :], (nTime, 1))
967
+ # get ordered microphone positions in radiant
1026
968
  x = newCoord[0]
1027
969
  for cntTime in range(nTime):
1028
970
  if self.method == 'linear':
@@ -1075,10 +1017,10 @@ class SpatialInterpolator(TimeInOut):
1075
1017
  elif self.array_dimension == '2D':
1076
1018
  # check rotation
1077
1019
  if not array_equal(phi_delay, []):
1078
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
1020
+ xInterpHelp = tile(virtNewCoord[0, :], (nTime, 1)) + tile(phi_delay, (virtNewCoord.shape[1], 1)).T
1079
1021
  xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
1080
1022
  else:
1081
- xInterp = repmat(virtNewCoord[0, :], nTime, 1)
1023
+ xInterp = tile(virtNewCoord[0, :], (nTime, 1))
1082
1024
 
1083
1025
  mesh = meshList[0][0]
1084
1026
  for cntTime in range(nTime):
@@ -1134,10 +1076,10 @@ class SpatialInterpolator(TimeInOut):
1134
1076
  # using inverse distance weighting
1135
1077
  elif self.method == 'IDW':
1136
1078
  newPoint2_M = newPoint.T
1137
- newPoint3_M = append(newPoint2_M, zeros([1, self.numchannels]), axis=0)
1079
+ newPoint3_M = append(newPoint2_M, zeros([1, self.num_channels]), axis=0)
1138
1080
  newPointCart = cylToCart(newPoint3_M)
1139
1081
  for ind in arange(len(newPoint[:, 0])):
1140
- newPoint_Rep = repmat(newPointCart[:, ind], len(newPoint[:, 0]), 1).T
1082
+ newPoint_Rep = tile(newPointCart[:, ind], (len(newPoint[:, 0]), 1)).T
1141
1083
  subtract = newPoint_Rep - newCoordCart
1142
1084
  normDistance = norm(subtract, axis=0)
1143
1085
  index_norm = argsort(normDistance)[: self.num_IDW]
@@ -1154,10 +1096,10 @@ class SpatialInterpolator(TimeInOut):
1154
1096
  elif self.array_dimension == '3D':
1155
1097
  # check rotation
1156
1098
  if not array_equal(phi_delay, []):
1157
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
1099
+ xInterpHelp = tile(virtNewCoord[0, :], (nTime, 1)) + tile(phi_delay, (virtNewCoord.shape[1], 1)).T
1158
1100
  xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
1159
1101
  else:
1160
- xInterp = repmat(virtNewCoord[0, :], nTime, 1)
1102
+ xInterp = tile(virtNewCoord[0, :], (nTime, 1))
1161
1103
 
1162
1104
  mesh = meshList[0][0]
1163
1105
  for cntTime in range(nTime):
@@ -1207,17 +1149,22 @@ class SpatialInterpolator(TimeInOut):
1207
1149
  # return interpolated pressure values
1208
1150
  return pInterp
1209
1151
 
1152
+ def result(self, num):
1153
+ msg = 'result method not implemented yet! Data from source will be passed without transformation.'
1154
+ warn(msg, Warning, stacklevel=2)
1155
+ yield from self.source.result(num)
1156
+
1210
1157
 
1211
- class SpatialInterpolatorRotation(SpatialInterpolator):
1158
+ class SpatialInterpolatorRotation(SpatialInterpolator): # pragma: no cover
1212
1159
  """Spatial Interpolation for rotating sources. Gets samples from :attr:`source`
1213
1160
  and angles from :attr:`AngleTracker`.Generates output via the generator :meth:`result`.
1214
1161
 
1215
1162
  """
1216
1163
 
1217
- #: Angle data from AngleTracker class
1164
+ # Angle data from AngleTracker class
1218
1165
  angle_source = Instance(AngleTracker)
1219
1166
 
1220
- #: Internal identifier
1167
+ # Internal identifier
1221
1168
  digest = Property(
1222
1169
  depends_on=[
1223
1170
  'source.digest',
@@ -1246,7 +1193,7 @@ class SpatialInterpolatorRotation(SpatialInterpolator):
1246
1193
 
1247
1194
  Returns
1248
1195
  -------
1249
- Samples in blocks of shape (num, :attr:`numchannels`).
1196
+ Samples in blocks of shape (num, :attr:`num_channels`).
1250
1197
  The last block may be shorter than num.
1251
1198
 
1252
1199
  """
@@ -1263,14 +1210,14 @@ class SpatialInterpolatorRotation(SpatialInterpolator):
1263
1210
  count += num
1264
1211
 
1265
1212
 
1266
- class SpatialInterpolatorConstantRotation(SpatialInterpolator):
1213
+ class SpatialInterpolatorConstantRotation(SpatialInterpolator): # pragma: no cover
1267
1214
  """Spatial linear Interpolation for constantly rotating sources.
1268
1215
  Gets samples from :attr:`source` and generates output via the
1269
1216
  generator :meth:`result`.
1270
1217
  """
1271
1218
 
1272
- #: Rotational speed in rps. Positive, if rotation is around positive z-axis sense,
1273
- #: which means from x to y axis.
1219
+ # Rotational speed in rps. Positive, if rotation is around positive z-axis sense,
1220
+ # which means from x to y axis.
1274
1221
  rotational_speed = Float(0.0)
1275
1222
 
1276
1223
  # internal identifier
@@ -1302,7 +1249,7 @@ class SpatialInterpolatorConstantRotation(SpatialInterpolator):
1302
1249
 
1303
1250
  Returns
1304
1251
  -------
1305
- Samples in blocks of shape (num, :attr:`numchannels`).
1252
+ Samples in blocks of shape (num, :attr:`num_channels`).
1306
1253
  The last block may be shorter than num.
1307
1254
 
1308
1255
  """
@@ -1317,30 +1264,30 @@ class SpatialInterpolatorConstantRotation(SpatialInterpolator):
1317
1264
  yield interpVal
1318
1265
 
1319
1266
 
1320
- class Mixer(TimeInOut):
1267
+ class Mixer(TimeOut):
1321
1268
  """Mixes the signals from several sources."""
1322
1269
 
1323
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` object.
1324
- source = Trait(SamplesGenerator)
1270
+ # Data source; :class:`~acoular.base.SamplesGenerator` object.
1271
+ source = Instance(SamplesGenerator)
1325
1272
 
1326
- #: List of additional :class:`~acoular.tprocess.SamplesGenerator` objects
1327
- #: to be mixed.
1273
+ # List of additional :class:`~acoular.base.SamplesGenerator` objects
1274
+ # to be mixed.
1328
1275
  sources = List(Instance(SamplesGenerator, ()))
1329
1276
 
1330
- #: Sampling frequency of the signal as given by :attr:`source`.
1277
+ # Sampling frequency of the signal as given by :attr:`source`.
1331
1278
  sample_freq = Delegate('source')
1332
1279
 
1333
- #: Number of channels in output as given by :attr:`source`.
1334
- numchannels = Delegate('source')
1280
+ # Number of channels in output as given by :attr:`source`.
1281
+ num_channels = Delegate('source')
1335
1282
 
1336
- #: Number of samples in output as given by :attr:`source`.
1337
- numsamples = Delegate('source')
1283
+ # Number of samples in output as given by :attr:`source`.
1284
+ num_samples = Delegate('source')
1338
1285
 
1339
1286
  # internal identifier
1340
1287
  sdigest = Str()
1341
1288
 
1342
1289
  @observe('sources.items.digest')
1343
- def _set_sources_digest(self, event): # noqa ARG002
1290
+ def _set_sourcesdigest(self, event): # noqa ARG002
1344
1291
  self.sdigest = ldigest(self.sources)
1345
1292
 
1346
1293
  # internal identifier
@@ -1355,9 +1302,11 @@ class Mixer(TimeInOut):
1355
1302
  if self.source:
1356
1303
  for s in self.sources:
1357
1304
  if self.sample_freq != s.sample_freq:
1358
- raise ValueError('Sample frequency of %s does not fit' % s)
1359
- if self.numchannels != s.numchannels:
1360
- raise ValueError('Channel count of %s does not fit' % s)
1305
+ msg = f'Sample frequency of {s} does not fit'
1306
+ raise ValueError(msg)
1307
+ if self.num_channels != s.num_channels:
1308
+ msg = f'Channel count of {s} does not fit'
1309
+ raise ValueError(msg)
1361
1310
 
1362
1311
  def result(self, num):
1363
1312
  """Python generator that yields the output block-wise.
@@ -1372,7 +1321,7 @@ class Mixer(TimeInOut):
1372
1321
 
1373
1322
  Returns
1374
1323
  -------
1375
- Samples in blocks of shape (num, numchannels).
1324
+ Samples in blocks of shape (num, num_channels).
1376
1325
  The last block may be shorter than num.
1377
1326
 
1378
1327
  """
@@ -1395,9 +1344,12 @@ class Mixer(TimeInOut):
1395
1344
  break
1396
1345
 
1397
1346
 
1398
- class TimePower(TimeInOut):
1347
+ class TimePower(TimeOut):
1399
1348
  """Calculates time-depended power of the signal."""
1400
1349
 
1350
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1351
+ source = Instance(SamplesGenerator)
1352
+
1401
1353
  def result(self, num):
1402
1354
  """Python generator that yields the output block-wise.
1403
1355
 
@@ -1410,7 +1362,7 @@ class TimePower(TimeInOut):
1410
1362
  Returns
1411
1363
  -------
1412
1364
  Squared output of source.
1413
- Yields samples in blocks of shape (num, numchannels).
1365
+ Yields samples in blocks of shape (num, num_channels).
1414
1366
  The last block may be shorter than num.
1415
1367
 
1416
1368
  """
@@ -1418,64 +1370,12 @@ class TimePower(TimeInOut):
1418
1370
  yield temp * temp
1419
1371
 
1420
1372
 
1421
- class TimeAverage(TimeInOut):
1422
- """Calculates time-dependent average of the signal."""
1423
-
1424
- #: Number of samples to average over, defaults to 64.
1425
- naverage = Int(64, desc='number of samples to average over')
1426
-
1427
- #: Sampling frequency of the output signal, is set automatically.
1428
- sample_freq = Property(depends_on='source.sample_freq, naverage')
1429
-
1430
- #: Number of samples of the output signal, is set automatically.
1431
- numsamples = Property(depends_on='source.numsamples, naverage')
1432
-
1433
- # internal identifier
1434
- digest = Property(depends_on=['source.digest', '__class__', 'naverage'])
1435
-
1436
- @cached_property
1437
- def _get_digest(self):
1438
- return digest(self)
1439
-
1440
- @cached_property
1441
- def _get_sample_freq(self):
1442
- if self.source:
1443
- return 1.0 * self.source.sample_freq / self.naverage
1444
- return None
1445
-
1446
- @cached_property
1447
- def _get_numsamples(self):
1448
- if self.source:
1449
- return self.source.numsamples / self.naverage
1450
- return None
1451
-
1452
- def result(self, num):
1453
- """Python generator that yields the output block-wise.
1454
-
1455
- Parameters
1456
- ----------
1457
- num : integer
1458
- This parameter defines the size of the blocks to be yielded
1459
- (i.e. the number of samples per block).
1460
-
1461
- Returns
1462
- -------
1463
- Average of the output of source.
1464
- Yields samples in blocks of shape (num, numchannels).
1465
- The last block may be shorter than num.
1466
-
1467
- """
1468
- nav = self.naverage
1469
- for temp in self.source.result(num * nav):
1470
- ns, nc = temp.shape
1471
- nso = int(ns / nav)
1472
- if nso > 0:
1473
- yield temp[: nso * nav].reshape((nso, -1, nc)).mean(axis=1)
1474
-
1475
-
1476
- class TimeCumAverage(TimeInOut):
1373
+ class TimeCumAverage(TimeOut):
1477
1374
  """Calculates cumulative average of the signal, useful for Leq."""
1478
1375
 
1376
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1377
+ source = Instance(SamplesGenerator)
1378
+
1479
1379
  def result(self, num):
1480
1380
  """Python generator that yields the output block-wise.
1481
1381
 
@@ -1488,7 +1388,7 @@ class TimeCumAverage(TimeInOut):
1488
1388
  Returns
1489
1389
  -------
1490
1390
  Cumulative average of the output of source.
1491
- Yields samples in blocks of shape (num, numchannels).
1391
+ Yields samples in blocks of shape (num, num_channels).
1492
1392
  The last block may be shorter than num.
1493
1393
 
1494
1394
  """
@@ -1503,9 +1403,12 @@ class TimeCumAverage(TimeInOut):
1503
1403
  yield temp
1504
1404
 
1505
1405
 
1506
- class TimeReverse(TimeInOut):
1406
+ class TimeReverse(TimeOut):
1507
1407
  """Calculates the time-reversed signal of a source."""
1508
1408
 
1409
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1410
+ source = Instance(SamplesGenerator)
1411
+
1509
1412
  def result(self, num):
1510
1413
  """Python generator that yields the output block-wise.
1511
1414
 
@@ -1517,7 +1420,7 @@ class TimeReverse(TimeInOut):
1517
1420
 
1518
1421
  Returns
1519
1422
  -------
1520
- Yields samples in blocks of shape (num, numchannels).
1423
+ Yields samples in blocks of shape (num, num_channels).
1521
1424
  Time-reversed output of source.
1522
1425
  The last block may be shorter than num.
1523
1426
 
@@ -1535,15 +1438,18 @@ class TimeReverse(TimeInOut):
1535
1438
  yield temp[:nsh]
1536
1439
 
1537
1440
 
1538
- class Filter(TimeInOut):
1441
+ class Filter(TimeOut):
1539
1442
  """Abstract base class for IIR filters based on scipy lfilter
1540
1443
  implements a filter with coefficients that may be changed
1541
1444
  during processing.
1542
1445
 
1543
- Should not be instanciated by itself
1446
+ Should not be instantiated by itself.
1544
1447
  """
1545
1448
 
1546
- #: Filter coefficients
1449
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1450
+ source = Instance(SamplesGenerator)
1451
+
1452
+ # Filter coefficients
1547
1453
  sos = Property()
1548
1454
 
1549
1455
  def _get_sos(self):
@@ -1560,13 +1466,13 @@ class Filter(TimeInOut):
1560
1466
 
1561
1467
  Returns
1562
1468
  -------
1563
- Samples in blocks of shape (num, numchannels).
1469
+ Samples in blocks of shape (num, num_channels).
1564
1470
  Delivers the bandpass filtered output of source.
1565
1471
  The last block may be shorter than num.
1566
1472
 
1567
1473
  """
1568
1474
  sos = self.sos
1569
- zi = zeros((sos.shape[0], 2, self.source.numchannels))
1475
+ zi = zeros((sos.shape[0], 2, self.source.num_channels))
1570
1476
  for block in self.source.result(num):
1571
1477
  sos = self.sos # this line is useful in case of changes
1572
1478
  # to self.sos during generator lifetime
@@ -1577,19 +1483,19 @@ class Filter(TimeInOut):
1577
1483
  class FiltOctave(Filter):
1578
1484
  """Octave or third-octave filter (causal, non-zero phase delay)."""
1579
1485
 
1580
- #: Band center frequency; defaults to 1000.
1486
+ # Band center frequency; defaults to 1000.
1581
1487
  band = Float(1000.0, desc='band center frequency')
1582
1488
 
1583
- #: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1584
- fraction = Trait('Octave', {'Octave': 1, 'Third octave': 3}, desc='fraction of octave')
1489
+ # Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1490
+ fraction = Map({'Octave': 1, 'Third octave': 3}, default_value='Octave', desc='fraction of octave')
1585
1491
 
1586
- #: Filter order
1492
+ # Filter order
1587
1493
  order = Int(3, desc='IIR filter order')
1588
1494
 
1589
1495
  sos = Property(depends_on=['band', 'fraction', 'source.digest', 'order'])
1590
1496
 
1591
1497
  # internal identifier
1592
- digest = Property(depends_on=['source.digest', '__class__', 'band', 'fraction', 'order'])
1498
+ digest = Property(depends_on=['source.digest', 'band', 'fraction', 'order'])
1593
1499
 
1594
1500
  @cached_property
1595
1501
  def _get_digest(self):
@@ -1622,11 +1528,11 @@ class FiltFiltOctave(FiltOctave):
1622
1528
  It requires large amounts of memory!
1623
1529
  """
1624
1530
 
1625
- #: Filter order (applied for forward filter and backward filter)
1531
+ # Filter order (applied for forward filter and backward filter)
1626
1532
  order = Int(2, desc='IIR filter half order')
1627
1533
 
1628
1534
  # internal identifier
1629
- digest = Property(depends_on=['source.digest', '__class__', 'band', 'fraction', 'order'])
1535
+ digest = Property(depends_on=['source.digest', 'band', 'fraction', 'order'])
1630
1536
 
1631
1537
  @cached_property
1632
1538
  def _get_digest(self):
@@ -1664,20 +1570,20 @@ class FiltFiltOctave(FiltOctave):
1664
1570
 
1665
1571
  Returns
1666
1572
  -------
1667
- Samples in blocks of shape (num, numchannels).
1573
+ Samples in blocks of shape (num, num_channels).
1668
1574
  Delivers the zero-phase bandpass filtered output of source.
1669
1575
  The last block may be shorter than num.
1670
1576
 
1671
1577
  """
1672
1578
  sos = self.sos
1673
- data = empty((self.source.numsamples, self.source.numchannels))
1579
+ data = empty((self.source.num_samples, self.source.num_channels))
1674
1580
  j = 0
1675
1581
  for block in self.source.result(num):
1676
1582
  ns, nc = block.shape
1677
1583
  data[j : j + ns] = block
1678
1584
  j += ns
1679
1585
  # filter one channel at a time to save memory
1680
- for j in range(self.source.numchannels):
1586
+ for j in range(self.source.num_channels):
1681
1587
  data[:, j] = sosfiltfilt(sos, data[:, j])
1682
1588
  j = 0
1683
1589
  ns = data.shape[0]
@@ -1692,13 +1598,13 @@ class TimeExpAverage(Filter):
1692
1598
  I (non-standard) -> 35 ms.
1693
1599
  """
1694
1600
 
1695
- #: time weighting
1696
- weight = Trait('F', {'F': 0.125, 'S': 1.0, 'I': 0.035}, desc='time weighting')
1601
+ # time weighting
1602
+ weight = Map({'F': 0.125, 'S': 1.0, 'I': 0.035}, default_value='F', desc='time weighting')
1697
1603
 
1698
1604
  sos = Property(depends_on=['weight', 'source.digest'])
1699
1605
 
1700
1606
  # internal identifier
1701
- digest = Property(depends_on=['source.digest', '__class__', 'weight'])
1607
+ digest = Property(depends_on=['source.digest', 'weight'])
1702
1608
 
1703
1609
  @cached_property
1704
1610
  def _get_digest(self):
@@ -1713,15 +1619,15 @@ class TimeExpAverage(Filter):
1713
1619
 
1714
1620
 
1715
1621
  class FiltFreqWeight(Filter):
1716
- """Frequency weighting filter accoring to IEC 61672."""
1622
+ """Frequency weighting filter according to IEC 61672."""
1717
1623
 
1718
- #: weighting characteristics
1719
- weight = Trait('A', ('A', 'C', 'Z'), desc='frequency weighting')
1624
+ # weighting characteristics
1625
+ weight = Enum('A', 'C', 'Z', desc='frequency weighting')
1720
1626
 
1721
1627
  sos = Property(depends_on=['weight', 'source.digest'])
1722
1628
 
1723
1629
  # internal identifier
1724
- digest = Property(depends_on=['source.digest', '__class__', 'weight'])
1630
+ digest = Property(depends_on=['source.digest', 'weight'])
1725
1631
 
1726
1632
  @cached_property
1727
1633
  def _get_digest(self):
@@ -1751,36 +1657,43 @@ class FiltFreqWeight(Filter):
1751
1657
  return tf2sos(b, a)
1752
1658
 
1753
1659
 
1754
- class FilterBank(TimeInOut):
1660
+ @deprecated_alias({'numbands': 'num_bands'}, read_only=True)
1661
+ class FilterBank(TimeOut):
1755
1662
  """Abstract base class for IIR filter banks based on scipy lfilter
1756
1663
  implements a bank of parallel filters.
1757
1664
 
1758
- Should not be instanciated by itself
1665
+ Should not be instantiated by itself.
1759
1666
  """
1760
1667
 
1761
- #: List of filter coefficients for all filters
1668
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1669
+ source = Instance(SamplesGenerator)
1670
+
1671
+ # List of filter coefficients for all filters
1762
1672
  sos = Property()
1763
1673
 
1764
- #: List of labels for bands
1674
+ # List of labels for bands
1765
1675
  bands = Property()
1766
1676
 
1767
- #: Number of bands
1768
- numbands = Property()
1677
+ # Number of bands
1678
+ num_bands = Property()
1769
1679
 
1770
- #: Number of bands
1771
- numchannels = Property()
1680
+ # Number of bands
1681
+ num_channels = Property()
1772
1682
 
1683
+ @abstractmethod
1773
1684
  def _get_sos(self):
1774
- return [tf2sos([1], [1])]
1685
+ """Returns a list of second order section coefficients."""
1775
1686
 
1687
+ @abstractmethod
1776
1688
  def _get_bands(self):
1777
- return ['']
1689
+ """Returns a list of labels for the bands."""
1778
1690
 
1779
- def _get_numbands(self):
1780
- return 0
1691
+ @abstractmethod
1692
+ def _get_num_bands(self):
1693
+ """Returns the number of bands."""
1781
1694
 
1782
- def _get_numchannels(self):
1783
- return self.numbands * self.source.numchannels
1695
+ def _get_num_channels(self):
1696
+ return self.num_bands * self.source.num_channels
1784
1697
 
1785
1698
  def result(self, num):
1786
1699
  """Python generator that yields the output block-wise.
@@ -1793,16 +1706,16 @@ class FilterBank(TimeInOut):
1793
1706
 
1794
1707
  Returns
1795
1708
  -------
1796
- Samples in blocks of shape (num, numchannels).
1709
+ Samples in blocks of shape (num, num_channels).
1797
1710
  Delivers the bandpass filtered output of source.
1798
1711
  The last block may be shorter than num.
1799
1712
 
1800
1713
  """
1801
- numbands = self.numbands
1802
- snumch = self.source.numchannels
1714
+ numbands = self.num_bands
1715
+ snumch = self.source.num_channels
1803
1716
  sos = self.sos
1804
1717
  zi = [zeros((sos[0].shape[0], 2, snumch)) for _ in range(numbands)]
1805
- res = zeros((num, self.numchannels), dtype='float')
1718
+ res = zeros((num, self.num_channels), dtype='float')
1806
1719
  for block in self.source.result(num):
1807
1720
  for i in range(numbands):
1808
1721
  res[:, i * snumch : (i + 1) * snumch], zi[i] = sosfilt(sos[i], block, axis=0, zi=zi[i])
@@ -1812,26 +1725,26 @@ class FilterBank(TimeInOut):
1812
1725
  class OctaveFilterBank(FilterBank):
1813
1726
  """Octave or third-octave filter bank."""
1814
1727
 
1815
- #: Lowest band center frequency index; defaults to 21 (=125 Hz).
1728
+ # Lowest band center frequency index; defaults to 21 (=125 Hz).
1816
1729
  lband = Int(21, desc='lowest band center frequency index')
1817
1730
 
1818
- #: Lowest band center frequency index + 1; defaults to 40 (=8000 Hz).
1731
+ # Lowest band center frequency index + 1; defaults to 40 (=8000 Hz).
1819
1732
  hband = Int(40, desc='lowest band center frequency index')
1820
1733
 
1821
- #: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1822
- fraction = Trait('Octave', {'Octave': 1, 'Third octave': 3}, desc='fraction of octave')
1734
+ # Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1735
+ fraction = Map({'Octave': 1, 'Third octave': 3}, default_value='Octave', desc='fraction of octave')
1823
1736
 
1824
- #: List of filter coefficients for all filters
1737
+ # List of filter coefficients for all filters
1825
1738
  ba = Property(depends_on=['lband', 'hband', 'fraction', 'source.digest'])
1826
1739
 
1827
- #: List of labels for bands
1740
+ # List of labels for bands
1828
1741
  bands = Property(depends_on=['lband', 'hband', 'fraction'])
1829
1742
 
1830
- #: Number of bands
1831
- numbands = Property(depends_on=['lband', 'hband', 'fraction'])
1743
+ # Number of bands
1744
+ num_bands = Property(depends_on=['lband', 'hband', 'fraction'])
1832
1745
 
1833
1746
  # internal identifier
1834
- digest = Property(depends_on=['source.digest', '__class__', 'lband', 'hband', 'fraction', 'order'])
1747
+ digest = Property(depends_on=['source.digest', 'lband', 'hband', 'fraction', 'order'])
1835
1748
 
1836
1749
  @cached_property
1837
1750
  def _get_digest(self):
@@ -1842,7 +1755,7 @@ class OctaveFilterBank(FilterBank):
1842
1755
  return [10 ** (i / 10) for i in range(self.lband, self.hband, 4 - self.fraction_)]
1843
1756
 
1844
1757
  @cached_property
1845
- def _get_numbands(self):
1758
+ def _get_num_bands(self):
1846
1759
  return len(self.bands)
1847
1760
 
1848
1761
  @cached_property
@@ -1856,148 +1769,27 @@ class OctaveFilterBank(FilterBank):
1856
1769
  return sos
1857
1770
 
1858
1771
 
1859
- class TimeCache(TimeInOut):
1860
- """Caches time signal in cache file."""
1861
-
1862
- # basename for cache
1863
- basename = Property(depends_on='digest')
1864
-
1865
- # hdf5 cache file
1866
- h5f = Instance(H5CacheFileBase, transient=True)
1867
-
1868
- # internal identifier
1869
- digest = Property(depends_on=['source.digest', '__class__'])
1870
-
1871
- @cached_property
1872
- def _get_digest(self):
1873
- return digest(self)
1874
-
1875
- @cached_property
1876
- def _get_basename(self):
1877
- obj = self.source # start with source
1878
- basename = 'void' # if no file source is found
1879
- while obj:
1880
- if 'basename' in obj.all_trait_names(): # at original source?
1881
- basename = obj.basename # get the name
1882
- break
1883
- try:
1884
- obj = obj.source # traverse down until original data source
1885
- except AttributeError:
1886
- obj = None
1887
- return basename
1888
-
1889
- def _pass_data(self, num):
1890
- yield from self.source.result(num)
1891
-
1892
- def _write_data_to_cache(self, num):
1893
- nodename = 'tc_' + self.digest
1894
- self.h5f.create_extendable_array(nodename, (0, self.numchannels), 'float32')
1895
- ac = self.h5f.get_data_by_reference(nodename)
1896
- self.h5f.set_node_attribute(ac, 'sample_freq', self.sample_freq)
1897
- self.h5f.set_node_attribute(ac, 'complete', False)
1898
- for data in self.source.result(num):
1899
- self.h5f.append_data(ac, data)
1900
- yield data
1901
- self.h5f.set_node_attribute(ac, 'complete', True)
1902
-
1903
- def _get_data_from_cache(self, num):
1904
- nodename = 'tc_' + self.digest
1905
- ac = self.h5f.get_data_by_reference(nodename)
1906
- i = 0
1907
- while i < ac.shape[0]:
1908
- yield ac[i : i + num]
1909
- i += num
1910
-
1911
- def _get_data_from_incomplete_cache(self, num):
1912
- nodename = 'tc_' + self.digest
1913
- ac = self.h5f.get_data_by_reference(nodename)
1914
- i = 0
1915
- nblocks = 0
1916
- while i + num <= ac.shape[0]:
1917
- yield ac[i : i + num]
1918
- nblocks += 1
1919
- i += num
1920
- self.h5f.remove_data(nodename)
1921
- self.h5f.create_extendable_array(nodename, (0, self.numchannels), 'float32')
1922
- ac = self.h5f.get_data_by_reference(nodename)
1923
- self.h5f.set_node_attribute(ac, 'sample_freq', self.sample_freq)
1924
- self.h5f.set_node_attribute(ac, 'complete', False)
1925
- for j, data in enumerate(self.source.result(num)):
1926
- self.h5f.append_data(ac, data)
1927
- if j >= nblocks:
1928
- yield data
1929
- self.h5f.set_node_attribute(ac, 'complete', True)
1930
-
1931
- # result generator: delivers input, possibly from cache
1932
- def result(self, num):
1933
- """Python generator that yields the output from cache block-wise.
1934
-
1935
- Parameters
1936
- ----------
1937
- num : integer
1938
- This parameter defines the size of the blocks to be yielded
1939
- (i.e. the number of samples per block).
1940
-
1941
- Returns
1942
- -------
1943
- Samples in blocks of shape (num, numchannels).
1944
- The last block may be shorter than num.
1945
- Echos the source output, but reads it from cache
1946
- when available and prevents unnecassary recalculation.
1947
-
1948
- """
1949
- if config.global_caching == 'none':
1950
- generator = self._pass_data
1951
- else:
1952
- nodename = 'tc_' + self.digest
1953
- H5cache.get_cache_file(self, self.basename)
1954
- if not self.h5f:
1955
- generator = self._pass_data
1956
- elif self.h5f.is_cached(nodename):
1957
- generator = self._get_data_from_cache
1958
- if config.global_caching == 'overwrite':
1959
- self.h5f.remove_data(nodename)
1960
- generator = self._write_data_to_cache
1961
- elif not self.h5f.get_data_by_reference(nodename).attrs.__contains__('complete'):
1962
- if config.global_caching == 'readonly':
1963
- generator = self._pass_data
1964
- else:
1965
- generator = self._get_data_from_incomplete_cache
1966
- elif not self.h5f.get_data_by_reference(nodename).attrs['complete']:
1967
- if config.global_caching == 'readonly':
1968
- warn(
1969
- "Cache file is incomplete for nodename %s. With config.global_caching='readonly', the cache file will not be used!"
1970
- % str(nodename),
1971
- Warning,
1972
- stacklevel=1,
1973
- )
1974
- generator = self._pass_data
1975
- else:
1976
- generator = self._get_data_from_incomplete_cache
1977
- elif not self.h5f.is_cached(nodename):
1978
- generator = self._write_data_to_cache
1979
- if config.global_caching == 'readonly':
1980
- generator = self._pass_data
1981
- yield from generator(num)
1982
-
1983
-
1984
- class WriteWAV(TimeInOut):
1772
+ @deprecated_alias({'name': 'file'})
1773
+ class WriteWAV(TimeOut):
1985
1774
  """Saves time signal from one or more channels as mono/stereo/multi-channel
1986
1775
  `*.wav` file.
1987
1776
  """
1988
1777
 
1989
- #: Name of the file to be saved. If none is given, the name will be
1990
- #: automatically generated from the sources.
1991
- name = File(filter=['*.wav'], desc='name of wave file')
1778
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1779
+ source = Instance(SamplesGenerator)
1780
+
1781
+ # Name of the file to be saved. If none is given, the name will be
1782
+ # automatically generated from the sources.
1783
+ file = File(filter=['*.wav'], desc='name of wave file')
1992
1784
 
1993
- #: Basename for cache, readonly.
1994
- basename = Property(depends_on='digest')
1785
+ # Basename for cache, readonly.
1786
+ basename = Property(depends_on=['digest'])
1995
1787
 
1996
- #: Channel(s) to save. List can only contain one or two channels.
1997
- channels = ListInt(desc='channel to save')
1788
+ # Channel(s) to save. List can only contain one or two channels.
1789
+ channels = List(int, desc='channel to save')
1998
1790
 
1999
1791
  # internal identifier
2000
- digest = Property(depends_on=['source.digest', 'channels', '__class__'])
1792
+ digest = Property(depends_on=['source.digest', 'channels'])
2001
1793
 
2002
1794
  @cached_property
2003
1795
  def _get_digest(self):
@@ -2005,18 +1797,15 @@ class WriteWAV(TimeInOut):
2005
1797
 
2006
1798
  @cached_property
2007
1799
  def _get_basename(self):
2008
- obj = self.source # start with source
2009
- try:
2010
- while obj:
2011
- if 'basename' in obj.all_trait_names(): # at original source?
2012
- basename = obj.basename # get the name
2013
- break
2014
- obj = obj.source # traverse down until original data source
2015
- else:
2016
- basename = 'void'
2017
- except AttributeError:
2018
- basename = 'void' # if no file source is found
2019
- return basename
1800
+ warn(
1801
+ (
1802
+ f'The basename attribute of a {self.__class__.__name__} object is deprecated'
1803
+ ' and will be removed in a future release!'
1804
+ ),
1805
+ DeprecationWarning,
1806
+ stacklevel=2,
1807
+ )
1808
+ return find_basename(self.source)
2020
1809
 
2021
1810
  def save(self):
2022
1811
  """Saves source output to one- or multiple-channel `*.wav` file."""
@@ -2025,51 +1814,59 @@ class WriteWAV(TimeInOut):
2025
1814
  msg = 'No channels given for output.'
2026
1815
  raise ValueError(msg)
2027
1816
  if nc > 2:
2028
- warn('More than two channels given for output, exported file will have %i channels' % nc, stacklevel=1)
2029
- if self.name == '':
1817
+ warn(f'More than two channels given for output, exported file will have {nc:d} channels', stacklevel=1)
1818
+ if self.file == '':
2030
1819
  name = self.basename
2031
1820
  for nr in self.channels:
2032
- name += '_%i' % nr
1821
+ name += f'{nr:d}'
2033
1822
  name += '.wav'
2034
1823
  else:
2035
- name = self.name
2036
- wf = wave.open(name, 'w')
2037
- wf.setnchannels(nc)
2038
- wf.setsampwidth(2)
2039
- wf.setframerate(self.source.sample_freq)
2040
- wf.setnframes(self.source.numsamples)
2041
- mx = 0.0
2042
- ind = array(self.channels)
2043
- for data in self.source.result(1024):
2044
- mx = max(abs(data[:, ind]).max(), mx)
2045
- scale = 0.9 * 2**15 / mx
2046
- for data in self.source.result(1024):
2047
- wf.writeframesraw(array(data[:, ind] * scale, dtype=int16).tostring())
2048
- wf.close()
2049
-
2050
-
2051
- class WriteH5(TimeInOut):
1824
+ name = self.file
1825
+ with wave.open(name, 'w') as wf:
1826
+ wf.setnchannels(nc)
1827
+ wf.setsampwidth(2)
1828
+ wf.setframerate(self.source.sample_freq)
1829
+ wf.setnframes(self.source.num_samples)
1830
+ mx = 0.0
1831
+ ind = array(self.channels)
1832
+ for data in self.source.result(1024):
1833
+ mx = max(abs(data[:, ind]).max(), mx)
1834
+ scale = 0.9 * 2**15 / mx
1835
+ for data in self.source.result(1024):
1836
+ wf.writeframesraw(array(data[:, ind] * scale, dtype=int16).tostring())
1837
+
1838
+ def result(self, num):
1839
+ msg = 'result method not implemented yet! Data from source will be passed without transformation.'
1840
+ warn(msg, Warning, stacklevel=2)
1841
+ yield from self.source.result(num)
1842
+
1843
+
1844
+ @deprecated_alias({'name': 'file', 'numsamples_write': 'num_samples_write', 'writeflag': 'write_flag'})
1845
+ class WriteH5(TimeOut):
2052
1846
  """Saves time signal as `*.h5` file."""
2053
1847
 
2054
- #: Name of the file to be saved. If none is given, the name will be
2055
- #: automatically generated from a time stamp.
2056
- name = File(filter=['*.h5'], desc='name of data file')
1848
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1849
+ source = Instance(SamplesGenerator)
1850
+
1851
+ # Name of the file to be saved. If none is given, the name will be
1852
+ # automatically generated from a time stamp.
1853
+ file = File(filter=['*.h5'], desc='name of data file')
2057
1854
 
2058
- #: Number of samples to write to file by `result` method.
2059
- #: defaults to -1 (write as long as source yields data).
2060
- numsamples_write = Int(-1)
1855
+ # Number of samples to write to file by `result` method.
1856
+ # defaults to -1 (write as long as source yields data).
1857
+ num_samples_write = Int(-1)
2061
1858
 
2062
1859
  # flag that can be raised to stop file writing
2063
- writeflag = Bool(True)
1860
+ write_flag = Bool(True)
2064
1861
 
2065
1862
  # internal identifier
2066
- digest = Property(depends_on=['source.digest', '__class__'])
1863
+ digest = Property(depends_on=['source.digest'])
2067
1864
 
2068
- #: The floating-number-precision of entries of H5 File corresponding
2069
- #: to numpy dtypes. Default is 32 bit.
2070
- precision = Trait('float32', 'float64', desc='precision of H5 File')
1865
+ # The floating-number-precision of entries of H5 File corresponding
1866
+ # to numpy dtypes. Default is 32 bit.
1867
+ precision = Enum('float32', 'float64', desc='precision of H5 File')
2071
1868
 
2072
- #: Metadata to be stored in HDF5 file object
1869
+ # Metadata to be stored in HDF5 file object
2073
1870
  metadata = Dict(desc='metadata to be stored in .h5 file')
2074
1871
 
2075
1872
  @cached_property
@@ -2077,15 +1874,15 @@ class WriteH5(TimeInOut):
2077
1874
  return digest(self)
2078
1875
 
2079
1876
  def create_filename(self):
2080
- if self.name == '':
1877
+ if self.file == '':
2081
1878
  name = datetime.now(tz=timezone.utc).isoformat('_').replace(':', '-').replace('.', '_')
2082
- self.name = path.join(config.td_dir, name + '.h5')
1879
+ self.file = path.join(config.td_dir, name + '.h5')
2083
1880
 
2084
1881
  def get_initialized_file(self):
2085
1882
  file = _get_h5file_class()
2086
1883
  self.create_filename()
2087
- f5h = file(self.name, mode='w')
2088
- f5h.create_extendable_array('time_data', (0, self.numchannels), self.precision)
1884
+ f5h = file(self.file, mode='w')
1885
+ f5h.create_extendable_array('time_data', (0, self.num_channels), self.precision)
2089
1886
  ac = f5h.get_data_by_reference('time_data')
2090
1887
  f5h.set_node_attribute(ac, 'sample_freq', self.sample_freq)
2091
1888
  self.add_metadata(f5h)
@@ -2105,6 +1902,8 @@ class WriteH5(TimeInOut):
2105
1902
  if nitems > 0:
2106
1903
  f5h.create_new_group('metadata', '/')
2107
1904
  for key, value in self.metadata.items():
1905
+ if isinstance(value, str):
1906
+ value = array(value, dtype='S')
2108
1907
  f5h.create_array('/metadata', key, value)
2109
1908
 
2110
1909
  def result(self, num):
@@ -2120,19 +1919,19 @@ class WriteH5(TimeInOut):
2120
1919
 
2121
1920
  Returns
2122
1921
  -------
2123
- Samples in blocks of shape (num, numchannels).
1922
+ Samples in blocks of shape (num, num_channels).
2124
1923
  The last block may be shorter than num.
2125
1924
  Echos the source output, but reads it from cache
2126
- when available and prevents unnecassary recalculation.
1925
+ when available and prevents unnecessary recalculation.
2127
1926
 
2128
1927
  """
2129
- self.writeflag = True
1928
+ self.write_flag = True
2130
1929
  f5h = self.get_initialized_file()
2131
1930
  ac = f5h.get_data_by_reference('time_data')
2132
1931
  scount = 0
2133
- stotal = self.numsamples_write
1932
+ stotal = self.num_samples_write
2134
1933
  source_gen = self.source.result(num)
2135
- while self.writeflag:
1934
+ while self.write_flag:
2136
1935
  sleft = stotal - scount
2137
1936
  if stotal != -1 and sleft > 0:
2138
1937
  anz = min(num, sleft)
@@ -2145,166 +1944,24 @@ class WriteH5(TimeInOut):
2145
1944
  except StopIteration:
2146
1945
  break
2147
1946
  f5h.append_data(ac, data[:anz])
2148
- yield data
2149
1947
  f5h.flush()
1948
+ yield data
2150
1949
  scount += anz
2151
1950
  f5h.close()
2152
1951
 
2153
1952
 
2154
- class LockedGenerator:
2155
- """Creates a Thread Safe Iterator.
2156
- Takes an iterator/generator and makes it thread-safe by
2157
- serializing call to the `next` method of given iterator/generator.
2158
- """
2159
-
2160
- def __init__(self, it):
2161
- self.it = it
2162
- self.lock = threading.Lock()
1953
+ class TimeConvolve(TimeOut):
1954
+ """Fast frequency domain convolution with the Uniformly partitioned overlap-save method (UPOLS).
2163
1955
 
2164
- def __next__(self): # this function implementation is not python 2 compatible!
2165
- with self.lock:
2166
- return self.it.__next__()
2167
-
2168
-
2169
- class SampleSplitter(TimeInOut):
2170
- """Distributes data blocks from source to several following objects.
2171
- A separate block buffer is created for each registered object in
2172
- (:attr:`block_buffer`) .
1956
+ See :cite:`Wefers2015` for details.
2173
1957
  """
2174
1958
 
2175
- #: dictionary with block buffers (dict values) of registered objects (dict
2176
- #: keys).
2177
- block_buffer = Dict(key_trait=Instance(SamplesGenerator))
2178
-
2179
- #: max elements/blocks in block buffers.
2180
- buffer_size = Int(100)
2181
-
2182
- #: defines behaviour in case of block_buffer overflow. Can be set individually
2183
- #: for each registered object.
2184
- #:
2185
- #: * 'error': an IOError is thrown by the class
2186
- #: * 'warning': a warning is displayed. Possibly leads to lost blocks of data
2187
- #: * 'none': nothing happens. Possibly leads to lost blocks of data
2188
- buffer_overflow_treatment = Dict(
2189
- key_trait=Instance(SamplesGenerator),
2190
- value_trait=Trait('error', 'warning', 'none'),
2191
- desc='defines buffer overflow behaviour.',
2192
- )
2193
-
2194
- # shadow trait to monitor if source deliver samples or is empty
2195
- _source_generator_exist = Bool(False)
2196
-
2197
- # shadow trait to monitor if buffer of objects with overflow treatment = 'error'
2198
- # or warning is overfilled. Error will be raised in all threads.
2199
- _buffer_overflow = Bool(False)
2200
-
2201
- # Helper Trait holds source generator
2202
- _source_generator = Trait()
2203
-
2204
- def _create_block_buffer(self, obj):
2205
- self.block_buffer[obj] = deque([], maxlen=self.buffer_size)
2206
-
2207
- def _create_buffer_overflow_treatment(self, obj):
2208
- self.buffer_overflow_treatment[obj] = 'error'
2209
-
2210
- def _clear_block_buffer(self, obj):
2211
- self.block_buffer[obj].clear()
2212
-
2213
- def _remove_block_buffer(self, obj):
2214
- del self.block_buffer[obj]
2215
-
2216
- def _remove_buffer_overflow_treatment(self, obj):
2217
- del self.buffer_overflow_treatment[obj]
2218
-
2219
- def _assert_obj_registered(self, obj):
2220
- if obj not in self.block_buffer:
2221
- raise OSError('calling object %s is not registered.' % obj)
2222
-
2223
- def _get_objs_to_inspect(self):
2224
- return [obj for obj in self.buffer_overflow_treatment if self.buffer_overflow_treatment[obj] != 'none']
2225
-
2226
- def _inspect_buffer_levels(self, inspect_objs):
2227
- for obj in inspect_objs:
2228
- if len(self.block_buffer[obj]) == self.buffer_size:
2229
- if self.buffer_overflow_treatment[obj] == 'error':
2230
- self._buffer_overflow = True
2231
- elif self.buffer_overflow_treatment[obj] == 'warning':
2232
- warn('overfilled buffer for object: %s data will get lost' % obj, UserWarning, stacklevel=1)
2233
-
2234
- def _create_source_generator(self, num):
2235
- for obj in self.block_buffer:
2236
- self._clear_block_buffer(obj)
2237
- self._buffer_overflow = False # reset overflow bool
2238
- self._source_generator = LockedGenerator(self.source.result(num))
2239
- self._source_generator_exist = True # indicates full generator
2240
-
2241
- def _fill_block_buffers(self):
2242
- next_block = next(self._source_generator)
2243
- [self.block_buffer[obj].appendleft(next_block) for obj in self.block_buffer]
2244
-
2245
- @on_trait_change('buffer_size')
2246
- def _change_buffer_size(self): #
2247
- for obj in self.block_buffer:
2248
- self._remove_block_buffer(obj)
2249
- self._create_block_buffer(obj)
2250
-
2251
- def register_object(self, *objects_to_register):
2252
- """Function that can be used to register objects that receive blocks from this class."""
2253
- for obj in objects_to_register:
2254
- if obj not in self.block_buffer:
2255
- self._create_block_buffer(obj)
2256
- self._create_buffer_overflow_treatment(obj)
2257
-
2258
- def remove_object(self, *objects_to_remove):
2259
- """Function that can be used to remove registered objects."""
2260
- for obj in objects_to_remove:
2261
- self._remove_block_buffer(obj)
2262
- self._remove_buffer_overflow_treatment(obj)
2263
-
2264
- def result(self, num):
2265
- """Python generator that yields the output block-wise from block-buffer.
2266
-
2267
- Parameters
2268
- ----------
2269
- num : integer
2270
- This parameter defines the size of the blocks to be yielded
2271
- (i.e. the number of samples per block).
2272
-
2273
- Returns
2274
- -------
2275
- Samples in blocks of shape (num, numchannels).
2276
- Delivers a block of samples to the calling object.
2277
- The last block may be shorter than num.
2278
-
2279
- """
2280
- calling_obj = currentframe().f_back.f_locals['self']
2281
- self._assert_obj_registered(calling_obj)
2282
- objs_to_inspect = self._get_objs_to_inspect()
2283
-
2284
- if not self._source_generator_exist:
2285
- self._create_source_generator(num)
2286
-
2287
- while not self._buffer_overflow:
2288
- if self.block_buffer[calling_obj]:
2289
- yield self.block_buffer[calling_obj].pop()
2290
- else:
2291
- self._inspect_buffer_levels(objs_to_inspect)
2292
- try:
2293
- self._fill_block_buffers()
2294
- except StopIteration:
2295
- self._source_generator_exist = False
2296
- return
2297
- else:
2298
- msg = 'Maximum size of block buffer is reached!'
2299
- raise OSError(msg)
2300
-
2301
-
2302
- class TimeConvolve(TimeInOut):
2303
- """Uniformly partitioned overlap-save method (UPOLS) for fast convolution in the frequency domain, see :ref:`Wefers, 2015<Wefers2015>`."""
1959
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1960
+ source = Instance(SamplesGenerator)
2304
1961
 
2305
- #: Convolution kernel in the time domain.
2306
- #: The second dimension of the kernel array has to be either 1 or match :attr:`~SamplesGenerator.numchannels`.
2307
- #: If only a single kernel is supplied, it is applied to all channels.
1962
+ # Convolution kernel in the time domain. The second dimension of the kernel array has to be
1963
+ # either 1 or match :attr:`~SamplesGenerator.num_channels`. If only a single kernel is supplied,
1964
+ # it is applied to all channels.
2308
1965
  kernel = CArray(dtype=float, desc='Convolution kernel.')
2309
1966
 
2310
1967
  _block_size = Int(desc='Block size')
@@ -2315,7 +1972,7 @@ class TimeConvolve(TimeInOut):
2315
1972
  )
2316
1973
 
2317
1974
  # internal identifier
2318
- digest = Property(depends_on=['source.digest', 'kernel', '__class__'])
1975
+ digest = Property(depends_on=['source.digest', 'kernel'])
2319
1976
 
2320
1977
  @cached_property
2321
1978
  def _get_digest(self):
@@ -2330,9 +1987,9 @@ class TimeConvolve(TimeInOut):
2330
1987
  if self.kernel.ndim > 2:
2331
1988
  msg = 'Only one or two dimensional kernels accepted.'
2332
1989
  raise ValueError(msg)
2333
- # check if number of kernels matches numchannels
2334
- if self.kernel.shape[1] not in (1, self.source.numchannels):
2335
- msg = 'Number of kernels must be either `numchannels` or one.'
1990
+ # check if number of kernels matches num_channels
1991
+ if self.kernel.shape[1] not in (1, self.source.num_channels):
1992
+ msg = 'Number of kernels must be either `num_channels` or one.'
2336
1993
  raise ValueError(msg)
2337
1994
 
2338
1995
  # compute the rfft of the kernel blockwise
@@ -2366,7 +2023,7 @@ class TimeConvolve(TimeInOut):
2366
2023
 
2367
2024
  Returns
2368
2025
  -------
2369
- Samples in blocks of shape (num, numchannels).
2026
+ Samples in blocks of shape (num, num_channels).
2370
2027
  The last block may be shorter than num.
2371
2028
 
2372
2029
  """
@@ -2374,8 +2031,8 @@ class TimeConvolve(TimeInOut):
2374
2031
  # initialize variables
2375
2032
  self._block_size = num
2376
2033
  L = self.kernel.shape[0]
2377
- N = self.source.numchannels
2378
- M = self.source.numsamples
2034
+ N = self.source.num_channels
2035
+ M = self.source.num_samples
2379
2036
  numblocks_kernel = int(ceil(L / num)) # number of kernel blocks
2380
2037
  Q = int(ceil(M / num)) # number of signal blocks
2381
2038
  R = int(ceil((L + M - 1) / num)) # number of output blocks
@@ -2425,13 +2082,13 @@ class TimeConvolve(TimeInOut):
2425
2082
 
2426
2083
 
2427
2084
  @nb.jit(nopython=True, cache=True)
2428
- def _append_to_fdl(fdl, idx, numblocks_kernel, buff):
2085
+ def _append_to_fdl(fdl, idx, numblocks_kernel, buff): # pragma: no cover
2429
2086
  fdl[idx] = buff
2430
2087
  idx = int(idx + 1 % numblocks_kernel)
2431
2088
 
2432
2089
 
2433
2090
  @nb.jit(nopython=True, cache=True)
2434
- def _spectral_sum(out, fdl, kb):
2091
+ def _spectral_sum(out, fdl, kb): # pragma: no cover
2435
2092
  P, B, N = kb.shape
2436
2093
  for n in range(N):
2437
2094
  for b in range(B):
@@ -2440,3 +2097,21 @@ def _spectral_sum(out, fdl, kb):
2440
2097
  out[b, n] += fdl[i, b, n] * kb[i, b, n]
2441
2098
 
2442
2099
  return out
2100
+
2101
+
2102
+ class MaskedTimeInOut(MaskedTimeOut):
2103
+ """Signal processing block for channel and sample selection.
2104
+
2105
+ .. deprecated:: 24.10
2106
+ Using :class:`~acoular.tprocess.MaskedTimeInOut` is deprecated and will be removed in
2107
+ Acoular version 25.07. Use :class:`~acoular.tprocess.MaskedTimeOut` instead.
2108
+ """
2109
+
2110
+ def __init__(self, *args, **kwargs):
2111
+ super().__init__(*args, **kwargs)
2112
+ warn(
2113
+ 'Using MaskedTimeInOut is deprecated and will be removed in Acoular version 25.07. \
2114
+ Use class MaskedTimeOut instead.',
2115
+ DeprecationWarning,
2116
+ stacklevel=2,
2117
+ )