acoular 25.1__py3-none-any.whl → 25.3.post1__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/aiaa/aiaa.py +1 -1
- acoular/demo/acoular_demo.py +5 -5
- acoular/environments.py +458 -218
- acoular/fprocess.py +199 -97
- acoular/grids.py +714 -303
- acoular/microphones.py +157 -25
- acoular/process.py +405 -201
- acoular/signals.py +382 -87
- acoular/sources.py +1147 -286
- acoular/spectra.py +280 -128
- acoular/trajectory.py +119 -43
- acoular/version.py +2 -2
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/METADATA +6 -5
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/RECORD +17 -17
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/WHEEL +0 -0
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.1.dist-info → acoular-25.3.post1.dist-info}/licenses/LICENSE +0 -0
acoular/spectra.py
CHANGED
|
@@ -63,33 +63,55 @@ from .tools.utils import find_basename
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True)
|
|
66
|
+
@deprecated_alias({'time_data': 'source'}, read_only=False)
|
|
66
67
|
class BaseSpectra(ABCHasStrictTraits):
|
|
67
|
-
|
|
68
|
+
"""
|
|
69
|
+
Base class for handling spectral data in Acoular.
|
|
70
|
+
|
|
71
|
+
This class defines the basic structure and functionality for computing and managing spectral
|
|
72
|
+
data derived from time-domain signals. It includes properties for configuring the Fast Fourier
|
|
73
|
+
Transformation (FFT), including overlap, and other parameters critical for spectral analysis.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
#: Data source; an instance of :class:`~acoular.base.SamplesGenerator` or derived object.
|
|
68
77
|
source = Instance(SamplesGenerator)
|
|
69
78
|
|
|
70
|
-
#: Sampling frequency of output signal,
|
|
79
|
+
#: Sampling frequency of the output signal, delegated from :attr:`source`.
|
|
71
80
|
sample_freq = Delegate('source')
|
|
72
81
|
|
|
73
|
-
#: Number of time
|
|
82
|
+
#: Number of time microphones, delegated from :attr:`source`.
|
|
74
83
|
num_channels = Delegate('source')
|
|
75
84
|
|
|
76
|
-
#: Window function
|
|
77
|
-
#:
|
|
78
|
-
#:
|
|
79
|
-
#:
|
|
80
|
-
#:
|
|
81
|
-
#:
|
|
85
|
+
#: Window function applied during FFT. Can be one of:
|
|
86
|
+
#:
|
|
87
|
+
#: - ``'Rectangular'`` (default)
|
|
88
|
+
#:
|
|
89
|
+
#: - ``'Hanning'``
|
|
90
|
+
#:
|
|
91
|
+
#: - ``'Hamming'``
|
|
92
|
+
#:
|
|
93
|
+
#: - ``'Bartlett'``
|
|
94
|
+
#:
|
|
95
|
+
#: - ``'Blackman'``
|
|
82
96
|
window = Map(
|
|
83
97
|
{'Rectangular': ones, 'Hanning': hanning, 'Hamming': hamming, 'Bartlett': bartlett, 'Blackman': blackman},
|
|
84
98
|
default_value='Rectangular',
|
|
85
99
|
desc='type of window for FFT',
|
|
86
100
|
)
|
|
87
101
|
|
|
88
|
-
#: Overlap factor for
|
|
102
|
+
#: Overlap factor for FFT block averaging. One of:
|
|
103
|
+
#:
|
|
104
|
+
#: - ``'None'`` (default)
|
|
105
|
+
#:
|
|
106
|
+
#: - ``'50%'``
|
|
107
|
+
#:
|
|
108
|
+
#: - ``'75%'``
|
|
109
|
+
#:
|
|
110
|
+
#: - ``'87.5%'``
|
|
89
111
|
overlap = Map({'None': 1, '50%': 2, '75%': 4, '87.5%': 8}, default_value='None', desc='overlap of FFT blocks')
|
|
90
112
|
|
|
91
|
-
#: FFT block size
|
|
92
|
-
#:
|
|
113
|
+
#: FFT block size. Must be one of: ``128``, ``256``, ``512``, ``1024``, ... ``65536``.
|
|
114
|
+
#: Default is ``1024``.
|
|
93
115
|
block_size = Enum(
|
|
94
116
|
1024,
|
|
95
117
|
128,
|
|
@@ -104,11 +126,10 @@ class BaseSpectra(ABCHasStrictTraits):
|
|
|
104
126
|
desc='number of samples per FFT block',
|
|
105
127
|
)
|
|
106
128
|
|
|
107
|
-
#:
|
|
108
|
-
#: Default is 'complex128'.
|
|
129
|
+
#: Precision of the FFT, corresponding to NumPy dtypes. Default is ``'complex128'``.
|
|
109
130
|
precision = Enum('complex128', 'complex64', desc='precision of the fft')
|
|
110
131
|
|
|
111
|
-
|
|
132
|
+
#: A unique identifier for the spectra, based on its properties. (read-only)
|
|
112
133
|
digest = Property(depends_on=['precision', 'block_size', 'window', 'overlap'])
|
|
113
134
|
|
|
114
135
|
@abstractmethod
|
|
@@ -116,13 +137,42 @@ class BaseSpectra(ABCHasStrictTraits):
|
|
|
116
137
|
"""Return internal identifier."""
|
|
117
138
|
|
|
118
139
|
def fftfreq(self):
|
|
119
|
-
"""
|
|
140
|
+
"""
|
|
141
|
+
Compute and return the Discrete Fourier Transform sample frequencies.
|
|
142
|
+
|
|
143
|
+
This method generates the frequency values corresponding to the FFT bins for the
|
|
144
|
+
configured :attr:`block_size` and sampling frequency from the data source.
|
|
120
145
|
|
|
121
146
|
Returns
|
|
122
147
|
-------
|
|
123
|
-
|
|
124
|
-
Array of
|
|
125
|
-
|
|
148
|
+
:obj:`numpy.ndarray` or :obj:`None`
|
|
149
|
+
Array of shape ``(`` :attr:`block_size` ``/ 2 + 1,)`` containing the sample frequencies.
|
|
150
|
+
If :attr:`source` is not set, returns ``None``.
|
|
151
|
+
|
|
152
|
+
Examples
|
|
153
|
+
--------
|
|
154
|
+
Using normally distributed data for time samples as in
|
|
155
|
+
:class:`~acoular.sources.TimeSamples`.
|
|
156
|
+
|
|
157
|
+
>>> import numpy as np
|
|
158
|
+
>>> from acoular import TimeSamples
|
|
159
|
+
>>> from acoular.spectra import PowerSpectra
|
|
160
|
+
>>>
|
|
161
|
+
>>> data = np.random.rand(1000, 4)
|
|
162
|
+
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
163
|
+
>>> print(ts.num_channels, ts.num_samples, ts.sample_freq)
|
|
164
|
+
4 1000 51200.0
|
|
165
|
+
>>> ps = PowerSpectra(source=ts, block_size=128, window='Blackman')
|
|
166
|
+
>>> ps.fftfreq()
|
|
167
|
+
array([ 0., 400., 800., 1200., 1600., 2000., 2400., 2800.,
|
|
168
|
+
3200., 3600., 4000., 4400., 4800., 5200., 5600., 6000.,
|
|
169
|
+
6400., 6800., 7200., 7600., 8000., 8400., 8800., 9200.,
|
|
170
|
+
9600., 10000., 10400., 10800., 11200., 11600., 12000., 12400.,
|
|
171
|
+
12800., 13200., 13600., 14000., 14400., 14800., 15200., 15600.,
|
|
172
|
+
16000., 16400., 16800., 17200., 17600., 18000., 18400., 18800.,
|
|
173
|
+
19200., 19600., 20000., 20400., 20800., 21200., 21600., 22000.,
|
|
174
|
+
22400., 22800., 23200., 23600., 24000., 24400., 24800., 25200.,
|
|
175
|
+
25600.])
|
|
126
176
|
"""
|
|
127
177
|
if self.source is not None:
|
|
128
178
|
return abs(fft.fftfreq(self.block_size, 1.0 / self.source.sample_freq)[: int(self.block_size / 2 + 1)])
|
|
@@ -146,24 +196,26 @@ class BaseSpectra(ABCHasStrictTraits):
|
|
|
146
196
|
|
|
147
197
|
|
|
148
198
|
class PowerSpectra(BaseSpectra):
|
|
149
|
-
"""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
This class
|
|
153
|
-
|
|
154
|
-
the
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
199
|
+
"""
|
|
200
|
+
Provides the cross-spectral matrix of multichannel time-domain data and its eigen-decomposition.
|
|
201
|
+
|
|
202
|
+
This class is designed to compute the cross-spectral matrix (CSM) efficiently using the Welch
|
|
203
|
+
method :cite:`Welch1967` with support for windowing and overlapping data segments. It also
|
|
204
|
+
calculates the eigenvalues and eigenvectors of the CSM, allowing for spectral analysis and
|
|
205
|
+
advanced signal processing tasks.
|
|
206
|
+
|
|
207
|
+
Key features:
|
|
208
|
+
- **Efficient Calculation**: Computes the CSM using FFT-based methods.
|
|
209
|
+
- **Caching**: Results can be cached in HDF5 files to avoid redundant calculations for
|
|
210
|
+
identical inputs and parameters.
|
|
211
|
+
- **Lazy Evaluation**: Calculations are triggered only when attributes like :attr:`csm`,
|
|
212
|
+
:attr:`eva`, or :attr:`eve` are accessed.
|
|
213
|
+
- **Dynamic Input Handling**: Automatically recomputes results when the input data or
|
|
214
|
+
parameters change.
|
|
164
215
|
"""
|
|
165
216
|
|
|
166
|
-
#:
|
|
217
|
+
#: The data source for the time-domain samples. It must be an instance of
|
|
218
|
+
#: :class:`SamplesGenerator<acoular.base.SamplesGenerator>` or a derived class.
|
|
167
219
|
source = Instance(SamplesGenerator)
|
|
168
220
|
|
|
169
221
|
# Shadow trait, should not be set directly, for internal use.
|
|
@@ -172,13 +224,12 @@ class PowerSpectra(BaseSpectra):
|
|
|
172
224
|
# Shadow trait, should not be set directly, for internal use.
|
|
173
225
|
_ind_high = Union(Int(-1), None, desc='index of highest frequency line')
|
|
174
226
|
|
|
175
|
-
#: Index of lowest frequency line to compute
|
|
176
|
-
#:
|
|
177
|
-
#: frequency line.
|
|
227
|
+
#: Index of lowest frequency line to compute. Default is ``1``. Only used by objects that fetch
|
|
228
|
+
#: the CSM. PowerSpectra computes every frequency line.
|
|
178
229
|
ind_low = Property(_ind_low, desc='index of lowest frequency line')
|
|
179
230
|
|
|
180
|
-
#: Index of highest frequency line to compute
|
|
181
|
-
#:
|
|
231
|
+
#: Index of highest frequency line to compute. Default is ``-1``
|
|
232
|
+
#: (last possible line for default :attr:`~BaseSpectra.block_size`).
|
|
182
233
|
ind_high = Property(_ind_high, desc='index of lowest frequency line')
|
|
183
234
|
|
|
184
235
|
# Stores the set lower frequency, for internal use, should not be set directly.
|
|
@@ -187,56 +238,50 @@ class PowerSpectra(BaseSpectra):
|
|
|
187
238
|
# Stores the set higher frequency, for internal use, should not be set directly.
|
|
188
239
|
_freqhc = Union(Float(0), None)
|
|
189
240
|
|
|
190
|
-
# Saves whether the user set indices or frequencies last, for internal use only,
|
|
191
|
-
#
|
|
192
|
-
#
|
|
241
|
+
# Saves whether the user set indices or frequencies last, for internal use only, not to be set
|
|
242
|
+
# directly, if ``True``, indices are used for setting the :attr:`freq_range` interval.
|
|
243
|
+
# Default is ``True``.
|
|
193
244
|
_index_set_last = Bool(True)
|
|
194
245
|
|
|
195
|
-
#:
|
|
196
|
-
#: to be recomputed during subsequent program runs.
|
|
246
|
+
#: A flag indicating whether the result should be cached in HDF5 files. Default is ``True``.
|
|
197
247
|
cached = Bool(True, desc='cached flag')
|
|
198
248
|
|
|
199
|
-
#:
|
|
200
|
-
#:
|
|
249
|
+
#: The number of FFT blocks used for averaging. This is derived from the
|
|
250
|
+
#: :attr:`~BaseSpectra.block_size` and :attr:`~BaseSpectra.overlap` parameters. (read-only)
|
|
201
251
|
num_blocks = Property(desc='overall number of FFT blocks')
|
|
202
252
|
|
|
203
|
-
#: 2-element array with the lowest and highest frequency. If
|
|
204
|
-
#:
|
|
205
|
-
#: the range.
|
|
206
|
-
#: The freq_range interval will be the smallest discrete frequency
|
|
207
|
-
#: inside the half-open interval [_freqlc, _freqhc[ and the smallest
|
|
208
|
-
#: upper frequency outside of the interval.
|
|
209
|
-
#: If user chooses the higher frequency larger than the max frequency,
|
|
210
|
-
#: the max frequency will be the upper bound.
|
|
253
|
+
#: 2-element array with the lowest and highest frequency. If the higher frequency is larger than
|
|
254
|
+
#: the max frequency, the max frequency will be the upper bound.
|
|
211
255
|
freq_range = Property(desc='frequency range')
|
|
256
|
+
# If set, will overwrite :attr:`_freqlc` and :attr:`_freqhc` according to the range. The
|
|
257
|
+
# freq_range interval will be the smallest discrete frequency inside the half-open interval
|
|
258
|
+
# [_freqlc, _freqhc[ and the smallest upper frequency outside of the interval.
|
|
212
259
|
|
|
213
|
-
#:
|
|
214
|
-
#: between :attr:`ind_low` and :attr:`ind_high` within the result, readonly.
|
|
260
|
+
#: The sequence of frequency indices between :attr:`ind_low` and :attr:`ind_high`. (read-only)
|
|
215
261
|
indices = Property(desc='index range')
|
|
216
262
|
|
|
217
|
-
#:
|
|
263
|
+
#: The name of the cache file (without the file extension) used for storing results. (read-only)
|
|
218
264
|
basename = Property(depends_on=['source.digest'], desc='basename for cache file')
|
|
219
265
|
|
|
220
|
-
#: The cross
|
|
221
|
-
#:
|
|
222
|
-
#: readonly.
|
|
266
|
+
#: The cross-spectral matrix, represented as an array of shape ``(n, m, m)`` of complex values
|
|
267
|
+
#: for ``n`` frequencies and ``m`` channels as in :attr:`~BaseSpectra.num_channels`. (read-only)
|
|
223
268
|
csm = Property(desc='cross spectral matrix')
|
|
224
269
|
|
|
225
|
-
#:
|
|
226
|
-
#: (
|
|
270
|
+
#: The eigenvalues of the CSM, stored as an array of shape ``(n,)`` of floats for ``n``
|
|
271
|
+
#: frequencies. (read-only)
|
|
227
272
|
eva = Property(desc='eigenvalues of cross spectral matrix')
|
|
228
273
|
|
|
229
|
-
#:
|
|
230
|
-
#:
|
|
231
|
-
#:
|
|
274
|
+
#: The eigenvectors of the cross spectral matrix, stored as an array of shape ``(n, m, m)`` of
|
|
275
|
+
#: floats for ``n`` frequencies and ``m`` channels as in :attr:`~BaseSpectra.num_channels`.
|
|
276
|
+
#: (read-only)
|
|
232
277
|
eve = Property(desc='eigenvectors of cross spectral matrix')
|
|
233
278
|
|
|
234
|
-
|
|
279
|
+
#: A unique identifier for the spectra, based on its properties. (read-only)
|
|
235
280
|
digest = Property(
|
|
236
281
|
depends_on=['source.digest', 'block_size', 'window', 'overlap', 'precision'],
|
|
237
282
|
)
|
|
238
283
|
|
|
239
|
-
|
|
284
|
+
#: The HDF5 cache file used for storing the results if :attr:`cached` is set to ``True``.
|
|
240
285
|
h5f = Instance(H5CacheFileBase, transient=True)
|
|
241
286
|
|
|
242
287
|
@property_depends_on(['source.num_samples', 'block_size', 'overlap'])
|
|
@@ -309,7 +354,34 @@ class PowerSpectra(BaseSpectra):
|
|
|
309
354
|
return find_basename(self.source, alternative_basename=self.source.__class__.__name__ + self.source.digest)
|
|
310
355
|
|
|
311
356
|
def calc_csm(self):
|
|
312
|
-
"""
|
|
357
|
+
"""
|
|
358
|
+
Calculate the CSM for the given source data.
|
|
359
|
+
|
|
360
|
+
This method computes the CSM by performing a block-wise Fast Fourier Transform (FFT) on the
|
|
361
|
+
source data, applying a window function, and averaging the results. Only the upper
|
|
362
|
+
triangular part of the matrix is computed for efficiency, and the lower triangular part is
|
|
363
|
+
constructed via transposition and complex conjugation.
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
:obj:`numpy.ndarray`
|
|
368
|
+
The computed cross spectral matrix as an array of shape ``(n, m, m)`` of complex values
|
|
369
|
+
for ``n`` frequencies and ``m`` channels as in :attr:`~BaseSpectra.num_channels`.
|
|
370
|
+
|
|
371
|
+
Examples
|
|
372
|
+
--------
|
|
373
|
+
>>> import numpy as np
|
|
374
|
+
>>> from acoular import TimeSamples
|
|
375
|
+
>>> from acoular.spectra import PowerSpectra
|
|
376
|
+
>>>
|
|
377
|
+
>>> data = np.random.rand(1000, 4)
|
|
378
|
+
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
379
|
+
>>> print(ts.num_channels, ts.num_samples, ts.sample_freq)
|
|
380
|
+
4 1000 51200.0
|
|
381
|
+
>>> ps = PowerSpectra(source=ts, block_size=128, window='Blackman')
|
|
382
|
+
>>> ps.csm.shape
|
|
383
|
+
(65, 4, 4)
|
|
384
|
+
"""
|
|
313
385
|
t = self.source
|
|
314
386
|
wind = self.window_(self.block_size)
|
|
315
387
|
weight = dot(wind, wind)
|
|
@@ -329,7 +401,43 @@ class PowerSpectra(BaseSpectra):
|
|
|
329
401
|
return csm * (2.0 / self.block_size / weight / self.num_blocks)
|
|
330
402
|
|
|
331
403
|
def calc_ev(self):
|
|
332
|
-
"""
|
|
404
|
+
"""
|
|
405
|
+
Calculate eigenvalues and eigenvectors of the CSM for each frequency.
|
|
406
|
+
|
|
407
|
+
The eigenvalues represent the spectral power, and the eigenvectors correspond to the
|
|
408
|
+
principal components of the matrix. This calculation is performed for all frequency slices
|
|
409
|
+
of the CSM.
|
|
410
|
+
|
|
411
|
+
Returns
|
|
412
|
+
-------
|
|
413
|
+
:class:`tuple` of :obj:`numpy.ndarray`
|
|
414
|
+
A tuple containing:
|
|
415
|
+
- :attr:`eva` (:obj:`numpy.ndarray`): Eigenvalues as a 2D array of shape ``(n, m)``,
|
|
416
|
+
where ``n`` is the number of frequencies and ``m`` is the number of channels. The
|
|
417
|
+
datatype depends on the precision.
|
|
418
|
+
- :attr:`eve` (:obj:`numpy.ndarray`): Eigenvectors as a 3D array of shape
|
|
419
|
+
``(n, m, m)``. The datatype is consistent with the precision of the input data.
|
|
420
|
+
|
|
421
|
+
Notes
|
|
422
|
+
-----
|
|
423
|
+
- The precision of the eigenvalues is determined by :attr:`~BaseSpectra.precision`
|
|
424
|
+
(``'float64'`` for ``complex128`` precision and ``'float32'`` for ``complex64``
|
|
425
|
+
precision).
|
|
426
|
+
- This method assumes the CSM is already computed and accessible via :attr:`csm`.
|
|
427
|
+
|
|
428
|
+
Examples
|
|
429
|
+
--------
|
|
430
|
+
>>> import numpy as np
|
|
431
|
+
>>> from acoular import TimeSamples
|
|
432
|
+
>>> from acoular.spectra import PowerSpectra
|
|
433
|
+
>>>
|
|
434
|
+
>>> data = np.random.rand(1000, 4)
|
|
435
|
+
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
436
|
+
>>> ps = PowerSpectra(source=ts, block_size=128, window='Hanning')
|
|
437
|
+
>>> eva, eve = ps.calc_ev()
|
|
438
|
+
>>> print(eva.shape, eve.shape)
|
|
439
|
+
(65, 4) (65, 4, 4)
|
|
440
|
+
"""
|
|
333
441
|
if self.precision == 'complex128':
|
|
334
442
|
eva_dtype = 'float64'
|
|
335
443
|
elif self.precision == 'complex64':
|
|
@@ -343,17 +451,47 @@ class PowerSpectra(BaseSpectra):
|
|
|
343
451
|
return (eva, eve)
|
|
344
452
|
|
|
345
453
|
def calc_eva(self):
|
|
346
|
-
"""
|
|
454
|
+
"""
|
|
455
|
+
Calculate eigenvalues of the CSM.
|
|
456
|
+
|
|
457
|
+
This method computes and returns the eigenvalues of the CSM for all frequency slices.
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
:obj:`numpy.ndarray`
|
|
462
|
+
A 2D array of shape ``(n, m)`` containing the eigenvalues for ``n`` frequencies and
|
|
463
|
+
``m`` channels. The datatype depends on :attr:`~BaseSpectra.precision` (``'float64'``
|
|
464
|
+
for ``complex128`` precision and ``'float32'`` for ``complex64`` precision).
|
|
465
|
+
|
|
466
|
+
Notes
|
|
467
|
+
-----
|
|
468
|
+
This method internally calls :meth:`calc_ev` and extracts only the eigenvalues.
|
|
469
|
+
"""
|
|
347
470
|
return self.calc_ev()[0]
|
|
348
471
|
|
|
349
472
|
def calc_eve(self):
|
|
350
|
-
"""
|
|
473
|
+
"""
|
|
474
|
+
Calculate eigenvectors of the Cross Spectral Matrix (CSM).
|
|
475
|
+
|
|
476
|
+
This method computes and returns the eigenvectors of the CSM for all frequency slices.
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
:obj:`numpy.ndarray`
|
|
481
|
+
A 3D array of shape ``(n, m, m)`` containing the eigenvectors for ``n`` frequencies and
|
|
482
|
+
``m`` channels. Each slice ``eve[f]`` represents an ``(m, m)`` matrix of eigenvectors
|
|
483
|
+
for frequency ``f``. The datatype matches the :attr:`~BaseSpectra.precision` of the CSM
|
|
484
|
+
(``complex128`` or ``complex64``).
|
|
485
|
+
|
|
486
|
+
Notes
|
|
487
|
+
-----
|
|
488
|
+
This method internally calls :meth:`calc_ev()` and extracts only the eigenvectors.
|
|
489
|
+
"""
|
|
351
490
|
return self.calc_ev()[1]
|
|
352
491
|
|
|
353
492
|
def _get_filecache(self, traitname):
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
"""
|
|
493
|
+
# Handle caching of results for CSM, eigenvalues, and eigenvectors.
|
|
494
|
+
# Returns the requested data (``csm``, ``eva``, or ``eve``) as a NumPy array.
|
|
357
495
|
if traitname == 'csm':
|
|
358
496
|
func = self.calc_csm
|
|
359
497
|
numfreq = int(self.block_size / 2 + 1)
|
|
@@ -395,42 +533,42 @@ class PowerSpectra(BaseSpectra):
|
|
|
395
533
|
|
|
396
534
|
@property_depends_on(['digest'])
|
|
397
535
|
def _get_csm(self):
|
|
398
|
-
"""Main work is done here:
|
|
399
|
-
Cross spectral matrix is either loaded from cache file or
|
|
400
|
-
calculated and then additionally stored into cache.
|
|
401
|
-
"""
|
|
402
536
|
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
403
537
|
return self.calc_csm()
|
|
404
538
|
return self._get_filecache('csm')
|
|
405
539
|
|
|
406
540
|
@property_depends_on(['digest'])
|
|
407
541
|
def _get_eva(self):
|
|
408
|
-
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
409
|
-
calculated and then additionally stored into cache.
|
|
410
|
-
"""
|
|
411
542
|
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
412
543
|
return self.calc_eva()
|
|
413
544
|
return self._get_filecache('eva')
|
|
414
545
|
|
|
415
546
|
@property_depends_on(['digest'])
|
|
416
547
|
def _get_eve(self):
|
|
417
|
-
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
418
|
-
calculated and then additionally stored into cache.
|
|
419
|
-
"""
|
|
420
548
|
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
421
549
|
return self.calc_eve()
|
|
422
550
|
return self._get_filecache('eve')
|
|
423
551
|
|
|
424
552
|
def synthetic_ev(self, freq, num=0):
|
|
425
|
-
"""
|
|
553
|
+
"""
|
|
554
|
+
Retrieve synthetic eigenvalues for a specified frequency or frequency range.
|
|
555
|
+
|
|
556
|
+
This method calculates the eigenvalues of the CSM for a single frequency or a synthetic
|
|
557
|
+
frequency range. If ``num`` is set to ``0``, it retrieves the eigenvalues at the exact
|
|
558
|
+
frequency. Otherwise, it averages eigenvalues across a range determined by ``freq`` and
|
|
559
|
+
``num``.
|
|
426
560
|
|
|
427
561
|
Parameters
|
|
428
562
|
----------
|
|
429
|
-
freq : float
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
563
|
+
freq : :class:`float`
|
|
564
|
+
The target frequency for which the eigenvalues are calculated. This is the center
|
|
565
|
+
frequency for synthetic averaging.
|
|
566
|
+
num : :class:`int`, optional
|
|
567
|
+
The number of subdivisions in the logarithmic frequency space around the center
|
|
568
|
+
frequency ``freq``.
|
|
569
|
+
|
|
570
|
+
- ``0`` (default): Only the eigenvalues for the exact frequency line are returned.
|
|
571
|
+
- Non-zero:
|
|
434
572
|
|
|
435
573
|
=== =====================
|
|
436
574
|
num frequency band width
|
|
@@ -443,10 +581,25 @@ class PowerSpectra(BaseSpectra):
|
|
|
443
581
|
|
|
444
582
|
Returns
|
|
445
583
|
-------
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
584
|
+
:obj:`numpy.ndarray`
|
|
585
|
+
An array of eigenvalues. If ``num == 0``, the eigenvalues for the single frequency are
|
|
586
|
+
returned. For ``num > 0``, a summed array of eigenvalues across the synthetic frequency
|
|
587
|
+
range is returned.
|
|
588
|
+
|
|
589
|
+
Examples
|
|
590
|
+
--------
|
|
591
|
+
>>> import numpy as np
|
|
592
|
+
>>> from acoular import TimeSamples
|
|
593
|
+
>>> from acoular.spectra import PowerSpectra
|
|
594
|
+
>>> np.random.seed(0)
|
|
595
|
+
>>>
|
|
596
|
+
>>> data = np.random.rand(1000, 4)
|
|
597
|
+
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
598
|
+
>>> ps = PowerSpectra(source=ts, block_size=128, window='Hamming')
|
|
599
|
+
>>> ps.synthetic_ev(freq=5000, num=5)
|
|
600
|
+
array([0.00048803, 0.0010141 , 0.00234248, 0.00457097])
|
|
601
|
+
>>> ps.synthetic_ev(freq=5000)
|
|
602
|
+
array([0.00022468, 0.0004589 , 0.00088059, 0.00245989])
|
|
450
603
|
"""
|
|
451
604
|
f = self.fftfreq()
|
|
452
605
|
if num == 0:
|
|
@@ -460,46 +613,48 @@ class PowerSpectra(BaseSpectra):
|
|
|
460
613
|
|
|
461
614
|
|
|
462
615
|
class PowerSpectraImport(PowerSpectra):
|
|
463
|
-
"""
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
This class does not calculate the
|
|
467
|
-
the
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
attr:`
|
|
472
|
-
|
|
473
|
-
In contrast to the PowerSpectra object, the attributes
|
|
474
|
-
:attr:`sample_freq`, :attr:`source`, :attr:`block_size`, :attr:`window`,
|
|
475
|
-
:attr:`overlap`, :attr:`cached`, and :attr:`num_blocks`
|
|
476
|
-
have no functionality.
|
|
616
|
+
"""
|
|
617
|
+
Provides a dummy class for using pre-calculated CSMs.
|
|
618
|
+
|
|
619
|
+
This class does not calculate the CSM. Instead, the user can inject one or multiple existing
|
|
620
|
+
CSMs by setting the :attr:`csm` attribute. This can be useful when algorithms shall be
|
|
621
|
+
evaluated with existing CSMs. The frequency or frequencies contained by the CSM must be set via
|
|
622
|
+
the :attr:`frequencies` attribute. The attr:`num_channels` attributes is determined on the basis
|
|
623
|
+
of the CSM shape. In contrast to the :class:`PowerSpectra` object, the attributes
|
|
624
|
+
:attr:`sample_freq`, :attr:`source`, :attr:`block_size`, :attr:`window`, :attr:`overlap`,
|
|
625
|
+
:attr:`cached`, and :attr:`num_blocks` have no functionality.
|
|
477
626
|
"""
|
|
478
627
|
|
|
479
|
-
#: The cross
|
|
480
|
-
#:
|
|
628
|
+
#: The cross-spectral matrix stored in an array of shape ``(n, m, m)`` of complex for ``n``
|
|
629
|
+
#: frequencies and ``m`` channels.
|
|
481
630
|
csm = Property(desc='cross spectral matrix')
|
|
482
631
|
|
|
483
|
-
#: frequencies included in the
|
|
484
|
-
#:
|
|
632
|
+
#: The frequencies included in the CSM in ascending order. Accepts list, array, or a single
|
|
633
|
+
#: float value.
|
|
485
634
|
frequencies = Union(CArray, Float, desc='frequencies included in the cross-spectral matrix')
|
|
486
635
|
|
|
487
|
-
#: Number of time data channels
|
|
636
|
+
#: Number of time data channels, inferred from the shape of the CSM.
|
|
488
637
|
num_channels = Property(depends_on=['digest'])
|
|
489
638
|
|
|
639
|
+
#: :class:`PowerSpectraImport` does not consume time data; source is always ``None``.
|
|
490
640
|
source = Enum(None, desc='PowerSpectraImport cannot consume time data')
|
|
491
641
|
|
|
492
|
-
|
|
642
|
+
#: Sampling frequency of the signal. Default is ``None``
|
|
493
643
|
sample_freq = Enum(None, desc='sampling frequency')
|
|
494
644
|
|
|
645
|
+
#: Block size for FFT, non-functional in this class.
|
|
495
646
|
block_size = Enum(None, desc='PowerSpectraImport does not operate on blocks of time data')
|
|
496
647
|
|
|
648
|
+
#: Windowing method, non-functional in this class.
|
|
497
649
|
window = Enum(None, desc='PowerSpectraImport does not perform windowing')
|
|
498
650
|
|
|
651
|
+
#: Overlap between blocks, non-functional in this class.
|
|
499
652
|
overlap = Enum(None, desc='PowerSpectraImport does not consume time data')
|
|
500
653
|
|
|
654
|
+
#: Caching capability, always disabled.
|
|
501
655
|
cached = Enum(False, desc='PowerSpectraImport has no caching capabilities')
|
|
502
656
|
|
|
657
|
+
#: Number of FFT blocks, always ``None``.
|
|
503
658
|
num_blocks = Enum(None, desc='PowerSpectraImport cannot determine the number of blocks')
|
|
504
659
|
|
|
505
660
|
# Shadow trait, should not be set directly, for internal use.
|
|
@@ -508,16 +663,16 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
508
663
|
# Shadow trait, should not be set directly, for internal use.
|
|
509
664
|
_ind_high = Union(None, Int, desc='index of highest frequency line')
|
|
510
665
|
|
|
511
|
-
|
|
666
|
+
#: A unique identifier for the spectra, based on its properties. (read-only)
|
|
512
667
|
digest = Property(depends_on=['_csmsum'])
|
|
513
668
|
|
|
514
|
-
#: Name of the cache file without extension
|
|
669
|
+
#: Name of the cache file without extension. (read-only)
|
|
515
670
|
basename = Property(depends_on=['digest'], desc='basename for cache file')
|
|
516
671
|
|
|
517
|
-
#
|
|
672
|
+
# Shadow trait for storing the CSM, for internal use only.
|
|
518
673
|
_csm = CArray()
|
|
519
674
|
|
|
520
|
-
# CSM
|
|
675
|
+
# Checksum for the CSM to trigger digest calculation, for internal use only.
|
|
521
676
|
_csmsum = Float()
|
|
522
677
|
|
|
523
678
|
def _get_basename(self):
|
|
@@ -543,26 +698,23 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
543
698
|
|
|
544
699
|
@property_depends_on(['digest'])
|
|
545
700
|
def _get_eva(self):
|
|
546
|
-
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
547
|
-
calculated and then additionally stored into cache.
|
|
548
|
-
"""
|
|
549
701
|
return self.calc_eva()
|
|
550
702
|
|
|
551
703
|
@property_depends_on(['digest'])
|
|
552
704
|
def _get_eve(self):
|
|
553
|
-
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
554
|
-
calculated and then additionally stored into cache.
|
|
555
|
-
"""
|
|
556
705
|
return self.calc_eve()
|
|
557
706
|
|
|
558
707
|
def fftfreq(self):
|
|
559
|
-
"""
|
|
708
|
+
"""
|
|
709
|
+
Return the Discrete Fourier Transform sample frequencies.
|
|
710
|
+
|
|
711
|
+
The method checks the type of :attr:`frequencies` and returns the corresponding frequency
|
|
712
|
+
array. If :attr:`frequencies` is not defined, a warning is raised.
|
|
560
713
|
|
|
561
714
|
Returns
|
|
562
715
|
-------
|
|
563
|
-
|
|
716
|
+
:obj:`numpy.ndarray`
|
|
564
717
|
Array containing the frequencies.
|
|
565
|
-
|
|
566
718
|
"""
|
|
567
719
|
if isinstance(self.frequencies, float):
|
|
568
720
|
return array([self.frequencies])
|