acoular 24.10__py3-none-any.whl → 25.3__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 +5 -2
- acoular/aiaa/__init__.py +12 -0
- acoular/{tools → aiaa}/aiaa.py +23 -28
- acoular/base.py +75 -55
- acoular/calib.py +129 -34
- acoular/configuration.py +11 -9
- acoular/demo/__init__.py +1 -0
- acoular/demo/acoular_demo.py +31 -18
- acoular/deprecation.py +85 -0
- acoular/environments.py +481 -229
- acoular/fastFuncs.py +90 -84
- acoular/fbeamform.py +203 -411
- acoular/fprocess.py +233 -123
- acoular/grids.py +793 -424
- acoular/h5cache.py +29 -40
- acoular/h5files.py +2 -6
- acoular/microphones.py +197 -74
- acoular/process.py +660 -149
- acoular/sdinput.py +23 -20
- acoular/signals.py +461 -159
- acoular/sources.py +1311 -489
- acoular/spectra.py +328 -352
- acoular/tbeamform.py +79 -202
- acoular/tfastfuncs.py +21 -21
- acoular/tools/__init__.py +2 -8
- acoular/tools/helpers.py +216 -2
- acoular/tools/metrics.py +4 -4
- acoular/tools/utils.py +106 -200
- acoular/tprocess.py +348 -309
- acoular/traitsviews.py +10 -10
- acoular/trajectory.py +126 -53
- acoular/version.py +2 -2
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/METADATA +39 -17
- acoular-25.3.dist-info/RECORD +56 -0
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/WHEEL +1 -1
- acoular-24.10.dist-info/RECORD +0 -54
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/licenses/LICENSE +0 -0
acoular/spectra.py
CHANGED
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
BaseSpectra
|
|
10
10
|
PowerSpectra
|
|
11
|
-
synthetic
|
|
12
11
|
PowerSpectraImport
|
|
13
12
|
"""
|
|
14
13
|
|
|
14
|
+
from abc import abstractmethod
|
|
15
15
|
from warnings import warn
|
|
16
16
|
|
|
17
17
|
from numpy import (
|
|
@@ -25,75 +25,98 @@ from numpy import (
|
|
|
25
25
|
hamming,
|
|
26
26
|
hanning,
|
|
27
27
|
imag,
|
|
28
|
-
isscalar,
|
|
29
28
|
linalg,
|
|
30
29
|
ndarray,
|
|
31
30
|
newaxis,
|
|
32
31
|
ones,
|
|
33
32
|
real,
|
|
34
33
|
searchsorted,
|
|
35
|
-
sum,
|
|
34
|
+
sum, # noqa A004
|
|
36
35
|
zeros,
|
|
37
|
-
zeros_like,
|
|
38
36
|
)
|
|
39
37
|
from scipy import fft
|
|
40
38
|
from traits.api import (
|
|
39
|
+
ABCHasStrictTraits,
|
|
41
40
|
Bool,
|
|
42
41
|
CArray,
|
|
43
42
|
Delegate,
|
|
44
43
|
Enum,
|
|
45
44
|
Float,
|
|
46
|
-
HasPrivateTraits,
|
|
47
45
|
Instance,
|
|
48
46
|
Int,
|
|
47
|
+
Map,
|
|
49
48
|
Property,
|
|
50
|
-
|
|
49
|
+
Union,
|
|
51
50
|
cached_property,
|
|
52
51
|
property_depends_on,
|
|
53
52
|
)
|
|
54
53
|
|
|
54
|
+
# acoular imports
|
|
55
55
|
from .base import SamplesGenerator
|
|
56
|
-
from .calib import Calib
|
|
57
56
|
from .configuration import config
|
|
57
|
+
from .deprecation import deprecated_alias
|
|
58
58
|
from .fastFuncs import calcCSM
|
|
59
59
|
from .h5cache import H5cache
|
|
60
60
|
from .h5files import H5CacheFileBase
|
|
61
61
|
from .internal import digest
|
|
62
|
+
from .tools.utils import find_basename
|
|
62
63
|
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True)
|
|
66
|
+
@deprecated_alias({'time_data': 'source'}, read_only=False)
|
|
67
|
+
class BaseSpectra(ABCHasStrictTraits):
|
|
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
|
+
"""
|
|
67
75
|
|
|
68
|
-
#:
|
|
76
|
+
#: Data source; an instance of :class:`~acoular.base.SamplesGenerator` or derived object.
|
|
77
|
+
source = Instance(SamplesGenerator)
|
|
78
|
+
|
|
79
|
+
#: Sampling frequency of the output signal, delegated from :attr:`source`.
|
|
69
80
|
sample_freq = Delegate('source')
|
|
70
81
|
|
|
71
|
-
#: Number of time
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
#: Window function
|
|
75
|
-
#:
|
|
76
|
-
#:
|
|
77
|
-
#:
|
|
78
|
-
#:
|
|
79
|
-
#:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
#: Number of time microphones, delegated from :attr:`source`.
|
|
83
|
+
num_channels = Delegate('source')
|
|
84
|
+
|
|
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'``
|
|
96
|
+
window = Map(
|
|
82
97
|
{'Rectangular': ones, 'Hanning': hanning, 'Hamming': hamming, 'Bartlett': bartlett, 'Blackman': blackman},
|
|
98
|
+
default_value='Rectangular',
|
|
83
99
|
desc='type of window for FFT',
|
|
84
100
|
)
|
|
85
101
|
|
|
86
|
-
#: Overlap factor for
|
|
87
|
-
|
|
102
|
+
#: Overlap factor for FFT block averaging. One of:
|
|
103
|
+
#:
|
|
104
|
+
#: - ``'None'`` (default)
|
|
105
|
+
#:
|
|
106
|
+
#: - ``'50%'``
|
|
107
|
+
#:
|
|
108
|
+
#: - ``'75%'``
|
|
109
|
+
#:
|
|
110
|
+
#: - ``'87.5%'``
|
|
111
|
+
overlap = Map({'None': 1, '50%': 2, '75%': 4, '87.5%': 8}, default_value='None', desc='overlap of FFT blocks')
|
|
88
112
|
|
|
89
|
-
#: FFT block size
|
|
90
|
-
#:
|
|
91
|
-
block_size =
|
|
113
|
+
#: FFT block size. Must be one of: ``128``, ``256``, ``512``, ``1024``, ... ``65536``.
|
|
114
|
+
#: Default is ``1024``.
|
|
115
|
+
block_size = Enum(
|
|
92
116
|
1024,
|
|
93
117
|
128,
|
|
94
118
|
256,
|
|
95
119
|
512,
|
|
96
|
-
1024,
|
|
97
120
|
2048,
|
|
98
121
|
4096,
|
|
99
122
|
8192,
|
|
@@ -103,25 +126,53 @@ class BaseSpectra(HasPrivateTraits):
|
|
|
103
126
|
desc='number of samples per FFT block',
|
|
104
127
|
)
|
|
105
128
|
|
|
106
|
-
#:
|
|
107
|
-
|
|
108
|
-
precision = Trait('complex128', 'complex64', desc='precision of the fft')
|
|
129
|
+
#: Precision of the FFT, corresponding to NumPy dtypes. Default is ``'complex128'``.
|
|
130
|
+
precision = Enum('complex128', 'complex64', desc='precision of the fft')
|
|
109
131
|
|
|
110
|
-
|
|
132
|
+
#: A unique identifier for the spectra, based on its properties. (read-only)
|
|
111
133
|
digest = Property(depends_on=['precision', 'block_size', 'window', 'overlap'])
|
|
112
134
|
|
|
113
|
-
@
|
|
135
|
+
@abstractmethod
|
|
114
136
|
def _get_digest(self):
|
|
115
|
-
|
|
137
|
+
"""Return internal identifier."""
|
|
116
138
|
|
|
117
139
|
def fftfreq(self):
|
|
118
|
-
"""
|
|
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.
|
|
119
145
|
|
|
120
146
|
Returns
|
|
121
147
|
-------
|
|
122
|
-
|
|
123
|
-
Array of
|
|
124
|
-
|
|
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.])
|
|
125
176
|
"""
|
|
126
177
|
if self.source is not None:
|
|
127
178
|
return abs(fft.fftfreq(self.block_size, 1.0 / self.source.sample_freq)[: int(self.block_size / 2 + 1)])
|
|
@@ -130,7 +181,7 @@ class BaseSpectra(HasPrivateTraits):
|
|
|
130
181
|
# generator that yields the time data blocks for every channel (with optional overlap)
|
|
131
182
|
def _get_source_data(self):
|
|
132
183
|
bs = self.block_size
|
|
133
|
-
temp = empty((2 * bs, self.
|
|
184
|
+
temp = empty((2 * bs, self.num_channels))
|
|
134
185
|
pos = bs
|
|
135
186
|
posinc = bs / self.overlap_
|
|
136
187
|
for data_block in self.source.result(bs):
|
|
@@ -145,134 +196,99 @@ class BaseSpectra(HasPrivateTraits):
|
|
|
145
196
|
|
|
146
197
|
|
|
147
198
|
class PowerSpectra(BaseSpectra):
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
This class
|
|
152
|
-
|
|
153
|
-
the
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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.
|
|
163
215
|
"""
|
|
164
216
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
#: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
|
|
169
|
-
source = Property(_source, desc='time data object')
|
|
170
|
-
|
|
171
|
-
#: The :class:`~acoular.base.SamplesGenerator` object that provides the data.
|
|
172
|
-
time_data = Property(
|
|
173
|
-
_source,
|
|
174
|
-
desc='deprecated attribute holding the time data object. Use PowerSpectra.source instead!',
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
#: The :class:`~acoular.calib.Calib` object that provides the calibration data,
|
|
178
|
-
#: defaults to no calibration, i.e. the raw time data is used.
|
|
179
|
-
#:
|
|
180
|
-
#: **deprecated, will be removed in version 25.01**: use :attr:`~acoular.sources.TimeSamples.calib` property of
|
|
181
|
-
#: :class:`~acoular.sources.TimeSamples` objects
|
|
182
|
-
calib = Property(desc='calibration object (deprecated, will be removed in version 25.01)')
|
|
183
|
-
|
|
184
|
-
_calib = Instance(Calib)
|
|
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.
|
|
219
|
+
source = Instance(SamplesGenerator)
|
|
185
220
|
|
|
186
221
|
# Shadow trait, should not be set directly, for internal use.
|
|
187
222
|
_ind_low = Int(1, desc='index of lowest frequency line')
|
|
188
223
|
|
|
189
224
|
# Shadow trait, should not be set directly, for internal use.
|
|
190
|
-
_ind_high =
|
|
225
|
+
_ind_high = Union(Int(-1), None, desc='index of highest frequency line')
|
|
191
226
|
|
|
192
|
-
#: Index of lowest frequency line to compute
|
|
193
|
-
#:
|
|
194
|
-
#: 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.
|
|
195
229
|
ind_low = Property(_ind_low, desc='index of lowest frequency line')
|
|
196
230
|
|
|
197
|
-
#: Index of highest frequency line to compute
|
|
198
|
-
#:
|
|
231
|
+
#: Index of highest frequency line to compute. Default is ``-1``
|
|
232
|
+
#: (last possible line for default :attr:`~BaseSpectra.block_size`).
|
|
199
233
|
ind_high = Property(_ind_high, desc='index of lowest frequency line')
|
|
200
234
|
|
|
201
235
|
# Stores the set lower frequency, for internal use, should not be set directly.
|
|
202
236
|
_freqlc = Float(0)
|
|
203
237
|
|
|
204
238
|
# Stores the set higher frequency, for internal use, should not be set directly.
|
|
205
|
-
_freqhc =
|
|
239
|
+
_freqhc = Union(Float(0), None)
|
|
206
240
|
|
|
207
|
-
# Saves whether the user set indices or frequencies last, for internal use only,
|
|
208
|
-
#
|
|
209
|
-
#
|
|
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``.
|
|
210
244
|
_index_set_last = Bool(True)
|
|
211
245
|
|
|
212
|
-
#:
|
|
213
|
-
#: to be recomputed during subsequent program runs.
|
|
246
|
+
#: A flag indicating whether the result should be cached in HDF5 files. Default is ``True``.
|
|
214
247
|
cached = Bool(True, desc='cached flag')
|
|
215
248
|
|
|
216
|
-
#:
|
|
217
|
-
#:
|
|
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)
|
|
218
251
|
num_blocks = Property(desc='overall number of FFT blocks')
|
|
219
252
|
|
|
220
|
-
#: 2-element array with the lowest and highest frequency. If
|
|
221
|
-
#:
|
|
222
|
-
#: the range.
|
|
223
|
-
#: The freq_range interval will be the smallest discrete frequency
|
|
224
|
-
#: inside the half-open interval [_freqlc, _freqhc[ and the smallest
|
|
225
|
-
#: upper frequency outside of the interval.
|
|
226
|
-
#: If user chooses the higher frequency larger than the max frequency,
|
|
227
|
-
#: 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.
|
|
228
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.
|
|
229
259
|
|
|
230
|
-
#:
|
|
231
|
-
#: 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)
|
|
232
261
|
indices = Property(desc='index range')
|
|
233
262
|
|
|
234
|
-
#:
|
|
235
|
-
basename = Property(depends_on='
|
|
263
|
+
#: The name of the cache file (without the file extension) used for storing results. (read-only)
|
|
264
|
+
basename = Property(depends_on=['source.digest'], desc='basename for cache file')
|
|
236
265
|
|
|
237
|
-
#: The cross
|
|
238
|
-
#:
|
|
239
|
-
#: 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)
|
|
240
268
|
csm = Property(desc='cross spectral matrix')
|
|
241
269
|
|
|
242
|
-
#:
|
|
243
|
-
#: (
|
|
270
|
+
#: The eigenvalues of the CSM, stored as an array of shape ``(n,)`` of floats for ``n``
|
|
271
|
+
#: frequencies. (read-only)
|
|
244
272
|
eva = Property(desc='eigenvalues of cross spectral matrix')
|
|
245
273
|
|
|
246
|
-
#:
|
|
247
|
-
#:
|
|
248
|
-
#:
|
|
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)
|
|
249
277
|
eve = Property(desc='eigenvectors of cross spectral matrix')
|
|
250
278
|
|
|
251
|
-
|
|
279
|
+
#: A unique identifier for the spectra, based on its properties. (read-only)
|
|
252
280
|
digest = Property(
|
|
253
|
-
depends_on=['
|
|
281
|
+
depends_on=['source.digest', 'block_size', 'window', 'overlap', 'precision'],
|
|
254
282
|
)
|
|
255
283
|
|
|
256
|
-
|
|
284
|
+
#: The HDF5 cache file used for storing the results if :attr:`cached` is set to ``True``.
|
|
257
285
|
h5f = Instance(H5CacheFileBase, transient=True)
|
|
258
286
|
|
|
259
|
-
|
|
260
|
-
return self._calib
|
|
261
|
-
|
|
262
|
-
def _set_calib(self, calib):
|
|
263
|
-
msg = (
|
|
264
|
-
"Using 'calib' attribute is deprecated and will be removed in version 25.01. "
|
|
265
|
-
'use :attr:`~acoular.sources.TimeSamples.calib` property of '
|
|
266
|
-
':class:`~acoular.sources.TimeSamples` object instead.'
|
|
267
|
-
)
|
|
268
|
-
warn(msg, DeprecationWarning, stacklevel=2)
|
|
269
|
-
self._calib = calib
|
|
270
|
-
|
|
271
|
-
@property_depends_on('_source.numsamples, block_size, overlap')
|
|
287
|
+
@property_depends_on(['source.num_samples', 'block_size', 'overlap'])
|
|
272
288
|
def _get_num_blocks(self):
|
|
273
|
-
return self.overlap_ * self.
|
|
289
|
+
return self.overlap_ * self.source.num_samples / self.block_size - self.overlap_ + 1
|
|
274
290
|
|
|
275
|
-
@property_depends_on('
|
|
291
|
+
@property_depends_on(['source.sample_freq', 'block_size', 'ind_low', 'ind_high'])
|
|
276
292
|
def _get_freq_range(self):
|
|
277
293
|
fftfreq = self.fftfreq()
|
|
278
294
|
if fftfreq is not None:
|
|
@@ -286,7 +302,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
286
302
|
self._freqlc = freq_range[0]
|
|
287
303
|
self._freqhc = freq_range[1]
|
|
288
304
|
|
|
289
|
-
@property_depends_on('
|
|
305
|
+
@property_depends_on(['source.sample_freq', 'block_size', '_ind_low', '_freqlc'])
|
|
290
306
|
def _get_ind_low(self):
|
|
291
307
|
fftfreq = self.fftfreq()
|
|
292
308
|
if fftfreq is not None:
|
|
@@ -295,7 +311,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
295
311
|
return searchsorted(fftfreq[:-1], self._freqlc)
|
|
296
312
|
return None
|
|
297
313
|
|
|
298
|
-
@property_depends_on('
|
|
314
|
+
@property_depends_on(['source.sample_freq', 'block_size', '_ind_high', '_freqhc'])
|
|
299
315
|
def _get_ind_high(self):
|
|
300
316
|
fftfreq = self.fftfreq()
|
|
301
317
|
if fftfreq is not None:
|
|
@@ -316,24 +332,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
316
332
|
self._index_set_last = True
|
|
317
333
|
self._ind_low = ind_low
|
|
318
334
|
|
|
319
|
-
|
|
320
|
-
msg = (
|
|
321
|
-
"Using 'time_data' attribute is deprecated and will be removed in version 25.01. "
|
|
322
|
-
"Use 'source' attribute instead."
|
|
323
|
-
)
|
|
324
|
-
warn(msg, DeprecationWarning, stacklevel=2)
|
|
325
|
-
self._source = time_data
|
|
326
|
-
|
|
327
|
-
def _set_source(self, source):
|
|
328
|
-
self._source = source
|
|
329
|
-
|
|
330
|
-
def _get_time_data(self):
|
|
331
|
-
return self._source
|
|
332
|
-
|
|
333
|
-
def _get_source(self):
|
|
334
|
-
return self._source
|
|
335
|
-
|
|
336
|
-
@property_depends_on('block_size, ind_low, ind_high')
|
|
335
|
+
@property_depends_on(['block_size', 'ind_low', 'ind_high'])
|
|
337
336
|
def _get_indices(self):
|
|
338
337
|
fftfreq = self.fftfreq()
|
|
339
338
|
if fftfreq is not None:
|
|
@@ -352,26 +351,44 @@ class PowerSpectra(BaseSpectra):
|
|
|
352
351
|
|
|
353
352
|
@cached_property
|
|
354
353
|
def _get_basename(self):
|
|
355
|
-
|
|
356
|
-
return self._source.basename
|
|
357
|
-
return self._source.__class__.__name__ + self._source.digest
|
|
354
|
+
return find_basename(self.source, alternative_basename=self.source.__class__.__name__ + self.source.digest)
|
|
358
355
|
|
|
359
356
|
def calc_csm(self):
|
|
360
|
-
"""
|
|
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
|
+
"""
|
|
361
385
|
t = self.source
|
|
362
386
|
wind = self.window_(self.block_size)
|
|
363
387
|
weight = dot(wind, wind)
|
|
364
388
|
wind = wind[newaxis, :].swapaxes(0, 1)
|
|
365
389
|
numfreq = int(self.block_size / 2 + 1)
|
|
366
|
-
csm_shape = (numfreq, t.
|
|
390
|
+
csm_shape = (numfreq, t.num_channels, t.num_channels)
|
|
367
391
|
csm_upper = zeros(csm_shape, dtype=self.precision)
|
|
368
|
-
# print "num blocks", self.num_blocks
|
|
369
|
-
# for backward compatibility
|
|
370
|
-
if self.calib and self.calib.num_mics > 0:
|
|
371
|
-
if self.calib.num_mics == t.numchannels:
|
|
372
|
-
wind = wind * self.calib.data[newaxis, :]
|
|
373
|
-
else:
|
|
374
|
-
raise ValueError('Calibration data not compatible: %i, %i' % (self.calib.num_mics, t.numchannels))
|
|
375
392
|
# get time data blockwise
|
|
376
393
|
for data in self._get_source_data():
|
|
377
394
|
ft = fft.rfft(data * wind, None, 0).astype(self.precision)
|
|
@@ -384,7 +401,43 @@ class PowerSpectra(BaseSpectra):
|
|
|
384
401
|
return csm * (2.0 / self.block_size / weight / self.num_blocks)
|
|
385
402
|
|
|
386
403
|
def calc_ev(self):
|
|
387
|
-
"""
|
|
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
|
+
"""
|
|
388
441
|
if self.precision == 'complex128':
|
|
389
442
|
eva_dtype = 'float64'
|
|
390
443
|
elif self.precision == 'complex64':
|
|
@@ -398,38 +451,51 @@ class PowerSpectra(BaseSpectra):
|
|
|
398
451
|
return (eva, eve)
|
|
399
452
|
|
|
400
453
|
def calc_eva(self):
|
|
401
|
-
"""
|
|
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
|
+
"""
|
|
402
470
|
return self.calc_ev()[0]
|
|
403
471
|
|
|
404
472
|
def calc_eve(self):
|
|
405
|
-
"""
|
|
406
|
-
|
|
473
|
+
"""
|
|
474
|
+
Calculate eigenvectors of the Cross Spectral Matrix (CSM).
|
|
407
475
|
|
|
408
|
-
|
|
409
|
-
obj = self.source # start with time_data obj
|
|
410
|
-
while obj:
|
|
411
|
-
if 'calib' in obj.all_trait_names(): # at original source?
|
|
412
|
-
if obj.calib and self.calib:
|
|
413
|
-
if obj.calib.digest == self.calib.digest:
|
|
414
|
-
self.calib = None # ignore it silently
|
|
415
|
-
else:
|
|
416
|
-
msg = 'Non-identical dual calibration for both TimeSamples and PowerSpectra object'
|
|
417
|
-
raise ValueError(msg)
|
|
418
|
-
obj = None
|
|
419
|
-
else:
|
|
420
|
-
try:
|
|
421
|
-
obj = obj.source # traverse down until original data source
|
|
422
|
-
except AttributeError:
|
|
423
|
-
obj = None
|
|
476
|
+
This method computes and returns the eigenvectors of the CSM for all frequency slices.
|
|
424
477
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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.
|
|
428
489
|
"""
|
|
490
|
+
return self.calc_ev()[1]
|
|
491
|
+
|
|
492
|
+
def _get_filecache(self, traitname):
|
|
493
|
+
# Handle caching of results for CSM, eigenvalues, and eigenvectors.
|
|
494
|
+
# Returns the requested data (``csm``, ``eva``, or ``eve``) as a NumPy array.
|
|
429
495
|
if traitname == 'csm':
|
|
430
496
|
func = self.calc_csm
|
|
431
497
|
numfreq = int(self.block_size / 2 + 1)
|
|
432
|
-
shape = (numfreq, self.
|
|
498
|
+
shape = (numfreq, self.source.num_channels, self.source.num_channels)
|
|
433
499
|
precision = self.precision
|
|
434
500
|
elif traitname == 'eva':
|
|
435
501
|
func = self.calc_eva
|
|
@@ -465,45 +531,44 @@ class PowerSpectra(BaseSpectra):
|
|
|
465
531
|
self.h5f.flush()
|
|
466
532
|
return ac
|
|
467
533
|
|
|
468
|
-
@property_depends_on('digest')
|
|
534
|
+
@property_depends_on(['digest'])
|
|
469
535
|
def _get_csm(self):
|
|
470
|
-
"""Main work is done here:
|
|
471
|
-
Cross spectral matrix is either loaded from cache file or
|
|
472
|
-
calculated and then additionally stored into cache.
|
|
473
|
-
"""
|
|
474
|
-
self._handle_dual_calibration()
|
|
475
536
|
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
476
537
|
return self.calc_csm()
|
|
477
538
|
return self._get_filecache('csm')
|
|
478
539
|
|
|
479
|
-
@property_depends_on('digest')
|
|
540
|
+
@property_depends_on(['digest'])
|
|
480
541
|
def _get_eva(self):
|
|
481
|
-
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
482
|
-
calculated and then additionally stored into cache.
|
|
483
|
-
"""
|
|
484
542
|
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
485
543
|
return self.calc_eva()
|
|
486
544
|
return self._get_filecache('eva')
|
|
487
545
|
|
|
488
|
-
@property_depends_on('digest')
|
|
546
|
+
@property_depends_on(['digest'])
|
|
489
547
|
def _get_eve(self):
|
|
490
|
-
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
491
|
-
calculated and then additionally stored into cache.
|
|
492
|
-
"""
|
|
493
548
|
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
494
549
|
return self.calc_eve()
|
|
495
550
|
return self._get_filecache('eve')
|
|
496
551
|
|
|
497
552
|
def synthetic_ev(self, freq, num=0):
|
|
498
|
-
"""
|
|
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``.
|
|
499
560
|
|
|
500
561
|
Parameters
|
|
501
562
|
----------
|
|
502
|
-
freq : float
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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:
|
|
507
572
|
|
|
508
573
|
=== =====================
|
|
509
574
|
num frequency band width
|
|
@@ -516,10 +581,25 @@ class PowerSpectra(BaseSpectra):
|
|
|
516
581
|
|
|
517
582
|
Returns
|
|
518
583
|
-------
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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])
|
|
523
603
|
"""
|
|
524
604
|
f = self.fftfreq()
|
|
525
605
|
if num == 0:
|
|
@@ -532,169 +612,67 @@ class PowerSpectra(BaseSpectra):
|
|
|
532
612
|
return sum(self.eva[f1:f2], 0)
|
|
533
613
|
|
|
534
614
|
|
|
535
|
-
def synthetic(data, freqs, f, num=3):
|
|
536
|
-
"""Returns synthesized frequency band values of spectral data.
|
|
537
|
-
|
|
538
|
-
If used with :meth:`Beamformer.result()<acoular.fbeamform.BeamformerBase.result>`
|
|
539
|
-
and only one frequency band, the output is identical to the result of the intrinsic
|
|
540
|
-
:meth:`Beamformer.synthetic<acoular.fbeamform.BeamformerBase.synthetic>` method.
|
|
541
|
-
It can, however, also be used with the
|
|
542
|
-
:meth:`Beamformer.integrate<acoular.fbeamform.BeamformerBase.integrate>`
|
|
543
|
-
output and more frequency bands.
|
|
544
|
-
|
|
545
|
-
Parameters
|
|
546
|
-
----------
|
|
547
|
-
data : array of floats
|
|
548
|
-
The spectral data (squared sound pressures in Pa^2) in an array with one value
|
|
549
|
-
per frequency line.
|
|
550
|
-
The number of entries must be identical to the number of
|
|
551
|
-
grid points.
|
|
552
|
-
freq : array of floats
|
|
553
|
-
The frequencies that correspon to the input *data* (as yielded by
|
|
554
|
-
the :meth:`PowerSpectra.fftfreq<acoular.spectra.PowerSpectra.fftfreq>`
|
|
555
|
-
method).
|
|
556
|
-
f : float or list of floats
|
|
557
|
-
Band center frequency/frequencies for which to return the results.
|
|
558
|
-
num : integer
|
|
559
|
-
Controls the width of the frequency bands considered; defaults to
|
|
560
|
-
3 (third-octave band).
|
|
561
|
-
|
|
562
|
-
=== =====================
|
|
563
|
-
num frequency band width
|
|
564
|
-
=== =====================
|
|
565
|
-
0 single frequency line
|
|
566
|
-
1 octave band
|
|
567
|
-
3 third-octave band
|
|
568
|
-
n 1/n-octave band
|
|
569
|
-
=== =====================
|
|
570
|
-
|
|
571
|
-
Returns
|
|
572
|
-
-------
|
|
573
|
-
array of floats
|
|
574
|
-
Synthesized frequency band values of the beamforming result at
|
|
575
|
-
each grid point (the sum of all values that are contained in the band).
|
|
576
|
-
Note that the frequency resolution and therefore the bandwidth
|
|
577
|
-
represented by a single frequency line depends on
|
|
578
|
-
the :attr:`sampling frequency<acoular.base.SamplesGenerator.sample_freq>`
|
|
579
|
-
and used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
580
|
-
|
|
581
|
-
"""
|
|
582
|
-
if isscalar(f):
|
|
583
|
-
f = (f,)
|
|
584
|
-
if num == 0:
|
|
585
|
-
# single frequency lines
|
|
586
|
-
res = []
|
|
587
|
-
for i in f:
|
|
588
|
-
ind = searchsorted(freqs, i)
|
|
589
|
-
if ind >= len(freqs):
|
|
590
|
-
warn(
|
|
591
|
-
'Queried frequency (%g Hz) not in resolved frequency range. Returning zeros.' % i,
|
|
592
|
-
Warning,
|
|
593
|
-
stacklevel=2,
|
|
594
|
-
)
|
|
595
|
-
h = zeros_like(data[0])
|
|
596
|
-
else:
|
|
597
|
-
if freqs[ind] != i:
|
|
598
|
-
warn(
|
|
599
|
-
f'Queried frequency ({i:g} Hz) not in set of '
|
|
600
|
-
'discrete FFT sample frequencies. '
|
|
601
|
-
f'Using frequency {freqs[ind]:g} Hz instead.',
|
|
602
|
-
Warning,
|
|
603
|
-
stacklevel=2,
|
|
604
|
-
)
|
|
605
|
-
h = data[ind]
|
|
606
|
-
res += [h]
|
|
607
|
-
else:
|
|
608
|
-
# fractional octave bands
|
|
609
|
-
res = []
|
|
610
|
-
for i in f:
|
|
611
|
-
f1 = i * 2.0 ** (-0.5 / num)
|
|
612
|
-
f2 = i * 2.0 ** (+0.5 / num)
|
|
613
|
-
ind1 = searchsorted(freqs, f1)
|
|
614
|
-
ind2 = searchsorted(freqs, f2)
|
|
615
|
-
if ind1 == ind2:
|
|
616
|
-
warn(
|
|
617
|
-
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
618
|
-
'include any discrete FFT sample frequencies. '
|
|
619
|
-
'Returning zeros.',
|
|
620
|
-
Warning,
|
|
621
|
-
stacklevel=2,
|
|
622
|
-
)
|
|
623
|
-
h = zeros_like(data[0])
|
|
624
|
-
else:
|
|
625
|
-
h = sum(data[ind1:ind2], 0)
|
|
626
|
-
res += [h]
|
|
627
|
-
return array(res)
|
|
628
|
-
|
|
629
|
-
|
|
630
615
|
class PowerSpectraImport(PowerSpectra):
|
|
631
|
-
"""
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
This class does not calculate the
|
|
635
|
-
the
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
attr:`
|
|
640
|
-
|
|
641
|
-
In contrast to the PowerSpectra object, the attributes
|
|
642
|
-
:attr:`sample_freq`, :attr:`time_data`, :attr:`source`,
|
|
643
|
-
:attr:`block_size`, :attr:`calib`, :attr:`window`,
|
|
644
|
-
:attr:`overlap`, :attr:`cached`, and :attr:`num_blocks`
|
|
645
|
-
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.
|
|
646
626
|
"""
|
|
647
627
|
|
|
648
|
-
#: The cross
|
|
649
|
-
#:
|
|
628
|
+
#: The cross-spectral matrix stored in an array of shape ``(n, m, m)`` of complex for ``n``
|
|
629
|
+
#: frequencies and ``m`` channels.
|
|
650
630
|
csm = Property(desc='cross spectral matrix')
|
|
651
631
|
|
|
652
|
-
#: frequencies included in the
|
|
653
|
-
#:
|
|
654
|
-
frequencies =
|
|
655
|
-
|
|
656
|
-
#: Number of time data channels
|
|
657
|
-
numchannels = Property(depends_on=['digest'])
|
|
632
|
+
#: The frequencies included in the CSM in ascending order. Accepts list, array, or a single
|
|
633
|
+
#: float value.
|
|
634
|
+
frequencies = Union(CArray, Float, desc='frequencies included in the cross-spectral matrix')
|
|
658
635
|
|
|
659
|
-
|
|
636
|
+
#: Number of time data channels, inferred from the shape of the CSM.
|
|
637
|
+
num_channels = Property(depends_on=['digest'])
|
|
660
638
|
|
|
639
|
+
#: :class:`PowerSpectraImport` does not consume time data; source is always ``None``.
|
|
661
640
|
source = Enum(None, desc='PowerSpectraImport cannot consume time data')
|
|
662
641
|
|
|
663
|
-
|
|
642
|
+
#: Sampling frequency of the signal. Default is ``None``
|
|
664
643
|
sample_freq = Enum(None, desc='sampling frequency')
|
|
665
644
|
|
|
645
|
+
#: Block size for FFT, non-functional in this class.
|
|
666
646
|
block_size = Enum(None, desc='PowerSpectraImport does not operate on blocks of time data')
|
|
667
647
|
|
|
668
|
-
|
|
669
|
-
|
|
648
|
+
#: Windowing method, non-functional in this class.
|
|
670
649
|
window = Enum(None, desc='PowerSpectraImport does not perform windowing')
|
|
671
650
|
|
|
651
|
+
#: Overlap between blocks, non-functional in this class.
|
|
672
652
|
overlap = Enum(None, desc='PowerSpectraImport does not consume time data')
|
|
673
653
|
|
|
654
|
+
#: Caching capability, always disabled.
|
|
674
655
|
cached = Enum(False, desc='PowerSpectraImport has no caching capabilities')
|
|
675
656
|
|
|
657
|
+
#: Number of FFT blocks, always ``None``.
|
|
676
658
|
num_blocks = Enum(None, desc='PowerSpectraImport cannot determine the number of blocks')
|
|
677
659
|
|
|
678
660
|
# Shadow trait, should not be set directly, for internal use.
|
|
679
661
|
_ind_low = Int(0, desc='index of lowest frequency line')
|
|
680
662
|
|
|
681
663
|
# Shadow trait, should not be set directly, for internal use.
|
|
682
|
-
_ind_high =
|
|
664
|
+
_ind_high = Union(None, Int, desc='index of highest frequency line')
|
|
683
665
|
|
|
684
|
-
|
|
685
|
-
digest = Property(
|
|
686
|
-
depends_on=[
|
|
687
|
-
'_csmsum',
|
|
688
|
-
],
|
|
689
|
-
)
|
|
666
|
+
#: A unique identifier for the spectra, based on its properties. (read-only)
|
|
667
|
+
digest = Property(depends_on=['_csmsum'])
|
|
690
668
|
|
|
691
|
-
#: Name of the cache file without extension
|
|
692
|
-
basename = Property(depends_on='digest', desc='basename for cache file')
|
|
669
|
+
#: Name of the cache file without extension. (read-only)
|
|
670
|
+
basename = Property(depends_on=['digest'], desc='basename for cache file')
|
|
693
671
|
|
|
694
|
-
#
|
|
672
|
+
# Shadow trait for storing the CSM, for internal use only.
|
|
695
673
|
_csm = CArray()
|
|
696
674
|
|
|
697
|
-
# CSM
|
|
675
|
+
# Checksum for the CSM to trigger digest calculation, for internal use only.
|
|
698
676
|
_csmsum = Float()
|
|
699
677
|
|
|
700
678
|
def _get_basename(self):
|
|
@@ -704,7 +682,7 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
704
682
|
def _get_digest(self):
|
|
705
683
|
return digest(self)
|
|
706
684
|
|
|
707
|
-
def
|
|
685
|
+
def _get_num_channels(self):
|
|
708
686
|
return self.csm.shape[1]
|
|
709
687
|
|
|
710
688
|
def _get_csm(self):
|
|
@@ -712,33 +690,31 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
712
690
|
|
|
713
691
|
def _set_csm(self, csm):
|
|
714
692
|
if (len(csm.shape) != 3) or (csm.shape[1] != csm.shape[2]):
|
|
715
|
-
msg = 'The cross spectral matrix must have the following shape:
|
|
693
|
+
msg = 'The cross spectral matrix must have the following shape: \
|
|
694
|
+
(number of frequencies, num_channels, num_channels)!'
|
|
716
695
|
raise ValueError(msg)
|
|
717
696
|
self._csmsum = real(self._csm).sum() + (imag(self._csm) ** 2).sum() # to trigger new digest creation
|
|
718
697
|
self._csm = csm
|
|
719
698
|
|
|
720
|
-
@property_depends_on('digest')
|
|
699
|
+
@property_depends_on(['digest'])
|
|
721
700
|
def _get_eva(self):
|
|
722
|
-
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
723
|
-
calculated and then additionally stored into cache.
|
|
724
|
-
"""
|
|
725
701
|
return self.calc_eva()
|
|
726
702
|
|
|
727
|
-
@property_depends_on('digest')
|
|
703
|
+
@property_depends_on(['digest'])
|
|
728
704
|
def _get_eve(self):
|
|
729
|
-
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
730
|
-
calculated and then additionally stored into cache.
|
|
731
|
-
"""
|
|
732
705
|
return self.calc_eve()
|
|
733
706
|
|
|
734
707
|
def fftfreq(self):
|
|
735
|
-
"""
|
|
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.
|
|
736
713
|
|
|
737
714
|
Returns
|
|
738
715
|
-------
|
|
739
|
-
|
|
716
|
+
:obj:`numpy.ndarray`
|
|
740
717
|
Array containing the frequencies.
|
|
741
|
-
|
|
742
718
|
"""
|
|
743
719
|
if isinstance(self.frequencies, float):
|
|
744
720
|
return array([self.frequencies])
|