acoular 24.5__py3-none-any.whl → 24.10__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,15 @@
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
41
35
  from datetime import datetime, timezone
42
- from inspect import currentframe
43
36
  from os import path
44
37
  from warnings import warn
45
38
 
@@ -78,13 +71,13 @@ from numpy import (
78
71
  sqrt,
79
72
  stack,
80
73
  sum,
74
+ tile,
81
75
  unique,
82
76
  zeros,
83
77
  )
84
- from numpy.linalg import norm
85
- from numpy.matlib import repmat
86
78
  from scipy.fft import irfft, rfft
87
79
  from scipy.interpolate import CloughTocher2DInterpolator, CubicSpline, LinearNDInterpolator, Rbf, splev, splrep
80
+ from scipy.linalg import norm
88
81
  from scipy.signal import bilinear, butter, sosfilt, sosfiltfilt, tf2sos
89
82
  from scipy.spatial import Delaunay
90
83
  from traits.api import (
@@ -96,7 +89,6 @@ from traits.api import (
96
89
  Dict,
97
90
  File,
98
91
  Float,
99
- HasPrivateTraits,
100
92
  Instance,
101
93
  Int,
102
94
  List,
@@ -109,100 +101,28 @@ from traits.api import (
109
101
  on_trait_change,
110
102
  )
111
103
 
104
+ # acoular imports
105
+ from .base import SamplesGenerator, TimeOut
112
106
  from .configuration import config
113
107
  from .environments import cartToCyl, cylToCart
114
- from .h5cache import H5cache
115
- from .h5files import H5CacheFileBase, _get_h5file_class
116
-
117
- # acoular imports
108
+ from .h5files import _get_h5file_class
118
109
  from .internal import digest, ldigest
119
110
  from .microphones import MicGeom
120
111
 
121
112
 
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):
113
+ class MaskedTimeOut(TimeOut):
197
114
  """Signal processing block for channel and sample selection.
198
115
 
199
116
  This class serves as intermediary to define (in)valid
200
117
  channels and samples for any
201
118
  :class:`~acoular.sources.SamplesGenerator` (or derived) object.
202
- It gets samples from :attr:`~acoular.tprocess.TimeInOut.source`
119
+ It gets samples from :attr:`~acoular.base.TimeOut.source`
203
120
  and generates output via the generator :meth:`result`.
204
121
  """
205
122
 
123
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
124
+ source = Instance(SamplesGenerator)
125
+
206
126
  #: Index of the first sample to be considered valid.
207
127
  start = CLong(0, desc='start of valid samples')
208
128
 
@@ -215,10 +135,10 @@ class MaskedTimeInOut(TimeInOut):
215
135
  #: Channel mask to serve as an index for all valid channels, is set automatically.
216
136
  channels = Property(depends_on=['invalid_channels', 'source.numchannels'], desc='channel mask')
217
137
 
218
- #: Number of channels in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
138
+ #: Number of channels in input, as given by :attr:`~acoular.base.TimeOut.source`.
219
139
  numchannels_total = Delegate('source', 'numchannels')
220
140
 
221
- #: Number of samples in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
141
+ #: Number of samples in input, as given by :attr:`~acoular.base.TimeOut.source`.
222
142
  numsamples_total = Delegate('source', 'numsamples')
223
143
 
224
144
  #: Number of valid channels, is set automatically.
@@ -323,11 +243,14 @@ class MaskedTimeInOut(TimeInOut):
323
243
  yield block[:, self.channels]
324
244
 
325
245
 
326
- class ChannelMixer(TimeInOut):
246
+ class ChannelMixer(TimeOut):
327
247
  """Class for directly mixing the channels of a multi-channel source.
328
248
  Outputs a single channel.
329
249
  """
330
250
 
251
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
252
+ source = Instance(SamplesGenerator)
253
+
331
254
  #: Amplitude weight(s) for the channels as array. If not set, all channels are equally weighted.
332
255
  weights = CArray(desc='channel weights')
333
256
 
@@ -369,7 +292,7 @@ class ChannelMixer(TimeInOut):
369
292
  yield sum(weights * block, 1, keepdims=True)
370
293
 
371
294
 
372
- class Trigger(TimeInOut):
295
+ class Trigger(TimeOut):
373
296
  """Class for identifying trigger signals.
374
297
  Gets samples from :attr:`source` and stores the trigger samples in :meth:`trigger_data`.
375
298
 
@@ -383,7 +306,7 @@ class Trigger(TimeInOut):
383
306
  vary too much.
384
307
  """
385
308
 
386
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
309
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
387
310
  source = Instance(SamplesGenerator)
388
311
 
389
312
  #: Threshold of trigger. Has different meanings for different
@@ -467,20 +390,20 @@ class Trigger(TimeInOut):
467
390
  def _get_trigger_data(self):
468
391
  self._check_trigger_existence()
469
392
  triggerFunc = {'dirac': self._trigger_dirac, 'rect': self._trigger_rect}[self.trigger_type]
470
- nSamples = 2048 # number samples for result-method of source
471
- threshold = self._threshold(nSamples)
393
+ num = 2048 # number samples for result-method of source
394
+ threshold = self._threshold(num)
472
395
 
473
396
  # get all samples which surpasse the threshold
474
397
  peakLoc = array([], dtype='int') # all indices which surpasse the threshold
475
- triggerData = array([])
398
+ trigger_data = array([])
476
399
  x0 = []
477
400
  dSamples = 0
478
- for triggerSignal in self.source.result(nSamples):
401
+ for triggerSignal in self.source.result(num):
479
402
  localTrigger = flatnonzero(triggerFunc(x0, triggerSignal, threshold))
480
403
  if len(localTrigger) != 0:
481
404
  peakLoc = append(peakLoc, localTrigger + dSamples)
482
- triggerData = append(triggerData, triggerSignal[localTrigger])
483
- dSamples += nSamples
405
+ trigger_data = append(trigger_data, triggerSignal[localTrigger])
406
+ dSamples += num
484
407
  x0 = triggerSignal[-1]
485
408
  if len(peakLoc) <= 1:
486
409
  msg = 'Not enough trigger info. Check *threshold* sign and value!'
@@ -498,12 +421,12 @@ class Trigger(TimeInOut):
498
421
  peakLocHelp = multiplePeaksWithinHunk[0]
499
422
  indHelp = [peakLocHelp, peakLocHelp + 1]
500
423
  if self.multiple_peaks_in_hunk == 'extremum':
501
- values = triggerData[indHelp]
424
+ values = trigger_data[indHelp]
502
425
  deleteInd = indHelp[argmin(abs(values))]
503
426
  elif self.multiple_peaks_in_hunk == 'first':
504
427
  deleteInd = indHelp[1]
505
428
  peakLoc = delete(peakLoc, deleteInd)
506
- triggerData = delete(triggerData, deleteInd)
429
+ trigger_data = delete(trigger_data, deleteInd)
507
430
  peakDist = peakLoc[1:] - peakLoc[:-1]
508
431
  multiplePeaksWithinHunk = flatnonzero(peakDist < self.hunk_length * maxPeakDist)
509
432
 
@@ -520,7 +443,7 @@ class Trigger(TimeInOut):
520
443
  )
