acoular 24.7__py3-none-any.whl → 25.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acoular/__init__.py +21 -9
- acoular/aiaa/__init__.py +12 -0
- acoular/{tools → aiaa}/aiaa.py +26 -31
- acoular/base.py +332 -0
- acoular/calib.py +129 -34
- acoular/configuration.py +13 -11
- acoular/demo/__init__.py +1 -0
- acoular/demo/acoular_demo.py +30 -17
- acoular/deprecation.py +85 -0
- acoular/environments.py +38 -24
- acoular/fastFuncs.py +90 -84
- acoular/fbeamform.py +342 -387
- acoular/fprocess.py +376 -0
- acoular/grids.py +122 -150
- acoular/h5cache.py +29 -40
- acoular/h5files.py +2 -6
- acoular/microphones.py +50 -59
- acoular/process.py +771 -0
- acoular/sdinput.py +35 -21
- acoular/signals.py +120 -113
- acoular/sources.py +208 -234
- acoular/spectra.py +59 -254
- acoular/tbeamform.py +280 -280
- acoular/tfastfuncs.py +21 -21
- acoular/tools/__init__.py +3 -7
- acoular/tools/helpers.py +218 -4
- acoular/tools/metrics.py +5 -5
- acoular/tools/utils.py +116 -0
- acoular/tprocess.py +416 -741
- acoular/traitsviews.py +15 -13
- acoular/trajectory.py +7 -10
- acoular/version.py +2 -2
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/METADATA +63 -21
- acoular-25.1.dist-info/RECORD +56 -0
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/WHEEL +1 -1
- acoular-24.7.dist-info/RECORD +0 -50
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/licenses/LICENSE +0 -0
acoular/spectra.py
CHANGED
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
:toctree: generated/
|
|
8
8
|
|
|
9
9
|
BaseSpectra
|
|
10
|
-
FFTSpectra
|
|
11
10
|
PowerSpectra
|
|
12
|
-
synthetic
|
|
13
11
|
PowerSpectraImport
|
|
14
12
|
"""
|
|
15
13
|
|
|
14
|
+
from abc import abstractmethod
|
|
16
15
|
from warnings import warn
|
|
17
16
|
|
|
18
17
|
from numpy import (
|
|
@@ -26,52 +25,53 @@ from numpy import (
|
|
|
26
25
|
hamming,
|
|
27
26
|
hanning,
|
|
28
27
|
imag,
|
|
29
|
-
isscalar,
|
|
30
28
|
linalg,
|
|
31
29
|
ndarray,
|
|
32
30
|
newaxis,
|
|
33
31
|
ones,
|
|
34
32
|
real,
|
|
35
33
|
searchsorted,
|
|
36
|
-
|
|
37
|
-
sum,
|
|
34
|
+
sum, # noqa A004
|
|
38
35
|
zeros,
|
|
39
|
-
zeros_like,
|
|
40
36
|
)
|
|
41
37
|
from scipy import fft
|
|
42
38
|
from traits.api import (
|
|
39
|
+
ABCHasStrictTraits,
|
|
43
40
|
Bool,
|
|
44
41
|
CArray,
|
|
45
42
|
Delegate,
|
|
46
43
|
Enum,
|
|
47
44
|
Float,
|
|
48
|
-
HasPrivateTraits,
|
|
49
45
|
Instance,
|
|
50
46
|
Int,
|
|
47
|
+
Map,
|
|
51
48
|
Property,
|
|
52
|
-
|
|
49
|
+
Union,
|
|
53
50
|
cached_property,
|
|
54
51
|
property_depends_on,
|
|
55
52
|
)
|
|
56
53
|
|
|
57
|
-
|
|
54
|
+
# acoular imports
|
|
55
|
+
from .base import SamplesGenerator
|
|
58
56
|
from .configuration import config
|
|
57
|
+
from .deprecation import deprecated_alias
|
|
59
58
|
from .fastFuncs import calcCSM
|
|
60
59
|
from .h5cache import H5cache
|
|
61
60
|
from .h5files import H5CacheFileBase
|
|
62
61
|
from .internal import digest
|
|
63
|
-
from .
|
|
62
|
+
from .tools.utils import find_basename
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True)
|
|
66
|
+
class BaseSpectra(ABCHasStrictTraits):
|
|
67
67
|
#: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
|
|
68
|
-
source =
|
|
68
|
+
source = Instance(SamplesGenerator)
|
|
69
69
|
|
|
70
70
|
#: Sampling frequency of output signal, as given by :attr:`source`.
|
|
71
71
|
sample_freq = Delegate('source')
|
|
72
72
|
|
|
73
73
|
#: Number of time data channels
|
|
74
|
-
|
|
74
|
+
num_channels = Delegate('source')
|
|
75
75
|
|
|
76
76
|
#: Window function for FFT, one of:
|
|
77
77
|
#: * 'Rectangular' (default)
|
|
@@ -79,23 +79,22 @@ class BaseSpectra(HasPrivateTraits):
|
|
|
79
79
|
#: * 'Hamming'
|
|
80
80
|
#: * 'Bartlett'
|
|
81
81
|
#: * 'Blackman'
|
|
82
|
-
window =
|
|
83
|
-
'Rectangular',
|
|
82
|
+
window = Map(
|
|
84
83
|
{'Rectangular': ones, 'Hanning': hanning, 'Hamming': hamming, 'Bartlett': bartlett, 'Blackman': blackman},
|
|
84
|
+
default_value='Rectangular',
|
|
85
85
|
desc='type of window for FFT',
|
|
86
86
|
)
|
|
87
87
|
|
|
88
88
|
#: Overlap factor for averaging: 'None'(default), '50%', '75%', '87.5%'.
|
|
89
|
-
overlap =
|
|
89
|
+
overlap = Map({'None': 1, '50%': 2, '75%': 4, '87.5%': 8}, default_value='None', desc='overlap of FFT blocks')
|
|
90
90
|
|
|
91
91
|
#: FFT block size, one of: 128, 256, 512, 1024, 2048 ... 65536,
|
|
92
92
|
#: defaults to 1024.
|
|
93
|
-
block_size =
|
|
93
|
+
block_size = Enum(
|
|
94
94
|
1024,
|
|
95
95
|
128,
|
|
96
96
|
256,
|
|
97
97
|
512,
|
|
98
|
-
1024,
|
|
99
98
|
2048,
|
|
100
99
|
4096,
|
|
101
100
|
8192,
|
|
@@ -105,16 +104,16 @@ class BaseSpectra(HasPrivateTraits):
|
|
|
105
104
|
desc='number of samples per FFT block',
|
|
106
105
|
)
|
|
107
106
|
|
|
108
|
-
#: The floating-number-precision of
|
|
109
|
-
#:
|
|
110
|
-
precision =
|
|
107
|
+
#: The floating-number-precision of the resulting spectra, corresponding to numpy dtypes.
|
|
108
|
+
#: Default is 'complex128'.
|
|
109
|
+
precision = Enum('complex128', 'complex64', desc='precision of the fft')
|
|
111
110
|
|
|
112
111
|
# internal identifier
|
|
113
112
|
digest = Property(depends_on=['precision', 'block_size', 'window', 'overlap'])
|
|
114
113
|
|
|
115
|
-
@
|
|
114
|
+
@abstractmethod
|
|
116
115
|
def _get_digest(self):
|
|
117
|
-
|
|
116
|
+
"""Return internal identifier."""
|
|
118
117
|
|
|
119
118
|
def fftfreq(self):
|
|
120
119
|
"""Return the Discrete Fourier Transform sample frequencies.
|
|
@@ -130,9 +129,9 @@ class BaseSpectra(HasPrivateTraits):
|
|
|
130
129
|
return None
|
|
131
130
|
|
|
132
131
|
# generator that yields the time data blocks for every channel (with optional overlap)
|
|
133
|
-
def
|
|
132
|
+
def _get_source_data(self):
|
|
134
133
|
bs = self.block_size
|
|
135
|
-
temp = empty((2 * bs, self.
|
|
134
|
+
temp = empty((2 * bs, self.num_channels))
|
|
136
135
|
pos = bs
|
|
137
136
|
posinc = bs / self.overlap_
|
|
138
137
|
for data_block in self.source.result(bs):
|
|
@@ -146,52 +145,16 @@ class BaseSpectra(HasPrivateTraits):
|
|
|
146
145
|
pos -= bs
|
|
147
146
|
|
|
148
147
|
|
|
149
|
-
class FFTSpectra(BaseSpectra, TimeInOut):
|
|
150
|
-
"""Provides the spectra of multichannel time data.
|
|
151
|
-
|
|
152
|
-
Returns Spectra per block over a Generator.
|
|
153
|
-
"""
|
|
154
|
-
|
|
155
|
-
# internal identifier
|
|
156
|
-
digest = Property(depends_on=['source.digest', 'precision', 'block_size', 'window', 'overlap'])
|
|
157
|
-
|
|
158
|
-
@cached_property
|
|
159
|
-
def _get_digest(self):
|
|
160
|
-
return digest(self)
|
|
161
|
-
|
|
162
|
-
# generator that yields the fft for every channel
|
|
163
|
-
def result(self):
|
|
164
|
-
"""Python generator that yields the output block-wise.
|
|
165
|
-
|
|
166
|
-
Parameters
|
|
167
|
-
----------
|
|
168
|
-
num : integer
|
|
169
|
-
This parameter defines the size of the blocks to be yielded
|
|
170
|
-
(i.e. the number of samples per block).
|
|
171
|
-
|
|
172
|
-
Returns
|
|
173
|
-
-------
|
|
174
|
-
Samples in blocks of shape (numfreq, :attr:`numchannels`).
|
|
175
|
-
The last block may be shorter than num.
|
|
176
|
-
|
|
177
|
-
"""
|
|
178
|
-
wind = self.window_(self.block_size)
|
|
179
|
-
weight = sqrt(2) / self.block_size * sqrt(self.block_size / dot(wind, wind)) * wind[:, newaxis]
|
|
180
|
-
for data in self.get_source_data():
|
|
181
|
-
ft = fft.rfft(data * weight, None, 0).astype(self.precision)
|
|
182
|
-
yield ft
|
|
183
|
-
|
|
184
|
-
|
|
185
148
|
class PowerSpectra(BaseSpectra):
|
|
186
149
|
"""Provides the cross spectral matrix of multichannel time data
|
|
187
150
|
and its eigen-decomposition.
|
|
188
151
|
|
|
189
152
|
This class includes the efficient calculation of the full cross spectral
|
|
190
|
-
matrix using the Welch method with windows and overlap. It also contains
|
|
153
|
+
matrix using the Welch method with windows and overlap (:cite:`Welch1967`). It also contains
|
|
191
154
|
the CSM's eigenvalues and eigenvectors and additional properties.
|
|
192
155
|
|
|
193
156
|
The result is computed only when needed, that is when the :attr:`csm`,
|
|
194
|
-
:attr:`eva`, or :attr:`eve` attributes are
|
|
157
|
+
:attr:`eva`, or :attr:`eve` attributes are actually read.
|
|
195
158
|
Any change in the input data or parameters leads to a new calculation,
|
|
196
159
|
again triggered when an attribute is read. The result may be
|
|
197
160
|
cached on disk in HDF5 files and need not to be recomputed during
|
|
@@ -200,30 +163,14 @@ class PowerSpectra(BaseSpectra):
|
|
|
200
163
|
and the same file name in case of that the data is read from a file.
|
|
201
164
|
"""
|
|
202
165
|
|
|
203
|
-
# Shadow trait, should not be set directly, for internal use.
|
|
204
|
-
_source = Trait(SamplesGenerator)
|
|
205
|
-
|
|
206
166
|
#: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
|
|
207
|
-
source =
|
|
208
|
-
|
|
209
|
-
#: The :class:`~acoular.tprocess.SamplesGenerator` object that provides the data.
|
|
210
|
-
time_data = Property(
|
|
211
|
-
_source,
|
|
212
|
-
desc='deprecated attribute holding the time data object. Use PowerSpectra.source instead!',
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
#: The :class:`~acoular.calib.Calib` object that provides the calibration data,
|
|
216
|
-
#: defaults to no calibration, i.e. the raw time data is used.
|
|
217
|
-
#:
|
|
218
|
-
#: **deprecated**: use :attr:`~acoular.sources.TimeSamples.calib` property of
|
|
219
|
-
#: :class:`~acoular.sources.TimeSamples` objects
|
|
220
|
-
calib = Instance(Calib)
|
|
167
|
+
source = Instance(SamplesGenerator)
|
|
221
168
|
|
|
222
169
|
# Shadow trait, should not be set directly, for internal use.
|
|
223
170
|
_ind_low = Int(1, desc='index of lowest frequency line')
|
|
224
171
|
|
|
225
172
|
# Shadow trait, should not be set directly, for internal use.
|
|
226
|
-
_ind_high =
|
|
173
|
+
_ind_high = Union(Int(-1), None, desc='index of highest frequency line')
|
|
227
174
|
|
|
228
175
|
#: Index of lowest frequency line to compute, integer, defaults to 1,
|
|
229
176
|
#: is used only by objects that fetch the csm, PowerSpectra computes every
|
|
@@ -238,7 +185,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
238
185
|
_freqlc = Float(0)
|
|
239
186
|
|
|
240
187
|
# Stores the set higher frequency, for internal use, should not be set directly.
|
|
241
|
-
_freqhc =
|
|
188
|
+
_freqhc = Union(Float(0), None)
|
|
242
189
|
|
|
243
190
|
# Saves whether the user set indices or frequencies last, for internal use only,
|
|
244
191
|
# not to be set directly, if True (default), indices are used for setting
|
|
@@ -268,10 +215,10 @@ class PowerSpectra(BaseSpectra):
|
|
|
268
215
|
indices = Property(desc='index range')
|
|
269
216
|
|
|
270
217
|
#: Name of the cache file without extension, readonly.
|
|
271
|
-
basename = Property(depends_on='
|
|
218
|
+
basename = Property(depends_on=['source.digest'], desc='basename for cache file')
|
|
272
219
|
|
|
273
220
|
#: The cross spectral matrix,
|
|
274
|
-
#: (number of frequencies,
|
|
221
|
+
#: (number of frequencies, num_channels, num_channels) array of complex;
|
|
275
222
|
#: readonly.
|
|
276
223
|
csm = Property(desc='cross spectral matrix')
|
|
277
224
|
|
|
@@ -280,23 +227,23 @@ class PowerSpectra(BaseSpectra):
|
|
|
280
227
|
eva = Property(desc='eigenvalues of cross spectral matrix')
|
|
281
228
|
|
|
282
229
|
#: Eigenvectors of the cross spectral matrix as an
|
|
283
|
-
#: (number of frequencies,
|
|
230
|
+
#: (number of frequencies, num_channels, num_channels) array of floats,
|
|
284
231
|
#: readonly.
|
|
285
232
|
eve = Property(desc='eigenvectors of cross spectral matrix')
|
|
286
233
|
|
|
287
234
|
# internal identifier
|
|
288
235
|
digest = Property(
|
|
289
|
-
depends_on=['
|
|
236
|
+
depends_on=['source.digest', 'block_size', 'window', 'overlap', 'precision'],
|
|
290
237
|
)
|
|
291
238
|
|
|
292
239
|
# hdf5 cache file
|
|
293
240
|
h5f = Instance(H5CacheFileBase, transient=True)
|
|
294
241
|
|
|
295
|
-
@property_depends_on('
|
|
242
|
+
@property_depends_on(['source.num_samples', 'block_size', 'overlap'])
|
|
296
243
|
def _get_num_blocks(self):
|
|
297
|
-
return self.overlap_ * self.
|
|
244
|
+
return self.overlap_ * self.source.num_samples / self.block_size - self.overlap_ + 1
|
|
298
245
|
|
|
299
|
-
@property_depends_on('
|
|
246
|
+
@property_depends_on(['source.sample_freq', 'block_size', 'ind_low', 'ind_high'])
|
|
300
247
|
def _get_freq_range(self):
|
|
301
248
|
fftfreq = self.fftfreq()
|
|
302
249
|
if fftfreq is not None:
|
|
@@ -310,7 +257,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
310
257
|
self._freqlc = freq_range[0]
|
|
311
258
|
self._freqhc = freq_range[1]
|
|
312
259
|
|
|
313
|
-
@property_depends_on('
|
|
260
|
+
@property_depends_on(['source.sample_freq', 'block_size', '_ind_low', '_freqlc'])
|
|
314
261
|
def _get_ind_low(self):
|
|
315
262
|
fftfreq = self.fftfreq()
|
|
316
263
|
if fftfreq is not None:
|
|
@@ -319,7 +266,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
319
266
|
return searchsorted(fftfreq[:-1], self._freqlc)
|
|
320
267
|
return None
|
|
321
268
|
|
|
322
|
-
@property_depends_on('
|
|
269
|
+
@property_depends_on(['source.sample_freq', 'block_size', '_ind_high', '_freqhc'])
|
|
323
270
|
def _get_ind_high(self):
|
|
324
271
|
fftfreq = self.fftfreq()
|
|
325
272
|
if fftfreq is not None:
|
|
@@ -340,19 +287,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
340
287
|
self._index_set_last = True
|
|
341
288
|
self._ind_low = ind_low
|
|
342
289
|
|
|
343
|
-
|
|
344
|
-
self._source = time_data
|
|
345
|
-
|
|
346
|
-
def _set_source(self, source):
|
|
347
|
-
self._source = source
|
|
348
|
-
|
|
349
|
-
def _get_time_data(self):
|
|
350
|
-
return self._source
|
|
351
|
-
|
|
352
|
-
def _get_source(self):
|
|
353
|
-
return self._source
|
|
354
|
-
|
|
355
|
-
@property_depends_on('block_size, ind_low, ind_high')
|
|
290
|
+
@property_depends_on(['block_size', 'ind_low', 'ind_high'])
|
|
356
291
|
def _get_indices(self):
|
|
357
292
|
fftfreq = self.fftfreq()
|
|
358
293
|
if fftfreq is not None:
|
|
@@ -371,9 +306,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
371
306
|
|
|
372
307
|
@cached_property
|
|
373
308
|
def _get_basename(self):
|
|
374
|
-
|
|
375
|
-
return self._source.basename
|
|
376
|
-
return self._source.__class__.__name__ + self._source.digest
|
|
309
|
+
return find_basename(self.source, alternative_basename=self.source.__class__.__name__ + self.source.digest)
|
|
377
310
|
|
|
378
311
|
def calc_csm(self):
|
|
379
312
|
"""Csm calculation."""
|
|
@@ -382,17 +315,10 @@ class PowerSpectra(BaseSpectra):
|
|
|
382
315
|
weight = dot(wind, wind)
|
|
383
316
|
wind = wind[newaxis, :].swapaxes(0, 1)
|
|
384
317
|
numfreq = int(self.block_size / 2 + 1)
|
|
385
|
-
csm_shape = (numfreq, t.
|
|
318
|
+
csm_shape = (numfreq, t.num_channels, t.num_channels)
|
|
386
319
|
csm_upper = zeros(csm_shape, dtype=self.precision)
|
|
387
|
-
# print "num blocks", self.num_blocks
|
|
388
|
-
# for backward compatibility
|
|
389
|
-
if self.calib and self.calib.num_mics > 0:
|
|
390
|
-
if self.calib.num_mics == t.numchannels:
|
|
391
|
-
wind = wind * self.calib.data[newaxis, :]
|
|
392
|
-
else:
|
|
393
|
-
raise ValueError('Calibration data not compatible: %i, %i' % (self.calib.num_mics, t.numchannels))
|
|
394
320
|
# get time data blockwise
|
|
395
|
-
for data in self.
|
|
321
|
+
for data in self._get_source_data():
|
|
396
322
|
ft = fft.rfft(data * wind, None, 0).astype(self.precision)
|
|
397
323
|
calcCSM(csm_upper, ft) # only upper triangular part of matrix is calculated (for speed reasons)
|
|
398
324
|
# create the full csm matrix via transposing and complex conj.
|
|
@@ -424,23 +350,6 @@ class PowerSpectra(BaseSpectra):
|
|
|
424
350
|
"""Calculates eigenvectors of csm."""
|
|
425
351
|
return self.calc_ev()[1]
|
|
426
352
|
|
|
427
|
-
def _handle_dual_calibration(self):
|
|
428
|
-
obj = self.source # start with time_data obj
|
|
429
|
-
while obj:
|
|
430
|
-
if 'calib' in obj.all_trait_names(): # at original source?
|
|
431
|
-
if obj.calib and self.calib:
|
|
432
|
-
if obj.calib.digest == self.calib.digest:
|
|
433
|
-
self.calib = None # ignore it silently
|
|
434
|
-
else:
|
|
435
|
-
msg = 'Non-identical dual calibration for both TimeSamples and PowerSpectra object'
|
|
436
|
-
raise ValueError(msg)
|
|
437
|
-
obj = None
|
|
438
|
-
else:
|
|
439
|
-
try:
|
|
440
|
-
obj = obj.source # traverse down until original data source
|
|
441
|
-
except AttributeError:
|
|
442
|
-
obj = None
|
|
443
|
-
|
|
444
353
|
def _get_filecache(self, traitname):
|
|
445
354
|
"""Function handles result caching of csm, eigenvectors and eigenvalues
|
|
446
355
|
calculation depending on global/local caching behaviour.
|
|
@@ -448,7 +357,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
448
357
|
if traitname == 'csm':
|
|
449
358
|
func = self.calc_csm
|
|
450
359
|
numfreq = int(self.block_size / 2 + 1)
|
|
451
|
-
shape = (numfreq, self.
|
|
360
|
+
shape = (numfreq, self.source.num_channels, self.source.num_channels)
|
|
452
361
|
precision = self.precision
|
|
453
362
|
elif traitname == 'eva':
|
|
454
363
|
func = self.calc_eva
|
|
@@ -484,18 +393,17 @@ class PowerSpectra(BaseSpectra):
|
|
|
484
393
|
self.h5f.flush()
|
|
485
394
|
return ac
|
|
486
395
|
|
|
487
|
-
@property_depends_on('digest')
|
|
396
|
+
@property_depends_on(['digest'])
|
|
488
397
|
def _get_csm(self):
|
|
489
398
|
"""Main work is done here:
|
|
490
399
|
Cross spectral matrix is either loaded from cache file or
|
|
491
400
|
calculated and then additionally stored into cache.
|
|
492
401
|
"""
|
|
493
|
-
self._handle_dual_calibration()
|
|
494
402
|
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
495
403
|
return self.calc_csm()
|
|
496
404
|
return self._get_filecache('csm')
|
|
497
405
|
|
|
498
|
-
@property_depends_on('digest')
|
|
406
|
+
@property_depends_on(['digest'])
|
|
499
407
|
def _get_eva(self):
|
|
500
408
|
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
501
409
|
calculated and then additionally stored into cache.
|
|
@@ -504,7 +412,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
504
412
|
return self.calc_eva()
|
|
505
413
|
return self._get_filecache('eva')
|
|
506
414
|
|
|
507
|
-
@property_depends_on('digest')
|
|
415
|
+
@property_depends_on(['digest'])
|
|
508
416
|
def _get_eve(self):
|
|
509
417
|
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
510
418
|
calculated and then additionally stored into cache.
|
|
@@ -551,101 +459,6 @@ class PowerSpectra(BaseSpectra):
|
|
|
551
459
|
return sum(self.eva[f1:f2], 0)
|
|
552
460
|
|
|
553
461
|
|
|
554
|
-
def synthetic(data, freqs, f, num=3):
|
|
555
|
-
"""Returns synthesized frequency band values of spectral data.
|
|
556
|
-
|
|
557
|
-
If used with :meth:`Beamformer.result()<acoular.fbeamform.BeamformerBase.result>`
|
|
558
|
-
and only one frequency band, the output is identical to the result of the intrinsic
|
|
559
|
-
:meth:`Beamformer.synthetic<acoular.fbeamform.BeamformerBase.synthetic>` method.
|
|
560
|
-
It can, however, also be used with the
|
|
561
|
-
:meth:`Beamformer.integrate<acoular.fbeamform.BeamformerBase.integrate>`
|
|
562
|
-
output and more frequency bands.
|
|
563
|
-
|
|
564
|
-
Parameters
|
|
565
|
-
----------
|
|
566
|
-
data : array of floats
|
|
567
|
-
The spectral data (squared sound pressures in Pa^2) in an array with one value
|
|
568
|
-
per frequency line.
|
|
569
|
-
The number of entries must be identical to the number of
|
|
570
|
-
grid points.
|
|
571
|
-
freq : array of floats
|
|
572
|
-
The frequencies that correspon to the input *data* (as yielded by
|
|
573
|
-
the :meth:`PowerSpectra.fftfreq<acoular.spectra.PowerSpectra.fftfreq>`
|
|
574
|
-
method).
|
|
575
|
-
f : float or list of floats
|
|
576
|
-
Band center frequency/frequencies for which to return the results.
|
|
577
|
-
num : integer
|
|
578
|
-
Controls the width of the frequency bands considered; defaults to
|
|
579
|
-
3 (third-octave band).
|
|
580
|
-
|
|
581
|
-
=== =====================
|
|
582
|
-
num frequency band width
|
|
583
|
-
=== =====================
|
|
584
|
-
0 single frequency line
|
|
585
|
-
1 octave band
|
|
586
|
-
3 third-octave band
|
|
587
|
-
n 1/n-octave band
|
|
588
|
-
=== =====================
|
|
589
|
-
|
|
590
|
-
Returns
|
|
591
|
-
-------
|
|
592
|
-
array of floats
|
|
593
|
-
Synthesized frequency band values of the beamforming result at
|
|
594
|
-
each grid point (the sum of all values that are contained in the band).
|
|
595
|
-
Note that the frequency resolution and therefore the bandwidth
|
|
596
|
-
represented by a single frequency line depends on
|
|
597
|
-
the :attr:`sampling frequency<acoular.tprocess.SamplesGenerator.sample_freq>`
|
|
598
|
-
and used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
599
|
-
|
|
600
|
-
"""
|
|
601
|
-
if isscalar(f):
|
|
602
|
-
f = (f,)
|
|
603
|
-
if num == 0:
|
|
604
|
-
# single frequency lines
|
|
605
|
-
res = []
|
|
606
|
-
for i in f:
|
|
607
|
-
ind = searchsorted(freqs, i)
|
|
608
|
-
if ind >= len(freqs):
|
|
609
|
-
warn(
|
|
610
|
-
'Queried frequency (%g Hz) not in resolved frequency range. Returning zeros.' % i,
|
|
611
|
-
Warning,
|
|
612
|
-
stacklevel=2,
|
|
613
|
-
)
|
|
614
|
-
h = zeros_like(data[0])
|
|
615
|
-
else:
|
|
616
|
-
if freqs[ind] != i:
|
|
617
|
-
warn(
|
|
618
|
-
f'Queried frequency ({i:g} Hz) not in set of '
|
|
619
|
-
'discrete FFT sample frequencies. '
|
|
620
|
-
f'Using frequency {freqs[ind]:g} Hz instead.',
|
|
621
|
-
Warning,
|
|
622
|
-
stacklevel=2,
|
|
623
|
-
)
|
|
624
|
-
h = data[ind]
|
|
625
|
-
res += [h]
|
|
626
|
-
else:
|
|
627
|
-
# fractional octave bands
|
|
628
|
-
res = []
|
|
629
|
-
for i in f:
|
|
630
|
-
f1 = i * 2.0 ** (-0.5 / num)
|
|
631
|
-
f2 = i * 2.0 ** (+0.5 / num)
|
|
632
|
-
ind1 = searchsorted(freqs, f1)
|
|
633
|
-
ind2 = searchsorted(freqs, f2)
|
|
634
|
-
if ind1 == ind2:
|
|
635
|
-
warn(
|
|
636
|
-
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
637
|
-
'include any discrete FFT sample frequencies. '
|
|
638
|
-
'Returning zeros.',
|
|
639
|
-
Warning,
|
|
640
|
-
stacklevel=2,
|
|
641
|
-
)
|
|
642
|
-
h = zeros_like(data[0])
|
|
643
|
-
else:
|
|
644
|
-
h = sum(data[ind1:ind2], 0)
|
|
645
|
-
res += [h]
|
|
646
|
-
return array(res)
|
|
647
|
-
|
|
648
|
-
|
|
649
462
|
class PowerSpectraImport(PowerSpectra):
|
|
650
463
|
"""Provides a dummy class for using pre-calculated cross-spectral
|
|
651
464
|
matrices.
|
|
@@ -655,27 +468,24 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
655
468
|
:attr:`csm` attribute. This can be useful when algorithms shall be
|
|
656
469
|
evaluated with existing CSM matrices.
|
|
657
470
|
The frequency or frequencies contained by the CSM must be set via the
|
|
658
|
-
attr:`frequencies` attribute. The attr:`
|
|
471
|
+
attr:`frequencies` attribute. The attr:`num_channels` attributes
|
|
659
472
|
is determined on the basis of the CSM shape.
|
|
660
473
|
In contrast to the PowerSpectra object, the attributes
|
|
661
|
-
:attr:`sample_freq`, :attr:`
|
|
662
|
-
:attr:`block_size`, :attr:`calib`, :attr:`window`,
|
|
474
|
+
:attr:`sample_freq`, :attr:`source`, :attr:`block_size`, :attr:`window`,
|
|
663
475
|
:attr:`overlap`, :attr:`cached`, and :attr:`num_blocks`
|
|
664
476
|
have no functionality.
|
|
665
477
|
"""
|
|
666
478
|
|
|
667
479
|
#: The cross spectral matrix,
|
|
668
|
-
#: (number of frequencies,
|
|
480
|
+
#: (number of frequencies, num_channels, num_channels) array of complex;
|
|
669
481
|
csm = Property(desc='cross spectral matrix')
|
|
670
482
|
|
|
671
483
|
#: frequencies included in the cross-spectral matrix in ascending order.
|
|
672
484
|
#: Compound trait that accepts arguments of type list, array, and float
|
|
673
|
-
frequencies =
|
|
485
|
+
frequencies = Union(CArray, Float, desc='frequencies included in the cross-spectral matrix')
|
|
674
486
|
|
|
675
487
|
#: Number of time data channels
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
time_data = Enum(None, desc='PowerSpectraImport cannot consume time data')
|
|
488
|
+
num_channels = Property(depends_on=['digest'])
|
|
679
489
|
|
|
680
490
|
source = Enum(None, desc='PowerSpectraImport cannot consume time data')
|
|
681
491
|
|
|
@@ -684,8 +494,6 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
684
494
|
|
|
685
495
|
block_size = Enum(None, desc='PowerSpectraImport does not operate on blocks of time data')
|
|
686
496
|
|
|
687
|
-
calib = Enum(None, desc='PowerSpectraImport cannot calibrate the time data')
|
|
688
|
-
|
|
689
497
|
window = Enum(None, desc='PowerSpectraImport does not perform windowing')
|
|
690
498
|
|
|
691
499
|
overlap = Enum(None, desc='PowerSpectraImport does not consume time data')
|
|
@@ -698,17 +506,13 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
698
506
|
_ind_low = Int(0, desc='index of lowest frequency line')
|
|
699
507
|
|
|
700
508
|
# Shadow trait, should not be set directly, for internal use.
|
|
701
|
-
_ind_high =
|
|
509
|
+
_ind_high = Union(None, Int, desc='index of highest frequency line')
|
|
702
510
|
|
|
703
511
|
# internal identifier
|
|
704
|
-
digest = Property(
|
|
705
|
-
depends_on=[
|
|
706
|
-
'_csmsum',
|
|
707
|
-
],
|
|
708
|
-
)
|
|
512
|
+
digest = Property(depends_on=['_csmsum'])
|
|
709
513
|
|
|
710
514
|
#: Name of the cache file without extension, readonly.
|
|
711
|
-
basename = Property(depends_on='digest', desc='basename for cache file')
|
|
515
|
+
basename = Property(depends_on=['digest'], desc='basename for cache file')
|
|
712
516
|
|
|
713
517
|
# csm shadow trait, only for internal use.
|
|
714
518
|
_csm = CArray()
|
|
@@ -723,7 +527,7 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
723
527
|
def _get_digest(self):
|
|
724
528
|
return digest(self)
|
|
725
529
|
|
|
726
|
-
def
|
|
530
|
+
def _get_num_channels(self):
|
|
727
531
|
return self.csm.shape[1]
|
|
728
532
|
|
|
729
533
|
def _get_csm(self):
|
|
@@ -731,19 +535,20 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
731
535
|
|
|
732
536
|
def _set_csm(self, csm):
|
|
733
537
|
if (len(csm.shape) != 3) or (csm.shape[1] != csm.shape[2]):
|
|
734
|
-
msg = 'The cross spectral matrix must have the following shape:
|
|
538
|
+
msg = 'The cross spectral matrix must have the following shape: \
|
|
539
|
+
(number of frequencies, num_channels, num_channels)!'
|
|
735
540
|
raise ValueError(msg)
|
|
736
541
|
self._csmsum = real(self._csm).sum() + (imag(self._csm) ** 2).sum() # to trigger new digest creation
|
|
737
542
|
self._csm = csm
|
|
738
543
|
|
|
739
|
-
@property_depends_on('digest')
|
|
544
|
+
@property_depends_on(['digest'])
|
|
740
545
|
def _get_eva(self):
|
|
741
546
|
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
742
547
|
calculated and then additionally stored into cache.
|
|
743
548
|
"""
|
|
744
549
|
return self.calc_eva()
|
|
745
550
|
|
|
746
|
-
@property_depends_on('digest')
|
|
551
|
+
@property_depends_on(['digest'])
|
|
747
552
|
def _get_eve(self):
|
|
748
553
|
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
749
554
|
calculated and then additionally stored into cache.
|