acoular 24.10__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
@@ -32,6 +32,7 @@
32
32
 
33
33
  # imports from other packages
34
34
  import wave
35
+ from abc import abstractmethod
35
36
  from datetime import datetime, timezone
36
37
  from os import path
37
38
  from warnings import warn
@@ -70,7 +71,7 @@ from numpy import (
70
71
  split,
71
72
  sqrt,
72
73
  stack,
73
- sum,
74
+ sum, # noqa: A004
74
75
  tile,
75
76
  unique,
76
77
  zeros,
@@ -83,19 +84,20 @@ from scipy.spatial import Delaunay
83
84
  from traits.api import (
84
85
  Bool,
85
86
  CArray,
86
- CLong,
87
+ CInt,
87
88
  Constant,
88
89
  Delegate,
89
90
  Dict,
91
+ Enum,
90
92
  File,
91
93
  Float,
92
94
  Instance,
93
95
  Int,
94
96
  List,
95
- ListInt,
97
+ Map,
96
98
  Property,
97
99
  Str,
98
- Trait,
100
+ Union,
99
101
  cached_property,
100
102
  observe,
101
103
  on_trait_change,
@@ -104,12 +106,15 @@ from traits.api import (
104
106
  # acoular imports
105
107
  from .base import SamplesGenerator, TimeOut
106
108
  from .configuration import config
109
+ from .deprecation import deprecated_alias
107
110
  from .environments import cartToCyl, cylToCart
108
111
  from .h5files import _get_h5file_class
109
112
  from .internal import digest, ldigest
110
113
  from .microphones import MicGeom
114
+ from .tools.utils import find_basename
111
115
 
112
116
 
117
+ @deprecated_alias({'numchannels_total': 'num_channels_total', 'numsamples_total': 'num_samples_total'})
113
118
  class MaskedTimeOut(TimeOut):
114
119
  """Signal processing block for channel and sample selection.
115
120
 
@@ -120,35 +125,39 @@ class MaskedTimeOut(TimeOut):
120
125
  and generates output via the generator :meth:`result`.
121
126
  """
122
127
 
123
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
128
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
124
129
  source = Instance(SamplesGenerator)
125
130
 
126
- #: Index of the first sample to be considered valid.
127
- start = CLong(0, desc='start of valid samples')
131
+ # Index of the first sample to be considered valid.
132
+ start = CInt(0, desc='start of valid samples')
128
133
 
129
- #: Index of the last sample to be considered valid.
130
- 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')
131
136
 
132
- #: Channels that are to be treated as invalid.
133
- 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')
134
139
 
135
- #: Channel mask to serve as an index for all valid channels, is set automatically.
136
- 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')
137
142
 
138
- #: Number of channels in input, as given by :attr:`~acoular.base.TimeOut.source`.
139
- 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')
140
145
 
141
- #: Number of samples in input, as given by :attr:`~acoular.base.TimeOut.source`.
142
- 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')
143
148
 
144
- #: Number of valid channels, is set automatically.
145
- 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
+ )
146
153
 
147
- #: Number of valid time samples, is set automatically.
148
- 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
+ )
149
158
 
150
- #: Name of the cache file without extension, readonly.
151
- 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')
152
161
 
153
162
  # internal identifier
154
163
  digest = Property(depends_on=['source.digest', 'start', 'stop', 'invalid_channels'])
@@ -159,26 +168,32 @@ class MaskedTimeOut(TimeOut):
159
168
 
160
169
  @cached_property
161
170
  def _get_basename(self):
162
- if 'basename' in self.source.all_trait_names():
163
- return self.source.basename
164
- 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)
165
180
 
166
181
  @cached_property
167
182
  def _get_channels(self):
168
183
  if len(self.invalid_channels) == 0:
169
184
  return slice(0, None, None)
170
- 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]
171
186
  return array(allr)
172
187
 
173
188
  @cached_property
174
- def _get_numchannels(self):
189
+ def _get_num_channels(self):
175
190
  if len(self.invalid_channels) == 0:
176
- return self.numchannels_total
191
+ return self.num_channels_total
177
192
  return len(self.channels)
178
193
 
179
194
  @cached_property
180
- def _get_numsamples(self):
181
- 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)
182
197
  return sli[1] - sli[0]
183
198
 
184
199
  def result(self, num):
@@ -192,22 +207,22 @@ class MaskedTimeOut(TimeOut):
192
207
 
193
208
  Returns
194
209
  -------
195
- Samples in blocks of shape (num, :attr:`numchannels`).
210
+ Samples in blocks of shape (num, :attr:`num_channels`).
196
211
  The last block may be shorter than num.
197
212
 
198
213
  """
199
- sli = slice(self.start, self.stop).indices(self.numsamples_total)
214
+ sli = slice(self.start, self.stop).indices(self.num_samples_total)
200
215
  start = sli[0]
201
216
  stop = sli[1]
202
217
  if start >= stop:
203
218
  msg = 'no samples available'
204
219
  raise OSError(msg)
205
220
 
206
- if start != 0 or stop != self.numsamples_total:
221
+ if start != 0 or stop != self.num_samples_total:
207
222
  offset = -start % num
208
223
  if offset == 0:
209
224
  offset = num
210
- buf = empty((num + offset, self.numchannels), dtype=float)
225
+ buf = empty((num + offset, self.num_channels), dtype=float)
211
226
  bsize = 0
212
227
  i = 0
213
228
  fblock = True
@@ -248,14 +263,14 @@ class ChannelMixer(TimeOut):
248
263
  Outputs a single channel.
249
264
  """
250
265
 
251
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
266
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
252
267
  source = Instance(SamplesGenerator)
253
268
 
254
- #: Amplitude weight(s) for the channels as array. If not set, all channels are equally weighted.
269
+ # Amplitude weight(s) for the channels as array. If not set, all channels are equally weighted.
255
270
  weights = CArray(desc='channel weights')
256
271
 
257
272
  # Number of channels is always one here.
258
- numchannels = Constant(1)
273
+ num_channels = Constant(1)
259
274
 
260
275
  # internal identifier
261
276
  digest = Property(depends_on=['source.digest', 'weights'])
@@ -280,10 +295,10 @@ class ChannelMixer(TimeOut):
280
295
 
281
296
  """
282
297
  if self.weights.size:
283
- if self.weights.shape in {(self.source.numchannels,), (1,)}:
298
+ if self.weights.shape in {(self.source.num_channels,), (1,)}:
284
299
  weights = self.weights
285
300
  else:
286
- 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,)}'
287
302
  raise ValueError(msg)
288
303
  else:
289
304
  weights = 1
@@ -292,7 +307,7 @@ class ChannelMixer(TimeOut):
292
307
  yield sum(weights * block, 1, keepdims=True)
293
308
 
294
309
 
295
- class Trigger(TimeOut):
310
+ class Trigger(TimeOut): # pragma: no cover
296
311
  """Class for identifying trigger signals.