521
444
  return peakLoc, max(peakDist), min(peakDist)
522
445
 
523
- def _trigger_dirac(self, x0, x, threshold):
446
+ def _trigger_dirac(self, x0, x, threshold): # noqa: ARG002
524
447
  # x0 not needed here, but needed in _trigger_rect
525
448
  return self._trigger_value_comp(x, threshold)
526
449
 
@@ -530,20 +453,20 @@ class Trigger(TimeInOut):
530
453
  # indPeakHunk = abs(xNew[1:] - xNew[:-1]) > abs(threshold) # with this line: every edge would be located
531
454
  return self._trigger_value_comp(xNew[1:] - xNew[:-1], threshold)
532
455
 
533
- def _trigger_value_comp(self, triggerData, threshold):
534
- return triggerData > threshold if threshold > 0.0 else triggerData < threshold
456
+ def _trigger_value_comp(self, trigger_data, threshold):
457
+ return trigger_data > threshold if threshold > 0.0 else trigger_data < threshold
535
458
 
536
- def _threshold(self, nSamples):
459
+ def _threshold(self, num):
537
460
  if self.threshold is None: # take a guessed threshold
538
461
  # get max and min values of whole trigger signal
539
462
  maxVal = -inf
540
463
  minVal = inf
541
464
  meanVal = 0
542
465
  cntMean = 0
543
- for triggerData in self.source.result(nSamples):
544
- maxVal = max(maxVal, triggerData.max())
545
- minVal = min(minVal, triggerData.min())
546
- meanVal += triggerData.mean()
466
+ for trigger_data in self.source.result(num):
467
+ maxVal = max(maxVal, trigger_data.max())
468
+ minVal = min(minVal, trigger_data.min())
469
+ meanVal += trigger_data.mean()
547
470
  cntMean += 1
548
471
  meanVal /= cntMean
549
472
 
@@ -563,7 +486,7 @@ class Trigger(TimeInOut):
563
486
  return 0
564
487
 
565
488
 
