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/__init__.py +17 -8
- acoular/base.py +312 -0
- acoular/configuration.py +3 -3
- acoular/demo/acoular_demo.py +1 -1
- acoular/environments.py +7 -5
- acoular/fbeamform.py +221 -58
- acoular/fprocess.py +368 -0
- acoular/grids.py +31 -17
- acoular/process.py +464 -0
- acoular/sdinput.py +14 -3
- acoular/signals.py +6 -6
- acoular/sources.py +20 -7
- acoular/spectra.py +29 -48
- acoular/tbeamform.py +264 -141
- acoular/tools/__init__.py +2 -0
- acoular/tools/aiaa.py +3 -3
- acoular/tools/helpers.py +2 -2
- acoular/tools/metrics.py +1 -1
- acoular/tools/utils.py +210 -0
- acoular/tprocess.py +89 -453
- acoular/traitsviews.py +5 -3
- acoular/version.py +2 -2
- {acoular-24.7.dist-info → acoular-24.10.dist-info}/METADATA +28 -7
- {acoular-24.7.dist-info → acoular-24.10.dist-info}/RECORD +27 -23
- {acoular-24.7.dist-info → acoular-24.10.dist-info}/WHEEL +0 -0
- {acoular-24.7.dist-info → acoular-24.10.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.7.dist-info → acoular-24.10.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
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
|
-
|
|
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 .
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
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.
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
1240
|
+
class Mixer(TimeOut):
|
|
1321
1241
|
"""Mixes the signals from several sources."""
|
|
1322
1242
|
|
|
1323
|
-
#: Data source; :class:`~acoular.
|
|
1243
|
+
#: Data source; :class:`~acoular.base.SamplesGenerator` object.
|
|
1324
1244
|
source = Trait(SamplesGenerator)
|
|
1325
1245
|
|
|
1326
|
-
#: List of additional :class:`~acoular.
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
2155
|
-
"""
|
|
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
|
-
|
|
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
|
-
#:
|
|
2176
|
-
|
|
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
|
+
)
|