297
312
  Gets samples from :attr:`source` and stores the trigger samples in :meth:`trigger_data`.
298
313
 
@@ -306,59 +321,56 @@ class Trigger(TimeOut):
306
321
  vary too much.
307
322
  """
308
323
 
309
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
324
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
310
325
  source = Instance(SamplesGenerator)
311
326
 
312
- #: Threshold of trigger. Has different meanings for different
313
- #: :attr:`~acoular.tprocess.Trigger.trigger_type`. The sign is relevant.
314
- #: If a sample of the signal is above/below the positive/negative threshold,
315
- #: it is assumed to be a peak.
316
- #: Default is None, in which case a first estimate is used: The threshold
317
- #: is assumed to be 75% of the max/min difference between all extremums and the
318
- #: mean value of the trigger signal. E.g: the mean value is 0 and there are positive
319
- #: extremums at 400 and negative extremums at -800. Then the estimated threshold would be
320
- #: 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.
321
336
  threshold = Float(None)
322
337
 
323
- #: Maximum allowable variation of length of each revolution duration. Default is
324
- #: 2%. A warning is thrown, if any revolution length surpasses this value:
325
- #: 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
326
341
  max_variation_of_duration = Float(0.02)
327
342
 
328
- #: Defines the length of hunks via lenHunk = hunk_length * maxOncePerRevDuration.
329
- #: If there are multiple peaks within lenHunk, then the algorithm will
330
- #: cancel all but one out (see :attr:`~acoular.tprocess.Trigger.multiple_peaks_in_hunk`).
331
- #: 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.
332
347
  hunk_length = Float(0.1)
333
348
 
334
- #: Type of trigger.
335
- #:
336
- #: 'dirac': a single puls is assumed (sign of
337
- #: :attr:`~acoular.tprocess.Trigger.trigger_type` is important).
338
- #: Sample will trigger if its value is above/below the pos/neg threshold.
339
- #:
340
- #: 'rect' : repeating rectangular functions. Only every second
341
- #: edge is assumed to be a trigger. The sign of
342
- #: :attr:`~acoular.tprocess.Trigger.trigger_type` gives information
343
- #: on which edge should be used (+ for rising edge, - for falling edge).
344
- #: Sample will trigger if the difference between its value and its predecessors value
345
- #: is above/below the pos/neg threshold.
346
- #:
347
- #: Default is 'dirac'.
348
- trigger_type = Trait('dirac', 'rect')
349
-
350
- #: Identifier which peak to consider, if there are multiple peaks in one hunk
351
- #: (see :attr:`~acoular.tprocess.Trigger.hunk_length`). Default is to 'extremum',
352
- #: in which case the extremal peak (maximum if threshold > 0, minimum if threshold < 0) is considered.
353
- multiple_peaks_in_hunk = Trait('extremum', 'first')
354
-
355
- #: Tuple consisting of 3 entries:
356
- #:
357
- #: 1.: -Vector with the sample indices of the 1/Rev trigger samples
358
- #:
359
- #: 2.: -maximum of number of samples between adjacent trigger samples
360
- #:
361
- #: 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
362
374
  trigger_data = Property(
363
375
  depends_on=[
364
376
  'source.digest',
@@ -436,8 +448,8 @@ class Trigger(TimeOut):
436
448
  faultyInd = flatnonzero(diffDist > self.max_variation_of_duration * meanDist)
437
449
  if faultyInd.size != 0:
438
450
  warn(
439
- 'In Trigger-Identification: The distances between the peaks (and therefor the lengths of the revolutions) vary too much (check samples %s).'
440
- % 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}).',
441
453
  Warning,
442
454
  stacklevel=2,
443
455
  )
@@ -450,7 +462,8 @@ class Trigger(TimeOut):
450
462
  def _trigger_rect(self, x0, x, threshold):
451
463
  # x0 stores the last value of the the last generator cycle
452
464
  xNew = append(x0, x)
453
- # 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
454
467
  return self._trigger_value_comp(xNew[1:] - xNew[:-1], threshold)
455
468
 
456
469
  def _trigger_value_comp(self, trigger_data, threshold):
@@ -474,27 +487,33 @@ class Trigger(TimeOut):
474
487
  maxTriggerHelp = [minVal, maxVal] - meanVal
475
488
  argInd = argmax(abs(maxTriggerHelp))
476
489
  thresh = maxTriggerHelp[argInd] * 0.75 # 0.75 for 75% of max trigger signal
477
- 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)
478
491
  else: # take user defined threshold
479
492
  thresh = self.threshold
480
493
  return thresh
481
494
 
482
495
  def _check_trigger_existence(self):
483
- nChannels = self.source.numchannels
484
- if not nChannels == 1:
485
- 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)
486
500
  return 0
487
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
+
488
507
 
489
508
  class AngleTracker(MaskedTimeOut):
490
509
  """Calculates rotation angle and rpm per sample from a trigger signal
491
510
  using spline interpolation in the time domain.
492
511
 