566
- class AngleTracker(MaskedTimeInOut):
489
+ class AngleTracker(MaskedTimeOut):
567
490
  """Calculates rotation angle and rpm per sample from a trigger signal
568
491
  using spline interpolation in the time domain.
569
492
 
@@ -571,9 +494,6 @@ class AngleTracker(MaskedTimeInOut):
571
494
 
572
495
  """
573
496
 
574
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
575
- source = Instance(SamplesGenerator)
576
-
577
497
  #: Trigger data from :class:`acoular.tprocess.Trigger`.
578
498
  trigger = Instance(Trigger)
579
499
 
@@ -642,18 +562,18 @@ class AngleTracker(MaskedTimeInOut):
642
562
  # init
643
563
  ind = 0
644
564
  # trigger data
645
- peakloc, maxdist, mindist = self.trigger._get_trigger_data()
565
+ peakloc, maxdist, mindist = self.trigger.trigger_data()
646
566
  TriggerPerRevo = self.trigger_per_revo
647
567
  rotDirection = self.rot_direction
648
- nSamples = self.source.numsamples
568
+ num = self.source.numsamples
649
569
  samplerate = self.source.sample_freq
650
- self._rpm = zeros(nSamples)
651
- self._angle = zeros(nSamples)
570
+ self._rpm = zeros(num)
571
+ self._angle = zeros(num)
652
572
  # number of spline points
653
573
  InterpPoints = self.interp_points
654
574
 
655
575
  # loop over alle timesamples
656
- while ind < nSamples:
576
+ while ind < num:
657
577
  # when starting spline forward
658
578
  if ind < peakloc[InterpPoints]:
659
579
  peakdist = (
@@ -716,17 +636,20 @@ class AngleTracker(MaskedTimeInOut):
716
636
 
717
637
  """
718
638
  # trigger indices data
719
- peakloc = self.trigger._get_trigger_data()[0]
639
+ peakloc = self.trigger.trigger_data()[0]
720
640
  # calculation of average rpm in 1/min
721
641
  return (len(peakloc) - 1) / (peakloc[-1] - peakloc[0]) / self.trigger_per_revo * self.source.sample_freq * 60
722
642
 
723
643
 
724
- class SpatialInterpolator(TimeInOut):
644
+ class SpatialInterpolator(TimeOut):
725
645
  """Base class for spatial interpolation of microphone data.
726
646
  Gets samples from :attr:`source` and generates output via the
727
647
  generator :meth:`result`.
728
648
  """
729
649
 
650
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
651
+ source = Instance(SamplesGenerator)
652
+
730
653
  #: :class:`~acoular.microphones.MicGeom` object that provides the real microphone locations.
731
654
  mics = Instance(MicGeom(), desc='microphone geometry')
732
655
 
@@ -743,9 +666,6 @@ class SpatialInterpolator(TimeInOut):
743
666
  def _set_mics_virtual(self, mics_virtual):
744
667
  self._mics_virtual = mics_virtual
745
668
 
746
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
747
- source = Instance(SamplesGenerator)
748
-
749
669
  #: Interpolation method in spacial domain, defaults to linear
750
670
  #: linear uses numpy linear interpolation
751
671
  #: spline uses scipy CloughTocher algorithm
@@ -784,7 +704,7 @@ class SpatialInterpolator(TimeInOut):
784
704
  #: The transformation is done via [x,y,z]_mod = Q * [x,y,z]. (default is Identity).
785
705
  Q = CArray(dtype=float64, shape=(3, 3), value=identity(3))
786
706
 
787
- num_IDW = Trait(3, dtype=int, desc='number of neighboring microphones, DEFAULT=3')
707
+ num_IDW = Trait(3, dtype=int, desc='number of neighboring microphones, DEFAULT=3') # noqa: N815
788
708
 
789
709
  p_weight = Trait(
790
710
  2,
@@ -793,7 +713,7 @@ class SpatialInterpolator(TimeInOut):
793
713
  )
794
714
 
795
715
  #: Stores the output of :meth:`_virtNewCoord_func`; Read-Only
796
- _virtNewCoord_func = Property(
716
+ _virtNewCoord_func = Property( # noqa: N815
797
717
  depends_on=['mics.digest', 'mics_virtual.digest', 'method', 'array_dimension', 'interp_at_zero'],
798
718
  )
799
719
 
@@ -818,21 +738,21 @@ class SpatialInterpolator(TimeInOut):
818
738
  return digest(self)
819
739
 
820
740
  @cached_property
821
- def _get_virtNewCoord(self):
741
+ def _get_virtNewCoord(self): # noqa N802
822
742
  return self._virtNewCoord_func(self.mics.mpos, self.mics_virtual.mpos, self.method, self.array_dimension)
823
743
 
824
744
  def sinc_mic(self, r):
825
745
  """Modified Sinc function for Radial Basis function approximation."""
826
746
  return sinc((r * self.mics_virtual.mpos.shape[1]) / (pi))
827
747
 
828
- def _virtNewCoord_func(self, mic, micVirt, method, array_dimension):
829
- """Core functionality for getting the interpolation .
748
+ def _virtNewCoord_func(self, mpos, mpos_virt, method, array_dimension): # noqa N802
749
+ """Core functionality for getting the interpolation.
830
750
 
831
751
  Parameters
832
752
  ----------
833
- mic : float[3, nPhysicalMics]
753
+ mpos : float[3, nPhysicalMics]
834
754
  The mic positions of the physical (really existing) mics
835
- micVirt : float[3, nVirtualMics]
755
+ mpos_virt : float[3, nVirtualMics]
836
756
  The mic positions of the virtual mics
837
757
  method : string
838
758
  The Interpolation method to use
@@ -864,11 +784,11 @@ class SpatialInterpolator(TimeInOut):
864
784
 
865
785
  """
866
786
  # init positions of virtual mics in cyl coordinates
867
- nVirtMics = micVirt.shape[1]
787
+ nVirtMics = mpos_virt.shape[1]
868
788
  virtNewCoord = zeros((3, nVirtMics))
869
789
  virtNewCoord.fill(nan)
870
790
  # init real positions in cyl coordinates
871
- nMics = mic.shape[1]
791
+ nMics = mpos.shape[1]
872
792
  newCoord = zeros((3, nMics))
873
793
  newCoord.fill(nan)
874
794
  # empty mesh object
@@ -876,24 +796,24 @@ class SpatialInterpolator(TimeInOut):
876
796
 
877
797
  if self.array_dimension == '1D' or self.array_dimension == 'ring':
878
798
  # get projections onto new coordinate, for real mics
879
- projectionOnNewAxis = cartToCyl(mic, self.Q)[0]
799
+ projectionOnNewAxis = cartToCyl(mpos, self.Q)[0]
880
800
  indReorderHelp = argsort(projectionOnNewAxis)
881
801
  mesh.append([projectionOnNewAxis[indReorderHelp], indReorderHelp])
882
802
 
883
803
  # new coordinates of real mics
884
- indReorderHelp = argsort(cartToCyl(mic, self.Q)[0])
885
- newCoord = (cartToCyl(mic, self.Q).T)[indReorderHelp].T
804
+ indReorderHelp = argsort(cartToCyl(mpos, self.Q)[0])
805
+ newCoord = (cartToCyl(mpos, self.Q).T)[indReorderHelp].T
886
806
 
887
807
  # and for virtual mics
888
- virtNewCoord = cartToCyl(micVirt)
808
+ virtNewCoord = cartToCyl(mpos_virt)
889
809
 
890
810
  elif self.array_dimension == '2D': # 2d case0
891
811
  # get virtual mic projections on new coord system
892
- virtNewCoord = cartToCyl(micVirt, self.Q)
812
+ virtNewCoord = cartToCyl(mpos_virt, self.Q)
893
813
 
894
814
  # new coordinates of real mics
895
- indReorderHelp = argsort(cartToCyl(mic, self.Q)[0])
896
- newCoord = cartToCyl(mic, self.Q)
815
+ indReorderHelp = argsort(cartToCyl(mpos, self.Q)[0])
816
+ newCoord = cartToCyl(mpos, self.Q)
897
817
 
898
818
  # scipy delauney triangulation
899
819
  # Delaunay
@@ -929,10 +849,10 @@ class SpatialInterpolator(TimeInOut):
929
849
 
930
850
  elif self.array_dimension == '3D': # 3d case
931
851
  # get virtual mic projections on new coord system
932
- virtNewCoord = cartToCyl(micVirt, self.Q)
852
+ virtNewCoord = cartToCyl(mpos_virt, self.Q)
933
853
  # get real mic projections on new coord system
934
- indReorderHelp = argsort(cartToCyl(mic, self.Q)[0])
935
- newCoord = cartToCyl(mic, self.Q)
854
+ indReorderHelp = argsort(cartToCyl(mpos, self.Q)[0])
855
+ newCoord = cartToCyl(mpos, self.Q)
936
856
  # Delaunay
937
857
  tri = Delaunay(newCoord.T, incremental=True) # , incremental=True,qhull_options = "Qc QJ Q12"
938
858
 
@@ -966,14 +886,14 @@ class SpatialInterpolator(TimeInOut):
966
886
 
967
887
  return mesh, virtNewCoord, newCoord
968
888
 
969
- def _result_core_func(self, p, phiDelay=None, period=None, Q=Q, interp_at_zero=False):
889
+ def _result_core_func(self, p, phi_delay=None, period=None, Q=Q, interp_at_zero=False): # noqa: N803, ARG002 (see #226)
970
890
  """Performs the actual Interpolation.
971
891
 
972
892
  Parameters
973
893
  ----------
974
- p : float[nSamples, nMicsReal]
894
+ p : float[num, nMicsReal]
975
895
  The pressure field of the yielded sample at real mics.
976
- phiDelay : empty list (default) or float[nSamples]
896
+ phi_delay : empty list (default) or float[num]
977
897
  If passed (rotational case), this list contains the angular delay
978
898
  of each sample in rad.
979
899
  period : None (default) or float
@@ -982,12 +902,12 @@ class SpatialInterpolator(TimeInOut):
982
902
 
983
903
  Returns
984
904
  -------
985
- pInterp : float[nSamples, nMicsVirtual]
905
+ pInterp : float[num, nMicsVirtual]
986
906
  The interpolated time data at the virtual mics
987
907
 
988
908
  """
989
- if phiDelay is None:
990
- phiDelay = []
909
+ if phi_delay is None:
910
+ phi_delay = []
991
911
  # number of time samples
992
912
  nTime = p.shape[0]
993
913
  # number of virtual mixcs
@@ -1015,13 +935,13 @@ class SpatialInterpolator(TimeInOut):
1015
935
 
1016
936
  # Interpolation for 1D Arrays
1017
937
  if self.array_dimension == '1D' or self.array_dimension == 'ring':
1018
- # for rotation add phidelay
1019
- if not array_equal(phiDelay, []):
1020
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
938
+ # for rotation add phi_delay
939
+ if not array_equal(phi_delay, []):
940
+ xInterpHelp = tile(virtNewCoord[0, :], (nTime, 1)) + tile(phi_delay, (virtNewCoord.shape[1], 1)).T
1021
941
  xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
1022
942
  # if no rotation given
1023
943
  else:
1024
- xInterp = repmat(virtNewCoord[0, :], nTime, 1)
944
+ xInterp = tile(virtNewCoord[0, :], (nTime, 1))
1025
945
  # get ordered microphone posions in radiant
1026
946
  x = newCoord[0]
1027
947
  for cntTime in range(nTime):
@@ -1074,11 +994,11 @@ class SpatialInterpolator(TimeInOut):
1074
994
  # Interpolation for arbitrary 2D Arrays
1075
995
  elif self.array_dimension == '2D':
1076
996
  # check rotation
1077
- if not array_equal(phiDelay, []):
1078
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
997
+ if not array_equal(phi_delay, []):
998
+ xInterpHelp = tile(virtNewCoord[0, :], (nTime, 1)) + tile(phi_delay, (virtNewCoord.shape[1], 1)).T
1079
999
  xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
1080
1000
  else:
1081
- xInterp = repmat(virtNewCoord[0, :], nTime, 1)
1001
+ xInterp = tile(virtNewCoord[0, :], (nTime, 1))
1082
1002
 
1083
1003
  mesh = meshList[0][0]
1084
1004
  for cntTime in range(nTime):
@@ -1137,7 +1057,7 @@ class SpatialInterpolator(TimeInOut):
1137
1057
  newPoint3_M = append(newPoint2_M, zeros([1, self.numchannels]), axis=0)
1138
1058
  newPointCart = cylToCart(newPoint3_M)
1139
1059
  for ind in arange(len(newPoint[:, 0])):
1140
- newPoint_Rep = repmat(newPointCart[:, ind], len(newPoint[:, 0]), 1).T
1060
+ newPoint_Rep = tile(newPointCart[:, ind], (len(newPoint[:, 0]), 1)).T
1141
1061
  subtract = newPoint_Rep - newCoordCart
1142
1062
  normDistance = norm(subtract, axis=0)
1143
1063
  index_norm = argsort(normDistance)[: self.num_IDW]
@@ -1153,11 +1073,11 @@ class SpatialInterpolator(TimeInOut):
1153
1073
  # Interpolation for arbitrary 3D Arrays
1154
1074
  elif self.array_dimension == '3D':
1155
1075
  # check rotation
1156
- if not array_equal(phiDelay, []):
1157
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
1076
+ if not array_equal(phi_delay, []):
1077
+ xInterpHelp = tile(virtNewCoord[0, :], (nTime, 1)) + tile(phi_delay, (virtNewCoord.shape[1], 1)).T
1158
1078
  xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
1159
1079
  else:
1160
- xInterp = repmat(virtNewCoord[0, :], nTime, 1)
1080
+ xInterp = tile(virtNewCoord[0, :], (nTime, 1))
1161
1081
 
1162
1082
  mesh = meshList[0][0]
1163
1083
  for cntTime in range(nTime):
@@ -1253,12 +1173,12 @@ class SpatialInterpolatorRotation(SpatialInterpolator):
1253
1173
  # period for rotation
1254
1174
  period = 2 * pi
1255
1175
  # get angle
1256
- angle = self.angle_source._get_angle()
1176
+ angle = self.angle_source.angle()
1257
1177
  # counter to track angle position in time for each block
1258
1178
  count = 0
1259
1179
  for timeData in self.source.result(num):
1260
- phiDelay = angle[count : count + num]
1261
- interpVal = self._result_core_func(timeData, phiDelay, period, self.Q, interp_at_zero=False)
1180
+ phi_delay = angle[count : count + num]
1181
+ interpVal = self._result_core_func(timeData, phi_delay, period, self.Q, interp_at_zero=False)
1262
1182
  yield interpVal
1263
1183
  count += num
1264
1184
 
@@ -1311,19 +1231,19 @@ class SpatialInterpolatorConstantRotation(SpatialInterpolator):
1311
1231
  phiOffset = 0.0
1312
1232
  for timeData in self.source.result(num):
1313
1233
  nTime = timeData.shape[0]
1314
- phiDelay = phiOffset + linspace(0, nTime / self.sample_freq * omega, nTime, endpoint=False)
1315
- interpVal = self._result_core_func(timeData, phiDelay, period, self.Q, interp_at_zero=False)
1316
- phiOffset = phiDelay[-1] + omega / self.sample_freq
1234
+ phi_delay = phiOffset + linspace(0, nTime / self.sample_freq * omega, nTime, endpoint=False)
1235
+ interpVal = self._result_core_func(timeData, phi_delay, period, self.Q, interp_at_zero=False)
1236
+ phiOffset = phi_delay[-1] + omega / self.sample_freq
1317
1237
  yield interpVal
1318
1238
 
1319
1239
 
1320
- class Mixer(TimeInOut):
1240
+ class Mixer(TimeOut):
1321
1241
  """Mixes the signals from several sources."""
1322
1242
 
1323
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` object.
1243
+ #: Data source; :class:`~acoular.base.SamplesGenerator` object.
1324
1244
  source = Trait(SamplesGenerator)
1325
1245
 
1326
- #: List of additional :class:`~acoular.tprocess.SamplesGenerator` objects
1246
+ #: List of additional :class:`~acoular.base.SamplesGenerator` objects
1327
1247
  #: to be mixed.
1328
1248
  sources = List(Instance(SamplesGenerator, ()))
1329
1249
 
@@ -1340,7 +1260,7 @@ class Mixer(TimeInOut):
1340
1260
  sdigest = Str()
1341
1261
 
1342
1262
  @observe('sources.items.digest')
1343
- def _set_sources_digest(self, event): # noqa ARG002
1263
+ def _set_sourcesdigest(self, event): # noqa ARG002
1344
1264
  self.sdigest = ldigest(self.sources)
1345
1265
 
1346
1266
  # internal identifier
@@ -1395,9 +1315,12 @@ class Mixer(TimeInOut):
1395
1315
  break
1396
1316
 
1397
1317
 
1398
- class TimePower(TimeInOut):
1318
+ class TimePower(TimeOut):
1399
1319
  """Calculates time-depended power of the signal."""
1400
1320
 
1321
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1322
+ source = Instance(SamplesGenerator)
1323
+
1401
1324
  def result(self, num):
1402
1325
  """Python generator that yields the output block-wise.
1403
1326
 
@@ -1418,64 +1341,12 @@ class TimePower(TimeInOut):
1418
1341
  yield temp * temp
1419
1342
 
1420
1343
 
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):
1344
+ class TimeCumAverage(TimeOut):
1477
1345
  """Calculates cumulative average of the signal, useful for Leq."""
1478
1346
 
1347
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1348
+ source = Instance(SamplesGenerator)
1349
+
1479
1350
  def result(self, num):
1480
1351
  """Python generator that yields the output block-wise.
1481
1352
 
@@ -1503,9 +1374,12 @@ class TimeCumAverage(TimeInOut):
1503
1374
  yield temp
1504
1375
 
1505
1376
 
1506
- class TimeReverse(TimeInOut):
1377
+ class TimeReverse(TimeOut):
1507
1378
  """Calculates the time-reversed signal of a source."""
1508
1379
 
1380
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1381
+ source = Instance(SamplesGenerator)
1382
+
1509
1383
  def result(self, num):
1510
1384
  """Python generator that yields the output block-wise.
1511
1385
 
@@ -1522,20 +1396,20 @@ class TimeReverse(TimeInOut):
1522
1396
  The last block may be shorter than num.
1523
1397
 
1524
1398
  """
1525
- l = []
1526
- l.extend(self.source.result(num))
1527
- temp = empty_like(l[0])
1528
- h = l.pop()
1399
+ result_list = []
1400
+ result_list.extend(self.source.result(num))
1401
+ temp = empty_like(result_list[0])
1402
+ h = result_list.pop()
1529
1403
  nsh = h.shape[0]
1530
1404
  temp[:nsh] = h[::-1]
1531
- for h in l[::-1]:
1405
+ for h in result_list[::-1]:
1532
1406
  temp[nsh:] = h[: nsh - 1 : -1]
1533
1407
  yield temp
1534
1408
  temp[:nsh] = h[nsh - 1 :: -1]
1535
1409
  yield temp[:nsh]
1536
1410
 
1537
1411
 
1538
- class Filter(TimeInOut):
1412
+ class Filter(TimeOut):
1539
1413
  """Abstract base class for IIR filters based on scipy lfilter
1540
1414
  implements a filter with coefficients that may be changed
1541
1415
  during processing.
@@ -1543,6 +1417,9 @@ class Filter(TimeInOut):
1543
1417
  Should not be instanciated by itself
1544
1418
  """
1545
1419
 
1420
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1421
+ source = Instance(SamplesGenerator)
1422
+
1546
1423
  #: Filter coefficients
1547
1424
  sos = Property()
1548
1425
 
@@ -1751,13 +1628,16 @@ class FiltFreqWeight(Filter):
1751
1628
  return tf2sos(b, a)
1752
1629
 
1753
1630
 
1754
- class FilterBank(TimeInOut):
1631
+ class FilterBank(TimeOut):
1755
1632
  """Abstract base class for IIR filter banks based on scipy lfilter
1756
1633
  implements a bank of parallel filters.
1757
1634
 
1758
1635
  Should not be instanciated by itself
1759
1636
  """
1760
1637
 
1638
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1639
+ source = Instance(SamplesGenerator)
1640
+
1761
1641
  #: List of filter coefficients for all filters
1762
1642
  sos = Property()
1763
1643
 
@@ -1856,136 +1736,14 @@ class OctaveFilterBank(FilterBank):
1856
1736
  return sos
1857
1737
 
1858
1738
 
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):
1739
+ class WriteWAV(TimeOut):
1985
1740
  """Saves time signal from one or more channels as mono/stereo/multi-channel
1986
1741
  `*.wav` file.
1987
1742
  """
1988
1743
 
1744
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1745
+ source = Instance(SamplesGenerator)
1746
+
1989
1747
  #: Name of the file to be saved. If none is given, the name will be
1990
1748
  #: automatically generated from the sources.
1991
1749
  name = File(filter=['*.wav'], desc='name of wave file')
@@ -2048,9 +1806,12 @@ class WriteWAV(TimeInOut):
2048
1806
  wf.close()
2049
1807
 
2050
1808
 
2051
- class WriteH5(TimeInOut):
1809
+ class WriteH5(TimeOut):
2052
1810
  """Saves time signal as `*.h5` file."""
2053
1811
 
1812
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1813
+ source = Instance(SamplesGenerator)
1814
+
2054
1815
  #: Name of the file to be saved. If none is given, the name will be
2055
1816
  #: automatically generated from a time stamp.
2056
1817
  name = File(filter=['*.h5'], desc='name of data file')
@@ -2142,165 +1903,23 @@ class WriteH5(TimeInOut):
2142
1903
  break
2143
1904
  try:
2144
1905
  data = next(source_gen)
2145
- except:
1906
+ except StopIteration:
2146
1907
  break
2147
1908
  f5h.append_data(ac, data[:anz])
2148
- yield data
2149
1909
  f5h.flush()
1910
+ yield data
2150
1911
  scount += anz
2151
1912
  f5h.close()
2152
1913
 
2153
1914
 
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()
2163
-
2164
- def __next__(self): # this function implementation is not python 2 compatible!
2165
- with self.lock:
2166
- return self.it.__next__()
2167
-
1915
+ class TimeConvolve(TimeOut):
1916
+ """Uniformly partitioned overlap-save method (UPOLS) for fast convolution in the frequency domain.
2168
1917
 
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`) .
1918
+ See :cite:`Wefers2015` for details.
2173
1919
  """
2174
1920
 
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>`."""
1921
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
1922
+ source = Instance(SamplesGenerator)
2304
1923
 
