acoular 24.7__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
@@ -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
 
@@ -721,12 +641,15 @@ class AngleTracker(MaskedTimeInOut):
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
@@ -1017,11 +937,11 @@ class SpatialInterpolator(TimeInOut):
1017
937
  if self.array_dimension == '1D' or self.array_dimension == 'ring':
1018
938
  # for rotation add phi_delay
1019
939
  if not array_equal(phi_delay, []):
1020
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
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):
@@ -1075,10 +995,10 @@ class SpatialInterpolator(TimeInOut):
1075
995
  elif self.array_dimension == '2D':
1076
996
  # check rotation
1077
997
  if not array_equal(phi_delay, []):
1078
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
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]
@@ -1154,10 +1074,10 @@ class SpatialInterpolator(TimeInOut):
1154
1074
  elif self.array_dimension == '3D':
1155
1075
  # check rotation
1156
1076
  if not array_equal(phi_delay, []):
1157
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
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):
@@ -1317,13 +1237,13 @@ class SpatialInterpolatorConstantRotation(SpatialInterpolator):
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
 
@@ -1535,7 +1409,7 @@ class TimeReverse(TimeInOut):
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')
@@ -2145,162 +1906,20 @@ class WriteH5(TimeInOut):
2145
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`.
@@ -2440,3 +2059,20 @@ def _spectral_sum(out, fdl, kb):
2440
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
+ )