493
- Gets samples from :attr:`trigger` and stores the angle and rpm samples in :meth:`angle` and :meth:`rpm`.
494
-
512
+ Gets samples from :attr:`trigger` and stores the angle and rpm samples in :meth:`angle` and
513
+ :meth:`rpm`.
495
514
  """
496
515
 
497
- #: Trigger data from :class:`acoular.tprocess.Trigger`.
516
+ # Trigger data from :class:`acoular.tprocess.Trigger`.
498
517
  trigger = Instance(Trigger)
499
518
 
500
519
  # internal identifier
@@ -509,29 +528,29 @@ class AngleTracker(MaskedTimeOut):
509
528
  ],
510
529
  )
511
530
 
512
- #: Trigger signals per revolution,
513
- #: defaults to 1.
531
+ # Trigger signals per revolution,
532
+ # defaults to 1.
514
533
  trigger_per_revo = Int(1, desc='trigger signals per revolution')
515
534
 
516
- #: Flag to set counter-clockwise (1) or clockwise (-1) rotation,
517
- #: defaults to -1.
535
+ # Flag to set counter-clockwise (1) or clockwise (-1) rotation,
536
+ # defaults to -1.
518
537
  rot_direction = Int(-1, desc='mathematical direction of rotation')
519
538
 
520
- #: Points of interpolation used for spline,
521
- #: defaults to 4.
539
+ # Points of interpolation used for spline,
540
+ # defaults to 4.
522
541
  interp_points = Int(4, desc='Points of interpolation used for spline')
523
542
 
524
- #: rotation angle in radians for first trigger position
543
+ # rotation angle in radians for first trigger position
525
544
  start_angle = Float(0, desc='rotation angle for trigger position')
526
545
 
527
- #: revolutions per minute for each sample, read-only
528
- 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')
529
548
 
530
- #: average revolutions per minute, read-only
531
- 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')
532
551
 
533
- #: rotation angle in radians for each sample, read-only
534
- 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')
535
554
 
536
555
  # Internal flag to determine whether rpm and angle calculation has been processed,
537
556
  # prevents recalculation
@@ -562,17 +581,17 @@ class AngleTracker(MaskedTimeOut):
562
581
  # init
563
582
  ind = 0
564
583
  # trigger data
565
- peakloc, maxdist, mindist = self.trigger.trigger_data()
584
+ peakloc, maxdist, mindist = self.trigger.trigger_data
566
585
  TriggerPerRevo = self.trigger_per_revo
567
586
  rotDirection = self.rot_direction
568
- num = self.source.numsamples
587
+ num = self.source.num_samples
569
588
  samplerate = self.source.sample_freq
570
589
  self._rpm = zeros(num)
571
590
  self._angle = zeros(num)
572
591
  # number of spline points
573
592
  InterpPoints = self.interp_points
574
593
 
575
- # loop over alle timesamples
594
+ # loop over all timesamples
576
595
  while ind < num:
577
596
  # when starting spline forward
578
597
  if ind < peakloc[InterpPoints]:
@@ -636,24 +655,24 @@ class AngleTracker(MaskedTimeOut):
636
655
 
637
656
  """
638
657
  # trigger indices data
639
- peakloc = self.trigger.trigger_data()[0]
658
+ peakloc = self.trigger.trigger_data[0]
640
659
  # calculation of average rpm in 1/min
641
660
  return (len(peakloc) - 1) / (peakloc[-1] - peakloc[0]) / self.trigger_per_revo * self.source.sample_freq * 60
642
661
 
643
662
 
644
- class SpatialInterpolator(TimeOut):
663
+ class SpatialInterpolator(TimeOut): # pragma: no cover
645
664
  """Base class for spatial interpolation of microphone data.
646
665
  Gets samples from :attr:`source` and generates output via the
647
666
  generator :meth:`result`.
648
667
  """
649
668
 
650
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
669
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
651
670
  source = Instance(SamplesGenerator)
652
671
 
653
- #: :class:`~acoular.microphones.MicGeom` object that provides the real microphone locations.
672
+ # :class:`~acoular.microphones.MicGeom` object that provides the real microphone locations.
654
673
  mics = Instance(MicGeom(), desc='microphone geometry')
655
674
 
656
- #: :class:`~acoular.microphones.MicGeom` object that provides the virtual microphone locations.
675
+ # :class:`~acoular.microphones.MicGeom` object that provides the virtual microphone locations.
657
676
  mics_virtual = Property(desc='microphone geometry')
658
677
 
659
678
  _mics_virtual = Instance(MicGeom, desc='internal microphone geometry;internal usage, read only')
@@ -666,12 +685,12 @@ class SpatialInterpolator(TimeOut):
666
685
  def _set_mics_virtual(self, mics_virtual):
667
686
  self._mics_virtual = mics_virtual
668
687
 
669
- #: Interpolation method in spacial domain, defaults to linear
670
- #: linear uses numpy linear interpolation
671
- #: spline uses scipy CloughTocher algorithm
672
- #: rbf is scipy radial basis function with multiquadric, cubic and sinc functions
673
- #: idw refers to the inverse distance weighting algorithm
674
- 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(
675
694
  'linear',
676
695
  'spline',
677
696
  'rbf-multiquadric',
@@ -682,42 +701,41 @@ class SpatialInterpolator(TimeOut):
682
701
  desc='method for interpolation used',
683
702
  )
684
703
 
685
- #: spacial dimensionality of the array geometry
686
- 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')
687
706
 
688
- #: Sampling frequency of output signal, as given by :attr:`source`.
707
+ # Sampling frequency of output signal, as given by :attr:`source`.
689
708
  sample_freq = Delegate('source', 'sample_freq')
690
709
 
691
- #: Number of channels in output.
692
- numchannels = Property()
710
+ # Number of channels in output.
711
+ num_channels = Property()
693
712
 
694
- #: Number of samples in output, as given by :attr:`source`.
695
- numsamples = Delegate('source', 'numsamples')
713
+ # Number of samples in output, as given by :attr:`source`.
714
+ num_samples = Delegate('source', 'num_samples')
696
715
 
697
- #:Interpolate a point at the origin of the Array geometry
716
+ # Interpolate a point at the origin of the Array geometry
698
717
  interp_at_zero = Bool(False)
699
718
 
700
- #: The rotation must be around the z-axis, which means from x to y axis.
701
- #: If the coordinates are not build like that, than this 3x3 orthogonal
702
- #: transformation matrix Q can be used to modify the coordinates.
703
- #: It is assumed that with the modified coordinates the rotation is around the z-axis.
704
- #: 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).
705
724
  Q = CArray(dtype=float64, shape=(3, 3), value=identity(3))
706
725
 
707
- 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
708
727
 
709
- p_weight = Trait(
728
+ p_weight = Float(
710
729
  2,
711
- dtype=float,
712
730
  desc='used in interpolation for virtual microphone, weighting power exponent for IDW',
713
731
  )
714
732
 
715
- #: Stores the output of :meth:`_virtNewCoord_func`; Read-Only
733
+ # Stores the output of :meth:`_virtNewCoord_func`; Read-Only
716
734
  _virtNewCoord_func = Property( # noqa: N815
717
735
  depends_on=['mics.digest', 'mics_virtual.digest', 'method', 'array_dimension', 'interp_at_zero'],
718
736
  )
719
737
 
720
- #: internal identifier
738
+ # internal identifier
721
739
  digest = Property(
722
740
  depends_on=[
723
741
  'mics.digest',
@@ -730,7 +748,7 @@ class SpatialInterpolator(TimeOut):
730
748
  ],
731
749
  )
732
750
 
733
- def _get_numchannels(self):
751
+ def _get_num_channels(self):
734
752
  return self.mics_virtual.num_mics
735
753
 
736
754
  @cached_property
@@ -762,26 +780,30 @@ class SpatialInterpolator(TimeOut):
762
780
  Returns
763
781
  -------
764
782
  mesh : List[]
765
- 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.
766
784
  If the Array is 1D the list items are:
767
785
  1. item : float64[nMicsInSpecificSubarray]
768
- 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.
769
788
  2. item : int64[nMicsInArray]
770
- Indices identifying how the measured pressures must be evaluated, s.t. the entries of the previous item (see last line)
771
- 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.
772
792
  If the Array is 2D or 3d the list items are:
773
793
  1. item : Delaunay mesh object
774
- Delauney mesh (see scipy.spatial.Delaunay) for the specific Array
794
+ Delaunay mesh (see scipy.spatial.Delaunay) for the specific Array
775
795
  2. item : int64[nMicsInArray]
776
- same as 1d case, BUT with the difference, that here the rotational periodicy is handled, when constructing the mesh.
777
- 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.
778
799
 
779
800
  virtNewCoord : float64[3, nVirtualMics]
780
- 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].
781
803
 