2305
1924
  #: Convolution kernel in the time domain.
2306
1925
  #: The second dimension of the kernel array has to be either 1 or match :attr:`~SamplesGenerator.numchannels`.
@@ -2376,13 +1995,13 @@ class TimeConvolve(TimeInOut):
2376
1995
  L = self.kernel.shape[0]
2377
1996
  N = self.source.numchannels
2378
1997
  M = self.source.numsamples
2379
- P = int(ceil(L / num)) # number of kernel blocks
1998
+ numblocks_kernel = int(ceil(L / num)) # number of kernel blocks
2380
1999
  Q = int(ceil(M / num)) # number of signal blocks
2381
2000
  R = int(ceil((L + M - 1) / num)) # number of output blocks
2382
2001
  last_size = (L + M - 1) % num # size of final block
2383
2002
 
2384
2003
  idx = 0
2385
- FDL = zeros([P, num + 1, N], dtype='complex128')
2004
+ fdl = zeros([numblocks_kernel, num + 1, N], dtype='complex128')
2386
2005
  buff = zeros([2 * num, N]) # time-domain input buffer
2387
2006
  spec_sum = zeros([num + 1, N], dtype='complex128')
2388
2007
 
@@ -2392,16 +2011,16 @@ class TimeConvolve(TimeInOut):
2392
2011
 
