acoular 24.3__py3-none-any.whl → 24.7__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 +119 -54
- acoular/calib.py +29 -38
- acoular/configuration.py +132 -82
- acoular/demo/__init__.py +10 -4
- acoular/demo/acoular_demo.py +73 -55
- acoular/environments.py +270 -264
- acoular/fastFuncs.py +366 -196
- acoular/fbeamform.py +1797 -1934
- acoular/grids.py +504 -548
- acoular/h5cache.py +74 -83
- acoular/h5files.py +159 -142
- acoular/internal.py +13 -14
- acoular/microphones.py +57 -53
- acoular/sdinput.py +57 -53
- acoular/signals.py +180 -178
- acoular/sources.py +920 -724
- acoular/spectra.py +353 -363
- acoular/tbeamform.py +416 -416
- acoular/tfastfuncs.py +180 -104
- acoular/tools/__init__.py +25 -0
- acoular/tools/aiaa.py +185 -0
- acoular/tools/helpers.py +189 -0
- acoular/tools/metrics.py +165 -0
- acoular/tprocess.py +1240 -1182
- acoular/traitsviews.py +513 -501
- acoular/trajectory.py +50 -52
- acoular/version.py +5 -6
- acoular/xml/minidsp_uma-16.xml +20 -0
- acoular/xml/{minidsp_uma16.xml → minidsp_uma-16_mirrored.xml} +3 -0
- {acoular-24.3.dist-info → acoular-24.7.dist-info}/METADATA +58 -39
- acoular-24.7.dist-info/RECORD +50 -0
- {acoular-24.3.dist-info → acoular-24.7.dist-info}/WHEEL +1 -1
- acoular-24.7.dist-info/licenses/LICENSE +28 -0
- acoular/fileimport.py +0 -380
- acoular/nidaqimport.py +0 -273
- acoular/tests/reference_data/BeamformerBase.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse1.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse2.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse3.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse4.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue1.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue2.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue3.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue4.npy +0 -0
- acoular/tests/reference_data/BeamformerCMFLassoLarsBIC.npy +0 -0
- acoular/tests/reference_data/BeamformerCMFNNLS.npy +0 -0
- acoular/tests/reference_data/BeamformerCapon.npy +0 -0
- acoular/tests/reference_data/BeamformerClean.npy +0 -0
- acoular/tests/reference_data/BeamformerCleansc.npy +0 -0
- acoular/tests/reference_data/BeamformerCleant.npy +0 -0
- acoular/tests/reference_data/BeamformerCleantSq.npy +0 -0
- acoular/tests/reference_data/BeamformerCleantSqTraj.npy +0 -0
- acoular/tests/reference_data/BeamformerCleantTraj.npy +0 -0
- acoular/tests/reference_data/BeamformerDamas.npy +0 -0
- acoular/tests/reference_data/BeamformerDamasPlus.npy +0 -0
- acoular/tests/reference_data/BeamformerEig.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse1.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse2.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse3.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse4.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue1.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue2.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue3.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue4.npy +0 -0
- acoular/tests/reference_data/BeamformerFunctional.npy +0 -0
- acoular/tests/reference_data/BeamformerGIB.npy +0 -0
- acoular/tests/reference_data/BeamformerGridlessOrth.npy +0 -0
- acoular/tests/reference_data/BeamformerMusic.npy +0 -0
- acoular/tests/reference_data/BeamformerOrth.npy +0 -0
- acoular/tests/reference_data/BeamformerSODIX.npy +0 -0
- acoular/tests/reference_data/BeamformerTime.npy +0 -0
- acoular/tests/reference_data/BeamformerTimeSq.npy +0 -0
- acoular/tests/reference_data/BeamformerTimeSqTraj.npy +0 -0
- acoular/tests/reference_data/BeamformerTimeTraj.npy +0 -0
- acoular/tests/reference_data/Environment.npy +0 -0
- acoular/tests/reference_data/Example1_numerical_values_testsum.h5 +0 -0
- acoular/tests/reference_data/FiltFiltOctave__.npy +0 -0
- acoular/tests/reference_data/FiltFiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
- acoular/tests/reference_data/FiltFreqWeight_weight_A_.npy +0 -0
- acoular/tests/reference_data/FiltFreqWeight_weight_C_.npy +0 -0
- acoular/tests/reference_data/FiltFreqWeight_weight_Z_.npy +0 -0
- acoular/tests/reference_data/FiltOctave__.npy +0 -0
- acoular/tests/reference_data/FiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
- acoular/tests/reference_data/Filter__.npy +0 -0
- acoular/tests/reference_data/GeneralFlowEnvironment.npy +0 -0
- acoular/tests/reference_data/OctaveFilterBank__.npy +0 -0
- acoular/tests/reference_data/OpenJet.npy +0 -0
- acoular/tests/reference_data/PointSource.npy +0 -0
- acoular/tests/reference_data/PowerSpectra_csm.npy +0 -0
- acoular/tests/reference_data/PowerSpectra_ev.npy +0 -0
- acoular/tests/reference_data/RotatingFlow.npy +0 -0
- acoular/tests/reference_data/SlotJet.npy +0 -0
- acoular/tests/reference_data/TimeAverage__.npy +0 -0
- acoular/tests/reference_data/TimeCumAverage__.npy +0 -0
- acoular/tests/reference_data/TimeExpAverage_weight_F_.npy +0 -0
- acoular/tests/reference_data/TimeExpAverage_weight_I_.npy +0 -0
- acoular/tests/reference_data/TimeExpAverage_weight_S_.npy +0 -0
- acoular/tests/reference_data/TimeInOut__.npy +0 -0
- acoular/tests/reference_data/TimePower__.npy +0 -0
- acoular/tests/reference_data/TimeReverse__.npy +0 -0
- acoular/tests/reference_data/UniformFlowEnvironment.npy +0 -0
- acoular/tests/reference_data/beamformer_traj_time_data.h5 +0 -0
- acoular/tests/run_tests.sh +0 -18
- acoular/tests/run_tests_osx.sh +0 -16
- acoular/tests/test.npy +0 -0
- acoular/tests/test_beamformer_results.py +0 -213
- acoular/tests/test_classes.py +0 -60
- acoular/tests/test_digest.py +0 -125
- acoular/tests/test_environments.py +0 -73
- acoular/tests/test_example1.py +0 -124
- acoular/tests/test_grid.py +0 -92
- acoular/tests/test_integrate.py +0 -102
- acoular/tests/test_signals.py +0 -60
- acoular/tests/test_sources.py +0 -65
- acoular/tests/test_spectra.py +0 -38
- acoular/tests/test_timecache.py +0 -35
- acoular/tests/test_tprocess.py +0 -90
- acoular/tests/test_traj_beamformer_results.py +0 -164
- acoular/tests/unsupported/SpeedComparison/OvernightTestcasesBeamformer_nMics32_nGridPoints100_nFreqs4_nTrials10.png +0 -0
- acoular/tests/unsupported/SpeedComparison/cythonBeamformer.pyx +0 -237
- acoular/tests/unsupported/SpeedComparison/mainForCython.py +0 -103
- acoular/tests/unsupported/SpeedComparison/mainForParallelJit.py +0 -143
- acoular/tests/unsupported/SpeedComparison/setupCythonOpenMP.py +0 -63
- acoular/tests/unsupported/SpeedComparison/sharedFunctions.py +0 -153
- acoular/tests/unsupported/SpeedComparison/timeOverNMics_AllImportantMethods.png +0 -0
- acoular/tests/unsupported/SpeedComparison/timeOverNMics_faverage.png +0 -0
- acoular/tests/unsupported/SpeedComparison/vglOptimierungFAverage.py +0 -204
- acoular/tests/unsupported/SpeedComparison/vglOptimierungGaussSeidel.py +0 -182
- acoular/tests/unsupported/SpeedComparison/vglOptimierungR_BEAMFULL_INVERSE.py +0 -764
- acoular/tests/unsupported/SpeedComparison/vglOptimierungR_BEAM_OS.py +0 -231
- acoular/tests/unsupported/SpeedComparison/whatsFastestWayFor_absASquared.py +0 -48
- acoular/tests/unsupported/functionalBeamformer.py +0 -123
- acoular/tests/unsupported/precisionTest.py +0 -153
- acoular/tests/unsupported/validationOfBeamformerFuncsPOSTAcoularIntegration.py +0 -254
- acoular/tests/unsupported/validationOfBeamformerFuncsPREeAcoularIntegration.py +0 -531
- acoular/tools.py +0 -422
- acoular-24.3.dist-info/RECORD +0 -148
- acoular-24.3.dist-info/licenses/LICENSE +0 -29
- {acoular-24.3.dist-info → acoular-24.7.dist-info}/licenses/AUTHORS.rst +0 -0
acoular/spectra.py
CHANGED
|
@@ -1,48 +1,76 @@
|
|
|
1
|
-
#
|
|
2
|
-
#pylint: disable-msg=E0611, E1101, E1103, C0103, R0901, R0902, R0903, R0904
|
|
3
|
-
#pylint: disable-msg=W0232
|
|
4
|
-
#------------------------------------------------------------------------------
|
|
1
|
+
# ------------------------------------------------------------------------------
|
|
5
2
|
# Copyright (c) Acoular Development Team.
|
|
6
|
-
|
|
7
|
-
"""Estimation of power spectra and related tools
|
|
3
|
+
# ------------------------------------------------------------------------------
|
|
4
|
+
"""Estimation of power spectra and related tools.
|
|
8
5
|
|
|
9
6
|
.. autosummary::
|
|
10
7
|
:toctree: generated/
|
|
11
8
|
|
|
12
9
|
BaseSpectra
|
|
13
|
-
FFTSpectra
|
|
10
|
+
FFTSpectra
|
|
14
11
|
PowerSpectra
|
|
15
12
|
synthetic
|
|
16
13
|
PowerSpectraImport
|
|
17
14
|
"""
|
|
15
|
+
|
|
18
16
|
from warnings import warn
|
|
19
17
|
|
|
20
|
-
from numpy import
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
from numpy import (
|
|
19
|
+
arange,
|
|
20
|
+
array,
|
|
21
|
+
bartlett,
|
|
22
|
+
blackman,
|
|
23
|
+
dot,
|
|
24
|
+
empty,
|
|
25
|
+
fill_diagonal,
|
|
26
|
+
hamming,
|
|
27
|
+
hanning,
|
|
28
|
+
imag,
|
|
29
|
+
isscalar,
|
|
30
|
+
linalg,
|
|
31
|
+
ndarray,
|
|
32
|
+
newaxis,
|
|
33
|
+
ones,
|
|
34
|
+
real,
|
|
35
|
+
searchsorted,
|
|
36
|
+
sqrt,
|
|
37
|
+
sum,
|
|
38
|
+
zeros,
|
|
39
|
+
zeros_like,
|
|
40
|
+
)
|
|
23
41
|
from scipy import fft
|
|
24
|
-
from traits.api import
|
|
25
|
-
Bool,
|
|
26
|
-
CArray
|
|
42
|
+
from traits.api import (
|
|
43
|
+
Bool,
|
|
44
|
+
CArray,
|
|
45
|
+
Delegate,
|
|
46
|
+
Enum,
|
|
47
|
+
Float,
|
|
48
|
+
HasPrivateTraits,
|
|
49
|
+
Instance,
|
|
50
|
+
Int,
|
|
51
|
+
Property,
|
|
52
|
+
Trait,
|
|
53
|
+
cached_property,
|
|
54
|
+
property_depends_on,
|
|
55
|
+
)
|
|
27
56
|
|
|
57
|
+
from .calib import Calib
|
|
58
|
+
from .configuration import config
|
|
28
59
|
from .fastFuncs import calcCSM
|
|
29
60
|
from .h5cache import H5cache
|
|
30
61
|
from .h5files import H5CacheFileBase
|
|
31
62
|
from .internal import digest
|
|
32
63
|
from .tprocess import SamplesGenerator, TimeInOut
|
|
33
|
-
from .calib import Calib
|
|
34
|
-
from .configuration import config
|
|
35
|
-
|
|
36
64
|
|
|
37
|
-
class BaseSpectra( HasPrivateTraits ):
|
|
38
65
|
|
|
66
|
+
class BaseSpectra(HasPrivateTraits):
|
|
39
67
|
#: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
|
|
40
68
|
source = Trait(SamplesGenerator)
|
|
41
69
|
|
|
42
70
|
#: Sampling frequency of output signal, as given by :attr:`source`.
|
|
43
71
|
sample_freq = Delegate('source')
|
|
44
72
|
|
|
45
|
-
#: Number of time data channels
|
|
73
|
+
#: Number of time data channels
|
|
46
74
|
numchannels = Delegate('source')
|
|
47
75
|
|
|
48
76
|
#: Window function for FFT, one of:
|
|
@@ -51,117 +79,121 @@ class BaseSpectra( HasPrivateTraits ):
|
|
|
51
79
|
#: * 'Hamming'
|
|
52
80
|
#: * 'Bartlett'
|
|
53
81
|
#: * 'Blackman'
|
|
54
|
-
window = Trait(
|
|
55
|
-
|
|
56
|
-
'Hanning':hanning,
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
'Blackman':blackman},
|
|
60
|
-
desc="type of window for FFT")
|
|
82
|
+
window = Trait(
|
|
83
|
+
'Rectangular',
|
|
84
|
+
{'Rectangular': ones, 'Hanning': hanning, 'Hamming': hamming, 'Bartlett': bartlett, 'Blackman': blackman},
|
|
85
|
+
desc='type of window for FFT',
|
|
86
|
+
)
|
|
61
87
|
|
|
62
88
|
#: Overlap factor for averaging: 'None'(default), '50%', '75%', '87.5%'.
|
|
63
|
-
overlap = Trait('None', {'None':1, '50%':2, '75%':4, '87.5%':8},
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
overlap = Trait('None', {'None': 1, '50%': 2, '75%': 4, '87.5%': 8}, desc='overlap of FFT blocks')
|
|
90
|
+
|
|
66
91
|
#: FFT block size, one of: 128, 256, 512, 1024, 2048 ... 65536,
|
|
67
92
|
#: defaults to 1024.
|
|
68
|
-
block_size = Trait(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
93
|
+
block_size = Trait(
|
|
94
|
+
1024,
|
|
95
|
+
128,
|
|
96
|
+
256,
|
|
97
|
+
512,
|
|
98
|
+
1024,
|
|
99
|
+
2048,
|
|
100
|
+
4096,
|
|
101
|
+
8192,
|
|
102
|
+
16384,
|
|
103
|
+
32768,
|
|
104
|
+
65536,
|
|
105
|
+
desc='number of samples per FFT block',
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
#: The floating-number-precision of entries of csm, eigenvalues and
|
|
72
109
|
#: eigenvectors, corresponding to numpy dtypes. Default is 64 bit.
|
|
73
|
-
precision = Trait('complex128', 'complex64',
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
precision = Trait('complex128', 'complex64', desc='precision of the fft')
|
|
111
|
+
|
|
76
112
|
# internal identifier
|
|
77
|
-
digest = Property(
|
|
78
|
-
'window','overlap'])
|
|
113
|
+
digest = Property(depends_on=['precision', 'block_size', 'window', 'overlap'])
|
|
79
114
|
|
|
80
115
|
@cached_property
|
|
81
|
-
def _get_digest(
|
|
116
|
+
def _get_digest(self):
|
|
82
117
|
return digest(self)
|
|
83
118
|
|
|
84
|
-
def fftfreq
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
|
|
119
|
+
def fftfreq(self):
|
|
120
|
+
"""Return the Discrete Fourier Transform sample frequencies.
|
|
121
|
+
|
|
88
122
|
Returns
|
|
89
123
|
-------
|
|
90
124
|
f : ndarray
|
|
91
125
|
Array of length *block_size/2+1* containing the sample frequencies.
|
|
126
|
+
|
|
92
127
|
"""
|
|
93
128
|
if self.source is not None:
|
|
94
|
-
return abs(fft.fftfreq(self.block_size, 1
|
|
95
|
-
|
|
96
|
-
else:
|
|
97
|
-
return None
|
|
129
|
+
return abs(fft.fftfreq(self.block_size, 1.0 / self.source.sample_freq)[: int(self.block_size / 2 + 1)])
|
|
130
|
+
return None
|
|
98
131
|
|
|
99
|
-
#generator that yields the time data blocks for every channel (with optional overlap)
|
|
132
|
+
# generator that yields the time data blocks for every channel (with optional overlap)
|
|
100
133
|
def get_source_data(self):
|
|
101
134
|
bs = self.block_size
|
|
102
|
-
temp = empty((2*bs, self.numchannels))
|
|
135
|
+
temp = empty((2 * bs, self.numchannels))
|
|
103
136
|
pos = bs
|
|
104
|
-
posinc = bs/self.overlap_
|
|
137
|
+
posinc = bs / self.overlap_
|
|
105
138
|
for data_block in self.source.result(bs):
|
|
106
139
|
ns = data_block.shape[0]
|
|
107
|
-
temp[bs:bs+ns] = data_block
|
|
108
|
-
while pos+bs <= bs+ns:
|
|
109
|
-
yield temp[int(pos):int(pos+bs)]
|
|
140
|
+
temp[bs : bs + ns] = data_block # fill from right
|
|
141
|
+
while pos + bs <= bs + ns:
|
|
142
|
+
yield temp[int(pos) : int(pos + bs)]
|
|
110
143
|
pos += posinc
|
|
111
144
|
else:
|
|
112
|
-
temp[0:bs] = temp[bs:]
|
|
145
|
+
temp[0:bs] = temp[bs:] # copy to left
|
|
113
146
|
pos -= bs
|
|
114
147
|
|
|
115
148
|
|
|
116
|
-
class FFTSpectra(
|
|
117
|
-
"""Provides the spectra of multichannel time data.
|
|
118
|
-
|
|
119
|
-
Returns Spectra per block over a Generator.
|
|
149
|
+
class FFTSpectra(BaseSpectra, TimeInOut):
|
|
150
|
+
"""Provides the spectra of multichannel time data.
|
|
151
|
+
|
|
152
|
+
Returns Spectra per block over a Generator.
|
|
120
153
|
"""
|
|
121
|
-
|
|
154
|
+
|
|
122
155
|
# internal identifier
|
|
123
|
-
digest = Property(
|
|
124
|
-
'window','overlap'])
|
|
156
|
+
digest = Property(depends_on=['source.digest', 'precision', 'block_size', 'window', 'overlap'])
|
|
125
157
|
|
|
126
158
|
@cached_property
|
|
127
|
-
def _get_digest(
|
|
159
|
+
def _get_digest(self):
|
|
128
160
|
return digest(self)
|
|
129
161
|
|
|
130
|
-
#generator that yields the fft for every channel
|
|
162
|
+
# generator that yields the fft for every channel
|
|
131
163
|
def result(self):
|
|
132
|
-
"""
|
|
133
|
-
|
|
134
|
-
|
|
164
|
+
"""Python generator that yields the output block-wise.
|
|
165
|
+
|
|
135
166
|
Parameters
|
|
136
167
|
----------
|
|
137
168
|
num : integer
|
|
138
169
|
This parameter defines the size of the blocks to be yielded
|
|
139
170
|
(i.e. the number of samples per block).
|
|
140
|
-
|
|
171
|
+
|
|
141
172
|
Returns
|
|
142
173
|
-------
|
|
143
|
-
Samples in blocks of shape (numfreq, :attr:`numchannels`).
|
|
174
|
+
Samples in blocks of shape (numfreq, :attr:`numchannels`).
|
|
144
175
|
The last block may be shorter than num.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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]
|
|
148
180
|
for data in self.get_source_data():
|
|
149
|
-
ft = fft.rfft(data*weight, None, 0).astype(self.precision)
|
|
181
|
+
ft = fft.rfft(data * weight, None, 0).astype(self.precision)
|
|
150
182
|
yield ft
|
|
151
183
|
|
|
152
184
|
|
|
153
|
-
class PowerSpectra(
|
|
185
|
+
class PowerSpectra(BaseSpectra):
|
|
154
186
|
"""Provides the cross spectral matrix of multichannel time data
|
|
155
187
|
and its eigen-decomposition.
|
|
156
|
-
|
|
188
|
+
|
|
157
189
|
This class includes the efficient calculation of the full cross spectral
|
|
158
|
-
matrix using the Welch method with windows and overlap. It also contains
|
|
159
|
-
the CSM's eigenvalues and eigenvectors and additional properties.
|
|
160
|
-
|
|
190
|
+
matrix using the Welch method with windows and overlap. It also contains
|
|
191
|
+
the CSM's eigenvalues and eigenvectors and additional properties.
|
|
192
|
+
|
|
161
193
|
The result is computed only when needed, that is when the :attr:`csm`,
|
|
162
194
|
:attr:`eva`, or :attr:`eve` attributes are acturally read.
|
|
163
|
-
Any change in the input data or parameters leads to a new calculation,
|
|
164
|
-
again triggered when an attribute is read. The result may be
|
|
195
|
+
Any change in the input data or parameters leads to a new calculation,
|
|
196
|
+
again triggered when an attribute is read. The result may be
|
|
165
197
|
cached on disk in HDF5 files and need not to be recomputed during
|
|
166
198
|
subsequent program runs with identical input data and parameters. The
|
|
167
199
|
input data is taken to be identical if the source has identical parameters
|
|
@@ -171,155 +203,140 @@ class PowerSpectra( BaseSpectra ):
|
|
|
171
203
|
# Shadow trait, should not be set directly, for internal use.
|
|
172
204
|
_source = Trait(SamplesGenerator)
|
|
173
205
|
|
|
174
|
-
#: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
|
|
175
|
-
source = Property(_source,
|
|
176
|
-
desc="time data object")
|
|
206
|
+
#: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
|
|
207
|
+
source = Property(_source, desc='time data object')
|
|
177
208
|
|
|
178
209
|
#: The :class:`~acoular.tprocess.SamplesGenerator` object that provides the data.
|
|
179
|
-
time_data = Property(
|
|
180
|
-
|
|
210
|
+
time_data = Property(
|
|
211
|
+
_source,
|
|
212
|
+
desc='deprecated attribute holding the time data object. Use PowerSpectra.source instead!',
|
|
213
|
+
)
|
|
181
214
|
|
|
182
|
-
#: The :class:`~acoular.calib.Calib` object that provides the calibration data,
|
|
215
|
+
#: The :class:`~acoular.calib.Calib` object that provides the calibration data,
|
|
183
216
|
#: defaults to no calibration, i.e. the raw time data is used.
|
|
184
217
|
#:
|
|
185
|
-
#: **deprecated**: use :attr:`~acoular.sources.TimeSamples.calib` property of
|
|
218
|
+
#: **deprecated**: use :attr:`~acoular.sources.TimeSamples.calib` property of
|
|
186
219
|
#: :class:`~acoular.sources.TimeSamples` objects
|
|
187
220
|
calib = Instance(Calib)
|
|
188
221
|
|
|
189
222
|
# Shadow trait, should not be set directly, for internal use.
|
|
190
|
-
_ind_low = Int(1,
|
|
191
|
-
desc="index of lowest frequency line")
|
|
223
|
+
_ind_low = Int(1, desc='index of lowest frequency line')
|
|
192
224
|
|
|
193
225
|
# Shadow trait, should not be set directly, for internal use.
|
|
194
|
-
_ind_high = Trait(-1,(Int,None),
|
|
195
|
-
desc="index of highest frequency line")
|
|
226
|
+
_ind_high = Trait(-1, (Int, None), desc='index of highest frequency line')
|
|
196
227
|
|
|
197
228
|
#: Index of lowest frequency line to compute, integer, defaults to 1,
|
|
198
229
|
#: is used only by objects that fetch the csm, PowerSpectra computes every
|
|
199
230
|
#: frequency line.
|
|
200
|
-
ind_low = Property(_ind_low,
|
|
201
|
-
desc="index of lowest frequency line")
|
|
231
|
+
ind_low = Property(_ind_low, desc='index of lowest frequency line')
|
|
202
232
|
|
|
203
|
-
#: Index of highest frequency line to compute, integer,
|
|
233
|
+
#: Index of highest frequency line to compute, integer,
|
|
204
234
|
#: defaults to -1 (last possible line for default block_size).
|
|
205
|
-
ind_high = Property(_ind_high,
|
|
206
|
-
desc="index of lowest frequency line")
|
|
235
|
+
ind_high = Property(_ind_high, desc='index of lowest frequency line')
|
|
207
236
|
|
|
208
237
|
# Stores the set lower frequency, for internal use, should not be set directly.
|
|
209
238
|
_freqlc = Float(0)
|
|
210
239
|
|
|
211
240
|
# Stores the set higher frequency, for internal use, should not be set directly.
|
|
212
|
-
_freqhc = Trait(0,(Float,None))
|
|
241
|
+
_freqhc = Trait(0, (Float, None))
|
|
213
242
|
|
|
214
243
|
# Saves whether the user set indices or frequencies last, for internal use only,
|
|
215
244
|
# not to be set directly, if True (default), indices are used for setting
|
|
216
245
|
# the freq_range interval.
|
|
217
246
|
_index_set_last = Bool(True)
|
|
218
|
-
|
|
247
|
+
|
|
219
248
|
#: Flag, if true (default), the result is cached in h5 files and need not
|
|
220
249
|
#: to be recomputed during subsequent program runs.
|
|
221
|
-
cached = Bool(True,
|
|
222
|
-
desc="cached flag")
|
|
250
|
+
cached = Bool(True, desc='cached flag')
|
|
223
251
|
|
|
224
252
|
#: Number of FFT blocks to average, readonly
|
|
225
253
|
#: (set from block_size and overlap).
|
|
226
|
-
num_blocks = Property(
|
|
227
|
-
desc="overall number of FFT blocks")
|
|
254
|
+
num_blocks = Property(desc='overall number of FFT blocks')
|
|
228
255
|
|
|
229
|
-
#: 2-element array with the lowest and highest frequency. If set,
|
|
256
|
+
#: 2-element array with the lowest and highest frequency. If set,
|
|
230
257
|
#: will overwrite :attr:`_freqlc` and :attr:`_freqhc` according to
|
|
231
|
-
#: the range.
|
|
258
|
+
#: the range.
|
|
232
259
|
#: The freq_range interval will be the smallest discrete frequency
|
|
233
260
|
#: inside the half-open interval [_freqlc, _freqhc[ and the smallest
|
|
234
261
|
#: upper frequency outside of the interval.
|
|
235
262
|
#: If user chooses the higher frequency larger than the max frequency,
|
|
236
263
|
#: the max frequency will be the upper bound.
|
|
237
|
-
freq_range = Property(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
#: Array with a sequence of indices for all frequencies
|
|
264
|
+
freq_range = Property(desc='frequency range')
|
|
265
|
+
|
|
266
|
+
#: Array with a sequence of indices for all frequencies
|
|
241
267
|
#: between :attr:`ind_low` and :attr:`ind_high` within the result, readonly.
|
|
242
|
-
indices = Property(
|
|
243
|
-
|
|
244
|
-
|
|
268
|
+
indices = Property(desc='index range')
|
|
269
|
+
|
|
245
270
|
#: Name of the cache file without extension, readonly.
|
|
246
|
-
basename = Property(
|
|
247
|
-
desc="basename for cache file")
|
|
271
|
+
basename = Property(depends_on='_source.digest', desc='basename for cache file')
|
|
248
272
|
|
|
249
|
-
#: The cross spectral matrix,
|
|
273
|
+
#: The cross spectral matrix,
|
|
250
274
|
#: (number of frequencies, numchannels, numchannels) array of complex;
|
|
251
275
|
#: readonly.
|
|
252
|
-
csm = Property(
|
|
253
|
-
|
|
254
|
-
|
|
276
|
+
csm = Property(desc='cross spectral matrix')
|
|
277
|
+
|
|
255
278
|
#: Eigenvalues of the cross spectral matrix as an
|
|
256
279
|
#: (number of frequencies) array of floats, readonly.
|
|
257
|
-
eva = Property(
|
|
258
|
-
desc="eigenvalues of cross spectral matrix")
|
|
280
|
+
eva = Property(desc='eigenvalues of cross spectral matrix')
|
|
259
281
|
|
|
260
282
|
#: Eigenvectors of the cross spectral matrix as an
|
|
261
283
|
#: (number of frequencies, numchannels, numchannels) array of floats,
|
|
262
284
|
#: readonly.
|
|
263
|
-
eve = Property(
|
|
264
|
-
desc="eigenvectors of cross spectral matrix")
|
|
285
|
+
eve = Property(desc='eigenvectors of cross spectral matrix')
|
|
265
286
|
|
|
266
287
|
# internal identifier
|
|
267
|
-
digest = Property(
|
|
268
|
-
depends_on
|
|
269
|
-
|
|
270
|
-
)
|
|
288
|
+
digest = Property(
|
|
289
|
+
depends_on=['_source.digest', 'calib.digest', 'block_size', 'window', 'overlap', 'precision'],
|
|
290
|
+
)
|
|
271
291
|
|
|
272
292
|
# hdf5 cache file
|
|
273
|
-
h5f = Instance(
|
|
274
|
-
|
|
293
|
+
h5f = Instance(H5CacheFileBase, transient=True)
|
|
294
|
+
|
|
275
295
|
@property_depends_on('_source.numsamples, block_size, overlap')
|
|
276
|
-
def _get_num_blocks
|
|
277
|
-
return self.overlap_*self._source.numsamples/self.block_size
|
|
278
|
-
self.overlap_+1
|
|
296
|
+
def _get_num_blocks(self):
|
|
297
|
+
return self.overlap_ * self._source.numsamples / self.block_size - self.overlap_ + 1
|
|
279
298
|
|
|
280
299
|
@property_depends_on('_source.sample_freq, block_size, ind_low, ind_high')
|
|
281
|
-
def _get_freq_range
|
|
300
|
+
def _get_freq_range(self):
|
|
282
301
|
fftfreq = self.fftfreq()
|
|
283
302
|
if fftfreq is not None:
|
|
284
303
|
if self._ind_high is None:
|
|
285
|
-
return array([fftfreq[self.ind_low],None])
|
|
286
|
-
|
|
287
|
-
|
|
304
|
+
return array([fftfreq[self.ind_low], None])
|
|
305
|
+
return fftfreq[[self.ind_low, self.ind_high]]
|
|
306
|
+
return None
|
|
288
307
|
|
|
289
|
-
def _set_freq_range(
|
|
308
|
+
def _set_freq_range(self, freq_range): # by setting this the user sets _freqlc and _freqhc
|
|
290
309
|
self._index_set_last = False
|
|
291
310
|
self._freqlc = freq_range[0]
|
|
292
311
|
self._freqhc = freq_range[1]
|
|
293
312
|
|
|
294
|
-
@property_depends_on(
|
|
295
|
-
def _get_ind_low(
|
|
313
|
+
@property_depends_on('_source.sample_freq, block_size, _ind_low, _freqlc')
|
|
314
|
+
def _get_ind_low(self):
|
|
296
315
|
fftfreq = self.fftfreq()
|
|
297
316
|
if fftfreq is not None:
|
|
298
317
|
if self._index_set_last:
|
|
299
|
-
return min(self._ind_low, fftfreq.shape[0]-1)
|
|
300
|
-
|
|
301
|
-
|
|
318
|
+
return min(self._ind_low, fftfreq.shape[0] - 1)
|
|
319
|
+
return searchsorted(fftfreq[:-1], self._freqlc)
|
|
320
|
+
return None
|
|
302
321
|
|
|
303
|
-
@property_depends_on(
|
|
304
|
-
def _get_ind_high(
|
|
322
|
+
@property_depends_on('_source.sample_freq, block_size, _ind_high, _freqhc')
|
|
323
|
+
def _get_ind_high(self):
|
|
305
324
|
fftfreq = self.fftfreq()
|
|
306
325
|
if fftfreq is not None:
|
|
307
326
|
if self._index_set_last:
|
|
308
|
-
if self._ind_high is None:
|
|
309
|
-
return None
|
|
310
|
-
else:
|
|
311
|
-
return min(self._ind_high, fftfreq.shape[0]-1)
|
|
312
|
-
else:
|
|
313
|
-
if self._freqhc is None:
|
|
327
|
+
if self._ind_high is None:
|
|
314
328
|
return None
|
|
315
|
-
|
|
316
|
-
|
|
329
|
+
return min(self._ind_high, fftfreq.shape[0] - 1)
|
|
330
|
+
if self._freqhc is None:
|
|
331
|
+
return None
|
|
332
|
+
return searchsorted(fftfreq[:-1], self._freqhc)
|
|
333
|
+
return None
|
|
317
334
|
|
|
318
|
-
def _set_ind_high(self, ind_high)
|
|
335
|
+
def _set_ind_high(self, ind_high): # by setting this the user sets the lower index
|
|
319
336
|
self._index_set_last = True
|
|
320
337
|
self._ind_high = ind_high
|
|
321
338
|
|
|
322
|
-
def _set_ind_low(
|
|
339
|
+
def _set_ind_low(self, ind_low): # by setting this the user sets the higher index
|
|
323
340
|
self._index_set_last = True
|
|
324
341
|
self._ind_low = ind_low
|
|
325
342
|
|
|
@@ -335,194 +352,178 @@ class PowerSpectra( BaseSpectra ):
|
|
|
335
352
|
def _get_source(self):
|
|
336
353
|
return self._source
|
|
337
354
|
|
|
338
|
-
@property_depends_on(
|
|
339
|
-
def _get_indices
|
|
355
|
+
@property_depends_on('block_size, ind_low, ind_high')
|
|
356
|
+
def _get_indices(self):
|
|
340
357
|
fftfreq = self.fftfreq()
|
|
341
358
|
if fftfreq is not None:
|
|
342
359
|
try:
|
|
343
|
-
indices = arange(fftfreq.shape[0],dtype=int)
|
|
360
|
+
indices = arange(fftfreq.shape[0], dtype=int)
|
|
344
361
|
if self.ind_high is None:
|
|
345
|
-
return indices[
|
|
346
|
-
|
|
347
|
-
return indices[ self.ind_low: self.ind_high ]
|
|
362
|
+
return indices[self.ind_low :]
|
|
363
|
+
return indices[self.ind_low : self.ind_high]
|
|
348
364
|
except IndexError:
|
|
349
365
|
return range(0)
|
|
366
|
+
return None
|
|
350
367
|
|
|
351
368
|
@cached_property
|
|
352
|
-
def _get_digest(
|
|
353
|
-
return digest(
|
|
369
|
+
def _get_digest(self):
|
|
370
|
+
return digest(self)
|
|
354
371
|
|
|
355
372
|
@cached_property
|
|
356
|
-
def _get_basename(
|
|
373
|
+
def _get_basename(self):
|
|
357
374
|
if 'basename' in self._source.all_trait_names():
|
|
358
375
|
return self._source.basename
|
|
359
|
-
|
|
360
|
-
return self._source.__class__.__name__ + self._source.digest
|
|
376
|
+
return self._source.__class__.__name__ + self._source.digest
|
|
361
377
|
|
|
362
|
-
def calc_csm(
|
|
363
|
-
"""
|
|
378
|
+
def calc_csm(self):
|
|
379
|
+
"""Csm calculation."""
|
|
364
380
|
t = self.source
|
|
365
|
-
wind = self.window_(
|
|
366
|
-
weight = dot(
|
|
367
|
-
wind = wind[newaxis, :].swapaxes(
|
|
368
|
-
numfreq = int(self.block_size/2 + 1)
|
|
381
|
+
wind = self.window_(self.block_size)
|
|
382
|
+
weight = dot(wind, wind)
|
|
383
|
+
wind = wind[newaxis, :].swapaxes(0, 1)
|
|
384
|
+
numfreq = int(self.block_size / 2 + 1)
|
|
369
385
|
csm_shape = (numfreq, t.numchannels, t.numchannels)
|
|
370
|
-
|
|
371
|
-
#print "num blocks", self.num_blocks
|
|
386
|
+
csm_upper = zeros(csm_shape, dtype=self.precision)
|
|
387
|
+
# print "num blocks", self.num_blocks
|
|
372
388
|
# for backward compatibility
|
|
373
389
|
if self.calib and self.calib.num_mics > 0:
|
|
374
390
|
if self.calib.num_mics == t.numchannels:
|
|
375
391
|
wind = wind * self.calib.data[newaxis, :]
|
|
376
392
|
else:
|
|
377
|
-
raise ValueError(
|
|
378
|
-
"Calibration data not compatible: %i, %i" % \
|
|
379
|
-
(self.calib.num_mics, t.numchannels))
|
|
393
|
+
raise ValueError('Calibration data not compatible: %i, %i' % (self.calib.num_mics, t.numchannels))
|
|
380
394
|
# get time data blockwise
|
|
381
395
|
for data in self.get_source_data():
|
|
382
|
-
ft = fft.rfft(data*wind, None, 0).astype(self.precision)
|
|
383
|
-
calcCSM(
|
|
396
|
+
ft = fft.rfft(data * wind, None, 0).astype(self.precision)
|
|
397
|
+
calcCSM(csm_upper, ft) # only upper triangular part of matrix is calculated (for speed reasons)
|
|
384
398
|
# create the full csm matrix via transposing and complex conj.
|
|
385
|
-
|
|
386
|
-
[fill_diagonal(
|
|
387
|
-
csm =
|
|
399
|
+
csm_lower = csm_upper.conj().transpose(0, 2, 1)
|
|
400
|
+
[fill_diagonal(csm_lower[cntFreq, :, :], 0) for cntFreq in range(csm_lower.shape[0])]
|
|
401
|
+
csm = csm_lower + csm_upper
|
|
388
402
|
# onesided spectrum: multiplication by 2.0=sqrt(2)^2
|
|
389
|
-
csm
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
elif self.precision == 'complex64':
|
|
396
|
-
|
|
403
|
+
return csm * (2.0 / self.block_size / weight / self.num_blocks)
|
|
404
|
+
|
|
405
|
+
def calc_ev(self):
|
|
406
|
+
"""Eigenvalues / eigenvectors calculation."""
|
|
407
|
+
if self.precision == 'complex128':
|
|
408
|
+
eva_dtype = 'float64'
|
|
409
|
+
elif self.precision == 'complex64':
|
|
410
|
+
eva_dtype = 'float32'
|
|
411
|
+
# csm = self.csm #trigger calculation
|
|
397
412
|
csm_shape = self.csm.shape
|
|
398
413
|
eva = empty(csm_shape[0:2], dtype=eva_dtype)
|
|
399
414
|
eve = empty(csm_shape, dtype=self.precision)
|
|
400
415
|
for i in range(csm_shape[0]):
|
|
401
|
-
(eva[i], eve[i])=linalg.eigh(self.csm[i])
|
|
402
|
-
return (eva,eve)
|
|
416
|
+
(eva[i], eve[i]) = linalg.eigh(self.csm[i])
|
|
417
|
+
return (eva, eve)
|
|
403
418
|
|
|
404
|
-
def calc_eva(
|
|
405
|
-
"""
|
|
419
|
+
def calc_eva(self):
|
|
420
|
+
"""Calculates eigenvalues of csm."""
|
|
406
421
|
return self.calc_ev()[0]
|
|
407
|
-
|
|
408
|
-
def calc_eve(
|
|
409
|
-
"""
|
|
422
|
+
|
|
423
|
+
def calc_eve(self):
|
|
424
|
+
"""Calculates eigenvectors of csm."""
|
|
410
425
|
return self.calc_ev()[1]
|
|
411
|
-
|
|
426
|
+
|
|
412
427
|
def _handle_dual_calibration(self):
|
|
413
|
-
obj = self.source
|
|
428
|
+
obj = self.source # start with time_data obj
|
|
414
429
|
while obj:
|
|
415
|
-
if 'calib' in obj.all_trait_names():
|
|
430
|
+
if 'calib' in obj.all_trait_names(): # at original source?
|
|
416
431
|
if obj.calib and self.calib:
|
|
417
432
|
if obj.calib.digest == self.calib.digest:
|
|
418
|
-
self.calib = None
|
|
433
|
+
self.calib = None # ignore it silently
|
|
419
434
|
else:
|
|
420
|
-
|
|
421
|
-
|
|
435
|
+
msg = 'Non-identical dual calibration for both TimeSamples and PowerSpectra object'
|
|
436
|
+
raise ValueError(msg)
|
|
422
437
|
obj = None
|
|
423
438
|
else:
|
|
424
439
|
try:
|
|
425
|
-
obj = obj.source
|
|
440
|
+
obj = obj.source # traverse down until original data source
|
|
426
441
|
except AttributeError:
|
|
427
442
|
obj = None
|
|
428
443
|
|
|
429
|
-
def _get_filecache(
|
|
430
|
-
"""
|
|
431
|
-
|
|
432
|
-
calculation depending on global/local caching behaviour.
|
|
444
|
+
def _get_filecache(self, traitname):
|
|
445
|
+
"""Function handles result caching of csm, eigenvectors and eigenvalues
|
|
446
|
+
calculation depending on global/local caching behaviour.
|
|
433
447
|
"""
|
|
434
448
|
if traitname == 'csm':
|
|
435
449
|
func = self.calc_csm
|
|
436
|
-
numfreq = int(self.block_size/2 + 1)
|
|
450
|
+
numfreq = int(self.block_size / 2 + 1)
|
|
437
451
|
shape = (numfreq, self._source.numchannels, self._source.numchannels)
|
|
438
452
|
precision = self.precision
|
|
439
453
|
elif traitname == 'eva':
|
|
440
454
|
func = self.calc_eva
|
|
441
455
|
shape = self.csm.shape[0:2]
|
|
442
|
-
if self.precision == 'complex128':
|
|
443
|
-
|
|
456
|
+
if self.precision == 'complex128':
|
|
457
|
+
precision = 'float64'
|
|
458
|
+
elif self.precision == 'complex64':
|
|
459
|
+
precision = 'float32'
|
|
444
460
|
elif traitname == 'eve':
|
|
445
461
|
func = self.calc_eve
|
|
446
462
|
shape = self.csm.shape
|
|
447
463
|
precision = self.precision
|
|
448
464
|
|
|
449
|
-
H5cache.get_cache_file(
|
|
450
|
-
if not self.h5f:
|
|
451
|
-
return func()
|
|
465
|
+
H5cache.get_cache_file(self, self.basename)
|
|
466
|
+
if not self.h5f: # in case of global caching readonly
|
|
467
|
+
return func()
|
|
452
468
|
|
|
453
|
-
nodename = traitname + '_' + self.digest
|
|
469
|
+
nodename = traitname + '_' + self.digest
|
|
454
470
|
if config.global_caching == 'overwrite' and self.h5f.is_cached(nodename):
|
|
455
|
-
#print("remove existing node",nodename)
|
|
456
|
-
self.h5f.remove_data(nodename)
|
|
457
|
-
|
|
458
|
-
if not self.h5f.is_cached(nodename):
|
|
459
|
-
if config.global_caching == 'readonly':
|
|
471
|
+
# print("remove existing node",nodename)
|
|
472
|
+
self.h5f.remove_data(nodename) # remove old data before writing in overwrite mode
|
|
473
|
+
|
|
474
|
+
if not self.h5f.is_cached(nodename):
|
|
475
|
+
if config.global_caching == 'readonly':
|
|
460
476
|
return func()
|
|
461
|
-
# print("create array, data not cached for",nodename)
|
|
462
|
-
self.h5f.create_compressible_array(nodename,shape,precision)
|
|
463
|
-
|
|
477
|
+
# print("create array, data not cached for",nodename)
|
|
478
|
+
self.h5f.create_compressible_array(nodename, shape, precision)
|
|
479
|
+
|
|
464
480
|
ac = self.h5f.get_data_by_reference(nodename)
|
|
465
|
-
if ac[:].sum() == 0:
|
|
466
|
-
# print("write {} to:".format(traitname),nodename)
|
|
481
|
+
if ac[:].sum() == 0: # only initialized
|
|
482
|
+
# print("write {} to:".format(traitname),nodename)
|
|
467
483
|
ac[:] = func()
|
|
468
484
|
self.h5f.flush()
|
|
469
485
|
return ac
|
|
470
|
-
|
|
486
|
+
|
|
471
487
|
@property_depends_on('digest')
|
|
472
|
-
def _get_csm
|
|
473
|
-
"""
|
|
474
|
-
Main work is done here:
|
|
488
|
+
def _get_csm(self):
|
|
489
|
+
"""Main work is done here:
|
|
475
490
|
Cross spectral matrix is either loaded from cache file or
|
|
476
491
|
calculated and then additionally stored into cache.
|
|
477
492
|
"""
|
|
478
493
|
self._handle_dual_calibration()
|
|
479
|
-
if (
|
|
480
|
-
config.global_caching == 'none' or
|
|
481
|
-
(config.global_caching == 'individual' and self.cached is False)
|
|
482
|
-
):
|
|
494
|
+
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
483
495
|
return self.calc_csm()
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
496
|
+
return self._get_filecache('csm')
|
|
497
|
+
|
|
487
498
|
@property_depends_on('digest')
|
|
488
|
-
def _get_eva
|
|
489
|
-
"""
|
|
490
|
-
Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
499
|
+
def _get_eva(self):
|
|
500
|
+
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
491
501
|
calculated and then additionally stored into cache.
|
|
492
502
|
"""
|
|
493
|
-
if (
|
|
494
|
-
config.global_caching == 'none' or
|
|
495
|
-
(config.global_caching == 'individual' and self.cached is False)
|
|
496
|
-
):
|
|
503
|
+
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
497
504
|
return self.calc_eva()
|
|
498
|
-
|
|
499
|
-
return self._get_filecache('eva')
|
|
505
|
+
return self._get_filecache('eva')
|
|
500
506
|
|
|
501
507
|
@property_depends_on('digest')
|
|
502
|
-
def _get_eve
|
|
503
|
-
"""
|
|
504
|
-
Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
508
|
+
def _get_eve(self):
|
|
509
|
+
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
505
510
|
calculated and then additionally stored into cache.
|
|
506
511
|
"""
|
|
507
|
-
if (
|
|
508
|
-
config.global_caching == 'none' or
|
|
509
|
-
(config.global_caching == 'individual' and self.cached is False)
|
|
510
|
-
):
|
|
512
|
+
if config.global_caching == 'none' or (config.global_caching == 'individual' and self.cached is False):
|
|
511
513
|
return self.calc_eve()
|
|
512
|
-
|
|
513
|
-
return self._get_filecache('eve')
|
|
514
|
+
return self._get_filecache('eve')
|
|
514
515
|
|
|
515
|
-
def synthetic_ev(
|
|
516
|
+
def synthetic_ev(self, freq, num=0):
|
|
516
517
|
"""Return synthesized frequency band values of the eigenvalues.
|
|
517
|
-
|
|
518
|
+
|
|
518
519
|
Parameters
|
|
519
520
|
----------
|
|
520
|
-
freq : float
|
|
521
|
+
freq : float
|
|
521
522
|
Band center frequency for which to return the results.
|
|
522
523
|
num : integer
|
|
523
524
|
Controls the width of the frequency bands considered; defaults to
|
|
524
525
|
3 (third-octave band).
|
|
525
|
-
|
|
526
|
+
|
|
526
527
|
=== =====================
|
|
527
528
|
num frequency band width
|
|
528
529
|
=== =====================
|
|
@@ -534,39 +535,36 @@ class PowerSpectra( BaseSpectra ):
|
|
|
534
535
|
|
|
535
536
|
Returns
|
|
536
537
|
-------
|
|
537
|
-
float
|
|
538
|
+
float
|
|
538
539
|
Synthesized frequency band value of the eigenvalues (the sum of
|
|
539
540
|
all values that are contained in the band).
|
|
541
|
+
|
|
540
542
|
"""
|
|
541
543
|
f = self.fftfreq()
|
|
542
544
|
if num == 0:
|
|
543
545
|
# single frequency line
|
|
544
546
|
return self.eva[searchsorted(f, freq)]
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
else:
|
|
551
|
-
return sum(self.eva[f1:f2], 0)
|
|
547
|
+
f1 = searchsorted(f, freq * 2.0 ** (-0.5 / num))
|
|
548
|
+
f2 = searchsorted(f, freq * 2.0 ** (0.5 / num))
|
|
549
|
+
if f1 == f2:
|
|
550
|
+
return self.eva[f1]
|
|
551
|
+
return sum(self.eva[f1:f2], 0)
|
|
552
552
|
|
|
553
553
|
|
|
554
|
+
def synthetic(data, freqs, f, num=3):
|
|
555
|
+
"""Returns synthesized frequency band values of spectral data.
|
|
554
556
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
Returns synthesized frequency band values of spectral data.
|
|
558
|
-
|
|
559
|
-
If used with :meth:`Beamformer.result()<acoular.fbeamform.BeamformerBase.result>`
|
|
560
|
-
and only one frequency band, the output is identical to the result of the intrinsic
|
|
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
|
|
561
559
|
:meth:`Beamformer.synthetic<acoular.fbeamform.BeamformerBase.synthetic>` method.
|
|
562
|
-
It can, however, also be used with the
|
|
560
|
+
It can, however, also be used with the
|
|
563
561
|
:meth:`Beamformer.integrate<acoular.fbeamform.BeamformerBase.integrate>`
|
|
564
562
|
output and more frequency bands.
|
|
565
|
-
|
|
563
|
+
|
|
566
564
|
Parameters
|
|
567
565
|
----------
|
|
568
566
|
data : array of floats
|
|
569
|
-
The spectral data (squared sound pressures in Pa^2) in an array with one value
|
|
567
|
+
The spectral data (squared sound pressures in Pa^2) in an array with one value
|
|
570
568
|
per frequency line.
|
|
571
569
|
The number of entries must be identical to the number of
|
|
572
570
|
grid points.
|
|
@@ -579,7 +577,7 @@ def synthetic (data, freqs, f, num=3):
|
|
|
579
577
|
num : integer
|
|
580
578
|
Controls the width of the frequency bands considered; defaults to
|
|
581
579
|
3 (third-octave band).
|
|
582
|
-
|
|
580
|
+
|
|
583
581
|
=== =====================
|
|
584
582
|
num frequency band width
|
|
585
583
|
=== =====================
|
|
@@ -592,46 +590,55 @@ def synthetic (data, freqs, f, num=3):
|
|
|
592
590
|
Returns
|
|
593
591
|
-------
|
|
594
592
|
array of floats
|
|
595
|
-
Synthesized frequency band values of the beamforming result at
|
|
593
|
+
Synthesized frequency band values of the beamforming result at
|
|
596
594
|
each grid point (the sum of all values that are contained in the band).
|
|
597
|
-
Note that the frequency resolution and therefore the bandwidth
|
|
598
|
-
represented by a single frequency line depends on
|
|
599
|
-
the :attr:`sampling frequency<acoular.tprocess.SamplesGenerator.sample_freq>`
|
|
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>`
|
|
600
598
|
and used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
599
|
+
|
|
601
600
|
"""
|
|
602
601
|
if isscalar(f):
|
|
603
602
|
f = (f,)
|
|
604
603
|
if num == 0:
|
|
605
604
|
# single frequency lines
|
|
606
|
-
res =
|
|
605
|
+
res = []
|
|
607
606
|
for i in f:
|
|
608
607
|
ind = searchsorted(freqs, i)
|
|
609
608
|
if ind >= len(freqs):
|
|
610
|
-
warn(
|
|
611
|
-
|
|
612
|
-
|
|
609
|
+
warn(
|
|
610
|
+
'Queried frequency (%g Hz) not in resolved frequency range. Returning zeros.' % i,
|
|
611
|
+
Warning,
|
|
612
|
+
stacklevel=2,
|
|
613
|
+
)
|
|
613
614
|
h = zeros_like(data[0])
|
|
614
615
|
else:
|
|
615
616
|
if freqs[ind] != i:
|
|
616
|
-
warn(
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
+
)
|
|
620
624
|
h = data[ind]
|
|
621
|
-
res += [h]
|
|
625
|
+
res += [h]
|
|
622
626
|
else:
|
|
623
627
|
# fractional octave bands
|
|
624
|
-
res =
|
|
628
|
+
res = []
|
|
625
629
|
for i in f:
|
|
626
|
-
f1 = i*2
|
|
627
|
-
f2 = i*2
|
|
630
|
+
f1 = i * 2.0 ** (-0.5 / num)
|
|
631
|
+
f2 = i * 2.0 ** (+0.5 / num)
|
|
628
632
|
ind1 = searchsorted(freqs, f1)
|
|
629
633
|
ind2 = searchsorted(freqs, f2)
|
|
630
634
|
if ind1 == ind2:
|
|
631
|
-
warn(
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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
|
+
)
|
|
635
642
|
h = zeros_like(data[0])
|
|
636
643
|
else:
|
|
637
644
|
h = sum(data[ind1:ind2], 0)
|
|
@@ -639,140 +646,123 @@ def synthetic (data, freqs, f, num=3):
|
|
|
639
646
|
return array(res)
|
|
640
647
|
|
|
641
648
|
|
|
642
|
-
class PowerSpectraImport(
|
|
649
|
+
class PowerSpectraImport(PowerSpectra):
|
|
643
650
|
"""Provides a dummy class for using pre-calculated cross-spectral
|
|
644
|
-
matrices.
|
|
651
|
+
matrices.
|
|
645
652
|
|
|
646
|
-
This class does not calculate the cross-spectral matrix. Instead,
|
|
647
|
-
the user can inject one or multiple existing CSMs by setting the
|
|
653
|
+
This class does not calculate the cross-spectral matrix. Instead,
|
|
654
|
+
the user can inject one or multiple existing CSMs by setting the
|
|
648
655
|
:attr:`csm` attribute. This can be useful when algorithms shall be
|
|
649
656
|
evaluated with existing CSM matrices.
|
|
650
|
-
The frequency or frequencies contained by the CSM must be set via the
|
|
657
|
+
The frequency or frequencies contained by the CSM must be set via the
|
|
651
658
|
attr:`frequencies` attribute. The attr:`numchannels` attributes
|
|
652
|
-
is determined on the basis of the CSM shape.
|
|
653
|
-
In contrast to the PowerSpectra object, the attributes
|
|
659
|
+
is determined on the basis of the CSM shape.
|
|
660
|
+
In contrast to the PowerSpectra object, the attributes
|
|
654
661
|
:attr:`sample_freq`, :attr:`time_data`, :attr:`source`,
|
|
655
|
-
:attr:`block_size`, :attr:`calib`, :attr:`window`,
|
|
662
|
+
:attr:`block_size`, :attr:`calib`, :attr:`window`,
|
|
656
663
|
:attr:`overlap`, :attr:`cached`, and :attr:`num_blocks`
|
|
657
|
-
have no functionality.
|
|
664
|
+
have no functionality.
|
|
658
665
|
"""
|
|
659
666
|
|
|
660
|
-
#: The cross spectral matrix,
|
|
667
|
+
#: The cross spectral matrix,
|
|
661
668
|
#: (number of frequencies, numchannels, numchannels) array of complex;
|
|
662
|
-
csm = Property(
|
|
663
|
-
desc="cross spectral matrix")
|
|
669
|
+
csm = Property(desc='cross spectral matrix')
|
|
664
670
|
|
|
665
671
|
#: frequencies included in the cross-spectral matrix in ascending order.
|
|
666
672
|
#: Compound trait that accepts arguments of type list, array, and float
|
|
667
|
-
frequencies = Trait(None,(CArray,Float),
|
|
668
|
-
desc="frequencies included in the cross-spectral matrix")
|
|
673
|
+
frequencies = Trait(None, (CArray, Float), desc='frequencies included in the cross-spectral matrix')
|
|
669
674
|
|
|
670
|
-
#: Number of time data channels
|
|
675
|
+
#: Number of time data channels
|
|
671
676
|
numchannels = Property(depends_on=['digest'])
|
|
672
677
|
|
|
673
|
-
time_data = Enum(None,
|
|
674
|
-
desc="PowerSpectraImport cannot consume time data")
|
|
678
|
+
time_data = Enum(None, desc='PowerSpectraImport cannot consume time data')
|
|
675
679
|
|
|
676
|
-
source = Enum(None,
|
|
677
|
-
desc="PowerSpectraImport cannot consume time data")
|
|
680
|
+
source = Enum(None, desc='PowerSpectraImport cannot consume time data')
|
|
678
681
|
|
|
679
682
|
# Sampling frequency of the signal, defaults to None
|
|
680
|
-
sample_freq = Enum(None,
|
|
681
|
-
desc="sampling frequency")
|
|
683
|
+
sample_freq = Enum(None, desc='sampling frequency')
|
|
682
684
|
|
|
683
|
-
block_size = Enum(None,
|
|
684
|
-
desc="PowerSpectraImport does not operate on blocks of time data")
|
|
685
|
+
block_size = Enum(None, desc='PowerSpectraImport does not operate on blocks of time data')
|
|
685
686
|
|
|
686
|
-
calib = Enum(None,
|
|
687
|
-
desc="PowerSpectraImport cannot calibrate the time data")
|
|
687
|
+
calib = Enum(None, desc='PowerSpectraImport cannot calibrate the time data')
|
|
688
688
|
|
|
689
|
-
window = Enum(None,
|
|
690
|
-
desc="PowerSpectraImport does not perform windowing")
|
|
689
|
+
window = Enum(None, desc='PowerSpectraImport does not perform windowing')
|
|
691
690
|
|
|
692
|
-
overlap = Enum(None,
|
|
693
|
-
desc="PowerSpectraImport does not consume time data")
|
|
691
|
+
overlap = Enum(None, desc='PowerSpectraImport does not consume time data')
|
|
694
692
|
|
|
695
|
-
cached = Enum(False,
|
|
696
|
-
desc="PowerSpectraImport has no caching capabilities")
|
|
693
|
+
cached = Enum(False, desc='PowerSpectraImport has no caching capabilities')
|
|
697
694
|
|
|
698
|
-
num_blocks = Enum(None,
|
|
699
|
-
desc="PowerSpectraImport cannot determine the number of blocks")
|
|
695
|
+
num_blocks = Enum(None, desc='PowerSpectraImport cannot determine the number of blocks')
|
|
700
696
|
|
|
701
697
|
# Shadow trait, should not be set directly, for internal use.
|
|
702
|
-
_ind_low = Int(0,
|
|
703
|
-
desc="index of lowest frequency line")
|
|
698
|
+
_ind_low = Int(0, desc='index of lowest frequency line')
|
|
704
699
|
|
|
705
700
|
# Shadow trait, should not be set directly, for internal use.
|
|
706
|
-
_ind_high = Trait(None,(Int,None),
|
|
707
|
-
desc="index of highest frequency line")
|
|
701
|
+
_ind_high = Trait(None, (Int, None), desc='index of highest frequency line')
|
|
708
702
|
|
|
709
703
|
# internal identifier
|
|
710
|
-
digest = Property(
|
|
711
|
-
depends_on
|
|
712
|
-
|
|
713
|
-
|
|
704
|
+
digest = Property(
|
|
705
|
+
depends_on=[
|
|
706
|
+
'_csmsum',
|
|
707
|
+
],
|
|
708
|
+
)
|
|
714
709
|
|
|
715
710
|
#: Name of the cache file without extension, readonly.
|
|
716
|
-
basename = Property(
|
|
717
|
-
desc="basename for cache file")
|
|
711
|
+
basename = Property(depends_on='digest', desc='basename for cache file')
|
|
718
712
|
|
|
719
713
|
# csm shadow trait, only for internal use.
|
|
720
714
|
_csm = CArray()
|
|
721
|
-
|
|
715
|
+
|
|
722
716
|
# CSM checksum to trigger digest calculation, only for internal use.
|
|
723
|
-
_csmsum = Float()
|
|
717
|
+
_csmsum = Float()
|
|
724
718
|
|
|
725
|
-
def _get_basename(
|
|
726
|
-
return
|
|
719
|
+
def _get_basename(self):
|
|
720
|
+
return 'csm_import_' + self.digest
|
|
727
721
|
|
|
728
722
|
@cached_property
|
|
729
|
-
def _get_digest(
|
|
730
|
-
return digest(
|
|
723
|
+
def _get_digest(self):
|
|
724
|
+
return digest(self)
|
|
731
725
|
|
|
732
|
-
def _get_numchannels(
|
|
726
|
+
def _get_numchannels(self):
|
|
733
727
|
return self.csm.shape[1]
|
|
734
728
|
|
|
735
|
-
def _get_csm
|
|
729
|
+
def _get_csm(self):
|
|
736
730
|
return self._csm
|
|
737
731
|
|
|
738
|
-
def _set_csm
|
|
732
|
+
def _set_csm(self, csm):
|
|
739
733
|
if (len(csm.shape) != 3) or (csm.shape[1] != csm.shape[2]):
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
self._csmsum = real(self._csm).sum() + (imag(self._csm)**2).sum()
|
|
734
|
+
msg = 'The cross spectral matrix must have the following shape: (number of frequencies, numchannels, numchannels)!'
|
|
735
|
+
raise ValueError(msg)
|
|
736
|
+
self._csmsum = real(self._csm).sum() + (imag(self._csm) ** 2).sum() # to trigger new digest creation
|
|
743
737
|
self._csm = csm
|
|
744
738
|
|
|
745
739
|
@property_depends_on('digest')
|
|
746
|
-
def _get_eva
|
|
747
|
-
"""
|
|
748
|
-
Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
740
|
+
def _get_eva(self):
|
|
741
|
+
"""Eigenvalues of cross spectral matrix are either loaded from cache file or
|
|
749
742
|
calculated and then additionally stored into cache.
|
|
750
743
|
"""
|
|
751
744
|
return self.calc_eva()
|
|
752
745
|
|
|
753
746
|
@property_depends_on('digest')
|
|
754
|
-
def _get_eve
|
|
755
|
-
"""
|
|
756
|
-
Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
747
|
+
def _get_eve(self):
|
|
748
|
+
"""Eigenvectors of cross spectral matrix are either loaded from cache file or
|
|
757
749
|
calculated and then additionally stored into cache.
|
|
758
750
|
"""
|
|
759
751
|
return self.calc_eve()
|
|
760
752
|
|
|
761
|
-
def fftfreq
|
|
762
|
-
"""
|
|
763
|
-
|
|
764
|
-
|
|
753
|
+
def fftfreq(self):
|
|
754
|
+
"""Return the Discrete Fourier Transform sample frequencies.
|
|
755
|
+
|
|
765
756
|
Returns
|
|
766
757
|
-------
|
|
767
758
|
f : ndarray
|
|
768
759
|
Array containing the frequencies.
|
|
760
|
+
|
|
769
761
|
"""
|
|
770
|
-
if isinstance(self.frequencies,float):
|
|
762
|
+
if isinstance(self.frequencies, float):
|
|
771
763
|
return array([self.frequencies])
|
|
772
|
-
|
|
773
|
-
return self.frequencies
|
|
774
|
-
elif self.frequencies is None:
|
|
775
|
-
warn("No frequencies defined for PowerSpectraImport object!")
|
|
764
|
+
if isinstance(self.frequencies, ndarray):
|
|
776
765
|
return self.frequencies
|
|
777
|
-
|
|
778
|
-
|
|
766
|
+
if self.frequencies is None:
|
|
767
|
+
warn('No frequencies defined for PowerSpectraImport object!', stacklevel=1)
|
|
768
|
+
return self.frequencies
|