782
804
  newCoord : float64[3, nMics]
783
- Projection of each mic onto its new coordinates. The columns of newCoordinates correspond to [phi, rho, z]
784
-
805
+ Projection of each mic onto its new coordinates. The columns of newCoordinates
806
+ correspond to [phi, rho, z].
785
807
  """
786
808
  # init positions of virtual mics in cyl coordinates
787
809
  nVirtMics = mpos_virt.shape[1]
@@ -942,7 +964,7 @@ class SpatialInterpolator(TimeOut):
942
964
  # if no rotation given
943
965
  else:
944
966
  xInterp = tile(virtNewCoord[0, :], (nTime, 1))
945
- # get ordered microphone posions in radiant
967
+ # get ordered microphone positions in radiant
946
968
  x = newCoord[0]
947
969
  for cntTime in range(nTime):
948
970
  if self.method == 'linear':
@@ -1054,7 +1076,7 @@ class SpatialInterpolator(TimeOut):
1054
1076
  # using inverse distance weighting
1055
1077
  elif self.method == 'IDW':
1056
1078
  newPoint2_M = newPoint.T
1057
- newPoint3_M = append(newPoint2_M, zeros([1, self.numchannels]), axis=0)
1079
+ newPoint3_M = append(newPoint2_M, zeros([1, self.num_channels]), axis=0)
1058
1080
  newPointCart = cylToCart(newPoint3_M)
1059
1081
  for ind in arange(len(newPoint[:, 0])):
1060
1082
  newPoint_Rep = tile(newPointCart[:, ind], (len(newPoint[:, 0]), 1)).T
@@ -1127,17 +1149,22 @@ class SpatialInterpolator(TimeOut):
1127
1149
  # return interpolated pressure values
1128
1150
  return pInterp
1129
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)
1130
1156
 
1131
- class SpatialInterpolatorRotation(SpatialInterpolator):
1157
+
1158
+ class SpatialInterpolatorRotation(SpatialInterpolator): # pragma: no cover
1132
1159
  """Spatial Interpolation for rotating sources. Gets samples from :attr:`source`
1133
1160
  and angles from :attr:`AngleTracker`.Generates output via the generator :meth:`result`.
1134
1161
 
1135
1162
  """
1136
1163
 
1137
- #: Angle data from AngleTracker class
1164
+ # Angle data from AngleTracker class
1138
1165
  angle_source = Instance(AngleTracker)
1139
1166
 
1140
- #: Internal identifier
1167
+ # Internal identifier
1141
1168
  digest = Property(
1142
1169
  depends_on=[
1143
1170
  'source.digest',
@@ -1166,7 +1193,7 @@ class SpatialInterpolatorRotation(SpatialInterpolator):
1166
1193
 
1167
1194
  Returns
1168
1195
  -------
1169
- Samples in blocks of shape (num, :attr:`numchannels`).
1196
+ Samples in blocks of shape (num, :attr:`num_channels`).
1170
1197
  The last block may be shorter than num.
1171
1198
 
1172
1199
  """
@@ -1183,14 +1210,14 @@ class SpatialInterpolatorRotation(SpatialInterpolator):
1183
1210
  count += num
1184
1211
 
1185
1212
 
1186
- class SpatialInterpolatorConstantRotation(SpatialInterpolator):
1213
+ class SpatialInterpolatorConstantRotation(SpatialInterpolator): # pragma: no cover
1187
1214
  """Spatial linear Interpolation for constantly rotating sources.
1188
1215
  Gets samples from :attr:`source` and generates output via the
1189
1216
  generator :meth:`result`.
1190
1217
  """
1191
1218
 
1192
- #: Rotational speed in rps. Positive, if rotation is around positive z-axis sense,
1193
- #: 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.
1194
1221
  rotational_speed = Float(0.0)
1195
1222
 
1196
1223
  # internal identifier
@@ -1222,7 +1249,7 @@ class SpatialInterpolatorConstantRotation(SpatialInterpolator):
1222
1249
 
1223
1250
  Returns
1224
1251
  -------
1225
- Samples in blocks of shape (num, :attr:`numchannels`).
1252
+ Samples in blocks of shape (num, :attr:`num_channels`).
1226
1253
  The last block may be shorter than num.
1227
1254
 
1228
1255
  """
@@ -1240,21 +1267,21 @@ class SpatialInterpolatorConstantRotation(SpatialInterpolator):
1240
1267
  class Mixer(TimeOut):
1241
1268
  """Mixes the signals from several sources."""
1242
1269
 
1243
- #: Data source; :class:`~acoular.base.SamplesGenerator` object.
1244
- source = Trait(SamplesGenerator)
1270
+ # Data source; :class:`~acoular.base.SamplesGenerator` object.
1271
+ source = Instance(SamplesGenerator)
1245
1272
 
1246
- #: List of additional :class:`~acoular.base.SamplesGenerator` objects
1247
- #: to be mixed.
1273
+ # List of additional :class:`~acoular.base.SamplesGenerator` objects
1274
+ # to be mixed.
1248
1275
  sources = List(Instance(SamplesGenerator, ()))
1249
1276
 
1250
- #: Sampling frequency of the signal as given by :attr:`source`.
1277
+ # Sampling frequency of the signal as given by :attr:`source`.
1251
1278
  sample_freq = Delegate('source')
1252
1279
 
1253
- #: Number of channels in output as given by :attr:`source`.
1254
- numchannels = Delegate('source')
1280
+ # Number of channels in output as given by :attr:`source`.
1281
+ num_channels = Delegate('source')
1255
1282
 
1256
- #: Number of samples in output as given by :attr:`source`.
1257
- numsamples = Delegate('source')
1283
+ # Number of samples in output as given by :attr:`source`.
1284
+ num_samples = Delegate('source')
1258
1285
 
1259
1286
  # internal identifier
1260
1287
  sdigest = Str()
@@ -1275,9 +1302,11 @@ class Mixer(TimeOut):
1275
1302
  if self.source:
1276
1303
  for s in self.sources:
1277
1304
  if self.sample_freq != s.sample_freq:
1278
- raise ValueError('Sample frequency of %s does not fit' % s)
1279
- if self.numchannels != s.numchannels:
1280
- 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)
1281
1310
 
1282
1311
  def result(self, num):
1283
1312
  """Python generator that yields the output block-wise.