2393
2012
  # for very short signals, we are already done
2394
2013
  if R == 1:
2395
- _append_to_FDL(FDL, idx, P, rfft(buff, axis=0))
2396
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks)
2014
+ _append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
2015
+ spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
2397
2016
  # truncate s.t. total length is L+M-1 (like numpy convolve w/ mode="full")
2398
2017
  yield irfft(spec_sum, axis=0)[num : last_size + num]
2399
2018
  return
2400
2019
 
2401
2020
  # stream processing of source signal
2402
2021
  for temp in signal_blocks:
2403
- _append_to_FDL(FDL, idx, P, rfft(buff, axis=0))
2404
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks)
2022
+ _append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
2023
+ spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
2405
2024
  yield irfft(spec_sum, axis=0)[num:]
2406
2025
  buff = concatenate(
2407
2026
  [buff[num:], zeros([num, N])],
@@ -2410,33 +2029,50 @@ class TimeConvolve(TimeInOut):
2410
2029
  buff[num : num + temp.shape[0]] = temp # append new time-data
2411
2030
 
2412
2031
  for _ in range(R - Q):
2413
- _append_to_FDL(FDL, idx, P, rfft(buff, axis=0))
2414
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks)
2032
+ _append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
2033
+ spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
2415
2034
  yield irfft(spec_sum, axis=0)[num:]