@@ -1292,7 +1321,7 @@ class Mixer(TimeOut):
1292
1321
 
1293
1322
  Returns
1294
1323
  -------
1295
- Samples in blocks of shape (num, numchannels).
1324
+ Samples in blocks of shape (num, num_channels).
1296
1325
  The last block may be shorter than num.
1297
1326
 
1298
1327
  """
@@ -1318,7 +1347,7 @@ class Mixer(TimeOut):
1318
1347
  class TimePower(TimeOut):
1319
1348
  """Calculates time-depended power of the signal."""
1320
1349
 
1321
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1350
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1322
1351
  source = Instance(SamplesGenerator)
1323
1352
 
1324
1353
  def result(self, num):
@@ -1333,7 +1362,7 @@ class TimePower(TimeOut):
1333
1362
  Returns
1334
1363
  -------
1335
1364
  Squared output of source.
1336
- Yields samples in blocks of shape (num, numchannels).
1365
+ Yields samples in blocks of shape (num, num_channels).
1337
1366
  The last block may be shorter than num.
1338
1367
 
1339
1368
  """
@@ -1344,7 +1373,7 @@ class TimePower(TimeOut):
1344
1373
  class TimeCumAverage(TimeOut):
1345
1374
  """Calculates cumulative average of the signal, useful for Leq."""
1346
1375
 
1347
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1376
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1348
1377
  source = Instance(SamplesGenerator)
1349
1378
 
1350
1379
  def result(self, num):
@@ -1359,7 +1388,7 @@ class TimeCumAverage(TimeOut):
1359
1388
  Returns
1360
1389
  -------
1361
1390
  Cumulative average of the output of source.
1362
- Yields samples in blocks of shape (num, numchannels).
1391
+ Yields samples in blocks of shape (num, num_channels).
1363
1392
  The last block may be shorter than num.
1364
1393
 
1365
1394
  """
@@ -1377,7 +1406,7 @@ class TimeCumAverage(TimeOut):
1377
1406
  class TimeReverse(TimeOut):
1378
1407
  """Calculates the time-reversed signal of a source."""
1379
1408
 
1380
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1409
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1381
1410
  source = Instance(SamplesGenerator)
1382
1411
 
1383
1412
  def result(self, num):
@@ -1391,7 +1420,7 @@ class TimeReverse(TimeOut):
1391
1420
 
1392
1421
  Returns
1393
1422
  -------
1394
- Yields samples in blocks of shape (num, numchannels).
1423
+ Yields samples in blocks of shape (num, num_channels).
1395
1424
  Time-reversed output of source.
1396
1425
  The last block may be shorter than num.
1397
1426
 
@@ -1414,13 +1443,13 @@ class Filter(TimeOut):
1414
1443
  implements a filter with coefficients that may be changed
1415
1444
  during processing.
1416
1445
 
1417
- Should not be instanciated by itself
1446
+ Should not be instantiated by itself.
1418
1447
  """
1419
1448
 
1420
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1449
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1421
1450
  source = Instance(SamplesGenerator)
1422
1451
 
1423
- #: Filter coefficients
1452
+ # Filter coefficients
1424
1453
  sos = Property()
1425
1454
 
1426
1455
  def _get_sos(self):
@@ -1437,13 +1466,13 @@ class Filter(TimeOut):
1437
1466
 
1438
1467
  Returns
1439
1468
  -------
1440
- Samples in blocks of shape (num, numchannels).
1469
+ Samples in blocks of shape (num, num_channels).
1441
1470
  Delivers the bandpass filtered output of source.
1442
1471
  The last block may be shorter than num.
1443
1472
 
1444
1473
  """
1445
1474
  sos = self.sos
1446
- zi = zeros((sos.shape[0], 2, self.source.numchannels))
1475
+ zi = zeros((sos.shape[0], 2, self.source.num_channels))
1447
1476
  for block in self.source.result(num):
1448
1477
  sos = self.sos # this line is useful in case of changes
1449
1478
  # to self.sos during generator lifetime
@@ -1454,19 +1483,19 @@ class Filter(TimeOut):
1454
1483
  class FiltOctave(Filter):
1455
1484
  """Octave or third-octave filter (causal, non-zero phase delay)."""
1456
1485
 
1457
- #: Band center frequency; defaults to 1000.
1486
+ # Band center frequency; defaults to 1000.
1458
1487
  band = Float(1000.0, desc='band center frequency')
1459
1488
 
1460
- #: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1461
- 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')
1462
1491
 
1463
- #: Filter order
1492
+ # Filter order
1464
1493
  order = Int(3, desc='IIR filter order')
1465
1494
 
1466
1495
  sos = Property(depends_on=['band', 'fraction', 'source.digest', 'order'])
1467
1496
 
1468
1497
  # internal identifier
1469
- digest = Property(depends_on=['source.digest', '__class__', 'band', 'fraction', 'order'])
1498
+ digest = Property(depends_on=['source.digest', 'band', 'fraction', 'order'])
1470
1499
 
1471
1500
  @cached_property
1472
1501
  def _get_digest(self):
@@ -1499,11 +1528,11 @@ class FiltFiltOctave(FiltOctave):
1499
1528
  It requires large amounts of memory!
1500
1529
  """
1501
1530
 
1502
- #: Filter order (applied for forward filter and backward filter)
1531
+ # Filter order (applied for forward filter and backward filter)
1503
1532
  order = Int(2, desc='IIR filter half order')
1504
1533
 
1505
1534
  # internal identifier
1506
- digest = Property(depends_on=['source.digest', '__class__', 'band', 'fraction', 'order'])
1535
+ digest = Property(depends_on=['source.digest', 'band', 'fraction', 'order'])
1507
1536
 
1508
1537
  @cached_property
1509
1538
  def _get_digest(self):
@@ -1541,20 +1570,20 @@ class FiltFiltOctave(FiltOctave):
1541
1570
 
1542
1571
  Returns
1543
1572
  -------
1544
- Samples in blocks of shape (num, numchannels).
1573
+ Samples in blocks of shape (num, num_channels).
1545
1574
  Delivers the zero-phase bandpass filtered output of source.
1546
1575
  The last block may be shorter than num.
1547
1576
 
1548
1577
  """
1549
1578
  sos = self.sos
1550
- data = empty((self.source.numsamples, self.source.numchannels))
1579
+ data = empty((self.source.num_samples, self.source.num_channels))
1551
1580
  j = 0
1552
1581
  for block in self.source.result(num):
1553
1582
  ns, nc = block.shape
1554
1583
  data[j : j + ns] = block
1555
1584
  j += ns
1556
1585
  # filter one channel at a time to save memory
1557
- for j in range(self.source.numchannels):
1586
+ for j in range(self.source.num_channels):
1558
1587
  data[:, j] = sosfiltfilt(sos, data[:, j])
1559
1588
  j = 0
1560
1589
  ns = data.shape[0]
@@ -1569,13 +1598,13 @@ class TimeExpAverage(Filter):
1569
1598
  I (non-standard) -> 35 ms.
1570
1599
  """
1571
1600
 
1572
- #: time weighting
1573
- 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')
1574
1603
 
1575
1604
  sos = Property(depends_on=['weight', 'source.digest'])
1576
1605
 
1577
1606
  # internal identifier
1578
- digest = Property(depends_on=['source.digest', '__class__', 'weight'])
1607
+ digest = Property(depends_on=['source.digest', 'weight'])
1579
1608
 
1580
1609
  @cached_property
1581
1610
  def _get_digest(self):
@@ -1590,15 +1619,15 @@ class TimeExpAverage(Filter):
1590
1619
 
1591
1620
 
1592
1621
  class FiltFreqWeight(Filter):
1593
- """Frequency weighting filter accoring to IEC 61672."""
1622
+ """Frequency weighting filter according to IEC 61672."""
1594
1623
 
1595
- #: weighting characteristics
1596
- weight = Trait('A', ('A', 'C', 'Z'), desc='frequency weighting')
1624
+ # weighting characteristics
1625
+ weight = Enum('A', 'C', 'Z', desc='frequency weighting')
1597
1626
 
1598
1627
  sos = Property(depends_on=['weight', 'source.digest'])
1599
1628
 
1600
1629
  # internal identifier
1601
- digest = Property(depends_on=['source.digest', '__class__', 'weight'])
1630
+ digest = Property(depends_on=['source.digest', 'weight'])
1602
1631
 
1603
1632
  @cached_property
1604
1633
  def _get_digest(self):
@@ -1628,39 +1657,43 @@ class FiltFreqWeight(Filter):
1628
1657
  return tf2sos(b, a)
1629
1658
 
1630
1659
 
1660
+ @deprecated_alias({'numbands': 'num_bands'}, read_only=True)
1631
1661
  class FilterBank(TimeOut):
1632
1662
  """Abstract base class for IIR filter banks based on scipy lfilter
1633
1663
  implements a bank of parallel filters.
1634
1664
 
1635
- Should not be instanciated by itself
1665
+ Should not be instantiated by itself.
1636
1666
  """
1637
1667
 
1638
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1668
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1639
1669
  source = Instance(SamplesGenerator)
1640
1670
 
1641
- #: List of filter coefficients for all filters
1671
+ # List of filter coefficients for all filters
1642
1672
  sos = Property()
1643
1673
 
1644
- #: List of labels for bands
1674
+ # List of labels for bands
1645
1675
  bands = Property()
1646
1676
 
1647
- #: Number of bands
1648
- numbands = Property()
1677
+ # Number of bands
1678
+ num_bands = Property()
1649
1679
 
1650
- #: Number of bands
1651
- numchannels = Property()
1680
+ # Number of bands
1681
+ num_channels = Property()
1652
1682
 
1683
+ @abstractmethod
1653
1684
  def _get_sos(self):
1654
- return [tf2sos([1], [1])]
1685
+ """Returns a list of second order section coefficients."""
1655
1686
 
1687
+ @abstractmethod
1656
1688
  def _get_bands(self):
1657
- return ['']
1689
+ """Returns a list of labels for the bands."""
1658
1690
 
1659
- def _get_numbands(self):
1660
- return 0
1691
+ @abstractmethod
1692
+ def _get_num_bands(self):
1693
+ """Returns the number of bands."""
1661
1694
 
1662
- def _get_numchannels(self):
1663
- return self.numbands * self.source.numchannels
1695
+ def _get_num_channels(self):
1696
+ return self.num_bands * self.source.num_channels
1664
1697
 
1665
1698
  def result(self, num):
1666
1699
  """Python generator that yields the output block-wise.
@@ -1673,16 +1706,16 @@ class FilterBank(TimeOut):
1673
1706
 
1674
1707
  Returns
1675
1708
  -------
1676
- Samples in blocks of shape (num, numchannels).
1709
+ Samples in blocks of shape (num, num_channels).
1677
1710
  Delivers the bandpass filtered output of source.
1678
1711
  The last block may be shorter than num.
1679
1712
 
1680
1713
  """
1681
- numbands = self.numbands
1682
- snumch = self.source.numchannels
1714
+ numbands = self.num_bands
1715
+ snumch = self.source.num_channels
1683
1716
  sos = self.sos
1684
1717
  zi = [zeros((sos[0].shape[0], 2, snumch)) for _ in range(numbands)]
1685
- res = zeros((num, self.numchannels), dtype='float')
1718
+ res = zeros((num, self.num_channels), dtype='float')
1686
1719
  for block in self.source.result(num):
1687
1720
  for i in range(numbands):
1688
1721
  res[:, i * snumch : (i + 1) * snumch], zi[i] = sosfilt(sos[i], block, axis=0, zi=zi[i])
@@ -1692,26 +1725,26 @@ class FilterBank(TimeOut):
1692
1725
  class OctaveFilterBank(FilterBank):
1693
1726
  """Octave or third-octave filter bank."""
1694
1727
 
1695
- #: Lowest band center frequency index; defaults to 21 (=125 Hz).
1728
+ # Lowest band center frequency index; defaults to 21 (=125 Hz).
1696
1729
  lband = Int(21, desc='lowest band center frequency index')
1697
1730
 
1698
- #: Lowest band center frequency index + 1; defaults to 40 (=8000 Hz).
1731
+ # Lowest band center frequency index + 1; defaults to 40 (=8000 Hz).
1699
1732
  hband = Int(40, desc='lowest band center frequency index')
1700
1733
 
1701
- #: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1702
- 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')
1703
1736
 
1704
- #: List of filter coefficients for all filters
1737
+ # List of filter coefficients for all filters
1705
1738
  ba = Property(depends_on=['lband', 'hband', 'fraction', 'source.digest'])
1706
1739
 
1707
- #: List of labels for bands
1740
+ # List of labels for bands
1708
1741
  bands = Property(depends_on=['lband', 'hband', 'fraction'])
1709
1742
 
1710
- #: Number of bands
1711
- numbands = Property(depends_on=['lband', 'hband', 'fraction'])
1743
+ # Number of bands
1744
+ num_bands = Property(depends_on=['lband', 'hband', 'fraction'])
1712
1745
 