2416
2035
  buff = concatenate(
2417
2036
  [buff[num:], zeros([num, N])],
2418
2037
  axis=0,
2419
2038
  ) # shift input buffer to the left
2420
2039
 
2421
- _append_to_FDL(FDL, idx, P, rfft(buff, axis=0))
2422
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks)
2040
+ _append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
2041
+ spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
2423
2042
  # truncate s.t. total length is L+M-1 (like numpy convolve w/ mode="full")
2424
2043
  yield irfft(spec_sum, axis=0)[num : last_size + num]
2425
2044
 
2426
2045
 
2427
2046
  @nb.jit(nopython=True, cache=True)
2428
- def _append_to_FDL(FDL, idx, P, buff):
2429
- FDL[idx] = buff
2430
- idx = int(idx + 1 % P)
2047
+ def _append_to_fdl(fdl, idx, numblocks_kernel, buff):
2048
+ fdl[idx] = buff
2049
+ idx = int(idx + 1 % numblocks_kernel)
2431
2050
 
2432
2051
 
2433
2052
  @nb.jit(nopython=True, cache=True)
2434
- def _spectral_sum(out, FDL, KB):
2435
- P, B, N = KB.shape
2053
+ def _spectral_sum(out, fdl, kb):
2054
+ P, B, N = kb.shape
2436
2055
  for n in range(N):
2437
2056
  for b in range(B):
2438
2057
  out[b, n] = 0
2439
2058
  for i in range(P):
2440
- out[b, n] += FDL[i, b, n] * KB[i, b, n]
2059
+ out[b, n] += fdl[i, b, n] * kb[i, b, n]
2441
2060
 
2442
2061
  return out
2062
+
2063
+
2064
+ class MaskedTimeInOut(MaskedTimeOut):
2065
+ """Signal processing block for channel and sample selection (alias for :class:`~acoular.tprocess.MaskedTimeOut`.).
2066
+
2067
+ .. 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.
2070
+ """
2071
+
2072
+ def __init__(self, *args, **kwargs):
2073
+ super().__init__(*args, **kwargs)
2074
+ warn(
2075
+ 'Using MaskedTimeInOut is deprecated and will be removed in Acoular version 25.07. Use class MaskedTimeOut instead.',
2076
+ DeprecationWarning,
2077
+ stacklevel=2,
2078
+ )