1713
1746
  # internal identifier
1714
- digest = Property(depends_on=['source.digest', '__class__', 'lband', 'hband', 'fraction', 'order'])
1747
+ digest = Property(depends_on=['source.digest', 'lband', 'hband', 'fraction', 'order'])
1715
1748
 
1716
1749
  @cached_property
1717
1750
  def _get_digest(self):
@@ -1722,7 +1755,7 @@ class OctaveFilterBank(FilterBank):
1722
1755
  return [10 ** (i / 10) for i in range(self.lband, self.hband, 4 - self.fraction_)]
1723
1756
 
1724
1757
  @cached_property
1725
- def _get_numbands(self):
1758
+ def _get_num_bands(self):
1726
1759
  return len(self.bands)
1727
1760
 
1728
1761
  @cached_property
@@ -1736,26 +1769,27 @@ class OctaveFilterBank(FilterBank):
1736
1769
  return sos
1737
1770
 
1738
1771
 
1772
+ @deprecated_alias({'name': 'file'})
1739
1773
  class WriteWAV(TimeOut):
1740
1774
  """Saves time signal from one or more channels as mono/stereo/multi-channel
1741
1775
  `*.wav` file.
1742
1776
  """
1743
1777
 
1744
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1778
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1745
1779
  source = Instance(SamplesGenerator)
1746
1780
 
1747
- #: Name of the file to be saved. If none is given, the name will be
1748
- #: automatically generated from the sources.
1749
- name = File(filter=['*.wav'], desc='name of wave file')
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')
1750
1784
 
1751
- #: Basename for cache, readonly.
1752
- basename = Property(depends_on='digest')
1785
+ # Basename for cache, readonly.
1786
+ basename = Property(depends_on=['digest'])
1753
1787
 
1754
- #: Channel(s) to save. List can only contain one or two channels.
1755
- 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')
1756
1790
 
1757
1791
  # internal identifier
1758
- digest = Property(depends_on=['source.digest', 'channels', '__class__'])
1792
+ digest = Property(depends_on=['source.digest', 'channels'])
1759
1793
 
1760
1794
  @cached_property
1761
1795
  def _get_digest(self):
@@ -1763,18 +1797,15 @@ class WriteWAV(TimeOut):
1763
1797
 
1764
1798
  @cached_property
1765
1799
  def _get_basename(self):
1766
- obj = self.source # start with source
1767
- try:
1768
- while obj:
1769
- if 'basename' in obj.all_trait_names(): # at original source?
1770
- basename = obj.basename # get the name
1771
- break
1772
- obj = obj.source # traverse down until original data source
1773
- else:
1774
- basename = 'void'
1775
- except AttributeError:
1776
- basename = 'void' # if no file source is found
1777
- 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)
1778
1809
 
1779
1810
  def save(self):
1780
1811
  """Saves source output to one- or multiple-channel `*.wav` file."""
@@ -1783,54 +1814,59 @@ class WriteWAV(TimeOut):
1783
1814
  msg = 'No channels given for output.'
1784
1815
  raise ValueError(msg)
1785
1816
  if nc > 2:
1786
- warn('More than two channels given for output, exported file will have %i channels' % nc, stacklevel=1)
1787
- 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 == '':
1788
1819
  name = self.basename
1789
1820
  for nr in self.channels:
1790
- name += '_%i' % nr
1821
+ name += f'{nr:d}'
1791
1822
  name += '.wav'
1792
1823
  else:
1793
- name = self.name
1794
- wf = wave.open(name, 'w')
1795
- wf.setnchannels(nc)
1796
- wf.setsampwidth(2)
1797
- wf.setframerate(self.source.sample_freq)
1798
- wf.setnframes(self.source.numsamples)
1799
- mx = 0.0
1800
- ind = array(self.channels)
1801
- for data in self.source.result(1024):
1802
- mx = max(abs(data[:, ind]).max(), mx)
1803
- scale = 0.9 * 2**15 / mx
1804
- for data in self.source.result(1024):
1805
- wf.writeframesraw(array(data[:, ind] * scale, dtype=int16).tostring())
1806
- wf.close()
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)
1807
1842
 
1808
1843
 
1844
+ @deprecated_alias({'name': 'file', 'numsamples_write': 'num_samples_write', 'writeflag': 'write_flag'})
1809
1845
  class WriteH5(TimeOut):
1810
1846
  """Saves time signal as `*.h5` file."""
1811
1847
 
1812
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1848
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1813
1849
  source = Instance(SamplesGenerator)
1814
1850
 
1815
- #: Name of the file to be saved. If none is given, the name will be
1816
- #: automatically generated from a time stamp.
1817
- name = File(filter=['*.h5'], desc='name of data file')
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')
1818
1854
 
1819
- #: Number of samples to write to file by `result` method.
1820
- #: defaults to -1 (write as long as source yields data).
1821
- 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)
1822
1858
 
1823
1859
  # flag that can be raised to stop file writing
1824
- writeflag = Bool(True)
1860
+ write_flag = Bool(True)
1825
1861
 
1826
1862
  # internal identifier
1827
- digest = Property(depends_on=['source.digest', '__class__'])
1863
+ digest = Property(depends_on=['source.digest'])
1828
1864
 
1829
- #: The floating-number-precision of entries of H5 File corresponding
1830
- #: to numpy dtypes. Default is 32 bit.
1831
- 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')
1832
1868
 
1833
- #: Metadata to be stored in HDF5 file object
1869
+ # Metadata to be stored in HDF5 file object
1834
1870
  metadata = Dict(desc='metadata to be stored in .h5 file')
1835
1871
 
1836
1872
  @cached_property
@@ -1838,15 +1874,15 @@ class WriteH5(TimeOut):
1838
1874
  return digest(self)
1839
1875
 
1840
1876
  def create_filename(self):
1841
- if self.name == '':
1877
+ if self.file == '':
1842
1878
  name = datetime.now(tz=timezone.utc).isoformat('_').replace(':', '-').replace('.', '_')
1843
- self.name = path.join(config.td_dir, name + '.h5')
1879
+ self.file = path.join(config.td_dir, name + '.h5')
1844
1880
 
1845
1881
  def get_initialized_file(self):
1846
1882
  file = _get_h5file_class()
1847
1883
  self.create_filename()
1848
- f5h = file(self.name, mode='w')
1849
- 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)
1850
1886
  ac = f5h.get_data_by_reference('time_data')
1851
1887
  f5h.set_node_attribute(ac, 'sample_freq', self.sample_freq)
1852
1888
  self.add_metadata(f5h)
@@ -1866,6 +1902,8 @@ class WriteH5(TimeOut):
1866
1902
  if nitems > 0:
1867
1903
  f5h.create_new_group('metadata', '/')
1868
1904
  for key, value in self.metadata.items():
1905
+ if isinstance(value, str):
1906
+ value = array(value, dtype='S')
1869
1907
  f5h.create_array('/metadata', key, value)
1870
1908
 
1871
1909
  def result(self, num):
@@ -1881,19 +1919,19 @@ class WriteH5(TimeOut):
1881
1919
 
1882
1920
  Returns
1883
1921
  -------
1884
- Samples in blocks of shape (num, numchannels).
1922
+ Samples in blocks of shape (num, num_channels).
1885
1923
  The last block may be shorter than num.
1886
1924
  Echos the source output, but reads it from cache
1887
- when available and prevents unnecassary recalculation.
1925
+ when available and prevents unnecessary recalculation.
1888
1926
 
1889
1927
  """
1890
- self.writeflag = True
1928
+ self.write_flag = True
1891
1929
  f5h = self.get_initialized_file()
1892
1930
  ac = f5h.get_data_by_reference('time_data')
1893
1931
  scount = 0
1894
- stotal = self.numsamples_write
1932
+ stotal = self.num_samples_write
1895
1933
  source_gen = self.source.result(num)
1896
- while self.writeflag:
1934
+ while self.write_flag:
1897
1935
  sleft = stotal - scount
1898
1936
  if stotal != -1 and sleft > 0:
1899
1937
  anz = min(num, sleft)
@@ -1913,17 +1951,17 @@ class WriteH5(TimeOut):
1913
1951
 
1914
1952
 
1915
1953
  class TimeConvolve(TimeOut):
1916
- """Uniformly partitioned overlap-save method (UPOLS) for fast convolution in the frequency domain.
1954
+ """Fast frequency domain convolution with the Uniformly partitioned overlap-save method (UPOLS).
1917
1955
 
1918
1956
  See :cite:`Wefers2015` for details.
1919
1957
  """
1920
1958
 
1921
- #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1959
+ # Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1922
1960
  source = Instance(SamplesGenerator)
1923
1961
 
1924
- #: Convolution kernel in the time domain.
1925
- #: The second dimension of the kernel array has to be either 1 or match :attr:`~SamplesGenerator.numchannels`.
1926
- #: 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.
1927
1965
  kernel = CArray(dtype=float, desc='Convolution kernel.')
1928
1966
 
1929
1967
  _block_size = Int(desc='Block size')
@@ -1934,7 +1972,7 @@ class TimeConvolve(TimeOut):
1934
1972
  )
1935
1973
 
1936
1974
  # internal identifier
1937
- digest = Property(depends_on=['source.digest', 'kernel', '__class__'])
1975
+ digest = Property(depends_on=['source.digest', 'kernel'])
1938
1976
 
1939
1977
  @cached_property
1940
1978
  def _get_digest(self):
@@ -1949,9 +1987,9 @@ class TimeConvolve(TimeOut):
1949
1987
  if self.kernel.ndim > 2:
1950
1988
  msg = 'Only one or two dimensional kernels accepted.'
1951
1989
  raise ValueError(msg)
1952
- # check if number of kernels matches numchannels
1953
- if self.kernel.shape[1] not in (1, self.source.numchannels):
1954
- 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.'
1955
1993
  raise ValueError(msg)
1956
1994
 
1957
1995
  # compute the rfft of the kernel blockwise
@@ -1985,7 +2023,7 @@ class TimeConvolve(TimeOut):
1985
2023
 
1986
2024
  Returns
1987
2025
  -------
1988
- Samples in blocks of shape (num, numchannels).
2026
+ Samples in blocks of shape (num, num_channels).
1989
2027
  The last block may be shorter than num.
1990
2028
 
1991
2029
  """
@@ -1993,8 +2031,8 @@ class TimeConvolve(TimeOut):
1993
2031
  # initialize variables
1994
2032
  self._block_size = num
1995
2033
  L = self.kernel.shape[0]
1996
- N = self.source.numchannels
1997
- M = self.source.numsamples
2034
+ N = self.source.num_channels
2035
+ M = self.source.num_samples
1998
2036
  numblocks_kernel = int(ceil(L / num)) # number of kernel blocks
1999
2037
  Q = int(ceil(M / num)) # number of signal blocks
2000
2038
  R = int(ceil((L + M - 1) / num)) # number of output blocks
@@ -2044,13 +2082,13 @@ class TimeConvolve(TimeOut):
2044
2082
 
2045
2083
 
2046
2084
  @nb.jit(nopython=True, cache=True)
2047
- def _append_to_fdl(fdl, idx, numblocks_kernel, buff):
2085
+ def _append_to_fdl(fdl, idx, numblocks_kernel, buff): # pragma: no cover
2048
2086
  fdl[idx] = buff
2049
2087
  idx = int(idx + 1 % numblocks_kernel)
2050
2088
 
2051
2089
 
2052
2090
  @nb.jit(nopython=True, cache=True)
2053
- def _spectral_sum(out, fdl, kb):
2091
+ def _spectral_sum(out, fdl, kb): # pragma: no cover
2054
2092
  P, B, N = kb.shape
2055
2093
  for n in range(N):
2056
2094
  for b in range(B):
@@ -2062,17 +2100,18 @@ def _spectral_sum(out, fdl, kb):
2062
2100
 
2063
2101
 
2064
2102
  class MaskedTimeInOut(MaskedTimeOut):
2065
- """Signal processing block for channel and sample selection (alias for :class:`~acoular.tprocess.MaskedTimeOut`.).
2103
+ """Signal processing block for channel and sample selection.
2066
2104
 
2067
2105
  .. deprecated:: 24.10
2068
- Using :class:`~acoular.tprocess.MaskedTimeInOut` is deprecated and will be removed in Acoular
2069
- version 25.07. Use :class:`~acoular.tprocess.MaskedTimeOut` instead.
2106
+ Using :class:`~acoular.tprocess.MaskedTimeInOut` is deprecated and will be removed in
2107
+ Acoular version 25.07. Use :class:`~acoular.tprocess.MaskedTimeOut` instead.
2070
2108
  """
2071
2109
 
2072
2110
  def __init__(self, *args, **kwargs):
2073
2111
  super().__init__(*args, **kwargs)
2074
2112
  warn(
2075
- 'Using MaskedTimeInOut is deprecated and will be removed in Acoular version 25.07. Use class MaskedTimeOut instead.',
2113
+ 'Using MaskedTimeInOut is deprecated and will be removed in Acoular version 25.07. \
2114
+ Use class MaskedTimeOut instead.',
2076
2115
  DeprecationWarning,
2077
2116
  stacklevel=2,
2078
2117
  )