acoular 23.11__py3-none-any.whl → 24.5__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 +118 -50
- acoular/calib.py +29 -38
- acoular/configuration.py +116 -73
- acoular/demo/__init__.py +10 -4
- acoular/demo/acoular_demo.py +78 -53
- acoular/environments.py +265 -262
- acoular/fastFuncs.py +361 -191
- acoular/fbeamform.py +1478 -1407
- acoular/grids.py +501 -545
- acoular/h5cache.py +50 -59
- acoular/h5files.py +154 -137
- acoular/internal.py +10 -11
- acoular/microphones.py +57 -53
- acoular/sdinput.py +47 -52
- acoular/signals.py +167 -179
- acoular/sources.py +818 -693
- acoular/spectra.py +349 -359
- acoular/tbeamform.py +414 -413
- acoular/tfastfuncs.py +178 -101
- acoular/tools/__init__.py +25 -0
- acoular/tools/aiaa.py +186 -0
- acoular/tools/helpers.py +189 -0
- acoular/tools/metrics.py +165 -0
- acoular/tprocess.py +1201 -1143
- 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-23.11.dist-info → acoular-24.5.dist-info}/METADATA +47 -40
- acoular-24.5.dist-info/RECORD +50 -0
- {acoular-23.11.dist-info → acoular-24.5.dist-info}/WHEEL +1 -1
- acoular-24.5.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/BeamformerCMF.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/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 -204
- 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 -418
- acoular-23.11.dist-info/RECORD +0 -146
- acoular-23.11.dist-info/licenses/LICENSE +0 -29
- {acoular-23.11.dist-info → acoular-24.5.dist-info}/licenses/AUTHORS.rst +0 -0
acoular/fbeamform.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
#
|
|
2
|
-
#pylint: disable-msg=E0611, E1101, C0103, R0901, R0902, R0903, R0904, W0232
|
|
3
|
-
#------------------------------------------------------------------------------
|
|
1
|
+
# ------------------------------------------------------------------------------
|
|
4
2
|
# Copyright (c) Acoular Development Team.
|
|
5
|
-
|
|
3
|
+
# ------------------------------------------------------------------------------
|
|
6
4
|
"""Implements beamformers in the frequency domain.
|
|
7
5
|
|
|
8
6
|
.. autosummary::
|
|
9
7
|
:toctree: generated/
|
|
10
|
-
|
|
8
|
+
|
|
11
9
|
SteeringVector
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
|
|
14
12
|
BeamformerBase
|
|
15
13
|
BeamformerFunctional
|
|
16
14
|
BeamformerCapon
|
|
@@ -34,548 +32,557 @@
|
|
|
34
32
|
"""
|
|
35
33
|
|
|
36
34
|
# imports from other packages
|
|
37
|
-
from __future__ import print_function, division
|
|
38
35
|
|
|
39
36
|
import warnings
|
|
40
|
-
|
|
41
|
-
from numpy import array, ones, full, \
|
|
42
|
-
invert, dot, newaxis, zeros, linalg, \
|
|
43
|
-
searchsorted, pi, sign, diag, arange, sqrt, log10, \
|
|
44
|
-
reshape, hstack, vstack, eye, tril, size, clip, tile, round, delete, \
|
|
45
|
-
absolute, argsort, sum, hsplit, fill_diagonal, zeros_like, \
|
|
46
|
-
einsum, ndarray, isscalar, inf, real, unique
|
|
47
|
-
|
|
48
|
-
from numpy.linalg import norm
|
|
49
|
-
|
|
50
|
-
from sklearn.linear_model import LassoLars, LassoLarsCV, LassoLarsIC,\
|
|
51
|
-
OrthogonalMatchingPursuitCV, LinearRegression
|
|
52
|
-
|
|
53
|
-
from scipy.optimize import nnls, linprog, fmin_l_bfgs_b, shgo
|
|
54
|
-
from scipy.linalg import inv, eigh, eigvals, fractional_matrix_power
|
|
55
37
|
from warnings import warn
|
|
56
38
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
39
|
+
# check for sklearn version to account for incompatible behavior
|
|
40
|
+
import sklearn
|
|
41
|
+
from numpy import (
|
|
42
|
+
absolute,
|
|
43
|
+
arange,
|
|
44
|
+
argsort,
|
|
45
|
+
array,
|
|
46
|
+
atleast_2d,
|
|
47
|
+
clip,
|
|
48
|
+
delete,
|
|
49
|
+
diag,
|
|
50
|
+
dot,
|
|
51
|
+
einsum,
|
|
52
|
+
einsum_path,
|
|
53
|
+
eye,
|
|
54
|
+
fill_diagonal,
|
|
55
|
+
full,
|
|
56
|
+
hsplit,
|
|
57
|
+
hstack,
|
|
58
|
+
inf,
|
|
59
|
+
invert,
|
|
60
|
+
isscalar,
|
|
61
|
+
linalg,
|
|
62
|
+
log10,
|
|
63
|
+
ndarray,
|
|
64
|
+
newaxis,
|
|
65
|
+
ones,
|
|
66
|
+
pi,
|
|
67
|
+
real,
|
|
68
|
+
reshape,
|
|
69
|
+
round,
|
|
70
|
+
searchsorted,
|
|
71
|
+
sign,
|
|
72
|
+
size,
|
|
73
|
+
sqrt,
|
|
74
|
+
sum,
|
|
75
|
+
tile,
|
|
76
|
+
trace,
|
|
77
|
+
tril,
|
|
78
|
+
unique,
|
|
79
|
+
vstack,
|
|
80
|
+
zeros,
|
|
81
|
+
zeros_like,
|
|
82
|
+
)
|
|
83
|
+
from numpy.linalg import norm
|
|
84
|
+
from packaging.version import parse
|
|
85
|
+
from scipy.linalg import eigh, eigvals, fractional_matrix_power, inv
|
|
86
|
+
from scipy.optimize import fmin_l_bfgs_b, linprog, nnls, shgo
|
|
87
|
+
from sklearn.linear_model import LassoLars, LassoLarsCV, LassoLarsIC, LinearRegression, OrthogonalMatchingPursuitCV
|
|
88
|
+
from traits.api import (
|
|
89
|
+
Any,
|
|
90
|
+
Bool,
|
|
91
|
+
CArray,
|
|
92
|
+
Delegate,
|
|
93
|
+
Dict,
|
|
94
|
+
Enum,
|
|
95
|
+
Float,
|
|
96
|
+
HasPrivateTraits,
|
|
97
|
+
Instance,
|
|
98
|
+
Int,
|
|
99
|
+
List,
|
|
100
|
+
Property,
|
|
101
|
+
Range,
|
|
102
|
+
Trait,
|
|
103
|
+
Tuple,
|
|
104
|
+
cached_property,
|
|
105
|
+
on_trait_change,
|
|
106
|
+
property_depends_on,
|
|
107
|
+
)
|
|
68
108
|
from traits.trait_errors import TraitError
|
|
69
109
|
|
|
70
|
-
from .
|
|
71
|
-
|
|
72
|
-
|
|
110
|
+
from .configuration import config
|
|
111
|
+
from .environments import Environment
|
|
112
|
+
from .fastFuncs import beamformerFreq, calcPointSpreadFunction, calcTransfer, damasSolverGaussSeidel
|
|
113
|
+
from .grids import Grid, Sector
|
|
73
114
|
from .h5cache import H5cache
|
|
74
115
|
from .h5files import H5CacheFileBase
|
|
75
116
|
from .internal import digest
|
|
76
|
-
from .grids import Grid, Sector
|
|
77
117
|
from .microphones import MicGeom
|
|
78
|
-
from .configuration import config
|
|
79
|
-
from .environments import Environment
|
|
80
118
|
from .spectra import PowerSpectra
|
|
81
119
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
120
|
+
sklearn_ndict = {}
|
|
121
|
+
if parse(sklearn.__version__) < parse('1.4'):
|
|
122
|
+
sklearn_ndict['normalize'] = False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SteeringVector(HasPrivateTraits):
|
|
126
|
+
"""Basic class for implementing steering vectors with monopole source transfer models."""
|
|
127
|
+
|
|
87
128
|
#: :class:`~acoular.grids.Grid`-derived object that provides the grid locations.
|
|
88
|
-
grid = Trait(Grid,
|
|
89
|
-
|
|
90
|
-
|
|
129
|
+
grid = Trait(Grid, desc='beamforming grid')
|
|
130
|
+
|
|
91
131
|
#: :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
92
|
-
mics = Trait(MicGeom,
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
mics = Trait(MicGeom, desc='microphone geometry')
|
|
133
|
+
|
|
95
134
|
#: Type of steering vectors, see also :ref:`Sarradj, 2012<Sarradj2012>`. Defaults to 'true level'.
|
|
96
|
-
steer_type = Trait('true level', 'true location', 'classic', 'inverse',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
#: :class:`~acoular.environments.Environment` or derived object,
|
|
135
|
+
steer_type = Trait('true level', 'true location', 'classic', 'inverse', desc='type of steering vectors used')
|
|
136
|
+
|
|
137
|
+
#: :class:`~acoular.environments.Environment` or derived object,
|
|
100
138
|
#: which provides information about the sound propagation in the medium.
|
|
101
139
|
#: Defaults to standard :class:`~acoular.environments.Environment` object.
|
|
102
140
|
env = Instance(Environment(), Environment)
|
|
103
|
-
|
|
141
|
+
|
|
104
142
|
# TODO: add caching capability for transfer function
|
|
105
|
-
# Flag, if "True" (not default), the transfer function is
|
|
106
|
-
# cached in h5 files and does not have to be recomputed during subsequent
|
|
107
|
-
# program runs.
|
|
143
|
+
# Flag, if "True" (not default), the transfer function is
|
|
144
|
+
# cached in h5 files and does not have to be recomputed during subsequent
|
|
145
|
+
# program runs.
|
|
108
146
|
# Be aware that setting this to "True" may result in high memory usage.
|
|
109
|
-
#cached = Bool(False,
|
|
110
|
-
# desc="cache flag for transfer function")
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# Sound travel distances from microphone array center to grid
|
|
147
|
+
# cached = Bool(False,
|
|
148
|
+
# desc="cache flag for transfer function")
|
|
149
|
+
|
|
150
|
+
# Sound travel distances from microphone array center to grid
|
|
114
151
|
# points or reference position (readonly). Feature may change.
|
|
115
|
-
r0 = Property(desc=
|
|
152
|
+
r0 = Property(desc='array center to grid distances')
|
|
116
153
|
|
|
117
|
-
# Sound travel distances from array microphones to grid
|
|
154
|
+
# Sound travel distances from array microphones to grid
|
|
118
155
|
# points (readonly). Feature may change.
|
|
119
|
-
rm = Property(desc=
|
|
120
|
-
|
|
156
|
+
rm = Property(desc='all array mics to grid distances')
|
|
157
|
+
|
|
121
158
|
# mirror trait for ref
|
|
122
|
-
_ref = Any(array([0
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
#:
|
|
126
|
-
#: of a grid point.
|
|
159
|
+
_ref = Any(array([0.0, 0.0, 0.0]), desc='reference position or distance')
|
|
160
|
+
|
|
161
|
+
#: Reference position or distance at which to evaluate the sound pressure
|
|
162
|
+
#: of a grid point.
|
|
127
163
|
#: If set to a scalar, this is used as reference distance to the grid points.
|
|
128
164
|
#: If set to a vector, this is interpreted as x,y,z coordinates of the reference position.
|
|
129
165
|
#: Defaults to [0.,0.,0.].
|
|
130
|
-
ref = Property(desc=
|
|
131
|
-
|
|
132
|
-
def _set_ref
|
|
166
|
+
ref = Property(desc='reference position or distance')
|
|
167
|
+
|
|
168
|
+
def _set_ref(self, ref):
|
|
133
169
|
if isscalar(ref):
|
|
134
170
|
try:
|
|
135
171
|
self._ref = absolute(float(ref))
|
|
136
172
|
except:
|
|
137
|
-
raise TraitError(args=self,
|
|
138
|
-
name='ref',
|
|
139
|
-
info='Float or CArray(3,)',
|
|
140
|
-
value=ref)
|
|
173
|
+
raise TraitError(args=self, name='ref', info='Float or CArray(3,)', value=ref)
|
|
141
174
|
elif len(ref) == 3:
|
|
142
175
|
self._ref = array(ref, dtype=float)
|
|
143
176
|
else:
|
|
144
|
-
raise TraitError(args=self,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
value=ref)
|
|
148
|
-
|
|
149
|
-
def _get_ref (self):
|
|
177
|
+
raise TraitError(args=self, name='ref', info='Float or CArray(3,)', value=ref)
|
|
178
|
+
|
|
179
|
+
def _get_ref(self):
|
|
150
180
|
return self._ref
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
|
|
153
182
|
# internal identifier
|
|
154
|
-
digest = Property(
|
|
155
|
-
|
|
156
|
-
|
|
183
|
+
digest = Property(depends_on=['steer_type', 'env.digest', 'grid.digest', 'mics.digest', '_ref'])
|
|
184
|
+
|
|
157
185
|
# internal identifier, use for inverse methods, excluding steering vector type
|
|
158
|
-
inv_digest = Property(
|
|
159
|
-
|
|
160
|
-
|
|
186
|
+
inv_digest = Property(depends_on=['env.digest', 'grid.digest', 'mics.digest', '_ref'])
|
|
187
|
+
|
|
161
188
|
@property_depends_on('grid.digest, env.digest, _ref')
|
|
162
|
-
def _get_r0
|
|
189
|
+
def _get_r0(self):
|
|
163
190
|
if isscalar(self.ref):
|
|
164
191
|
if self.ref > 0:
|
|
165
192
|
return full((self.grid.size,), self.ref)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
else:
|
|
169
|
-
return self.env._r(self.grid.pos(), self.ref[:,newaxis])
|
|
193
|
+
return self.env._r(self.grid.pos())
|
|
194
|
+
return self.env._r(self.grid.pos(), self.ref[:, newaxis])
|
|
170
195
|
|
|
171
196
|
@property_depends_on('grid.digest, mics.digest, env.digest')
|
|
172
|
-
def _get_rm
|
|
173
|
-
return self.env._r(self.grid.pos(), self.mics.mpos)
|
|
174
|
-
|
|
197
|
+
def _get_rm(self):
|
|
198
|
+
return atleast_2d(self.env._r(self.grid.pos(), self.mics.mpos))
|
|
199
|
+
|
|
175
200
|
@cached_property
|
|
176
|
-
def _get_digest(
|
|
177
|
-
return digest(
|
|
178
|
-
|
|
201
|
+
def _get_digest(self):
|
|
202
|
+
return digest(self)
|
|
203
|
+
|
|
179
204
|
@cached_property
|
|
180
|
-
def _get_inv_digest(
|
|
181
|
-
return digest(
|
|
182
|
-
|
|
205
|
+
def _get_inv_digest(self):
|
|
206
|
+
return digest(self)
|
|
207
|
+
|
|
183
208
|
def transfer(self, f, ind=None):
|
|
184
|
-
"""
|
|
185
|
-
|
|
186
|
-
|
|
209
|
+
"""Calculates the transfer matrix for one frequency.
|
|
210
|
+
|
|
187
211
|
Parameters
|
|
188
212
|
----------
|
|
189
213
|
f : float
|
|
190
214
|
Frequency for which to calculate the transfer matrix
|
|
191
215
|
ind : (optional) array of ints
|
|
192
|
-
If set, only the transfer function of the gridpoints addressed by
|
|
216
|
+
If set, only the transfer function of the gridpoints addressed by
|
|
193
217
|
the given indices will be calculated. Useful for algorithms like CLEAN-SC,
|
|
194
218
|
where not the full transfer matrix is needed
|
|
195
|
-
|
|
219
|
+
|
|
196
220
|
Returns
|
|
197
221
|
-------
|
|
198
222
|
array of complex128
|
|
199
223
|
array of shape (ngridpts, nmics) containing the transfer matrix for the given frequency
|
|
224
|
+
|
|
200
225
|
"""
|
|
201
|
-
#if self.cached:
|
|
226
|
+
# if self.cached:
|
|
202
227
|
# warn('Caching of transfer function is not yet supported!', Warning)
|
|
203
228
|
# self.cached = False
|
|
204
|
-
|
|
229
|
+
|
|
205
230
|
if ind is None:
|
|
206
|
-
trans = calcTransfer(self.r0, self.rm, array(2*pi*f/self.env.c))
|
|
207
|
-
elif not isinstance(ind,ndarray):
|
|
208
|
-
trans = calcTransfer(self.r0[ind], self.rm[ind, :][newaxis], array(2*pi*f/self.env.c))#[0, :]
|
|
231
|
+
trans = calcTransfer(self.r0, self.rm, array(2 * pi * f / self.env.c))
|
|
232
|
+
elif not isinstance(ind, ndarray):
|
|
233
|
+
trans = calcTransfer(self.r0[ind], self.rm[ind, :][newaxis], array(2 * pi * f / self.env.c)) # [0, :]
|
|
209
234
|
else:
|
|
210
|
-
trans = calcTransfer(self.r0[ind], self.rm[ind, :], array(2*pi*f/self.env.c))
|
|
235
|
+
trans = calcTransfer(self.r0[ind], self.rm[ind, :], array(2 * pi * f / self.env.c))
|
|
211
236
|
return trans
|
|
212
|
-
|
|
237
|
+
|
|
213
238
|
def steer_vector(self, f, ind=None):
|
|
214
|
-
"""
|
|
215
|
-
Calculates the steering vectors based on the transfer function
|
|
239
|
+
"""Calculates the steering vectors based on the transfer function
|
|
216
240
|
See also :ref:`Sarradj, 2012<Sarradj2012>`.
|
|
217
|
-
|
|
241
|
+
|
|
218
242
|
Parameters
|
|
219
243
|
----------
|
|
220
244
|
f : float
|
|
221
245
|
Frequency for which to calculate the transfer matrix
|
|
222
246
|
ind : (optional) array of ints
|
|
223
|
-
If set, only the steering vectors of the gridpoints addressed by
|
|
247
|
+
If set, only the steering vectors of the gridpoints addressed by
|
|
224
248
|
the given indices will be calculated. Useful for algorithms like CLEAN-SC,
|
|
225
249
|
where not the full transfer matrix is needed
|
|
226
|
-
|
|
250
|
+
|
|
227
251
|
Returns
|
|
228
252
|
-------
|
|
229
253
|
array of complex128
|
|
230
254
|
array of shape (ngridpts, nmics) containing the steering vectors for the given frequency
|
|
255
|
+
|
|
231
256
|
"""
|
|
232
|
-
func = {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
257
|
+
func = {
|
|
258
|
+
'classic': lambda x: x / absolute(x) / x.shape[-1],
|
|
259
|
+
'inverse': lambda x: 1.0 / x.conj() / x.shape[-1],
|
|
260
|
+
'true level': lambda x: x / einsum('ij,ij->i', x, x.conj())[:, newaxis],
|
|
261
|
+
'true location': lambda x: x / sqrt(einsum('ij,ij->i', x, x.conj()) * x.shape[-1])[:, newaxis],
|
|
262
|
+
}[self.steer_type]
|
|
237
263
|
return func(self.transfer(f, ind))
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
class BeamformerBase(
|
|
241
|
-
"""
|
|
242
|
-
|
|
243
|
-
"""
|
|
244
|
-
|
|
245
|
-
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class BeamformerBase(HasPrivateTraits):
|
|
267
|
+
"""Beamforming using the basic delay-and-sum algorithm in the frequency domain."""
|
|
268
|
+
|
|
246
269
|
# Instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes
|
|
247
270
|
# that contains information about the steering vector. This is a private trait.
|
|
248
271
|
# Do not set this directly, use `steer` trait instead.
|
|
249
|
-
_steer_obj = Instance(SteeringVector(), SteeringVector)
|
|
250
|
-
|
|
251
|
-
#: :class:`~acoular.fbeamform.SteeringVector` or derived object.
|
|
272
|
+
_steer_obj = Instance(SteeringVector(), SteeringVector)
|
|
273
|
+
|
|
274
|
+
#: :class:`~acoular.fbeamform.SteeringVector` or derived object.
|
|
252
275
|
#: Defaults to :class:`~acoular.fbeamform.SteeringVector` object.
|
|
253
|
-
steer = Property(desc=
|
|
254
|
-
|
|
276
|
+
steer = Property(desc='steering vector object')
|
|
277
|
+
|
|
255
278
|
def _get_steer(self):
|
|
256
279
|
return self._steer_obj
|
|
257
|
-
|
|
280
|
+
|
|
258
281
|
def _set_steer(self, steer):
|
|
259
282
|
if isinstance(steer, SteeringVector):
|
|
260
283
|
self._steer_obj = steer
|
|
261
284
|
elif steer in ('true level', 'true location', 'classic', 'inverse'):
|
|
262
285
|
# Type of steering vectors, see also :ref:`Sarradj, 2012<Sarradj2012>`.
|
|
263
|
-
warn(
|
|
264
|
-
|
|
265
|
-
|
|
286
|
+
warn(
|
|
287
|
+
"Deprecated use of 'steer' trait. Please use object of class 'SteeringVector' in the future.",
|
|
288
|
+
Warning,
|
|
289
|
+
stacklevel=2,
|
|
290
|
+
)
|
|
266
291
|
self._steer_obj.steer_type = steer
|
|
267
292
|
else:
|
|
268
|
-
raise(TraitError(args=self,
|
|
269
|
-
name='steer',
|
|
270
|
-
info='SteeringVector',
|
|
271
|
-
value=steer))
|
|
293
|
+
raise (TraitError(args=self, name='steer', info='SteeringVector', value=steer))
|
|
272
294
|
|
|
273
295
|
# --- List of backwards compatibility traits and their setters/getters -----------
|
|
274
|
-
|
|
275
|
-
# :class:`~acoular.environments.Environment` or derived object.
|
|
276
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
296
|
+
|
|
297
|
+
# :class:`~acoular.environments.Environment` or derived object.
|
|
298
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
277
299
|
# Now governed by :attr:`steer` trait.
|
|
278
300
|
env = Property()
|
|
279
|
-
|
|
301
|
+
|
|
280
302
|
def _get_env(self):
|
|
281
|
-
return self._steer_obj.env
|
|
282
|
-
|
|
303
|
+
return self._steer_obj.env
|
|
304
|
+
|
|
283
305
|
def _set_env(self, env):
|
|
284
|
-
warn("Deprecated use of 'env' trait. ", Warning, stacklevel
|
|
306
|
+
warn("Deprecated use of 'env' trait. ", Warning, stacklevel=2)
|
|
285
307
|
self._steer_obj.env = env
|
|
286
|
-
|
|
308
|
+
|
|
287
309
|
# The speed of sound.
|
|
288
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
310
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
289
311
|
# Now governed by :attr:`steer` trait.
|
|
290
312
|
c = Property()
|
|
291
|
-
|
|
313
|
+
|
|
292
314
|
def _get_c(self):
|
|
293
315
|
return self._steer_obj.env.c
|
|
294
|
-
|
|
316
|
+
|
|
295
317
|
def _set_c(self, c):
|
|
296
|
-
warn("Deprecated use of 'c' trait. ", Warning, stacklevel
|
|
318
|
+
warn("Deprecated use of 'c' trait. ", Warning, stacklevel=2)
|
|
297
319
|
self._steer_obj.env.c = c
|
|
298
|
-
|
|
320
|
+
|
|
299
321
|
# :class:`~acoular.grids.Grid`-derived object that provides the grid locations.
|
|
300
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
322
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
301
323
|
# Now governed by :attr:`steer` trait.
|
|
302
324
|
grid = Property()
|
|
303
325
|
|
|
304
326
|
def _get_grid(self):
|
|
305
327
|
return self._steer_obj.grid
|
|
306
|
-
|
|
328
|
+
|
|
307
329
|
def _set_grid(self, grid):
|
|
308
|
-
warn("Deprecated use of 'grid' trait. ", Warning, stacklevel
|
|
330
|
+
warn("Deprecated use of 'grid' trait. ", Warning, stacklevel=2)
|
|
309
331
|
self._steer_obj.grid = grid
|
|
310
|
-
|
|
332
|
+
|
|
311
333
|
# :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
312
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
334
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
313
335
|
# Now governed by :attr:`steer` trait
|
|
314
336
|
mpos = Property()
|
|
315
|
-
|
|
337
|
+
|
|
316
338
|
def _get_mpos(self):
|
|
317
339
|
return self._steer_obj.mics
|
|
318
|
-
|
|
340
|
+
|
|
319
341
|
def _set_mpos(self, mpos):
|
|
320
|
-
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel
|
|
342
|
+
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2)
|
|
321
343
|
self._steer_obj.mics = mpos
|
|
322
|
-
|
|
323
|
-
|
|
344
|
+
|
|
324
345
|
# Sound travel distances from microphone array center to grid points (r0)
|
|
325
346
|
# and all array mics to grid points (rm). Readonly.
|
|
326
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
347
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
327
348
|
# Now governed by :attr:`steer` trait
|
|
328
349
|
r0 = Property()
|
|
350
|
+
|
|
329
351
|
def _get_r0(self):
|
|
330
352
|
return self._steer_obj.r0
|
|
331
|
-
|
|
353
|
+
|
|
332
354
|
rm = Property()
|
|
355
|
+
|
|
333
356
|
def _get_rm(self):
|
|
334
357
|
return self._steer_obj.rm
|
|
335
|
-
|
|
358
|
+
|
|
336
359
|
# --- End of backwards compatibility traits --------------------------------------
|
|
337
360
|
|
|
338
|
-
#: :class:`~acoular.spectra.PowerSpectra` object that provides the
|
|
361
|
+
#: :class:`~acoular.spectra.PowerSpectra` object that provides the
|
|
339
362
|
#: cross spectral matrix and eigenvalues
|
|
340
|
-
freq_data = Trait(PowerSpectra,
|
|
341
|
-
desc="freq data object")
|
|
363
|
+
freq_data = Trait(PowerSpectra, desc='freq data object')
|
|
342
364
|
|
|
343
365
|
#: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
|
|
344
|
-
r_diag = Bool(True,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
#:
|
|
348
|
-
#:
|
|
349
|
-
#: If
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
366
|
+
r_diag = Bool(True, desc='removal of diagonal')
|
|
367
|
+
|
|
368
|
+
#: If r_diag==True: if r_diag_norm==0.0, the standard
|
|
369
|
+
#: normalization = num_mics/(num_mics-1) is used.
|
|
370
|
+
#: If r_diag_norm !=0.0, the user input is used instead.
|
|
371
|
+
#: If r_diag==False, the normalization is 1.0 either way.
|
|
372
|
+
r_diag_norm = Float(
|
|
373
|
+
0.0,
|
|
374
|
+
desc='If diagonal of the csm is removed, some signal energy is lost.'
|
|
375
|
+
'This is handled via this normalization factor.'
|
|
376
|
+
'Internally, the default is: num_mics / (num_mics - 1).',
|
|
377
|
+
)
|
|
378
|
+
|
|
356
379
|
#: Floating point precision of property result. Corresponding to numpy dtypes. Default = 64 Bit.
|
|
357
|
-
precision = Trait('float64', 'float32',
|
|
358
|
-
|
|
359
|
-
|
|
380
|
+
precision = Trait('float64', 'float32', desc='precision (32/64 Bit) of result, corresponding to numpy dtypes')
|
|
381
|
+
|
|
360
382
|
#: Boolean flag, if 'True' (default), the result is cached in h5 files.
|
|
361
|
-
cached = Bool(True,
|
|
362
|
-
|
|
363
|
-
|
|
383
|
+
cached = Bool(True, desc='cached flag')
|
|
384
|
+
|
|
364
385
|
# hdf5 cache file
|
|
365
|
-
h5f = Instance(
|
|
366
|
-
|
|
367
|
-
#: The beamforming result as squared sound pressure values
|
|
386
|
+
h5f = Instance(H5CacheFileBase, transient=True)
|
|
387
|
+
|
|
388
|
+
#: The beamforming result as squared sound pressure values
|
|
368
389
|
#: at all grid point locations (readonly).
|
|
369
390
|
#: Returns a (number of frequencies, number of gridpoints) array of floats.
|
|
370
|
-
result = Property(
|
|
371
|
-
|
|
372
|
-
|
|
391
|
+
result = Property(desc='beamforming result')
|
|
392
|
+
|
|
373
393
|
# internal identifier
|
|
374
|
-
digest = Property(
|
|
375
|
-
depends_on = ['freq_data.digest', 'r_diag', 'r_diag_norm', 'precision', '_steer_obj.digest'])
|
|
394
|
+
digest = Property(depends_on=['freq_data.digest', 'r_diag', 'r_diag_norm', 'precision', '_steer_obj.digest'])
|
|
376
395
|
|
|
377
396
|
# internal identifier
|
|
378
|
-
ext_digest = Property(
|
|
379
|
-
depends_on
|
|
380
|
-
|
|
397
|
+
ext_digest = Property(
|
|
398
|
+
depends_on=['digest', 'freq_data.ind_low', 'freq_data.ind_high'],
|
|
399
|
+
)
|
|
381
400
|
|
|
382
401
|
@cached_property
|
|
383
|
-
def _get_digest(
|
|
384
|
-
return digest(
|
|
385
|
-
|
|
402
|
+
def _get_digest(self):
|
|
403
|
+
return digest(self)
|
|
404
|
+
|
|
386
405
|
@cached_property
|
|
387
|
-
def _get_ext_digest(
|
|
388
|
-
return digest(
|
|
389
|
-
|
|
390
|
-
def _get_filecache(
|
|
391
|
-
"""
|
|
392
|
-
|
|
393
|
-
global/local caching behaviour. Returns (None, None) if no cachefile/data
|
|
406
|
+
def _get_ext_digest(self):
|
|
407
|
+
return digest(self, 'ext_digest')
|
|
408
|
+
|
|
409
|
+
def _get_filecache(self):
|
|
410
|
+
"""Function collects cached results from file depending on
|
|
411
|
+
global/local caching behaviour. Returns (None, None) if no cachefile/data
|
|
394
412
|
exist and global caching mode is 'readonly'.
|
|
395
413
|
"""
|
|
396
|
-
# print("get cachefile:", self.freq_data.basename)
|
|
397
|
-
H5cache.get_cache_file(
|
|
398
|
-
if not self.h5f:
|
|
399
|
-
# print("no cachefile:", self.freq_data.basename)
|
|
400
|
-
return (None, None, None)# only happens in case of global caching readonly
|
|
414
|
+
# print("get cachefile:", self.freq_data.basename)
|
|
415
|
+
H5cache.get_cache_file(self, self.freq_data.basename)
|
|
416
|
+
if not self.h5f:
|
|
417
|
+
# print("no cachefile:", self.freq_data.basename)
|
|
418
|
+
return (None, None, None) # only happens in case of global caching readonly
|
|
401
419
|
|
|
402
420
|
nodename = self.__class__.__name__ + self.digest
|
|
403
|
-
# print("collect filecache for nodename:",nodename)
|
|
421
|
+
# print("collect filecache for nodename:",nodename)
|
|
404
422
|
if config.global_caching == 'overwrite' and self.h5f.is_cached(nodename):
|
|
405
|
-
# print("remove existing data for nodename",nodename)
|
|
406
|
-
self.h5f.remove_data(nodename)
|
|
407
|
-
|
|
423
|
+
# print("remove existing data for nodename",nodename)
|
|
424
|
+
self.h5f.remove_data(nodename) # remove old data before writing in overwrite mode
|
|
425
|
+
|
|
408
426
|
if not self.h5f.is_cached(nodename):
|
|
409
|
-
# print("no data existent for nodename:", nodename)
|
|
410
|
-
if config.global_caching == 'readonly':
|
|
427
|
+
# print("no data existent for nodename:", nodename)
|
|
428
|
+
if config.global_caching == 'readonly':
|
|
411
429
|
return (None, None, None)
|
|
430
|
+
numfreq = self.freq_data.fftfreq().shape[0] # block_size/2 + 1steer_obj
|
|
431
|
+
group = self.h5f.create_new_group(nodename)
|
|
432
|
+
self.h5f.create_compressible_array(
|
|
433
|
+
'freqs',
|
|
434
|
+
(numfreq,),
|
|
435
|
+
'int8', #'bool',
|
|
436
|
+
group,
|
|
437
|
+
)
|
|
438
|
+
if isinstance(self, BeamformerAdaptiveGrid):
|
|
439
|
+
self.h5f.create_compressible_array('gpos', (3, self.size), 'float64', group)
|
|
440
|
+
self.h5f.create_compressible_array('result', (numfreq, self.size), self.precision, group)
|
|
412
441
|
else:
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
group)
|
|
420
|
-
if isinstance(self,BeamformerAdaptiveGrid):
|
|
421
|
-
self.h5f.create_compressible_array('gpos',
|
|
422
|
-
(3, self.size),
|
|
423
|
-
'float64',
|
|
424
|
-
group)
|
|
425
|
-
self.h5f.create_compressible_array('result',
|
|
426
|
-
(numfreq, self.size),
|
|
427
|
-
self.precision,
|
|
428
|
-
group)
|
|
429
|
-
else:
|
|
430
|
-
self.h5f.create_compressible_array('result',
|
|
431
|
-
(numfreq, self.steer.grid.size),
|
|
432
|
-
self.precision,
|
|
433
|
-
group)
|
|
434
|
-
|
|
435
|
-
ac = self.h5f.get_data_by_reference('result','/'+nodename)
|
|
436
|
-
fr = self.h5f.get_data_by_reference('freqs','/'+nodename)
|
|
437
|
-
if isinstance(self,BeamformerAdaptiveGrid):
|
|
438
|
-
gpos = self.h5f.get_data_by_reference('gpos','/'+nodename)
|
|
442
|
+
self.h5f.create_compressible_array('result', (numfreq, self.steer.grid.size), self.precision, group)
|
|
443
|
+
|
|
444
|
+
ac = self.h5f.get_data_by_reference('result', '/' + nodename)
|
|
445
|
+
fr = self.h5f.get_data_by_reference('freqs', '/' + nodename)
|
|
446
|
+
if isinstance(self, BeamformerAdaptiveGrid):
|
|
447
|
+
gpos = self.h5f.get_data_by_reference('gpos', '/' + nodename)
|
|
439
448
|
else:
|
|
440
449
|
gpos = None
|
|
441
|
-
return (ac,fr,gpos)
|
|
450
|
+
return (ac, fr, gpos)
|
|
442
451
|
|
|
443
452
|
def _assert_equal_channels(self):
|
|
444
453
|
numchannels = self.freq_data.numchannels
|
|
445
|
-
if
|
|
446
|
-
raise ValueError(
|
|
454
|
+
if numchannels != self.steer.mics.num_mics or numchannels == 0:
|
|
455
|
+
raise ValueError('%i channels do not fit %i mics' % (numchannels, self.steer.mics.num_mics))
|
|
447
456
|
|
|
448
457
|
@property_depends_on('ext_digest')
|
|
449
|
-
def _get_result
|
|
450
|
-
"""
|
|
451
|
-
This is the :attr:`result` getter routine.
|
|
458
|
+
def _get_result(self):
|
|
459
|
+
"""Implements the :attr:`result` getter routine.
|
|
452
460
|
The beamforming result is either loaded or calculated.
|
|
453
461
|
"""
|
|
454
462
|
f = self.freq_data
|
|
455
|
-
numfreq = f.fftfreq().shape[0]# block_size/2 + 1steer_obj
|
|
463
|
+
numfreq = f.fftfreq().shape[0] # block_size/2 + 1steer_obj
|
|
456
464
|
_digest = ''
|
|
457
465
|
while self.digest != _digest:
|
|
458
466
|
_digest = self.digest
|
|
459
467
|
self._assert_equal_channels()
|
|
460
468
|
ac, fr = (None, None)
|
|
461
|
-
if not (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
)
|
|
465
|
-
|
|
466
|
-
(ac,fr,gpos) = self._get_filecache()
|
|
469
|
+
if not ( # if result caching is active
|
|
470
|
+
config.global_caching == 'none' or (config.global_caching == 'individual' and not self.cached)
|
|
471
|
+
):
|
|
472
|
+
# print("get filecache..")
|
|
473
|
+
(ac, fr, gpos) = self._get_filecache()
|
|
467
474
|
if gpos:
|
|
468
475
|
self._gpos = gpos
|
|
469
|
-
if ac and fr:
|
|
470
|
-
# print("cached data existent")
|
|
471
|
-
if not fr[f.ind_low:f.ind_high].all():
|
|
472
|
-
# print("calculate missing results")
|
|
473
|
-
if config.global_caching == 'readonly':
|
|
476
|
+
if ac and fr:
|
|
477
|
+
# print("cached data existent")
|
|
478
|
+
if not fr[f.ind_low : f.ind_high].all():
|
|
479
|
+
# print("calculate missing results")
|
|
480
|
+
if config.global_caching == 'readonly':
|
|
474
481
|
(ac, fr) = (ac[:], fr[:])
|
|
475
|
-
self.calc(ac,fr)
|
|
482
|
+
self.calc(ac, fr)
|
|
476
483
|
self.h5f.flush()
|
|
477
|
-
# else:
|
|
478
|
-
# print("cached results are complete! return.")
|
|
484
|
+
# else:
|
|
485
|
+
# print("cached results are complete! return.")
|
|
479
486
|
else:
|
|
480
|
-
# print("no caching or not activated, calculate result")
|
|
481
|
-
if isinstance(self,BeamformerAdaptiveGrid):
|
|
487
|
+
# print("no caching or not activated, calculate result")
|
|
488
|
+
if isinstance(self, BeamformerAdaptiveGrid):
|
|
482
489
|
self._gpos = zeros((3, self.size), dtype=self.precision)
|
|
483
490
|
ac = zeros((numfreq, self.size), dtype=self.precision)
|
|
484
491
|
else:
|
|
485
492
|
ac = zeros((numfreq, self.steer.grid.size), dtype=self.precision)
|
|
486
493
|
fr = zeros(numfreq, dtype='int8')
|
|
487
|
-
self.calc(ac,fr)
|
|
494
|
+
self.calc(ac, fr)
|
|
488
495
|
return ac
|
|
489
|
-
|
|
496
|
+
|
|
490
497
|
def sig_loss_norm(self):
|
|
491
|
-
"""
|
|
492
|
-
If the diagonal of the CSM is removed one has to handle the loss
|
|
498
|
+
"""If the diagonal of the CSM is removed one has to handle the loss
|
|
493
499
|
of signal energy --> Done via a normalization factor.
|
|
494
500
|
"""
|
|
495
|
-
if not self.r_diag: # Full CSM --> no normalization needed
|
|
496
|
-
normFactor = 1.0
|
|
497
|
-
elif self.r_diag_norm == 0.0: # Removed diag: standard normalization factor
|
|
498
|
-
nMics = float(self.freq_data.numchannels)
|
|
499
|
-
normFactor = nMics / (nMics - 1)
|
|
500
|
-
elif self.r_diag_norm != 0.0: # Removed diag: user defined normalization factor
|
|
501
|
-
normFactor = self.r_diag_norm
|
|
501
|
+
if not self.r_diag: # Full CSM --> no normalization needed
|
|
502
|
+
normFactor = 1.0
|
|
503
|
+
elif self.r_diag_norm == 0.0: # Removed diag: standard normalization factor
|
|
504
|
+
nMics = float(self.freq_data.numchannels)
|
|
505
|
+
normFactor = nMics / (nMics - 1)
|
|
506
|
+
elif self.r_diag_norm != 0.0: # Removed diag: user defined normalization factor
|
|
507
|
+
normFactor = self.r_diag_norm
|
|
502
508
|
return normFactor
|
|
503
509
|
|
|
504
|
-
|
|
505
510
|
def _beamformer_params(self):
|
|
506
|
-
"""
|
|
507
|
-
Manages the parameters for calling of the core beamformer functionality.
|
|
511
|
+
"""Manages the parameters for calling of the core beamformer functionality.
|
|
508
512
|
This is a workaround to allow faster calculation and may change in the
|
|
509
513
|
future.
|
|
510
|
-
|
|
514
|
+
|
|
511
515
|
Returns
|
|
512
516
|
-------
|
|
513
517
|
- String containing the steering vector type
|
|
514
518
|
- Function for frequency-dependent steering vector calculation
|
|
515
|
-
|
|
519
|
+
|
|
516
520
|
"""
|
|
517
|
-
if type(self.steer) == SteeringVector:
|
|
521
|
+
if type(self.steer) == SteeringVector: # for simple steering vector, use faster method
|
|
518
522
|
param_type = self.steer.steer_type
|
|
519
|
-
|
|
523
|
+
|
|
524
|
+
def param_steer_func(f):
|
|
525
|
+
return (self.steer.r0, self.steer.rm, 2 * pi * f / self.steer.env.c)
|
|
520
526
|
else:
|
|
521
527
|
param_type = 'custom'
|
|
522
528
|
param_steer_func = self.steer.steer_vector
|
|
523
529
|
return param_type, param_steer_func
|
|
524
530
|
|
|
525
531
|
def calc(self, ac, fr):
|
|
526
|
-
"""
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
This is an internal helper function that is automatically called when
|
|
532
|
+
"""Calculates the delay-and-sum beamforming result for the frequencies
|
|
533
|
+
defined by :attr:`freq_data`.
|
|
534
|
+
|
|
535
|
+
This is an internal helper function that is automatically called when
|
|
531
536
|
accessing the beamformer's :attr:`result` or calling
|
|
532
|
-
its :meth:`synthetic` method.
|
|
533
|
-
|
|
537
|
+
its :meth:`synthetic` method.
|
|
538
|
+
|
|
534
539
|
Parameters
|
|
535
540
|
----------
|
|
536
541
|
ac : array of floats
|
|
537
542
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
538
543
|
is used as call-by-reference parameter and contains the calculated
|
|
539
|
-
value after calling this method.
|
|
544
|
+
value after calling this method.
|
|
540
545
|
fr : array of booleans
|
|
541
|
-
The entries of this [number of frequencies]-sized array are either
|
|
546
|
+
The entries of this [number of frequencies]-sized array are either
|
|
542
547
|
'True' (if the result for this frequency has already been calculated)
|
|
543
548
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
544
549
|
After the calculation at a certain frequency the value will be set
|
|
545
550
|
to 'True'
|
|
546
|
-
|
|
551
|
+
|
|
547
552
|
Returns
|
|
548
553
|
-------
|
|
549
554
|
This method only returns values through the *ac* and *fr* parameters
|
|
555
|
+
|
|
550
556
|
"""
|
|
551
|
-
f = self.freq_data.fftfreq()#[inds]
|
|
557
|
+
f = self.freq_data.fftfreq() # [inds]
|
|
552
558
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
553
559
|
for i in self.freq_data.indices:
|
|
554
560
|
if not fr[i]:
|
|
555
561
|
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
556
|
-
beamformerOutput = beamformerFreq(
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
562
|
+
beamformerOutput = beamformerFreq(
|
|
563
|
+
param_steer_type,
|
|
564
|
+
self.r_diag,
|
|
565
|
+
self.sig_loss_norm(),
|
|
566
|
+
steer_vector(f[i]),
|
|
567
|
+
csm,
|
|
568
|
+
)[0]
|
|
561
569
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
562
570
|
indNegSign = sign(beamformerOutput) < 0
|
|
563
571
|
beamformerOutput[indNegSign] = 0.0
|
|
564
572
|
ac[i] = beamformerOutput
|
|
565
573
|
fr[i] = 1
|
|
566
|
-
|
|
567
|
-
def synthetic(
|
|
568
|
-
"""
|
|
569
|
-
|
|
570
|
-
|
|
574
|
+
|
|
575
|
+
def synthetic(self, f, num=0):
|
|
576
|
+
"""Evaluates the beamforming result for an arbitrary frequency band.
|
|
577
|
+
|
|
571
578
|
Parameters
|
|
572
579
|
----------
|
|
573
580
|
f: float
|
|
574
|
-
Band center frequency.
|
|
581
|
+
Band center frequency.
|
|
575
582
|
num : integer
|
|
576
583
|
Controls the width of the frequency bands considered; defaults to
|
|
577
584
|
0 (single frequency line).
|
|
578
|
-
|
|
585
|
+
|
|
579
586
|
=== =====================
|
|
580
587
|
num frequency band width
|
|
581
588
|
=== =====================
|
|
@@ -584,110 +591,126 @@ class BeamformerBase( HasPrivateTraits ):
|
|
|
584
591
|
3 third-octave band
|
|
585
592
|
n 1/n-octave band
|
|
586
593
|
=== =====================
|
|
587
|
-
|
|
594
|
+
|
|
588
595
|
Returns
|
|
589
596
|
-------
|
|
590
597
|
array of floats
|
|
591
|
-
The synthesized frequency band values of the beamforming result at
|
|
598
|
+
The synthesized frequency band values of the beamforming result at
|
|
592
599
|
each grid point .
|
|
593
|
-
Note that the frequency resolution and therefore the bandwidth
|
|
594
|
-
represented by a single frequency line depends on
|
|
595
|
-
the :attr:`sampling frequency<acoular.tprocess.SamplesGenerator.sample_freq>` and
|
|
600
|
+
Note that the frequency resolution and therefore the bandwidth
|
|
601
|
+
represented by a single frequency line depends on
|
|
602
|
+
the :attr:`sampling frequency<acoular.tprocess.SamplesGenerator.sample_freq>` and
|
|
596
603
|
used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
604
|
+
|
|
597
605
|
"""
|
|
598
|
-
res = self.result
|
|
606
|
+
res = self.result # trigger calculation
|
|
599
607
|
freq = self.freq_data.fftfreq()
|
|
600
608
|
if len(freq) == 0:
|
|
601
609
|
return None
|
|
602
|
-
|
|
610
|
+
|
|
603
611
|
indices = self.freq_data.indices
|
|
604
|
-
|
|
612
|
+
|
|
605
613
|
if num == 0:
|
|
606
614
|
# single frequency line
|
|
607
615
|
ind = searchsorted(freq, f)
|
|
608
616
|
if ind >= len(freq):
|
|
609
|
-
warn(
|
|
610
|
-
|
|
611
|
-
|
|
617
|
+
warn(
|
|
618
|
+
'Queried frequency (%g Hz) not in resolved frequency range. Returning zeros.' % f,
|
|
619
|
+
Warning,
|
|
620
|
+
stacklevel=2,
|
|
621
|
+
)
|
|
612
622
|
h = zeros_like(res[0])
|
|
613
623
|
else:
|
|
614
624
|
if freq[ind] != f:
|
|
615
|
-
warn(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
625
|
+
warn(
|
|
626
|
+
f'Queried frequency ({f:g} Hz) not in set of '
|
|
627
|
+
'discrete FFT sample frequencies. '
|
|
628
|
+
f'Using frequency {freq[ind]:g} Hz instead.',
|
|
629
|
+
Warning,
|
|
630
|
+
stacklevel=2,
|
|
631
|
+
)
|
|
632
|
+
if ind not in indices:
|
|
633
|
+
warn(
|
|
634
|
+
'Beamforming result may not have been calculated '
|
|
635
|
+
'for queried frequency. Check '
|
|
636
|
+
'freq_data.ind_low and freq_data.ind_high!',
|
|
637
|
+
Warning,
|
|
638
|
+
stacklevel=2,
|
|
639
|
+
)
|
|
624
640
|
h = res[ind]
|
|
625
641
|
else:
|
|
626
642
|
# fractional octave band
|
|
627
|
-
if isinstance(num,list):
|
|
628
|
-
f1=num[0]
|
|
629
|
-
f2=num[-1]
|
|
643
|
+
if isinstance(num, list):
|
|
644
|
+
f1 = num[0]
|
|
645
|
+
f2 = num[-1]
|
|
630
646
|
else:
|
|
631
|
-
f1 = f*2
|
|
632
|
-
f2 = f*2
|
|
647
|
+
f1 = f * 2.0 ** (-0.5 / num)
|
|
648
|
+
f2 = f * 2.0 ** (+0.5 / num)
|
|
633
649
|
ind1 = searchsorted(freq, f1)
|
|
634
650
|
ind2 = searchsorted(freq, f2)
|
|
635
651
|
if ind1 == ind2:
|
|
636
|
-
warn(
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
652
|
+
warn(
|
|
653
|
+
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
654
|
+
'include any discrete FFT sample frequencies. '
|
|
655
|
+
'Returning zeros.',
|
|
656
|
+
Warning,
|
|
657
|
+
stacklevel=2,
|
|
658
|
+
)
|
|
640
659
|
h = zeros_like(res[0])
|
|
641
660
|
else:
|
|
642
661
|
h = sum(res[ind1:ind2], 0)
|
|
643
662
|
if not ((ind1 in indices) and (ind2 in indices)):
|
|
644
|
-
warn(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
663
|
+
warn(
|
|
664
|
+
'Beamforming result may not have been calculated '
|
|
665
|
+
'for all queried frequencies. Check '
|
|
666
|
+
'freq_data.ind_low and freq_data.ind_high!',
|
|
667
|
+
Warning,
|
|
668
|
+
stacklevel=2,
|
|
669
|
+
)
|
|
670
|
+
if isinstance(self, BeamformerAdaptiveGrid):
|
|
649
671
|
return h
|
|
650
|
-
|
|
651
|
-
return h.reshape(self.steer.grid.shape)
|
|
652
|
-
|
|
672
|
+
return h.reshape(self.steer.grid.shape)
|
|
653
673
|
|
|
654
674
|
def integrate(self, sector):
|
|
655
|
-
"""
|
|
656
|
-
|
|
657
|
-
|
|
675
|
+
"""Integrates result map over a given sector.
|
|
676
|
+
|
|
658
677
|
Parameters
|
|
659
678
|
----------
|
|
660
679
|
sector: array of floats
|
|
661
|
-
Tuple with arguments for the 'indices' method
|
|
662
|
-
of a :class:`~acoular.grids.Grid`-derived class
|
|
663
|
-
(e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
|
|
680
|
+
Tuple with arguments for the 'indices' method
|
|
681
|
+
of a :class:`~acoular.grids.Grid`-derived class
|
|
682
|
+
(e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
|
|
664
683
|
or :meth:`RectGrid3D.indices<acoular.grids.RectGrid3D.indices>`).
|
|
665
|
-
Possible sectors would be *array([xmin, ymin, xmax, ymax])*
|
|
684
|
+
Possible sectors would be *array([xmin, ymin, xmax, ymax])*
|
|
666
685
|
or *array([x, y, radius])*.
|
|
667
|
-
|
|
686
|
+
|
|
668
687
|
Returns
|
|
669
688
|
-------
|
|
670
689
|
array of floats
|
|
671
690
|
The spectrum (all calculated frequency bands) for the integrated sector.
|
|
691
|
+
|
|
672
692
|
"""
|
|
673
|
-
#resp. array([rmin, phimin, rmax, phimax]), array([r, phi, radius]).
|
|
674
|
-
|
|
675
|
-
# ind = self.grid.indices(*sector)
|
|
676
|
-
# gshape = self.grid.shape
|
|
677
|
-
# r = self.result
|
|
678
|
-
# rshape = r.shape
|
|
679
|
-
# mapshape = (rshape[0], ) + gshape
|
|
680
|
-
# h = r[:].reshape(mapshape)[ (s_[:], ) + ind ]
|
|
681
|
-
# return h.reshape(h.shape[0], prod(h.shape[1:])).sum(axis=1)
|
|
693
|
+
# resp. array([rmin, phimin, rmax, phimax]), array([r, phi, radius]).
|
|
694
|
+
|
|
695
|
+
# ind = self.grid.indices(*sector)
|
|
696
|
+
# gshape = self.grid.shape
|
|
697
|
+
# r = self.result
|
|
698
|
+
# rshape = r.shape
|
|
699
|
+
# mapshape = (rshape[0], ) + gshape
|
|
700
|
+
# h = r[:].reshape(mapshape)[ (s_[:], ) + ind ]
|
|
701
|
+
# return h.reshape(h.shape[0], prod(h.shape[1:])).sum(axis=1)
|
|
682
702
|
if isinstance(sector, Sector):
|
|
683
703
|
ind = self.steer.grid.subdomain(sector)
|
|
684
704
|
elif hasattr(self.steer.grid, 'indices'):
|
|
685
705
|
ind = self.steer.grid.indices(*sector)
|
|
686
706
|
else:
|
|
707
|
+
msg = (
|
|
708
|
+
f'Grid of type {self.steer.grid.__class__.__name__} does not have an indices method! '
|
|
709
|
+
f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
|
|
710
|
+
'instead of type numpy.array.'
|
|
711
|
+
)
|
|
687
712
|
raise NotImplementedError(
|
|
688
|
-
|
|
689
|
-
f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
|
|
690
|
-
'instead of type numpy.array.'
|
|
713
|
+
msg,
|
|
691
714
|
)
|
|
692
715
|
gshape = self.steer.grid.shape
|
|
693
716
|
r = self.result
|
|
@@ -696,50 +719,47 @@ class BeamformerBase( HasPrivateTraits ):
|
|
|
696
719
|
h[i] = r[i].reshape(gshape)[ind].sum()
|
|
697
720
|
return h
|
|
698
721
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
Functional beamforming after :ref:`Dougherty, 2014<Dougherty2014>`.
|
|
702
|
-
"""
|
|
722
|
+
|
|
723
|
+
class BeamformerFunctional(BeamformerBase):
|
|
724
|
+
"""Functional beamforming after :ref:`Dougherty, 2014<Dougherty2014>`."""
|
|
703
725
|
|
|
704
726
|
#: Functional exponent, defaults to 1 (= Classic Beamforming).
|
|
705
|
-
gamma = Float(1,
|
|
706
|
-
desc="functional exponent")
|
|
727
|
+
gamma = Float(1, desc='functional exponent')
|
|
707
728
|
|
|
708
729
|
# internal identifier
|
|
709
|
-
digest = Property(depends_on
|
|
710
|
-
|
|
730
|
+
digest = Property(depends_on=['freq_data.digest', '_steer_obj.digest', 'r_diag', 'gamma'])
|
|
731
|
+
|
|
711
732
|
#: Functional Beamforming is only well defined for full CSM
|
|
712
|
-
r_diag = Enum(False,
|
|
713
|
-
desc="False, as Functional Beamformer is only well defined for the full CSM")
|
|
733
|
+
r_diag = Enum(False, desc='False, as Functional Beamformer is only well defined for the full CSM')
|
|
714
734
|
|
|
715
735
|
@cached_property
|
|
716
|
-
def _get_digest(
|
|
717
|
-
return digest(
|
|
736
|
+
def _get_digest(self):
|
|
737
|
+
return digest(self)
|
|
718
738
|
|
|
719
739
|
def calc(self, ac, fr):
|
|
720
|
-
"""
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
This is an internal helper function that is automatically called when
|
|
740
|
+
"""Calculates the Functional Beamformer result for the frequencies defined by :attr:`freq_data`.
|
|
741
|
+
|
|
742
|
+
This is an internal helper function that is automatically called when
|
|
724
743
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
725
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
726
|
-
|
|
744
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
745
|
+
|
|
727
746
|
Parameters
|
|
728
747
|
----------
|
|
729
748
|
ac : array of floats
|
|
730
749
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
731
750
|
is used as call-by-reference parameter and contains the calculated
|
|
732
|
-
value after calling this method.
|
|
751
|
+
value after calling this method.
|
|
733
752
|
fr : array of booleans
|
|
734
|
-
The entries of this [number of frequencies]-sized array are either
|
|
753
|
+
The entries of this [number of frequencies]-sized array are either
|
|
735
754
|
'True' (if the result for this frequency has already been calculated)
|
|
736
755
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
737
756
|
After the calculation at a certain frequency the value will be set
|
|
738
757
|
to 'True'
|
|
739
|
-
|
|
758
|
+
|
|
740
759
|
Returns
|
|
741
760
|
-------
|
|
742
761
|
This method only returns values through the *ac* and *fr* parameters
|
|
762
|
+
|
|
743
763
|
"""
|
|
744
764
|
f = self.freq_data.fftfreq()
|
|
745
765
|
normFactor = self.sig_loss_norm()
|
|
@@ -747,75 +767,81 @@ class BeamformerFunctional( BeamformerBase ):
|
|
|
747
767
|
for i in self.freq_data.indices:
|
|
748
768
|
if not fr[i]:
|
|
749
769
|
if self.r_diag:
|
|
750
|
-
# This case is not used at the moment (see Trait r_diag)
|
|
770
|
+
# This case is not used at the moment (see Trait r_diag)
|
|
751
771
|
# It would need some testing as structural changes were not tested...
|
|
752
|
-
|
|
753
|
-
# One cannot use spectral decomposition when diagonal of csm is removed,
|
|
754
|
-
# as the resulting modified eigenvectors are not orthogonal to each other anymore.
|
|
755
|
-
# Therefor potentiating cannot be applied only to the eigenvalues.
|
|
756
|
-
# --> To avoid this the root of the csm (removed diag) is calculated directly.
|
|
757
|
-
# WATCH OUT: This doesn't really produce good results.
|
|
758
|
-
|
|
772
|
+
# ==============================================================================
|
|
773
|
+
# One cannot use spectral decomposition when diagonal of csm is removed,
|
|
774
|
+
# as the resulting modified eigenvectors are not orthogonal to each other anymore.
|
|
775
|
+
# Therefor potentiating cannot be applied only to the eigenvalues.
|
|
776
|
+
# --> To avoid this the root of the csm (removed diag) is calculated directly.
|
|
777
|
+
# WATCH OUT: This doesn't really produce good results.
|
|
778
|
+
# ==============================================================================
|
|
759
779
|
csm = self.freq_data.csm[i]
|
|
760
780
|
fill_diagonal(csm, 0)
|
|
761
781
|
csmRoot = fractional_matrix_power(csm, 1.0 / self.gamma)
|
|
762
|
-
beamformerOutput, steerNorm = beamformerFreq(
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
782
|
+
beamformerOutput, steerNorm = beamformerFreq(
|
|
783
|
+
param_steer_type,
|
|
784
|
+
self.r_diag,
|
|
785
|
+
1.0,
|
|
786
|
+
steer_vector(f[i]),
|
|
787
|
+
csmRoot,
|
|
788
|
+
)
|
|
767
789
|
beamformerOutput /= steerNorm # take normalized steering vec
|
|
768
|
-
|
|
790
|
+
|
|
769
791
|
# set (unphysical) negative output values to 0
|
|
770
792
|
indNegSign = sign(beamformerOutput) < 0
|
|
771
793
|
beamformerOutput[indNegSign] = 0.0
|
|
772
794
|
else:
|
|
773
795
|
eva = array(self.freq_data.eva[i], dtype='float64') ** (1.0 / self.gamma)
|
|
774
796
|
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
775
|
-
beamformerOutput, steerNorm = beamformerFreq(
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
797
|
+
beamformerOutput, steerNorm = beamformerFreq(
|
|
798
|
+
param_steer_type,
|
|
799
|
+
self.r_diag,
|
|
800
|
+
1.0,
|
|
801
|
+
steer_vector(f[i]),
|
|
802
|
+
(eva, eve),
|
|
803
|
+
)
|
|
780
804
|
beamformerOutput /= steerNorm # take normalized steering vec
|
|
781
|
-
ac[i] = (
|
|
805
|
+
ac[i] = (
|
|
806
|
+
(beamformerOutput**self.gamma) * steerNorm * normFactor
|
|
807
|
+
) # the normalization must be done outside the beamformer
|
|
782
808
|
fr[i] = 1
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
Beamforming using the Capon (Mininimum Variance) algorithm,
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
class BeamformerCapon(BeamformerBase):
|
|
812
|
+
"""Beamforming using the Capon (Mininimum Variance) algorithm,
|
|
787
813
|
see :ref:`Capon, 1969<Capon1969>`.
|
|
788
814
|
"""
|
|
815
|
+
|
|
789
816
|
# Boolean flag, if 'True', the main diagonal is removed before beamforming;
|
|
790
817
|
# for Capon beamforming r_diag is set to 'False'.
|
|
791
|
-
r_diag = Enum(False,
|
|
792
|
-
desc="removal of diagonal")
|
|
818
|
+
r_diag = Enum(False, desc='removal of diagonal')
|
|
793
819
|
|
|
794
820
|
def calc(self, ac, fr):
|
|
795
|
-
"""
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
This is an internal helper function that is automatically called when
|
|
821
|
+
"""Calculates the Capon result for the frequencies defined by :attr:`freq_data`.
|
|
822
|
+
|
|
823
|
+
This is an internal helper function that is automatically called when
|
|
799
824
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
800
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
801
|
-
|
|
825
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
826
|
+
|
|
802
827
|
Parameters
|
|
803
828
|
----------
|
|
804
829
|
ac : array of floats
|
|
805
830
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
806
831
|
is used as call-by-reference parameter and contains the calculated
|
|
807
|
-
value after calling this method.
|
|
832
|
+
value after calling this method.
|
|
808
833
|
fr : array of booleans
|
|
809
|
-
The entries of this [number of frequencies]-sized array are either
|
|
834
|
+
The entries of this [number of frequencies]-sized array are either
|
|
810
835
|
'True' (if the result for this frequency has already been calculated)
|
|
811
836
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
812
837
|
After the calculation at a certain frequency the value will be set
|
|
813
838
|
to 'True'
|
|
814
|
-
|
|
839
|
+
|
|
815
840
|
Returns
|
|
816
841
|
-------
|
|
817
842
|
This method only returns values through the *ac* and *fr* parameters
|
|
818
|
-
|
|
843
|
+
|
|
844
|
+
"""
|
|
819
845
|
f = self.freq_data.fftfreq()
|
|
820
846
|
nMics = self.freq_data.numchannels
|
|
821
847
|
normFactor = self.sig_loss_norm() * nMics**2
|
|
@@ -823,39 +849,33 @@ class BeamformerCapon( BeamformerBase ):
|
|
|
823
849
|
for i in self.freq_data.indices:
|
|
824
850
|
if not fr[i]:
|
|
825
851
|
csm = array(linalg.inv(array(self.freq_data.csm[i], dtype='complex128')), order='C')
|
|
826
|
-
beamformerOutput = beamformerFreq(param_steer_type,
|
|
827
|
-
self.r_diag,
|
|
828
|
-
normFactor,
|
|
829
|
-
steer_vector(f[i]),
|
|
830
|
-
csm)[0]
|
|
852
|
+
beamformerOutput = beamformerFreq(param_steer_type, self.r_diag, normFactor, steer_vector(f[i]), csm)[0]
|
|
831
853
|
ac[i] = 1.0 / beamformerOutput
|
|
832
854
|
fr[i] = 1
|
|
833
855
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
Beamforming using eigenvalue and eigenvector techniques,
|
|
856
|
+
|
|
857
|
+
class BeamformerEig(BeamformerBase):
|
|
858
|
+
"""Beamforming using eigenvalue and eigenvector techniques,
|
|
837
859
|
see :ref:`Sarradj et al., 2005<Sarradj2005>`.
|
|
838
860
|
"""
|
|
839
|
-
|
|
861
|
+
|
|
862
|
+
#: Number of component to calculate:
|
|
840
863
|
#: 0 (smallest) ... :attr:`~acoular.tprocess.SamplesGenerator.numchannels`-1;
|
|
841
864
|
#: defaults to -1, i.e. numchannels-1
|
|
842
|
-
n = Int(-1,
|
|
843
|
-
desc="No. of eigenvalue")
|
|
865
|
+
n = Int(-1, desc='No. of eigenvalue')
|
|
844
866
|
|
|
845
867
|
# Actual component to calculate, internal, readonly.
|
|
846
|
-
na = Property(
|
|
847
|
-
desc="No. of eigenvalue")
|
|
868
|
+
na = Property(desc='No. of eigenvalue')
|
|
848
869
|
|
|
849
870
|
# internal identifier
|
|
850
|
-
digest = Property(
|
|
851
|
-
depends_on = ['freq_data.digest', '_steer_obj.digest', 'r_diag', 'n'])
|
|
871
|
+
digest = Property(depends_on=['freq_data.digest', '_steer_obj.digest', 'r_diag', 'n'])
|
|
852
872
|
|
|
853
873
|
@cached_property
|
|
854
|
-
def _get_digest(
|
|
855
|
-
return digest(
|
|
856
|
-
|
|
874
|
+
def _get_digest(self):
|
|
875
|
+
return digest(self)
|
|
876
|
+
|
|
857
877
|
@property_depends_on('steer.mics, n')
|
|
858
|
-
def _get_na(
|
|
878
|
+
def _get_na(self):
|
|
859
879
|
na = self.n
|
|
860
880
|
nm = self.steer.mics.num_mics
|
|
861
881
|
if na < 0:
|
|
@@ -863,29 +883,29 @@ class BeamformerEig( BeamformerBase ):
|
|
|
863
883
|
return min(nm - 1, na)
|
|
864
884
|
|
|
865
885
|
def calc(self, ac, fr):
|
|
866
|
-
"""
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
This is an internal helper function that is automatically called when
|
|
886
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
887
|
+
|
|
888
|
+
This is an internal helper function that is automatically called when
|
|
870
889
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
871
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
872
|
-
|
|
890
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
891
|
+
|
|
873
892
|
Parameters
|
|
874
893
|
----------
|
|
875
894
|
ac : array of floats
|
|
876
895
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
877
896
|
is used as call-by-reference parameter and contains the calculated
|
|
878
|
-
value after calling this method.
|
|
897
|
+
value after calling this method.
|
|
879
898
|
fr : array of booleans
|
|
880
|
-
The entries of this [number of frequencies]-sized array are either
|
|
899
|
+
The entries of this [number of frequencies]-sized array are either
|
|
881
900
|
'True' (if the result for this frequency has already been calculated)
|
|
882
901
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
883
902
|
After the calculation at a certain frequency the value will be set
|
|
884
903
|
to 'True'
|
|
885
|
-
|
|
904
|
+
|
|
886
905
|
Returns
|
|
887
906
|
-------
|
|
888
907
|
This method only returns values through the *ac* and *fr* parameters
|
|
908
|
+
|
|
889
909
|
"""
|
|
890
910
|
f = self.freq_data.fftfreq()
|
|
891
911
|
na = int(self.na) # eigenvalue taken into account
|
|
@@ -895,181 +915,184 @@ class BeamformerEig( BeamformerBase ):
|
|
|
895
915
|
if not fr[i]:
|
|
896
916
|
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
897
917
|
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
898
|
-
beamformerOutput = beamformerFreq(
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
918
|
+
beamformerOutput = beamformerFreq(
|
|
919
|
+
param_steer_type,
|
|
920
|
+
self.r_diag,
|
|
921
|
+
normFactor,
|
|
922
|
+
steer_vector(f[i]),
|
|
923
|
+
(eva[na : na + 1], eve[:, na : na + 1]),
|
|
924
|
+
)[0]
|
|
903
925
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
904
926
|
indNegSign = sign(beamformerOutput) < 0
|
|
905
927
|
beamformerOutput[indNegSign] = 0
|
|
906
928
|
ac[i] = beamformerOutput
|
|
907
929
|
fr[i] = 1
|
|
908
930
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
Beamforming using the MUSIC algorithm, see :ref:`Schmidt, 1986<Schmidt1986>`.
|
|
912
|
-
"""
|
|
931
|
+
|
|
932
|
+
class BeamformerMusic(BeamformerEig):
|
|
933
|
+
"""Beamforming using the MUSIC algorithm, see :ref:`Schmidt, 1986<Schmidt1986>`."""
|
|
913
934
|
|
|
914
935
|
# Boolean flag, if 'True', the main diagonal is removed before beamforming;
|
|
915
936
|
# for MUSIC beamforming r_diag is set to 'False'.
|
|
916
|
-
r_diag = Enum(False,
|
|
917
|
-
desc="removal of diagonal")
|
|
937
|
+
r_diag = Enum(False, desc='removal of diagonal')
|
|
918
938
|
|
|
919
939
|
# assumed number of sources, should be set to a value not too small
|
|
920
940
|
# defaults to 1
|
|
921
|
-
n = Int(1,
|
|
922
|
-
desc="assumed number of sources")
|
|
941
|
+
n = Int(1, desc='assumed number of sources')
|
|
923
942
|
|
|
924
943
|
def calc(self, ac, fr):
|
|
925
|
-
"""
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
This is an internal helper function that is automatically called when
|
|
944
|
+
"""Calculates the MUSIC result for the frequencies defined by :attr:`freq_data`.
|
|
945
|
+
|
|
946
|
+
This is an internal helper function that is automatically called when
|
|
929
947
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
930
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
931
|
-
|
|
948
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
949
|
+
|
|
932
950
|
Parameters
|
|
933
951
|
----------
|
|
934
952
|
ac : array of floats
|
|
935
953
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
936
954
|
is used as call-by-reference parameter and contains the calculated
|
|
937
|
-
value after calling this method.
|
|
955
|
+
value after calling this method.
|
|
938
956
|
fr : array of booleans
|
|
939
|
-
The entries of this [number of frequencies]-sized array are either
|
|
957
|
+
The entries of this [number of frequencies]-sized array are either
|
|
940
958
|
'True' (if the result for this frequency has already been calculated)
|
|
941
959
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
942
960
|
After the calculation at a certain frequency the value will be set
|
|
943
961
|
to 'True'
|
|
944
|
-
|
|
962
|
+
|
|
945
963
|
Returns
|
|
946
964
|
-------
|
|
947
965
|
This method only returns values through the *ac* and *fr* parameters
|
|
966
|
+
|
|
948
967
|
"""
|
|
949
968
|
f = self.freq_data.fftfreq()
|
|
950
969
|
nMics = self.freq_data.numchannels
|
|
951
|
-
n = int(self.steer.mics.num_mics-self.na)
|
|
970
|
+
n = int(self.steer.mics.num_mics - self.na)
|
|
952
971
|
normFactor = self.sig_loss_norm() * nMics**2
|
|
953
972
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
954
973
|
for i in self.freq_data.indices:
|
|
955
974
|
if not fr[i]:
|
|
956
975
|
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
957
976
|
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
958
|
-
beamformerOutput = beamformerFreq(
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
977
|
+
beamformerOutput = beamformerFreq(
|
|
978
|
+
param_steer_type,
|
|
979
|
+
self.r_diag,
|
|
980
|
+
normFactor,
|
|
981
|
+
steer_vector(f[i]),
|
|
982
|
+
(eva[:n], eve[:, :n]),
|
|
983
|
+
)[0]
|
|
984
|
+
ac[i] = 4e-10 * beamformerOutput.min() / beamformerOutput
|
|
964
985
|
fr[i] = 1
|
|
965
986
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
The point spread function.
|
|
969
|
-
|
|
970
|
-
This class provides tools to calculate the PSF depending on the used
|
|
987
|
+
|
|
988
|
+
class PointSpreadFunction(HasPrivateTraits):
|
|
989
|
+
"""The point spread function.
|
|
990
|
+
|
|
991
|
+
This class provides tools to calculate the PSF depending on the used
|
|
971
992
|
microphone geometry, focus grid, flow environment, etc.
|
|
972
993
|
The PSF is needed by several deconvolution algorithms to correct
|
|
973
994
|
the aberrations when using simple delay-and-sum beamforming.
|
|
974
995
|
"""
|
|
975
|
-
|
|
976
|
-
|
|
996
|
+
|
|
997
|
+
# Instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes
|
|
977
998
|
# that contains information about the steering vector. This is a private trait.
|
|
978
999
|
# Do not set this directly, use `steer` trait instead.
|
|
979
|
-
_steer_obj = Instance(SteeringVector(), SteeringVector)
|
|
980
|
-
|
|
981
|
-
#: :class:`~acoular.fbeamform.SteeringVector` or derived object.
|
|
1000
|
+
_steer_obj = Instance(SteeringVector(), SteeringVector)
|
|
1001
|
+
|
|
1002
|
+
#: :class:`~acoular.fbeamform.SteeringVector` or derived object.
|
|
982
1003
|
#: Defaults to :class:`~acoular.fbeamform.SteeringVector` object.
|
|
983
|
-
steer = Property(desc=
|
|
984
|
-
|
|
1004
|
+
steer = Property(desc='steering vector object')
|
|
1005
|
+
|
|
985
1006
|
def _get_steer(self):
|
|
986
1007
|
return self._steer_obj
|
|
987
|
-
|
|
1008
|
+
|
|
988
1009
|
def _set_steer(self, steer):
|
|
989
1010
|
if isinstance(steer, SteeringVector):
|
|
990
1011
|
self._steer_obj = steer
|
|
991
1012
|
elif steer in ('true level', 'true location', 'classic', 'inverse'):
|
|
992
1013
|
# Type of steering vectors, see also :ref:`Sarradj, 2012<Sarradj2012>`.
|
|
993
|
-
warn(
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1014
|
+
warn(
|
|
1015
|
+
"Deprecated use of 'steer' trait. Please use object of class 'SteeringVector' in the future.",
|
|
1016
|
+
Warning,
|
|
1017
|
+
stacklevel=2,
|
|
1018
|
+
)
|
|
1019
|
+
self._steer_obj = SteeringVector(steer_type=steer)
|
|
997
1020
|
else:
|
|
998
|
-
raise(TraitError(args=self,
|
|
999
|
-
name='steer',
|
|
1000
|
-
info='SteeringVector',
|
|
1001
|
-
value=steer))
|
|
1021
|
+
raise (TraitError(args=self, name='steer', info='SteeringVector', value=steer))
|
|
1002
1022
|
|
|
1003
1023
|
# --- List of backwards compatibility traits and their setters/getters -----------
|
|
1004
|
-
|
|
1005
|
-
# :class:`~acoular.environments.Environment` or derived object.
|
|
1006
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
1024
|
+
|
|
1025
|
+
# :class:`~acoular.environments.Environment` or derived object.
|
|
1026
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
1007
1027
|
# Now governed by :attr:`steer` trait.
|
|
1008
1028
|
env = Property()
|
|
1009
|
-
|
|
1029
|
+
|
|
1010
1030
|
def _get_env(self):
|
|
1011
|
-
return self._steer_obj.env
|
|
1012
|
-
|
|
1031
|
+
return self._steer_obj.env
|
|
1032
|
+
|
|
1013
1033
|
def _set_env(self, env):
|
|
1014
|
-
warn("Deprecated use of 'env' trait. ", Warning, stacklevel
|
|
1034
|
+
warn("Deprecated use of 'env' trait. ", Warning, stacklevel=2)
|
|
1015
1035
|
self._steer_obj.env = env
|
|
1016
|
-
|
|
1036
|
+
|
|
1017
1037
|
# The speed of sound.
|
|
1018
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
1038
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
1019
1039
|
# Now governed by :attr:`steer` trait.
|
|
1020
1040
|
c = Property()
|
|
1021
|
-
|
|
1041
|
+
|
|
1022
1042
|
def _get_c(self):
|
|
1023
1043
|
return self._steer_obj.env.c
|
|
1024
|
-
|
|
1044
|
+
|
|
1025
1045
|
def _set_c(self, c):
|
|
1026
|
-
warn("Deprecated use of 'c' trait. ", Warning, stacklevel
|
|
1046
|
+
warn("Deprecated use of 'c' trait. ", Warning, stacklevel=2)
|
|
1027
1047
|
self._steer_obj.env.c = c
|
|
1028
|
-
|
|
1048
|
+
|
|
1029
1049
|
# :class:`~acoular.grids.Grid`-derived object that provides the grid locations.
|
|
1030
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
1050
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
1031
1051
|
# Now governed by :attr:`steer` trait.
|
|
1032
1052
|
grid = Property()
|
|
1033
1053
|
|
|
1034
1054
|
def _get_grid(self):
|
|
1035
1055
|
return self._steer_obj.grid
|
|
1036
|
-
|
|
1056
|
+
|
|
1037
1057
|
def _set_grid(self, grid):
|
|
1038
|
-
warn("Deprecated use of 'grid' trait. ", Warning, stacklevel
|
|
1058
|
+
warn("Deprecated use of 'grid' trait. ", Warning, stacklevel=2)
|
|
1039
1059
|
self._steer_obj.grid = grid
|
|
1040
|
-
|
|
1060
|
+
|
|
1041
1061
|
# :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
1042
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
1062
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
1043
1063
|
# Now governed by :attr:`steer` trait
|
|
1044
1064
|
mpos = Property()
|
|
1045
|
-
|
|
1065
|
+
|
|
1046
1066
|
def _get_mpos(self):
|
|
1047
1067
|
return self._steer_obj.mics
|
|
1048
|
-
|
|
1068
|
+
|
|
1049
1069
|
def _set_mpos(self, mpos):
|
|
1050
|
-
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel
|
|
1070
|
+
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2)
|
|
1051
1071
|
self._steer_obj.mics = mpos
|
|
1052
|
-
|
|
1053
|
-
|
|
1072
|
+
|
|
1054
1073
|
# Sound travel distances from microphone array center to grid points (r0)
|
|
1055
1074
|
# and all array mics to grid points (rm). Readonly.
|
|
1056
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
1075
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
1057
1076
|
# Now governed by :attr:`steer` trait
|
|
1058
1077
|
r0 = Property()
|
|
1078
|
+
|
|
1059
1079
|
def _get_r0(self):
|
|
1060
1080
|
return self._steer_obj.r0
|
|
1061
|
-
|
|
1081
|
+
|
|
1062
1082
|
rm = Property()
|
|
1083
|
+
|
|
1063
1084
|
def _get_rm(self):
|
|
1064
1085
|
return self._steer_obj.rm
|
|
1065
|
-
|
|
1086
|
+
|
|
1066
1087
|
# --- End of backwards compatibility traits --------------------------------------
|
|
1067
|
-
|
|
1068
|
-
|
|
1088
|
+
|
|
1069
1089
|
#: Indices of grid points to calculate the PSF for.
|
|
1070
|
-
grid_indices = CArray(
|
|
1071
|
-
|
|
1072
|
-
|
|
1090
|
+
grid_indices = CArray(
|
|
1091
|
+
dtype=int,
|
|
1092
|
+
value=array([]),
|
|
1093
|
+
desc='indices of grid points for psf',
|
|
1094
|
+
) # value=array([]), value=self.grid.pos(),
|
|
1095
|
+
|
|
1073
1096
|
#: Flag that defines how to calculate and store the point spread function
|
|
1074
1097
|
#: defaults to 'single'.
|
|
1075
1098
|
#:
|
|
@@ -1077,142 +1100,127 @@ class PointSpreadFunction (HasPrivateTraits):
|
|
|
1077
1100
|
#: * 'single': Calculate the PSF for the grid points defined by :attr:`grid_indices`, one by one (useful if not all PSFs are needed, as with :class:`CLEAN<BeamformerClean>`)
|
|
1078
1101
|
#: * 'block': Calculate the PSF for the grid points defined by :attr:`grid_indices`, in one go (useful if not all PSFs are needed, as with :class:`CLEAN<BeamformerClean>`)
|
|
1079
1102
|
#: * 'readonly': Do not attempt to calculate the PSF since it should already be cached (useful if multiple processes have to access the cache file)
|
|
1080
|
-
calcmode = Trait('single', 'block', 'full', 'readonly',
|
|
1081
|
-
|
|
1082
|
-
|
|
1103
|
+
calcmode = Trait('single', 'block', 'full', 'readonly', desc='mode of calculation / storage')
|
|
1104
|
+
|
|
1083
1105
|
#: Floating point precision of property psf. Corresponding to numpy dtypes. Default = 64 Bit.
|
|
1084
|
-
precision = Trait('float64', 'float32',
|
|
1085
|
-
|
|
1086
|
-
|
|
1106
|
+
precision = Trait('float64', 'float32', desc='precision (32/64 Bit) of result, corresponding to numpy dtypes')
|
|
1107
|
+
|
|
1087
1108
|
#: The actual point spread function.
|
|
1088
|
-
psf = Property(desc=
|
|
1089
|
-
|
|
1090
|
-
#: Frequency to evaluate the PSF for; defaults to 1.0.
|
|
1091
|
-
freq = Float(1.0, desc=
|
|
1109
|
+
psf = Property(desc='point spread function')
|
|
1110
|
+
|
|
1111
|
+
#: Frequency to evaluate the PSF for; defaults to 1.0.
|
|
1112
|
+
freq = Float(1.0, desc='frequency')
|
|
1092
1113
|
|
|
1093
1114
|
# hdf5 cache file
|
|
1094
|
-
h5f = Instance(
|
|
1095
|
-
|
|
1115
|
+
h5f = Instance(H5CacheFileBase, transient=True)
|
|
1116
|
+
|
|
1096
1117
|
# internal identifier
|
|
1097
|
-
digest = Property(
|
|
1118
|
+
digest = Property(depends_on=['_steer_obj.digest', 'precision'], cached=True)
|
|
1098
1119
|
|
|
1099
1120
|
@cached_property
|
|
1100
|
-
def _get_digest(
|
|
1101
|
-
return digest(
|
|
1102
|
-
|
|
1103
|
-
def _get_filecache(
|
|
1104
|
-
"""
|
|
1105
|
-
|
|
1106
|
-
global/local caching behaviour. Returns (None, None) if no cachefile/data
|
|
1121
|
+
def _get_digest(self):
|
|
1122
|
+
return digest(self)
|
|
1123
|
+
|
|
1124
|
+
def _get_filecache(self):
|
|
1125
|
+
"""Function collects cached results from file depending on
|
|
1126
|
+
global/local caching behaviour. Returns (None, None) if no cachefile/data
|
|
1107
1127
|
exist and global caching mode is 'readonly'.
|
|
1108
1128
|
"""
|
|
1109
1129
|
filename = 'psf' + self.digest
|
|
1110
1130
|
nodename = ('Hz_%.2f' % self.freq).replace('.', '_')
|
|
1111
|
-
# print("get cachefile:", filename)
|
|
1112
|
-
H5cache.get_cache_file(
|
|
1113
|
-
if not self.h5f:
|
|
1114
|
-
# print("no cachefile:", filename)
|
|
1115
|
-
return (None, None)# only happens in case of global caching readonly
|
|
1116
|
-
|
|
1131
|
+
# print("get cachefile:", filename)
|
|
1132
|
+
H5cache.get_cache_file(self, filename)
|
|
1133
|
+
if not self.h5f: # only happens in case of global caching readonly
|
|
1134
|
+
# print("no cachefile:", filename)
|
|
1135
|
+
return (None, None) # only happens in case of global caching readonly
|
|
1136
|
+
|
|
1117
1137
|
if config.global_caching == 'overwrite' and self.h5f.is_cached(nodename):
|
|
1118
|
-
# print("remove existing data for nodename",nodename)
|
|
1119
|
-
self.h5f.remove_data(nodename)
|
|
1120
|
-
|
|
1138
|
+
# print("remove existing data for nodename",nodename)
|
|
1139
|
+
self.h5f.remove_data(nodename) # remove old data before writing in overwrite mode
|
|
1140
|
+
|
|
1121
1141
|
if not self.h5f.is_cached(nodename):
|
|
1122
|
-
# print("no data existent for nodename:", nodename)
|
|
1142
|
+
# print("no data existent for nodename:", nodename)
|
|
1123
1143
|
if config.global_caching == 'readonly':
|
|
1124
1144
|
return (None, None)
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
def _get_psf ( self ):
|
|
1142
|
-
"""
|
|
1143
|
-
This is the :attr:`psf` getter routine.
|
|
1145
|
+
gs = self.steer.grid.size
|
|
1146
|
+
group = self.h5f.create_new_group(nodename)
|
|
1147
|
+
self.h5f.create_compressible_array('result', (gs, gs), self.precision, group)
|
|
1148
|
+
self.h5f.create_compressible_array(
|
|
1149
|
+
'gridpts',
|
|
1150
|
+
(gs,),
|
|
1151
|
+
'int8', #'bool',
|
|
1152
|
+
group,
|
|
1153
|
+
)
|
|
1154
|
+
ac = self.h5f.get_data_by_reference('result', '/' + nodename)
|
|
1155
|
+
gp = self.h5f.get_data_by_reference('gridpts', '/' + nodename)
|
|
1156
|
+
return (ac, gp)
|
|
1157
|
+
|
|
1158
|
+
def _get_psf(self):
|
|
1159
|
+
"""Implements the :attr:`psf` getter routine.
|
|
1144
1160
|
The point spread function is either loaded or calculated.
|
|
1145
1161
|
"""
|
|
1146
1162
|
gs = self.steer.grid.size
|
|
1147
|
-
if not self.grid_indices.size:
|
|
1163
|
+
if not self.grid_indices.size:
|
|
1148
1164
|
self.grid_indices = arange(gs)
|
|
1149
1165
|
|
|
1150
|
-
if
|
|
1151
|
-
# print("get filecache..")
|
|
1152
|
-
(ac,gp) = self._get_filecache()
|
|
1153
|
-
if ac and gp:
|
|
1154
|
-
# print("cached data existent")
|
|
1166
|
+
if config.global_caching != 'none':
|
|
1167
|
+
# print("get filecache..")
|
|
1168
|
+
(ac, gp) = self._get_filecache()
|
|
1169
|
+
if ac and gp:
|
|
1170
|
+
# print("cached data existent")
|
|
1155
1171
|
if not gp[:][self.grid_indices].all():
|
|
1156
|
-
# print("calculate missing results")
|
|
1172
|
+
# print("calculate missing results")
|
|
1157
1173
|
if self.calcmode == 'readonly':
|
|
1158
|
-
|
|
1174
|
+
msg = "Cannot calculate missing PSF (points) in 'readonly' mode."
|
|
1175
|
+
raise ValueError(msg)
|
|
1159
1176
|
if config.global_caching == 'readonly':
|
|
1160
1177
|
(ac, gp) = (ac[:], gp[:])
|
|
1161
|
-
self.calc_psf(ac,gp)
|
|
1162
|
-
return ac[:,self.grid_indices]
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
#
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
else: # no cached data/file
|
|
1171
|
-
# print("no caching, calculate result")
|
|
1172
|
-
ac = zeros((gs, gs), dtype=self.precision)
|
|
1173
|
-
gp = zeros((gs,), dtype='int8')
|
|
1174
|
-
self.calc_psf(ac,gp)
|
|
1175
|
-
else: # no caching activated
|
|
1176
|
-
# print("no caching activated, calculate result")
|
|
1178
|
+
self.calc_psf(ac, gp)
|
|
1179
|
+
return ac[:, self.grid_indices]
|
|
1180
|
+
self.calc_psf(ac, gp)
|
|
1181
|
+
self.h5f.flush()
|
|
1182
|
+
return ac[:, self.grid_indices]
|
|
1183
|
+
# else:
|
|
1184
|
+
# print("cached results are complete! return.")
|
|
1185
|
+
return ac[:, self.grid_indices]
|
|
1186
|
+
# print("no caching, calculate result")
|
|
1177
1187
|
ac = zeros((gs, gs), dtype=self.precision)
|
|
1178
1188
|
gp = zeros((gs,), dtype='int8')
|
|
1179
|
-
self.calc_psf(ac,gp)
|
|
1180
|
-
|
|
1189
|
+
self.calc_psf(ac, gp)
|
|
1190
|
+
else: # no caching activated
|
|
1191
|
+
# print("no caching activated, calculate result")
|
|
1192
|
+
ac = zeros((gs, gs), dtype=self.precision)
|
|
1193
|
+
gp = zeros((gs,), dtype='int8')
|
|
1194
|
+
self.calc_psf(ac, gp)
|
|
1195
|
+
return ac[:, self.grid_indices]
|
|
1181
1196
|
|
|
1182
|
-
def calc_psf(
|
|
1183
|
-
"""
|
|
1184
|
-
point-spread function calculation
|
|
1185
|
-
"""
|
|
1197
|
+
def calc_psf(self, ac, gp):
|
|
1198
|
+
"""point-spread function calculation."""
|
|
1186
1199
|
if self.calcmode != 'full':
|
|
1187
1200
|
# calc_ind has the form [True, True, False, True], except
|
|
1188
1201
|
# when it has only 1 entry (value True/1 would be ambiguous)
|
|
1189
|
-
if self.grid_indices.size == 1:
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
calc_ind = invert(gp[:][self.grid_indices])
|
|
1193
|
-
|
|
1194
|
-
# get indices which have the value True = not yet calculated
|
|
1202
|
+
calc_ind = [0] if self.grid_indices.size == 1 else invert(gp[:][self.grid_indices])
|
|
1203
|
+
|
|
1204
|
+
# get indices which have the value True = not yet calculated
|
|
1195
1205
|
g_ind_calc = self.grid_indices[calc_ind]
|
|
1196
|
-
|
|
1197
|
-
if self.calcmode == 'single':
|
|
1206
|
+
|
|
1207
|
+
if self.calcmode == 'single': # calculate selected psfs one-by-one
|
|
1198
1208
|
for ind in g_ind_calc:
|
|
1199
|
-
ac[:,ind] = self._psfCall([ind])[:,0]
|
|
1209
|
+
ac[:, ind] = self._psfCall([ind])[:, 0]
|
|
1200
1210
|
gp[ind] = 1
|
|
1201
|
-
elif self.calcmode == 'full':
|
|
1211
|
+
elif self.calcmode == 'full': # calculate all psfs in one go
|
|
1202
1212
|
gp[:] = 1
|
|
1203
1213
|
ac[:] = self._psfCall(arange(self.steer.grid.size))
|
|
1204
|
-
else:
|
|
1214
|
+
else: # 'block' # calculate selected psfs in one go
|
|
1205
1215
|
hh = self._psfCall(g_ind_calc)
|
|
1206
|
-
indh
|
|
1207
|
-
for ind in g_ind_calc:
|
|
1216
|
+
for indh, ind in enumerate(g_ind_calc):
|
|
1208
1217
|
gp[ind] = 1
|
|
1209
|
-
ac[:,ind] = hh[:,indh]
|
|
1218
|
+
ac[:, ind] = hh[:, indh]
|
|
1210
1219
|
indh += 1
|
|
1211
1220
|
|
|
1212
1221
|
def _psfCall(self, ind):
|
|
1213
|
-
"""
|
|
1214
|
-
|
|
1215
|
-
|
|
1222
|
+
"""Manages the calling of the core psf functionality.
|
|
1223
|
+
|
|
1216
1224
|
Parameters
|
|
1217
1225
|
----------
|
|
1218
1226
|
ind : list of int
|
|
@@ -1222,103 +1230,103 @@ class PointSpreadFunction (HasPrivateTraits):
|
|
|
1222
1230
|
Returns
|
|
1223
1231
|
-------
|
|
1224
1232
|
The psf [1, nGridPoints, len(ind)]
|
|
1233
|
+
|
|
1225
1234
|
"""
|
|
1226
|
-
if type(self.steer) == SteeringVector:
|
|
1227
|
-
result = calcPointSpreadFunction(
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1235
|
+
if type(self.steer) == SteeringVector: # for simple steering vector, use faster method
|
|
1236
|
+
result = calcPointSpreadFunction(
|
|
1237
|
+
self.steer.steer_type,
|
|
1238
|
+
self.steer.r0,
|
|
1239
|
+
self.steer.rm,
|
|
1240
|
+
2 * pi * self.freq / self.env.c,
|
|
1241
|
+
ind,
|
|
1242
|
+
self.precision,
|
|
1243
|
+
)
|
|
1244
|
+
else: # for arbitrary steering sectors, use general calculation
|
|
1233
1245
|
# there is a version of this in fastFuncs, may be used later after runtime testing and debugging
|
|
1234
|
-
product = dot(self.steer.steer_vector(self.freq).conj(), self.steer.transfer(self.freq,ind).T)
|
|
1246
|
+
product = dot(self.steer.steer_vector(self.freq).conj(), self.steer.transfer(self.freq, ind).T)
|
|
1235
1247
|
result = (product * product.conj()).real
|
|
1236
1248
|
return result
|
|
1237
1249
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
DAMAS deconvolution, see :ref:`Brooks and Humphreys, 2006<BrooksHumphreys2006>`.
|
|
1250
|
+
|
|
1251
|
+
class BeamformerDamas(BeamformerBase):
|
|
1252
|
+
"""DAMAS deconvolution, see :ref:`Brooks and Humphreys, 2006<BrooksHumphreys2006>`.
|
|
1241
1253
|
Needs a-priori delay-and-sum beamforming (:class:`BeamformerBase`).
|
|
1242
1254
|
"""
|
|
1243
1255
|
|
|
1244
1256
|
#: :class:`BeamformerBase` object that provides data for deconvolution.
|
|
1245
1257
|
beamformer = Trait(BeamformerBase)
|
|
1246
1258
|
|
|
1247
|
-
#: :class:`~acoular.spectra.PowerSpectra` object that provides the cross spectral matrix;
|
|
1259
|
+
#: :class:`~acoular.spectra.PowerSpectra` object that provides the cross spectral matrix;
|
|
1248
1260
|
#: is set automatically.
|
|
1249
1261
|
freq_data = Delegate('beamformer')
|
|
1250
1262
|
|
|
1251
|
-
#: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming;
|
|
1263
|
+
#: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming;
|
|
1252
1264
|
#: is set automatically.
|
|
1253
|
-
r_diag =
|
|
1254
|
-
|
|
1265
|
+
r_diag = Delegate('beamformer')
|
|
1266
|
+
|
|
1255
1267
|
#: instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes,
|
|
1256
1268
|
#: that contains information about the steering vector. Is set automatically.
|
|
1257
1269
|
steer = Delegate('beamformer')
|
|
1258
|
-
|
|
1270
|
+
|
|
1259
1271
|
#: Floating point precision of result, is set automatically.
|
|
1260
1272
|
precision = Delegate('beamformer')
|
|
1261
|
-
|
|
1273
|
+
|
|
1262
1274
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1263
|
-
psf_precision = Trait('float64', 'float32',
|
|
1264
|
-
desc="precision of PSF")
|
|
1275
|
+
psf_precision = Trait('float64', 'float32', desc='precision of PSF')
|
|
1265
1276
|
|
|
1266
1277
|
#: Number of iterations, defaults to 100.
|
|
1267
|
-
n_iter = Int(100,
|
|
1268
|
-
|
|
1269
|
-
|
|
1278
|
+
n_iter = Int(100, desc='number of iterations')
|
|
1279
|
+
|
|
1270
1280
|
#: Damping factor in modified gauss-seidel
|
|
1271
|
-
damp = Float(1.0,
|
|
1272
|
-
desc="damping factor in modified gauss-seidel-DAMAS-approach")
|
|
1281
|
+
damp = Float(1.0, desc='damping factor in modified gauss-seidel-DAMAS-approach')
|
|
1273
1282
|
|
|
1274
|
-
#: Flag that defines how to calculate and store the point spread function,
|
|
1283
|
+
#: Flag that defines how to calculate and store the point spread function,
|
|
1275
1284
|
#: defaults to 'full'. See :attr:`PointSpreadFunction.calcmode` for details.
|
|
1276
|
-
calcmode = Trait('full', 'single', 'block', 'readonly',
|
|
1277
|
-
|
|
1278
|
-
|
|
1285
|
+
calcmode = Trait('full', 'single', 'block', 'readonly', desc='mode of psf calculation / storage')
|
|
1286
|
+
|
|
1279
1287
|
# internal identifier
|
|
1280
|
-
digest = Property(
|
|
1281
|
-
depends_on
|
|
1282
|
-
|
|
1288
|
+
digest = Property(
|
|
1289
|
+
depends_on=['beamformer.digest', 'n_iter', 'damp', 'psf_precision'],
|
|
1290
|
+
)
|
|
1283
1291
|
|
|
1284
1292
|
# internal identifier
|
|
1285
|
-
ext_digest = Property(
|
|
1286
|
-
depends_on
|
|
1287
|
-
|
|
1288
|
-
|
|
1293
|
+
ext_digest = Property(
|
|
1294
|
+
depends_on=['digest', 'beamformer.ext_digest'],
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1289
1297
|
@cached_property
|
|
1290
|
-
def _get_digest(
|
|
1291
|
-
return digest(
|
|
1292
|
-
|
|
1298
|
+
def _get_digest(self):
|
|
1299
|
+
return digest(self)
|
|
1300
|
+
|
|
1293
1301
|
@cached_property
|
|
1294
|
-
def _get_ext_digest(
|
|
1295
|
-
return digest(
|
|
1296
|
-
|
|
1302
|
+
def _get_ext_digest(self):
|
|
1303
|
+
return digest(self, 'ext_digest')
|
|
1304
|
+
|
|
1297
1305
|
def calc(self, ac, fr):
|
|
1298
|
-
"""
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
This is an internal helper function that is automatically called when
|
|
1306
|
+
"""Calculates the DAMAS result for the frequencies defined by :attr:`freq_data`.
|
|
1307
|
+
|
|
1308
|
+
This is an internal helper function that is automatically called when
|
|
1302
1309
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
1303
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
1310
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
1304
1311
|
A Gauss-Seidel algorithm implemented in C is used for computing the result.
|
|
1305
|
-
|
|
1312
|
+
|
|
1306
1313
|
Parameters
|
|
1307
1314
|
----------
|
|
1308
1315
|
ac : array of floats
|
|
1309
1316
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
1310
1317
|
is used as call-by-reference parameter and contains the calculated
|
|
1311
|
-
value after calling this method.
|
|
1318
|
+
value after calling this method.
|
|
1312
1319
|
fr : array of booleans
|
|
1313
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1320
|
+
The entries of this [number of frequencies]-sized array are either
|
|
1314
1321
|
'True' (if the result for this frequency has already been calculated)
|
|
1315
1322
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1316
1323
|
After the calculation at a certain frequency the value will be set
|
|
1317
1324
|
to 'True'
|
|
1318
|
-
|
|
1325
|
+
|
|
1319
1326
|
Returns
|
|
1320
1327
|
-------
|
|
1321
1328
|
This method only returns values through the *ac* and *fr* parameters
|
|
1329
|
+
|
|
1322
1330
|
"""
|
|
1323
1331
|
f = self.freq_data.fftfreq()
|
|
1324
1332
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
@@ -1332,85 +1340,80 @@ class BeamformerDamas (BeamformerBase):
|
|
|
1332
1340
|
ac[i] = x
|
|
1333
1341
|
fr[i] = 1
|
|
1334
1342
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
DAMAS deconvolution, see :ref:`Brooks and Humphreys, 2006<BrooksHumphreys2006>`,
|
|
1338
|
-
for solving the system of equations, instead of the original Gauss-Seidel
|
|
1339
|
-
iterations, this class employs the NNLS or linear programming solvers from
|
|
1343
|
+
|
|
1344
|
+
class BeamformerDamasPlus(BeamformerDamas):
|
|
1345
|
+
"""DAMAS deconvolution, see :ref:`Brooks and Humphreys, 2006<BrooksHumphreys2006>`,
|
|
1346
|
+
for solving the system of equations, instead of the original Gauss-Seidel
|
|
1347
|
+
iterations, this class employs the NNLS or linear programming solvers from
|
|
1340
1348
|
scipy.optimize or one of several optimization algorithms from the scikit-learn module.
|
|
1341
1349
|
Needs a-priori delay-and-sum beamforming (:class:`BeamformerBase`).
|
|
1342
1350
|
"""
|
|
1343
|
-
|
|
1344
|
-
#: Type of fit method to be used ('LassoLars',
|
|
1351
|
+
|
|
1352
|
+
#: Type of fit method to be used ('LassoLars',
|
|
1345
1353
|
#: 'OMPCV', 'LP', or 'NNLS', defaults to 'NNLS').
|
|
1346
|
-
#: These methods are implemented in
|
|
1347
|
-
#: the `scikit-learn <http://scikit-learn.org/stable/user_guide.html>`_
|
|
1354
|
+
#: These methods are implemented in
|
|
1355
|
+
#: the `scikit-learn <http://scikit-learn.org/stable/user_guide.html>`_
|
|
1348
1356
|
#: module or within scipy.optimize respectively.
|
|
1349
|
-
method = Trait('NNLS','LP','LassoLars', 'OMPCV',
|
|
1350
|
-
|
|
1351
|
-
|
|
1357
|
+
method = Trait('NNLS', 'LP', 'LassoLars', 'OMPCV', desc='method used for solving deconvolution problem')
|
|
1358
|
+
|
|
1352
1359
|
#: Weight factor for LassoLars method,
|
|
1353
1360
|
#: defaults to 0.0.
|
|
1354
1361
|
# (Values in the order of 10^⁻9 should produce good results.)
|
|
1355
|
-
alpha = Range(0.0, 1.0, 0.0,
|
|
1356
|
-
|
|
1357
|
-
|
|
1362
|
+
alpha = Range(0.0, 1.0, 0.0, desc='Lasso weight factor')
|
|
1363
|
+
|
|
1358
1364
|
#: Maximum number of iterations,
|
|
1359
1365
|
#: tradeoff between speed and precision;
|
|
1360
1366
|
#: defaults to 500
|
|
1361
|
-
max_iter = Int(500,
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
#:
|
|
1365
|
-
#: Values are converted back before returning.
|
|
1367
|
+
max_iter = Int(500, desc='maximum number of iterations')
|
|
1368
|
+
|
|
1369
|
+
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
1370
|
+
#: Values are converted back before returning.
|
|
1366
1371
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
1367
1372
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
1368
|
-
unit_mult = Float(1e9,
|
|
1369
|
-
|
|
1370
|
-
|
|
1373
|
+
unit_mult = Float(1e9, desc='unit multiplier')
|
|
1374
|
+
|
|
1371
1375
|
# internal identifier
|
|
1372
|
-
digest = Property(
|
|
1373
|
-
depends_on
|
|
1374
|
-
|
|
1375
|
-
)
|
|
1376
|
+
digest = Property(
|
|
1377
|
+
depends_on=['beamformer.digest', 'alpha', 'method', 'max_iter', 'unit_mult'],
|
|
1378
|
+
)
|
|
1376
1379
|
|
|
1377
1380
|
# internal identifier
|
|
1378
|
-
ext_digest = Property(
|
|
1379
|
-
depends_on
|
|
1380
|
-
|
|
1381
|
-
|
|
1381
|
+
ext_digest = Property(
|
|
1382
|
+
depends_on=['digest', 'beamformer.ext_digest'],
|
|
1383
|
+
)
|
|
1384
|
+
|
|
1382
1385
|
@cached_property
|
|
1383
|
-
def _get_digest(
|
|
1384
|
-
return digest(
|
|
1385
|
-
|
|
1386
|
+
def _get_digest(self):
|
|
1387
|
+
return digest(self)
|
|
1388
|
+
|
|
1386
1389
|
@cached_property
|
|
1387
|
-
def _get_ext_digest(
|
|
1388
|
-
return digest(
|
|
1389
|
-
|
|
1390
|
+
def _get_ext_digest(self):
|
|
1391
|
+
return digest(self, 'ext_digest')
|
|
1392
|
+
|
|
1390
1393
|
def calc(self, ac, fr):
|
|
1391
|
-
"""
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
This is an internal helper function that is automatically called when
|
|
1394
|
+
"""Calculates the DAMAS result for the frequencies defined by :attr:`freq_data`.
|
|
1395
|
+
|
|
1396
|
+
This is an internal helper function that is automatically called when
|
|
1395
1397
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
1396
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
1397
|
-
|
|
1398
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
1399
|
+
|
|
1398
1400
|
Parameters
|
|
1399
1401
|
----------
|
|
1400
1402
|
ac : array of floats
|
|
1401
1403
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
1402
1404
|
is used as call-by-reference parameter and contains the calculated
|
|
1403
|
-
value after calling this method.
|
|
1405
|
+
value after calling this method.
|
|
1404
1406
|
fr : array of booleans
|
|
1405
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1407
|
+
The entries of this [number of frequencies]-sized array are either
|
|
1406
1408
|
'True' (if the result for this frequency has already been calculated)
|
|
1407
1409
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1408
1410
|
After the calculation at a certain frequency the value will be set
|
|
1409
1411
|
to 'True'
|
|
1410
|
-
|
|
1412
|
+
|
|
1411
1413
|
Returns
|
|
1412
1414
|
-------
|
|
1413
1415
|
This method only returns values through the *ac* and *fr* parameters
|
|
1416
|
+
|
|
1414
1417
|
"""
|
|
1415
1418
|
f = self.freq_data.fftfreq()
|
|
1416
1419
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
@@ -1421,29 +1424,29 @@ class BeamformerDamasPlus (BeamformerDamas):
|
|
|
1421
1424
|
p.freq = f[i]
|
|
1422
1425
|
psf = p.psf[:]
|
|
1423
1426
|
|
|
1424
|
-
if self.method ==
|
|
1427
|
+
if self.method == 'NNLS':
|
|
1425
1428
|
ac[i] = nnls(psf, y)[0] / unit
|
|
1426
|
-
elif self.method ==
|
|
1429
|
+
elif self.method == 'LP': # linear programming (Dougherty)
|
|
1427
1430
|
if self.r_diag:
|
|
1428
1431
|
warn(
|
|
1429
|
-
|
|
1430
|
-
|
|
1432
|
+
'Linear programming solver may fail when CSM main '
|
|
1433
|
+
'diagonal is removed for delay-and-sum beamforming.',
|
|
1431
1434
|
Warning,
|
|
1432
1435
|
stacklevel=5,
|
|
1433
1436
|
)
|
|
1434
1437
|
cT = -1 * psf.sum(1) # turn the minimization into a maximization
|
|
1435
|
-
ac[i] = (
|
|
1436
|
-
linprog(c=cT, A_ub=psf, b_ub=y).x / unit
|
|
1437
|
-
) # defaults to simplex method and non-negative x
|
|
1438
|
+
ac[i] = linprog(c=cT, A_ub=psf, b_ub=y).x / unit # defaults to simplex method and non-negative x
|
|
1438
1439
|
else:
|
|
1439
|
-
if self.method ==
|
|
1440
|
+
if self.method == 'LassoLars':
|
|
1440
1441
|
model = LassoLars(
|
|
1441
|
-
alpha=self.alpha * unit,
|
|
1442
|
+
alpha=self.alpha * unit,
|
|
1443
|
+
max_iter=self.max_iter,
|
|
1442
1444
|
)
|
|
1443
|
-
elif self.method ==
|
|
1445
|
+
elif self.method == 'OMPCV':
|
|
1444
1446
|
model = OrthogonalMatchingPursuitCV()
|
|
1445
1447
|
else:
|
|
1446
|
-
|
|
1448
|
+
msg = f'Method {self.method} not implemented.'
|
|
1449
|
+
raise NotImplementedError(msg)
|
|
1447
1450
|
model.normalize = False
|
|
1448
1451
|
# from sklearn 1.2, normalize=True does not work the same way anymore and the pipeline approach
|
|
1449
1452
|
# with StandardScaler does scale in a different way, thus we monkeypatch the code and normalize
|
|
@@ -1452,20 +1455,20 @@ class BeamformerDamasPlus (BeamformerDamas):
|
|
|
1452
1455
|
# get rid of annoying sklearn warnings that appear
|
|
1453
1456
|
# for sklearn<1.2 despite any settings
|
|
1454
1457
|
with warnings.catch_warnings():
|
|
1455
|
-
warnings.simplefilter(
|
|
1458
|
+
warnings.simplefilter('ignore', category=FutureWarning)
|
|
1456
1459
|
# normalized psf
|
|
1457
1460
|
model.fit(psf / norms, y)
|
|
1458
1461
|
# recover normalization in the coef's
|
|
1459
1462
|
ac[i] = model.coef_[:] / norms / unit
|
|
1460
|
-
|
|
1463
|
+
|
|
1461
1464
|
fr[i] = 1
|
|
1462
1465
|
|
|
1463
1466
|
|
|
1464
|
-
class BeamformerOrth(
|
|
1465
|
-
"""
|
|
1466
|
-
Orthogonal deconvolution, see :ref:`Sarradj, 2010<Sarradj2010>`.
|
|
1467
|
+
class BeamformerOrth(BeamformerBase):
|
|
1468
|
+
"""Orthogonal deconvolution, see :ref:`Sarradj, 2010<Sarradj2010>`.
|
|
1467
1469
|
New faster implementation without explicit (:class:`BeamformerEig`).
|
|
1468
1470
|
"""
|
|
1471
|
+
|
|
1469
1472
|
#: (only for backward compatibility) :class:`BeamformerEig` object
|
|
1470
1473
|
#: if set, provides :attr:`freq_data`, :attr:`steer`, :attr:`r_diag`
|
|
1471
1474
|
#: if not set, these have to be set explicitly
|
|
@@ -1473,28 +1476,26 @@ class BeamformerOrth( BeamformerBase ):
|
|
|
1473
1476
|
|
|
1474
1477
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
1475
1478
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
1476
|
-
eva_list = CArray(dtype=int,
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
#: Number of components to consider, defaults to 1. If set,
|
|
1479
|
+
eva_list = CArray(dtype=int, desc='components')
|
|
1480
|
+
|
|
1481
|
+
#: Number of components to consider, defaults to 1. If set,
|
|
1480
1482
|
#: :attr:`eva_list` will contain
|
|
1481
|
-
#: the indices of the n largest eigenvalues. Setting :attr:`eva_list`
|
|
1483
|
+
#: the indices of the n largest eigenvalues. Setting :attr:`eva_list`
|
|
1482
1484
|
#: afterwards will override this value.
|
|
1483
1485
|
n = Int(1)
|
|
1484
1486
|
|
|
1485
1487
|
# internal identifier
|
|
1486
|
-
digest = Property(
|
|
1487
|
-
depends_on
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1488
|
+
digest = Property(
|
|
1489
|
+
depends_on=['freq_data.digest', '_steer_obj.digest', 'r_diag', 'eva_list'],
|
|
1490
|
+
)
|
|
1491
|
+
|
|
1491
1492
|
@cached_property
|
|
1492
|
-
def _get_digest(
|
|
1493
|
-
return digest(
|
|
1493
|
+
def _get_digest(self):
|
|
1494
|
+
return digest(self)
|
|
1494
1495
|
|
|
1495
1496
|
@cached_property
|
|
1496
|
-
def _get_ext_digest(
|
|
1497
|
-
return digest(
|
|
1497
|
+
def _get_ext_digest(self):
|
|
1498
|
+
return digest(self, 'ext_digest')
|
|
1498
1499
|
|
|
1499
1500
|
@on_trait_change('beamformer.digest')
|
|
1500
1501
|
def delegate_beamformer_traits(self):
|
|
@@ -1504,164 +1505,156 @@ class BeamformerOrth( BeamformerBase ):
|
|
|
1504
1505
|
|
|
1505
1506
|
@on_trait_change('n')
|
|
1506
1507
|
def set_eva_list(self):
|
|
1507
|
-
"""
|
|
1508
|
-
self.eva_list = arange(-1, -1-self.n, -1)
|
|
1508
|
+
"""Sets the list of eigenvalues to consider."""
|
|
1509
|
+
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
1509
1510
|
|
|
1510
1511
|
def calc(self, ac, fr):
|
|
1511
|
-
"""
|
|
1512
|
-
Calculates the Orthogonal Beamforming result for the frequencies
|
|
1512
|
+
"""Calculates the Orthogonal Beamforming result for the frequencies
|
|
1513
1513
|
defined by :attr:`freq_data`.
|
|
1514
|
-
|
|
1515
|
-
This is an internal helper function that is automatically called when
|
|
1514
|
+
|
|
1515
|
+
This is an internal helper function that is automatically called when
|
|
1516
1516
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
1517
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
1518
|
-
|
|
1517
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
1518
|
+
|
|
1519
1519
|
Parameters
|
|
1520
1520
|
----------
|
|
1521
1521
|
ac : array of floats
|
|
1522
1522
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
1523
1523
|
is used as call-by-reference parameter and contains the calculated
|
|
1524
|
-
value after calling this method.
|
|
1524
|
+
value after calling this method.
|
|
1525
1525
|
fr : array of booleans
|
|
1526
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1526
|
+
The entries of this [number of frequencies]-sized array are either
|
|
1527
1527
|
'True' (if the result for this frequency has already been calculated)
|
|
1528
1528
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1529
1529
|
After the calculation at a certain frequency the value will be set
|
|
1530
1530
|
to 'True'
|
|
1531
|
-
|
|
1531
|
+
|
|
1532
1532
|
Returns
|
|
1533
1533
|
-------
|
|
1534
1534
|
This method only returns values through the *ac* and *fr* parameters
|
|
1535
|
+
|
|
1535
1536
|
"""
|
|
1536
1537
|
# prepare calculation
|
|
1537
1538
|
f = self.freq_data.fftfreq()
|
|
1538
1539
|
numchannels = self.freq_data.numchannels
|
|
1539
1540
|
normFactor = self.sig_loss_norm()
|
|
1540
1541
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1541
|
-
for i in self.freq_data.indices:
|
|
1542
|
+
for i in self.freq_data.indices:
|
|
1542
1543
|
if not fr[i]:
|
|
1543
1544
|
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
1544
1545
|
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
1545
1546
|
for n in self.eva_list:
|
|
1546
|
-
beamformerOutput = beamformerFreq(
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1547
|
+
beamformerOutput = beamformerFreq(
|
|
1548
|
+
param_steer_type,
|
|
1549
|
+
self.r_diag,
|
|
1550
|
+
normFactor,
|
|
1551
|
+
steer_vector(f[i]),
|
|
1552
|
+
(ones(1), eve[:, n].reshape((-1, 1))),
|
|
1553
|
+
)[0]
|
|
1554
|
+
ac[i, beamformerOutput.argmax()] += eva[n] / numchannels
|
|
1552
1555
|
fr[i] = 1
|
|
1553
1556
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
CLEAN-SC deconvolution, see :ref:`Sijtsma, 2007<Sijtsma2007>`.
|
|
1557
|
+
|
|
1558
|
+
class BeamformerCleansc(BeamformerBase):
|
|
1559
|
+
"""CLEAN-SC deconvolution, see :ref:`Sijtsma, 2007<Sijtsma2007>`.
|
|
1557
1560
|
Classic delay-and-sum beamforming is already included.
|
|
1558
1561
|
"""
|
|
1559
1562
|
|
|
1560
1563
|
#: no of CLEAN-SC iterations
|
|
1561
1564
|
#: defaults to 0, i.e. automatic (max 2*numchannels)
|
|
1562
|
-
n = Int(0,
|
|
1563
|
-
desc="no of iterations")
|
|
1565
|
+
n = Int(0, desc='no of iterations')
|
|
1564
1566
|
|
|
1565
1567
|
#: iteration damping factor
|
|
1566
1568
|
#: defaults to 0.6
|
|
1567
|
-
damp = Range(0.01, 1.0, 0.6,
|
|
1568
|
-
desc="damping factor")
|
|
1569
|
+
damp = Range(0.01, 1.0, 0.6, desc='damping factor')
|
|
1569
1570
|
|
|
1570
1571
|
#: iteration stop criterion for automatic detection
|
|
1571
1572
|
#: iteration stops if power[i]>power[i-stopn]
|
|
1572
1573
|
#: defaults to 3
|
|
1573
|
-
stopn = Int(3,
|
|
1574
|
-
desc="stop criterion index")
|
|
1574
|
+
stopn = Int(3, desc='stop criterion index')
|
|
1575
1575
|
|
|
1576
1576
|
# internal identifier
|
|
1577
|
-
digest = Property(
|
|
1578
|
-
depends_on = ['freq_data.digest', '_steer_obj.digest', 'r_diag', 'n', 'damp', 'stopn'])
|
|
1577
|
+
digest = Property(depends_on=['freq_data.digest', '_steer_obj.digest', 'r_diag', 'n', 'damp', 'stopn'])
|
|
1579
1578
|
|
|
1580
1579
|
@cached_property
|
|
1581
|
-
def _get_digest(
|
|
1582
|
-
return digest(
|
|
1580
|
+
def _get_digest(self):
|
|
1581
|
+
return digest(self)
|
|
1583
1582
|
|
|
1584
1583
|
def calc(self, ac, fr):
|
|
1585
|
-
"""
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
This is an internal helper function that is automatically called when
|
|
1584
|
+
"""Calculates the CLEAN-SC result for the frequencies defined by :attr:`freq_data`.
|
|
1585
|
+
|
|
1586
|
+
This is an internal helper function that is automatically called when
|
|
1589
1587
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
1590
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
1591
|
-
|
|
1588
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
1589
|
+
|
|
1592
1590
|
Parameters
|
|
1593
1591
|
----------
|
|
1594
1592
|
ac : array of floats
|
|
1595
1593
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
1596
1594
|
is used as call-by-reference parameter and contains the calculated
|
|
1597
|
-
value after calling this method.
|
|
1595
|
+
value after calling this method.
|
|
1598
1596
|
fr : array of booleans
|
|
1599
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1597
|
+
The entries of this [number of frequencies]-sized array are either
|
|
1600
1598
|
'True' (if the result for this frequency has already been calculated)
|
|
1601
1599
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1602
1600
|
After the calculation at a certain frequency the value will be set
|
|
1603
1601
|
to 'True'
|
|
1604
|
-
|
|
1602
|
+
|
|
1605
1603
|
Returns
|
|
1606
1604
|
-------
|
|
1607
1605
|
This method only returns values through the *ac* and *fr* parameters
|
|
1608
|
-
"""
|
|
1609
1606
|
|
|
1607
|
+
"""
|
|
1610
1608
|
# prepare calculation
|
|
1611
1609
|
normFactor = self.sig_loss_norm()
|
|
1612
1610
|
numchannels = self.freq_data.numchannels
|
|
1613
1611
|
f = self.freq_data.fftfreq()
|
|
1614
|
-
result = zeros((self.steer.grid.size), 'f')
|
|
1612
|
+
result = zeros((self.steer.grid.size), 'f')
|
|
1615
1613
|
normFac = self.sig_loss_norm()
|
|
1616
|
-
if not self.n
|
|
1617
|
-
J = numchannels*2
|
|
1618
|
-
else:
|
|
1619
|
-
J = self.n
|
|
1614
|
+
J = numchannels * 2 if not self.n else self.n
|
|
1620
1615
|
powers = zeros(J, 'd')
|
|
1621
|
-
|
|
1616
|
+
|
|
1622
1617
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1623
1618
|
for i in self.freq_data.indices:
|
|
1624
1619
|
if not fr[i]:
|
|
1625
1620
|
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
1626
|
-
#h = self.steer._beamformerCall(f[i], self.r_diag, normFactor, (csm,))[0]
|
|
1627
|
-
h = beamformerFreq(param_steer_type,
|
|
1628
|
-
self.r_diag,
|
|
1629
|
-
normFactor,
|
|
1630
|
-
steer_vector(f[i]),
|
|
1631
|
-
csm)[0]
|
|
1621
|
+
# h = self.steer._beamformerCall(f[i], self.r_diag, normFactor, (csm,))[0]
|
|
1622
|
+
h = beamformerFreq(param_steer_type, self.r_diag, normFactor, steer_vector(f[i]), csm)[0]
|
|
1632
1623
|
# CLEANSC Iteration
|
|
1633
1624
|
result *= 0.0
|
|
1634
1625
|
for j in range(J):
|
|
1635
|
-
xi_max = h.argmax()
|
|
1636
|
-
powers[j] = hmax = h[xi_max]
|
|
1626
|
+
xi_max = h.argmax() # index of maximum
|
|
1627
|
+
powers[j] = hmax = h[xi_max] # maximum
|
|
1637
1628
|
result[xi_max] += self.damp * hmax
|
|
1638
|
-
if
|
|
1629
|
+
if j > self.stopn and hmax > powers[j - self.stopn]:
|
|
1639
1630
|
break
|
|
1640
|
-
wmax = self.steer.steer_vector(f[i],xi_max) * sqrt(normFac)
|
|
1631
|
+
wmax = self.steer.steer_vector(f[i], xi_max) * sqrt(normFac)
|
|
1641
1632
|
wmax = wmax[0].conj() # as old code worked with conjugated csm..should be updated
|
|
1642
1633
|
hh = wmax.copy()
|
|
1643
|
-
D1 = dot(csm.T - diag(diag(csm)), wmax)/hmax
|
|
1644
|
-
ww = wmax.conj()*wmax
|
|
1645
|
-
for
|
|
1646
|
-
H = hh.conj()*hh
|
|
1647
|
-
hh = (D1+H*wmax)/sqrt(1+dot(ww, H))
|
|
1634
|
+
D1 = dot(csm.T - diag(diag(csm)), wmax) / hmax
|
|
1635
|
+
ww = wmax.conj() * wmax
|
|
1636
|
+
for _m in range(20):
|
|
1637
|
+
H = hh.conj() * hh
|
|
1638
|
+
hh = (D1 + H * wmax) / sqrt(1 + dot(ww, H))
|
|
1648
1639
|
hh = hh[:, newaxis]
|
|
1649
|
-
csm1 = hmax*(hh*hh.conj().T)
|
|
1650
|
-
|
|
1651
|
-
#h1 = self.steer._beamformerCall(f[i], self.r_diag, normFactor, (array((hmax, ))[newaxis, :], hh[newaxis, :].conjugate()))[0]
|
|
1652
|
-
h1 = beamformerFreq(
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1640
|
+
csm1 = hmax * (hh * hh.conj().T)
|
|
1641
|
+
|
|
1642
|
+
# h1 = self.steer._beamformerCall(f[i], self.r_diag, normFactor, (array((hmax, ))[newaxis, :], hh[newaxis, :].conjugate()))[0]
|
|
1643
|
+
h1 = beamformerFreq(
|
|
1644
|
+
param_steer_type,
|
|
1645
|
+
self.r_diag,
|
|
1646
|
+
normFactor,
|
|
1647
|
+
steer_vector(f[i]),
|
|
1648
|
+
(array((hmax,)), hh.conj()),
|
|
1649
|
+
)[0]
|
|
1657
1650
|
h -= self.damp * h1
|
|
1658
|
-
csm -= self.damp * csm1.T#transpose(0,2,1)
|
|
1651
|
+
csm -= self.damp * csm1.T # transpose(0,2,1)
|
|
1659
1652
|
ac[i] = result
|
|
1660
1653
|
fr[i] = 1
|
|
1661
1654
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
CLEAN deconvolution, see :ref:`Hoegbom, 1974<Hoegbom1974>`.
|
|
1655
|
+
|
|
1656
|
+
class BeamformerClean(BeamformerBase):
|
|
1657
|
+
"""CLEAN deconvolution, see :ref:`Hoegbom, 1974<Hoegbom1974>`.
|
|
1665
1658
|
Needs a-priori delay-and-sum beamforming (:class:`BeamformerBase`).
|
|
1666
1659
|
"""
|
|
1667
1660
|
|
|
@@ -1672,88 +1665,87 @@ class BeamformerClean (BeamformerBase):
|
|
|
1672
1665
|
freq_data = Delegate('beamformer')
|
|
1673
1666
|
|
|
1674
1667
|
# flag, if true (default), the main diagonal is removed before beamforming
|
|
1675
|
-
#r_diag = Delegate('beamformer')
|
|
1676
|
-
|
|
1668
|
+
# r_diag = Delegate('beamformer')
|
|
1669
|
+
|
|
1677
1670
|
#: instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes,
|
|
1678
1671
|
#: that contains information about the steering vector. Is set automatically.
|
|
1679
1672
|
steer = Delegate('beamformer')
|
|
1680
|
-
|
|
1673
|
+
|
|
1681
1674
|
#: Floating point precision of result, is set automatically.
|
|
1682
1675
|
precision = Delegate('beamformer')
|
|
1683
|
-
|
|
1676
|
+
|
|
1684
1677
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1685
|
-
psf_precision = Trait('float64', 'float32',
|
|
1686
|
-
|
|
1687
|
-
|
|
1678
|
+
psf_precision = Trait('float64', 'float32', desc='precision of PSF.')
|
|
1679
|
+
|
|
1688
1680
|
# iteration damping factor
|
|
1689
1681
|
# defaults to 0.6
|
|
1690
|
-
damp = Range(0.01, 1.0, 0.6,
|
|
1691
|
-
|
|
1692
|
-
|
|
1682
|
+
damp = Range(0.01, 1.0, 0.6, desc='damping factor')
|
|
1683
|
+
|
|
1693
1684
|
# max number of iterations
|
|
1694
|
-
n_iter = Int(100,
|
|
1695
|
-
desc="maximum number of iterations")
|
|
1685
|
+
n_iter = Int(100, desc='maximum number of iterations')
|
|
1696
1686
|
|
|
1697
1687
|
# how to calculate and store the psf
|
|
1698
|
-
calcmode = Trait('block', 'full', 'single', 'readonly',
|
|
1699
|
-
|
|
1700
|
-
|
|
1688
|
+
calcmode = Trait('block', 'full', 'single', 'readonly', desc='mode of psf calculation / storage')
|
|
1689
|
+
|
|
1701
1690
|
# internal identifier
|
|
1702
|
-
digest = Property(
|
|
1703
|
-
depends_on
|
|
1704
|
-
|
|
1691
|
+
digest = Property(
|
|
1692
|
+
depends_on=['beamformer.digest', 'n_iter', 'damp', 'psf_precision'],
|
|
1693
|
+
)
|
|
1705
1694
|
|
|
1706
1695
|
# internal identifier
|
|
1707
|
-
ext_digest = Property(
|
|
1708
|
-
depends_on
|
|
1709
|
-
|
|
1710
|
-
|
|
1696
|
+
ext_digest = Property(
|
|
1697
|
+
depends_on=['digest', 'beamformer.ext_digest'],
|
|
1698
|
+
)
|
|
1699
|
+
|
|
1711
1700
|
@cached_property
|
|
1712
|
-
def _get_digest(
|
|
1713
|
-
return digest(
|
|
1714
|
-
|
|
1701
|
+
def _get_digest(self):
|
|
1702
|
+
return digest(self)
|
|
1703
|
+
|
|
1715
1704
|
@cached_property
|
|
1716
|
-
def _get_ext_digest(
|
|
1717
|
-
return digest(
|
|
1718
|
-
|
|
1705
|
+
def _get_ext_digest(self):
|
|
1706
|
+
return digest(self, 'ext_digest')
|
|
1707
|
+
|
|
1719
1708
|
def calc(self, ac, fr):
|
|
1720
|
-
"""
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
This is an internal helper function that is automatically called when
|
|
1709
|
+
"""Calculates the CLEAN result for the frequencies defined by :attr:`freq_data`.
|
|
1710
|
+
|
|
1711
|
+
This is an internal helper function that is automatically called when
|
|
1724
1712
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
1725
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
1726
|
-
|
|
1713
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
1714
|
+
|
|
1727
1715
|
Parameters
|
|
1728
1716
|
----------
|
|
1729
1717
|
ac : array of floats
|
|
1730
1718
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
1731
1719
|
is used as call-by-reference parameter and contains the calculated
|
|
1732
|
-
value after calling this method.
|
|
1720
|
+
value after calling this method.
|
|
1733
1721
|
fr : array of booleans
|
|
1734
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1722
|
+
The entries of this [number of frequencies]-sized array are either
|
|
1735
1723
|
'True' (if the result for this frequency has already been calculated)
|
|
1736
1724
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1737
1725
|
After the calculation at a certain frequency the value will be set
|
|
1738
1726
|
to 'True'
|
|
1739
|
-
|
|
1727
|
+
|
|
1740
1728
|
Returns
|
|
1741
1729
|
-------
|
|
1742
1730
|
This method only returns values through the *ac* and *fr* parameters
|
|
1731
|
+
|
|
1743
1732
|
"""
|
|
1744
1733
|
f = self.freq_data.fftfreq()
|
|
1745
1734
|
gs = self.steer.grid.size
|
|
1746
|
-
|
|
1735
|
+
|
|
1747
1736
|
if self.calcmode == 'full':
|
|
1748
|
-
warn(
|
|
1749
|
-
|
|
1737
|
+
warn(
|
|
1738
|
+
"calcmode = 'full', possibly slow CLEAN performance. Better use 'block' or 'single'.",
|
|
1739
|
+
Warning,
|
|
1740
|
+
stacklevel=2,
|
|
1741
|
+
)
|
|
1750
1742
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
1751
1743
|
for i in self.freq_data.indices:
|
|
1752
1744
|
if not fr[i]:
|
|
1753
1745
|
p.freq = f[i]
|
|
1754
1746
|
dirty = self.beamformer.result[i].copy()
|
|
1755
1747
|
clean = zeros(gs, dtype=dirty.dtype)
|
|
1756
|
-
|
|
1748
|
+
|
|
1757
1749
|
i_iter = 0
|
|
1758
1750
|
flag = True
|
|
1759
1751
|
while flag:
|
|
@@ -1761,98 +1753,105 @@ class BeamformerClean (BeamformerBase):
|
|
|
1761
1753
|
dirty_sum = abs(dirty).sum(0)
|
|
1762
1754
|
next_max = dirty.argmax(0)
|
|
1763
1755
|
p.grid_indices = array([next_max])
|
|
1764
|
-
psf = p.psf.reshape(gs
|
|
1765
|
-
new_amp = self.damp * dirty[next_max]
|
|
1756
|
+
psf = p.psf.reshape(gs)
|
|
1757
|
+
new_amp = self.damp * dirty[next_max] # / psf[next_max]
|
|
1766
1758
|
clean[next_max] += new_amp
|
|
1767
1759
|
dirty -= psf * new_amp
|
|
1768
1760
|
i_iter += 1
|
|
1769
|
-
flag =
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
ac[i] = clean
|
|
1761
|
+
flag = dirty_sum > abs(dirty).sum(0) and i_iter < self.n_iter and max(dirty) > 0
|
|
1762
|
+
|
|
1763
|
+
ac[i] = clean
|
|
1774
1764
|
fr[i] = 1
|
|
1775
1765
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
Covariance Matrix Fitting, see :ref:`Yardibi et al., 2008<Yardibi2008>`.
|
|
1766
|
+
|
|
1767
|
+
class BeamformerCMF(BeamformerBase):
|
|
1768
|
+
"""Covariance Matrix Fitting, see :ref:`Yardibi et al., 2008<Yardibi2008>`.
|
|
1779
1769
|
This is not really a beamformer, but an inverse method.
|
|
1780
1770
|
"""
|
|
1781
1771
|
|
|
1782
|
-
#: Type of fit method to be used ('LassoLars', 'LassoLarsBIC',
|
|
1772
|
+
#: Type of fit method to be used ('LassoLars', 'LassoLarsBIC',
|
|
1783
1773
|
#: 'OMPCV' or 'NNLS', defaults to 'LassoLars').
|
|
1784
|
-
#: These methods are implemented in
|
|
1785
|
-
#: the `scikit-learn <http://scikit-learn.org/stable/user_guide.html>`_
|
|
1774
|
+
#: These methods are implemented in
|
|
1775
|
+
#: the `scikit-learn <http://scikit-learn.org/stable/user_guide.html>`_
|
|
1786
1776
|
#: module.
|
|
1787
|
-
method = Trait(
|
|
1788
|
-
'
|
|
1789
|
-
|
|
1777
|
+
method = Trait(
|
|
1778
|
+
'LassoLars',
|
|
1779
|
+
'LassoLarsBIC',
|
|
1780
|
+
'OMPCV',
|
|
1781
|
+
'NNLS',
|
|
1782
|
+
'fmin_l_bfgs_b',
|
|
1783
|
+
'Split_Bregman',
|
|
1784
|
+
'FISTA',
|
|
1785
|
+
desc='fit method used',
|
|
1786
|
+
)
|
|
1787
|
+
|
|
1790
1788
|
#: Weight factor for LassoLars method,
|
|
1791
1789
|
#: defaults to 0.0.
|
|
1792
1790
|
#: (Use values in the order of 10^⁻9 for good results.)
|
|
1793
|
-
alpha = Range(0.0, 1.0, 0.0,
|
|
1794
|
-
|
|
1795
|
-
|
|
1791
|
+
alpha = Range(0.0, 1.0, 0.0, desc='Lasso weight factor')
|
|
1792
|
+
|
|
1796
1793
|
#: Maximum number of iterations,
|
|
1797
1794
|
#: tradeoff between speed and precision;
|
|
1798
1795
|
#: defaults to 500
|
|
1799
|
-
max_iter = Int(500,
|
|
1800
|
-
desc="maximum number of iterations")
|
|
1796
|
+
max_iter = Int(500, desc='maximum number of iterations')
|
|
1801
1797
|
|
|
1802
|
-
|
|
1803
|
-
#:
|
|
1804
|
-
#: Values are converted back before returning.
|
|
1798
|
+
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
1799
|
+
#: Values are converted back before returning.
|
|
1805
1800
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
1806
1801
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
1807
|
-
unit_mult = Float(1e9,
|
|
1808
|
-
|
|
1809
|
-
|
|
1802
|
+
unit_mult = Float(1e9, desc='unit multiplier')
|
|
1803
|
+
|
|
1810
1804
|
#: If True, shows the status of the PyLops solver. Only relevant in case of FISTA or Split_Bregman
|
|
1811
|
-
show = Bool(False,
|
|
1812
|
-
desc = "show output of PyLops solvers")
|
|
1813
|
-
|
|
1805
|
+
show = Bool(False, desc='show output of PyLops solvers')
|
|
1814
1806
|
|
|
1815
1807
|
# internal identifier
|
|
1816
|
-
digest = Property(
|
|
1817
|
-
depends_on
|
|
1818
|
-
|
|
1808
|
+
digest = Property(
|
|
1809
|
+
depends_on=['freq_data.digest', 'alpha', 'method', 'max_iter', 'unit_mult', 'r_diag', 'steer.inv_digest'],
|
|
1810
|
+
)
|
|
1819
1811
|
|
|
1820
1812
|
@cached_property
|
|
1821
|
-
def _get_digest(
|
|
1822
|
-
return digest(
|
|
1823
|
-
|
|
1813
|
+
def _get_digest(self):
|
|
1814
|
+
return digest(self)
|
|
1815
|
+
|
|
1816
|
+
@on_trait_change('method')
|
|
1817
|
+
def _validate(self):
|
|
1818
|
+
if self.method in ['FISTA', 'Split_Bregman'] and not config.have_pylops:
|
|
1819
|
+
msg = (
|
|
1820
|
+
'Cannot import Pylops package. No Pylops installed.'
|
|
1821
|
+
f'Solver for {self.method} in BeamformerCMF not available.'
|
|
1822
|
+
)
|
|
1823
|
+
raise ImportError(msg)
|
|
1824
1824
|
|
|
1825
1825
|
def calc(self, ac, fr):
|
|
1826
|
-
"""
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
This is an internal helper function that is automatically called when
|
|
1826
|
+
"""Calculates the CMF result for the frequencies defined by :attr:`freq_data`.
|
|
1827
|
+
|
|
1828
|
+
This is an internal helper function that is automatically called when
|
|
1830
1829
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
1831
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
1832
|
-
|
|
1830
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
1831
|
+
|
|
1833
1832
|
Parameters
|
|
1834
1833
|
----------
|
|
1835
1834
|
ac : array of floats
|
|
1836
1835
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
1837
1836
|
is used as call-by-reference parameter and contains the calculated
|
|
1838
|
-
value after calling this method.
|
|
1837
|
+
value after calling this method.
|
|
1839
1838
|
fr : array of booleans
|
|
1840
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1839
|
+
The entries of this [number of frequencies]-sized array are either
|
|
1841
1840
|
'True' (if the result for this frequency has already been calculated)
|
|
1842
1841
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1843
1842
|
After the calculation at a certain frequency the value will be set
|
|
1844
1843
|
to 'True'
|
|
1845
|
-
|
|
1844
|
+
|
|
1846
1845
|
Returns
|
|
1847
1846
|
-------
|
|
1848
1847
|
This method only returns values through the *ac* and *fr* parameters
|
|
1848
|
+
|
|
1849
1849
|
"""
|
|
1850
|
-
|
|
1850
|
+
|
|
1851
1851
|
# function to repack complex matrices to deal with them in real number space
|
|
1852
1852
|
def realify(M):
|
|
1853
|
-
return vstack([M.real,M.imag])
|
|
1853
|
+
return vstack([M.real, M.imag])
|
|
1854
1854
|
|
|
1855
|
-
|
|
1856
1855
|
# prepare calculation
|
|
1857
1856
|
i = self.freq_data.indices
|
|
1858
1857
|
f = self.freq_data.fftfreq()
|
|
@@ -1860,235 +1859,254 @@ class BeamformerCMF ( BeamformerBase ):
|
|
|
1860
1859
|
numpoints = self.steer.grid.size
|
|
1861
1860
|
unit = self.unit_mult
|
|
1862
1861
|
|
|
1863
|
-
for i in self.freq_data.indices:
|
|
1862
|
+
for i in self.freq_data.indices:
|
|
1864
1863
|
if not fr[i]:
|
|
1865
|
-
csm = array(self.freq_data.csm[i], dtype='complex128',copy=1)
|
|
1864
|
+
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
1866
1865
|
|
|
1867
1866
|
h = self.steer.transfer(f[i]).T
|
|
1868
|
-
|
|
1867
|
+
|
|
1869
1868
|
# reduced Kronecker product (only where solution matrix != 0)
|
|
1870
|
-
Bc = (
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
Ac = Bc.reshape(nc*nc,numpoints)
|
|
1874
|
-
|
|
1869
|
+
Bc = (h[:, :, newaxis] * h.conjugate().T[newaxis, :, :]).transpose(2, 0, 1)
|
|
1870
|
+
Ac = Bc.reshape(nc * nc, numpoints)
|
|
1871
|
+
|
|
1875
1872
|
# get indices for upper triangular matrices (use tril b/c transposed)
|
|
1876
|
-
ind = reshape(tril(ones((nc,nc))), (nc*nc,)) > 0
|
|
1877
|
-
|
|
1878
|
-
ind_im0 = (reshape(eye(nc),(nc*nc,)) == 0)[ind]
|
|
1873
|
+
ind = reshape(tril(ones((nc, nc))), (nc * nc,)) > 0
|
|
1874
|
+
|
|
1875
|
+
ind_im0 = (reshape(eye(nc), (nc * nc,)) == 0)[ind]
|
|
1879
1876
|
if self.r_diag:
|
|
1880
1877
|
# omit main diagonal for noise reduction
|
|
1881
1878
|
ind_reim = hstack([ind_im0, ind_im0])
|
|
1882
1879
|
else:
|
|
1883
1880
|
# take all real parts -- also main diagonal
|
|
1884
|
-
ind_reim = hstack([ones(size(ind_im0)
|
|
1885
|
-
ind_reim[0]=True
|
|
1881
|
+
ind_reim = hstack([ones(size(ind_im0)) > 0, ind_im0])
|
|
1882
|
+
ind_reim[0] = True # why this ?
|
|
1886
1883
|
|
|
1887
|
-
A = realify(
|
|
1884
|
+
A = realify(Ac[ind, :])[ind_reim, :]
|
|
1888
1885
|
# use csm.T for column stacking reshape!
|
|
1889
|
-
R = realify(
|
|
1886
|
+
R = realify(reshape(csm.T, (nc * nc, 1))[ind, :])[ind_reim, :] * unit
|
|
1890
1887
|
# choose method
|
|
1891
1888
|
if self.method == 'LassoLars':
|
|
1892
|
-
model = LassoLars(alpha
|
|
1893
|
-
max_iter = self.max_iter,
|
|
1894
|
-
normalize=False)
|
|
1889
|
+
model = LassoLars(alpha=self.alpha * unit, max_iter=self.max_iter, **sklearn_ndict)
|
|
1895
1890
|
elif self.method == 'LassoLarsBIC':
|
|
1896
|
-
model = LassoLarsIC(criterion
|
|
1897
|
-
max_iter = self.max_iter,
|
|
1898
|
-
normalize=False,)
|
|
1891
|
+
model = LassoLarsIC(criterion='bic', max_iter=self.max_iter, **sklearn_ndict)
|
|
1899
1892
|
elif self.method == 'OMPCV':
|
|
1900
|
-
model = OrthogonalMatchingPursuitCV(
|
|
1893
|
+
model = OrthogonalMatchingPursuitCV(**sklearn_ndict)
|
|
1901
1894
|
elif self.method == 'NNLS':
|
|
1902
|
-
model = LinearRegression(
|
|
1903
|
-
|
|
1904
|
-
if self.method == 'Split_Bregman' and
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1895
|
+
model = LinearRegression(positive=True)
|
|
1896
|
+
|
|
1897
|
+
if self.method == 'Split_Bregman' and config.have_pylops:
|
|
1898
|
+
from pylops import Identity, MatrixMult, SplitBregman
|
|
1899
|
+
|
|
1900
|
+
Oop = MatrixMult(A) # tranfer operator
|
|
1901
|
+
Iop = self.alpha * Identity(numpoints) # regularisation
|
|
1902
|
+
ac[i], iterations = SplitBregman(
|
|
1903
|
+
Oop,
|
|
1904
|
+
[Iop],
|
|
1905
|
+
R[:, 0],
|
|
1906
|
+
niter_outer=self.max_iter,
|
|
1907
|
+
niter_inner=5,
|
|
1908
|
+
RegsL2=None,
|
|
1909
|
+
dataregsL2=None,
|
|
1910
|
+
mu=1.0,
|
|
1911
|
+
epsRL1s=[1],
|
|
1912
|
+
tol=1e-10,
|
|
1913
|
+
tau=1.0,
|
|
1914
|
+
show=self.show,
|
|
1915
|
+
)
|
|
1912
1916
|
ac[i] /= unit
|
|
1913
|
-
|
|
1914
|
-
elif self.method == 'FISTA' and
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1917
|
+
|
|
1918
|
+
elif self.method == 'FISTA' and config.have_pylops:
|
|
1919
|
+
from pylops import FISTA, MatrixMult
|
|
1920
|
+
|
|
1921
|
+
Oop = MatrixMult(A) # tranfer operator
|
|
1922
|
+
ac[i], iterations = FISTA(
|
|
1923
|
+
Op=Oop,
|
|
1924
|
+
data=R[:, 0],
|
|
1925
|
+
niter=self.max_iter,
|
|
1926
|
+
eps=self.alpha,
|
|
1927
|
+
alpha=None,
|
|
1928
|
+
eigsiter=None,
|
|
1929
|
+
eigstol=0,
|
|
1930
|
+
tol=1e-10,
|
|
1931
|
+
show=self.show,
|
|
1932
|
+
)
|
|
1920
1933
|
ac[i] /= unit
|
|
1921
|
-
elif self.method == 'FISTA' or self.method == 'Split_Bregman' and not PYLOPS_TRUE :
|
|
1922
|
-
raise Exception(f'No Pylops installed. Solver for {self.method} in BeamformerCMF not available.')
|
|
1923
1934
|
elif self.method == 'fmin_l_bfgs_b':
|
|
1924
|
-
#function to minimize
|
|
1935
|
+
# function to minimize
|
|
1925
1936
|
def function(x):
|
|
1926
|
-
#function
|
|
1927
|
-
func = x.T@A.T@A@x - 2*R.T@A@x + R.T@R
|
|
1928
|
-
#derivitaive
|
|
1929
|
-
der = 2*A.T@A@x.T[:, newaxis] - 2*A.T@R
|
|
1930
|
-
return
|
|
1931
|
-
|
|
1937
|
+
# function
|
|
1938
|
+
func = x.T @ A.T @ A @ x - 2 * R.T @ A @ x + R.T @ R
|
|
1939
|
+
# derivitaive
|
|
1940
|
+
der = 2 * A.T @ A @ x.T[:, newaxis] - 2 * A.T @ R
|
|
1941
|
+
return func[0].T, der[:, 0]
|
|
1942
|
+
|
|
1932
1943
|
# initial guess
|
|
1933
|
-
x0 = ones([numpoints])
|
|
1934
|
-
#boundarys - set to non negative
|
|
1935
|
-
boundarys = tile((0, +inf), (len(x0),1))
|
|
1936
|
-
|
|
1937
|
-
#optimize
|
|
1938
|
-
ac[i], yval, dicts =
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
+
x0 = ones([numpoints])
|
|
1945
|
+
# boundarys - set to non negative
|
|
1946
|
+
boundarys = tile((0, +inf), (len(x0), 1))
|
|
1947
|
+
|
|
1948
|
+
# optimize
|
|
1949
|
+
ac[i], yval, dicts = fmin_l_bfgs_b(
|
|
1950
|
+
function,
|
|
1951
|
+
x0,
|
|
1952
|
+
fprime=None,
|
|
1953
|
+
args=(),
|
|
1954
|
+
approx_grad=0,
|
|
1955
|
+
bounds=boundarys,
|
|
1956
|
+
m=10,
|
|
1957
|
+
factr=10000000.0,
|
|
1958
|
+
pgtol=1e-05,
|
|
1959
|
+
epsilon=1e-08,
|
|
1960
|
+
iprint=-1,
|
|
1961
|
+
maxfun=15000,
|
|
1962
|
+
maxiter=self.max_iter,
|
|
1963
|
+
disp=None,
|
|
1964
|
+
callback=None,
|
|
1965
|
+
maxls=20,
|
|
1966
|
+
)
|
|
1967
|
+
|
|
1944
1968
|
ac[i] /= unit
|
|
1945
1969
|
else:
|
|
1946
1970
|
# from sklearn 1.2, normalize=True does not work the same way anymore and the pipeline
|
|
1947
|
-
# approach with StandardScaler does scale in a different way, thus we monkeypatch the
|
|
1971
|
+
# approach with StandardScaler does scale in a different way, thus we monkeypatch the
|
|
1948
1972
|
# code and normalize ourselves to make results the same over different sklearn versions
|
|
1949
1973
|
norms = norm(A, axis=0)
|
|
1950
1974
|
# get rid of annoying sklearn warnings that appear for sklearn<1.2 despite any settings
|
|
1951
1975
|
with warnings.catch_warnings():
|
|
1952
|
-
warnings.simplefilter(
|
|
1976
|
+
warnings.simplefilter('ignore', category=FutureWarning)
|
|
1953
1977
|
# normalized A
|
|
1954
|
-
model.fit(A/norms,R[:,0])
|
|
1978
|
+
model.fit(A / norms, R[:, 0])
|
|
1955
1979
|
# recover normalization in the coef's
|
|
1956
1980
|
ac[i] = model.coef_[:] / norms / unit
|
|
1957
1981
|
fr[i] = 1
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
1982
|
|
|
1961
1983
|
|
|
1962
|
-
class BeamformerSODIX(
|
|
1963
|
-
"""
|
|
1964
|
-
|
|
1965
|
-
Schallabstrahlung von Turbofantriebwerken, 2017. and
|
|
1984
|
+
class BeamformerSODIX(BeamformerBase):
|
|
1985
|
+
"""SODIX, see Funke, Ein Mikrofonarray-Verfahren zur Untersuchung der
|
|
1986
|
+
Schallabstrahlung von Turbofantriebwerken, 2017. and
|
|
1966
1987
|
Oertwig, Advancements in the source localization method SODIX and
|
|
1967
|
-
application to short cowl engine data, 2019
|
|
1968
|
-
|
|
1988
|
+
application to short cowl engine data, 2019.
|
|
1989
|
+
|
|
1969
1990
|
Source directivity modeling in the cross-spectral matrix
|
|
1970
1991
|
"""
|
|
1992
|
+
|
|
1971
1993
|
#: Type of fit method to be used ('fmin_l_bfgs_b').
|
|
1972
|
-
#: These methods are implemented in
|
|
1994
|
+
#: These methods are implemented in
|
|
1973
1995
|
#: the scipy module.
|
|
1974
|
-
method = Trait('fmin_l_bfgs_b', desc=
|
|
1975
|
-
|
|
1996
|
+
method = Trait('fmin_l_bfgs_b', desc='fit method used')
|
|
1997
|
+
|
|
1976
1998
|
#: Maximum number of iterations,
|
|
1977
1999
|
#: tradeoff between speed and precision;
|
|
1978
2000
|
#: defaults to 200
|
|
1979
|
-
max_iter = Int(200,
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
#: Norm to consider for the regularization
|
|
2001
|
+
max_iter = Int(200, desc='maximum number of iterations')
|
|
2002
|
+
|
|
2003
|
+
#: Norm to consider for the regularization
|
|
1983
2004
|
#: defaults to L-1 Norm
|
|
1984
|
-
pnorm= Float(1,desc=
|
|
1985
|
-
|
|
2005
|
+
pnorm = Float(1, desc='Norm for regularization')
|
|
2006
|
+
|
|
1986
2007
|
#: Weight factor for regularization,
|
|
1987
2008
|
#: defaults to 0.0.
|
|
1988
|
-
alpha = Range(0.0, 1.0, 0.0,
|
|
1989
|
-
desc="regularization factor")
|
|
2009
|
+
alpha = Range(0.0, 1.0, 0.0, desc='regularization factor')
|
|
1990
2010
|
|
|
1991
|
-
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
1992
|
-
#: Values are converted back before returning.
|
|
2011
|
+
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
2012
|
+
#: Values are converted back before returning.
|
|
1993
2013
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
1994
2014
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
1995
|
-
unit_mult = Float(1e9,
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
#: The beamforming result as squared sound pressure values
|
|
2015
|
+
unit_mult = Float(1e9, desc='unit multiplier')
|
|
2016
|
+
|
|
2017
|
+
#: The beamforming result as squared sound pressure values
|
|
1999
2018
|
#: at all grid point locations (readonly).
|
|
2000
2019
|
#: Returns a (number of frequencies, number of gridpoints) array of floats.
|
|
2001
|
-
sodix_result = Property(
|
|
2002
|
-
desc="beamforming result")
|
|
2020
|
+
sodix_result = Property(desc='beamforming result')
|
|
2003
2021
|
|
|
2004
2022
|
# internal identifier
|
|
2005
|
-
digest = Property(
|
|
2006
|
-
depends_on
|
|
2007
|
-
|
|
2023
|
+
digest = Property(
|
|
2024
|
+
depends_on=['freq_data.digest', 'alpha', 'method', 'max_iter', 'unit_mult', 'r_diag', 'steer.inv_digest'],
|
|
2025
|
+
)
|
|
2008
2026
|
|
|
2009
2027
|
@cached_property
|
|
2010
|
-
def _get_digest(
|
|
2011
|
-
return digest(
|
|
2012
|
-
|
|
2013
|
-
def _get_filecache(
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
exist and global caching mode is 'readonly'.
|
|
2018
|
-
"""
|
|
2019
|
-
H5cache.get_cache_file( self, self.freq_data.basename )
|
|
2020
|
-
if not self.h5f:
|
|
2021
|
-
return (None, None)# only happens in case of global caching readonly
|
|
2022
|
-
|
|
2023
|
-
nodename = self.__class__.__name__ + self.digest
|
|
2024
|
-
if config.global_caching == 'overwrite' and self.h5f.is_cached(nodename):
|
|
2025
|
-
self.h5f.remove_data(nodename) # remove old data before writing in overwrite mode
|
|
2026
|
-
|
|
2027
|
-
if not self.h5f.is_cached(nodename):
|
|
2028
|
-
if config.global_caching == 'readonly':
|
|
2029
|
-
return (None, None)
|
|
2030
|
-
else:
|
|
2031
|
-
# print("initialize data.")
|
|
2032
|
-
numfreq = self.freq_data.fftfreq().shape[0]# block_size/2 + 1steer_obj
|
|
2033
|
-
group = self.h5f.create_new_group(nodename)
|
|
2034
|
-
self.h5f.create_compressible_array('result',
|
|
2035
|
-
(numfreq, self.steer.grid.size*self.steer.mics.num_mics),
|
|
2036
|
-
self.precision,
|
|
2037
|
-
group)
|
|
2038
|
-
self.h5f.create_compressible_array('freqs',
|
|
2039
|
-
(numfreq, ),
|
|
2040
|
-
'int8',#'bool',
|
|
2041
|
-
group)
|
|
2042
|
-
ac = self.h5f.get_data_by_reference('result','/'+nodename)
|
|
2043
|
-
fr = self.h5f.get_data_by_reference('freqs','/'+nodename)
|
|
2044
|
-
return (ac,fr)
|
|
2045
|
-
|
|
2046
|
-
@property_depends_on('ext_digest')
|
|
2047
|
-
def _get_sodix_result ( self ):
|
|
2028
|
+
def _get_digest(self):
|
|
2029
|
+
return digest(self)
|
|
2030
|
+
|
|
2031
|
+
def _get_filecache(self):
|
|
2032
|
+
"""Function collects cached results from file depending on
|
|
2033
|
+
global/local caching behaviour. Returns (None, None) if no cachefile/data
|
|
2034
|
+
exist and global caching mode is 'readonly'.
|
|
2048
2035
|
"""
|
|
2049
|
-
|
|
2036
|
+
H5cache.get_cache_file(self, self.freq_data.basename)
|
|
2037
|
+
if not self.h5f:
|
|
2038
|
+
return (None, None) # only happens in case of global caching readonly
|
|
2039
|
+
|
|
2040
|
+
nodename = self.__class__.__name__ + self.digest
|
|
2041
|
+
if config.global_caching == 'overwrite' and self.h5f.is_cached(nodename):
|
|
2042
|
+
self.h5f.remove_data(nodename) # remove old data before writing in overwrite mode
|
|
2043
|
+
|
|
2044
|
+
if not self.h5f.is_cached(nodename):
|
|
2045
|
+
if config.global_caching == 'readonly':
|
|
2046
|
+
return (None, None)
|
|
2047
|
+
# print("initialize data.")
|
|
2048
|
+
numfreq = self.freq_data.fftfreq().shape[0] # block_size/2 + 1steer_obj
|
|
2049
|
+
group = self.h5f.create_new_group(nodename)
|
|
2050
|
+
self.h5f.create_compressible_array(
|
|
2051
|
+
'result',
|
|
2052
|
+
(numfreq, self.steer.grid.size * self.steer.mics.num_mics),
|
|
2053
|
+
self.precision,
|
|
2054
|
+
group,
|
|
2055
|
+
)
|
|
2056
|
+
self.h5f.create_compressible_array(
|
|
2057
|
+
'freqs',
|
|
2058
|
+
(numfreq,),
|
|
2059
|
+
'int8', #'bool',
|
|
2060
|
+
group,
|
|
2061
|
+
)
|
|
2062
|
+
ac = self.h5f.get_data_by_reference('result', '/' + nodename)
|
|
2063
|
+
fr = self.h5f.get_data_by_reference('freqs', '/' + nodename)
|
|
2064
|
+
gpos = None
|
|
2065
|
+
return (ac, fr, gpos)
|
|
2066
|
+
|
|
2067
|
+
@property_depends_on('ext_digest')
|
|
2068
|
+
def _get_sodix_result(self):
|
|
2069
|
+
"""Implements the :attr:`result` getter routine.
|
|
2050
2070
|
The sodix beamforming result is either loaded or calculated.
|
|
2051
2071
|
"""
|
|
2052
2072
|
f = self.freq_data
|
|
2053
|
-
numfreq = f.fftfreq().shape[0]# block_size/2 + 1steer_obj
|
|
2073
|
+
numfreq = f.fftfreq().shape[0] # block_size/2 + 1steer_obj
|
|
2054
2074
|
_digest = ''
|
|
2055
2075
|
while self.digest != _digest:
|
|
2056
2076
|
_digest = self.digest
|
|
2057
2077
|
self._assert_equal_channels()
|
|
2058
|
-
if not (
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
)
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
if config.global_caching == 'readonly':
|
|
2078
|
+
if not ( # if result caching is active
|
|
2079
|
+
config.global_caching == 'none' or (config.global_caching == 'individual' and not self.cached)
|
|
2080
|
+
):
|
|
2081
|
+
(ac, fr, gpos) = self._get_filecache()
|
|
2082
|
+
if ac and fr:
|
|
2083
|
+
if not fr[f.ind_low : f.ind_high].all():
|
|
2084
|
+
if config.global_caching == 'readonly':
|
|
2066
2085
|
(ac, fr) = (ac[:], fr[:])
|
|
2067
|
-
self.calc(ac,fr)
|
|
2086
|
+
self.calc(ac, fr)
|
|
2068
2087
|
self.h5f.flush()
|
|
2069
2088
|
|
|
2070
2089
|
else:
|
|
2071
|
-
ac = zeros((numfreq, self.steer.grid.size*self.steer.mics.num_mics), dtype=self.precision)
|
|
2090
|
+
ac = zeros((numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
2072
2091
|
fr = zeros(numfreq, dtype='int8')
|
|
2073
|
-
self.calc(ac,fr)
|
|
2092
|
+
self.calc(ac, fr)
|
|
2074
2093
|
else:
|
|
2075
|
-
ac = zeros((numfreq, self.steer.grid.size*self.steer.mics.num_mics), dtype=self.precision)
|
|
2094
|
+
ac = zeros((numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
2076
2095
|
fr = zeros(numfreq, dtype='int8')
|
|
2077
|
-
self.calc(ac,fr)
|
|
2096
|
+
self.calc(ac, fr)
|
|
2078
2097
|
return ac
|
|
2079
|
-
|
|
2080
|
-
def synthetic(
|
|
2081
|
-
"""
|
|
2082
|
-
|
|
2083
|
-
|
|
2098
|
+
|
|
2099
|
+
def synthetic(self, f, num=0):
|
|
2100
|
+
"""Evaluates the beamforming result for an arbitrary frequency band.
|
|
2101
|
+
|
|
2084
2102
|
Parameters
|
|
2085
2103
|
----------
|
|
2086
2104
|
f: float
|
|
2087
|
-
Band center frequency.
|
|
2105
|
+
Band center frequency.
|
|
2088
2106
|
num : integer
|
|
2089
2107
|
Controls the width of the frequency bands considered; defaults to
|
|
2090
2108
|
0 (single frequency line).
|
|
2091
|
-
|
|
2109
|
+
|
|
2092
2110
|
=== =====================
|
|
2093
2111
|
num frequency band width
|
|
2094
2112
|
=== =====================
|
|
@@ -2097,116 +2115,127 @@ class BeamformerSODIX( BeamformerBase ):
|
|
|
2097
2115
|
3 third-octave band
|
|
2098
2116
|
n 1/n-octave band
|
|
2099
2117
|
=== =====================
|
|
2100
|
-
|
|
2118
|
+
|
|
2101
2119
|
Returns
|
|
2102
2120
|
-------
|
|
2103
2121
|
array of floats
|
|
2104
|
-
The synthesized frequency band values of the beamforming result at
|
|
2122
|
+
The synthesized frequency band values of the beamforming result at
|
|
2105
2123
|
each grid point and each microphone .
|
|
2106
|
-
Note that the frequency resolution and therefore the bandwidth
|
|
2107
|
-
represented by a single frequency line depends on
|
|
2124
|
+
Note that the frequency resolution and therefore the bandwidth
|
|
2125
|
+
represented by a single frequency line depends on
|
|
2108
2126
|
the :attr:`sampling frequency<acoular.sources.SamplesGenerator.sample_freq>` and conjugate
|
|
2109
2127
|
used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
2128
|
+
|
|
2110
2129
|
"""
|
|
2111
|
-
res = self.sodix_result
|
|
2130
|
+
res = self.sodix_result # trigger calculation
|
|
2112
2131
|
freq = self.freq_data.fftfreq()
|
|
2113
2132
|
if len(freq) == 0:
|
|
2114
2133
|
return None
|
|
2115
|
-
|
|
2134
|
+
|
|
2116
2135
|
indices = self.freq_data.indices
|
|
2117
|
-
|
|
2136
|
+
|
|
2118
2137
|
if num == 0:
|
|
2119
2138
|
# single frequency line
|
|
2120
2139
|
ind = searchsorted(freq, f)
|
|
2121
2140
|
if ind >= len(freq):
|
|
2122
|
-
warn(
|
|
2123
|
-
|
|
2124
|
-
|
|
2141
|
+
warn(
|
|
2142
|
+
'Queried frequency (%g Hz) not in resolved frequency range. Returning zeros.' % f,
|
|
2143
|
+
Warning,
|
|
2144
|
+
stacklevel=2,
|
|
2145
|
+
)
|
|
2125
2146
|
h = zeros_like(res[0])
|
|
2126
2147
|
else:
|
|
2127
2148
|
if freq[ind] != f:
|
|
2128
|
-
warn(
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2149
|
+
warn(
|
|
2150
|
+
f'Queried frequency ({f:g} Hz) not in set of '
|
|
2151
|
+
'discrete FFT sample frequencies. '
|
|
2152
|
+
f'Using frequency {freq[ind]:g} Hz instead.',
|
|
2153
|
+
Warning,
|
|
2154
|
+
stacklevel=2,
|
|
2155
|
+
)
|
|
2156
|
+
if ind not in indices:
|
|
2157
|
+
warn(
|
|
2158
|
+
'Beamforming result may not have been calculated '
|
|
2159
|
+
'for queried frequency. Check '
|
|
2160
|
+
'freq_data.ind_low and freq_data.ind_high!',
|
|
2161
|
+
Warning,
|
|
2162
|
+
stacklevel=2,
|
|
2163
|
+
)
|
|
2137
2164
|
h = res[ind]
|
|
2138
2165
|
else:
|
|
2139
2166
|
# fractional octave band
|
|
2140
|
-
f1 = f*2
|
|
2141
|
-
f2 = f*2
|
|
2167
|
+
f1 = f * 2.0 ** (-0.5 / num)
|
|
2168
|
+
f2 = f * 2.0 ** (+0.5 / num)
|
|
2142
2169
|
ind1 = searchsorted(freq, f1)
|
|
2143
2170
|
ind2 = searchsorted(freq, f2)
|
|
2144
2171
|
if ind1 == ind2:
|
|
2145
|
-
warn(
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2172
|
+
warn(
|
|
2173
|
+
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
2174
|
+
'include any discrete FFT sample frequencies. '
|
|
2175
|
+
'Returning zeros.',
|
|
2176
|
+
Warning,
|
|
2177
|
+
stacklevel=2,
|
|
2178
|
+
)
|
|
2149
2179
|
h = zeros_like(res[0])
|
|
2150
2180
|
else:
|
|
2151
2181
|
h = sum(res[ind1:ind2], 0)
|
|
2152
2182
|
if not ((ind1 in indices) and (ind2 in indices)):
|
|
2153
|
-
warn(
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2183
|
+
warn(
|
|
2184
|
+
'Beamforming result may not have been calculated '
|
|
2185
|
+
'for all queried frequencies. Check '
|
|
2186
|
+
'freq_data.ind_low and freq_data.ind_high!',
|
|
2187
|
+
Warning,
|
|
2188
|
+
stacklevel=2,
|
|
2189
|
+
)
|
|
2190
|
+
return h.reshape([self.steer.grid.size, self.steer.mics.num_mics])
|
|
2159
2191
|
|
|
2160
2192
|
def calc(self, ac, fr):
|
|
2161
|
-
"""
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
This is an internal helper function that is automatically called when
|
|
2193
|
+
"""Calculates the SODIX result for the frequencies defined by :attr:`freq_data`.
|
|
2194
|
+
|
|
2195
|
+
This is an internal helper function that is automatically called when
|
|
2165
2196
|
accessing the beamformer's :attr:`~Beamformer.sodix_result` or calling
|
|
2166
|
-
its :meth:`~BeamformerSODIX.synthetic` method.
|
|
2167
|
-
|
|
2197
|
+
its :meth:`~BeamformerSODIX.synthetic` method.
|
|
2198
|
+
|
|
2168
2199
|
Parameters
|
|
2169
2200
|
----------
|
|
2170
2201
|
ac : array of floats
|
|
2171
2202
|
This array of dimension ([number of frequencies]x[number of gridpoints]x[number of microphones])
|
|
2172
2203
|
is used as call-by-reference parameter and contains the calculated
|
|
2173
|
-
value after calling this method.
|
|
2204
|
+
value after calling this method.
|
|
2174
2205
|
fr : array of booleans
|
|
2175
|
-
The entries of this [number of frequencies]-sized array are either
|
|
2206
|
+
The entries of this [number of frequencies]-sized array are either
|
|
2176
2207
|
'True' (if the result for this frequency has already been calculated)
|
|
2177
2208
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
2178
2209
|
After the calculation at a certain frequency the value will be set
|
|
2179
2210
|
to 'True'
|
|
2180
|
-
|
|
2211
|
+
|
|
2181
2212
|
Returns
|
|
2182
2213
|
-------
|
|
2183
2214
|
This method only returns values through the *ac* and *fr* parameters
|
|
2215
|
+
|
|
2184
2216
|
"""
|
|
2185
|
-
|
|
2186
2217
|
# prepare calculation
|
|
2187
2218
|
i = self.freq_data.indices
|
|
2188
2219
|
f = self.freq_data.fftfreq()
|
|
2189
2220
|
numpoints = self.steer.grid.size
|
|
2190
|
-
#unit = self.unit_mult
|
|
2221
|
+
# unit = self.unit_mult
|
|
2191
2222
|
num_mics = self.steer.mics.num_mics
|
|
2192
2223
|
|
|
2193
|
-
for i in self.freq_data.indices:
|
|
2224
|
+
for i in self.freq_data.indices:
|
|
2194
2225
|
if not fr[i]:
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2226
|
+
# measured csm
|
|
2227
|
+
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
2228
|
+
# transfer function
|
|
2229
|
+
h = self.steer.transfer(f[i]).T
|
|
2230
|
+
|
|
2201
2231
|
if self.method == 'fmin_l_bfgs_b':
|
|
2202
|
-
#function to minimize
|
|
2203
|
-
def function(D):
|
|
2204
|
-
|
|
2205
|
-
Parameters
|
|
2232
|
+
# function to minimize
|
|
2233
|
+
def function(D):
|
|
2234
|
+
"""Parameters
|
|
2206
2235
|
----------
|
|
2207
|
-
D
|
|
2236
|
+
D
|
|
2208
2237
|
[numpoints*num_mics]
|
|
2209
|
-
|
|
2238
|
+
|
|
2210
2239
|
Returns
|
|
2211
2240
|
-------
|
|
2212
2241
|
func - Sodix function to optimize
|
|
@@ -2214,109 +2243,138 @@ class BeamformerSODIX( BeamformerBase ):
|
|
|
2214
2243
|
derdrl - derivitaives in direction of D
|
|
2215
2244
|
[num_mics*numpoints].
|
|
2216
2245
|
|
|
2217
|
-
|
|
2246
|
+
"""
|
|
2218
2247
|
#### the sodix function ####
|
|
2219
|
-
Djm = D.reshape([numpoints,num_mics])
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2248
|
+
Djm = D.reshape([numpoints, num_mics])
|
|
2249
|
+
p = h.T * Djm
|
|
2250
|
+
csm_mod = dot(p.T, p.conj())
|
|
2251
|
+
Q = csm - csm_mod
|
|
2252
|
+
func = sum((absolute(Q)) ** 2)
|
|
2253
|
+
|
|
2254
|
+
# subscripts and operands for numpy einsum and einsum_path
|
|
2255
|
+
subscripts = 'rl,rm,ml->rl'
|
|
2256
|
+
operands = (h.T, h.T.conj() * Djm, Q)
|
|
2257
|
+
es_path = einsum_path(subscripts, *operands, optimize='greedy')[0]
|
|
2258
|
+
|
|
2259
|
+
#### the sodix derivative ####
|
|
2260
|
+
derdrl = einsum(subscripts, *operands, optimize=es_path)
|
|
2261
|
+
derdrl = -4 * real(derdrl)
|
|
2262
|
+
return func, derdrl.ravel()
|
|
2263
|
+
|
|
2264
|
+
##### initial guess ####
|
|
2265
|
+
if all(ac[(i - 1)] == 0):
|
|
2266
|
+
D0 = ones([numpoints, num_mics])
|
|
2232
2267
|
else:
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
#
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2268
|
+
D0 = sqrt(
|
|
2269
|
+
ac[(i - 1)]
|
|
2270
|
+
* real(trace(csm) / trace(array(self.freq_data.csm[i - 1], dtype='complex128', copy=1))),
|
|
2271
|
+
)
|
|
2272
|
+
|
|
2273
|
+
# boundarys - set to non negative [2*(numpoints*num_mics)]
|
|
2274
|
+
boundarys = tile((0, +inf), (numpoints * num_mics, 1))
|
|
2275
|
+
|
|
2276
|
+
# optimize with gradient solver
|
|
2277
|
+
# see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html
|
|
2278
|
+
|
|
2279
|
+
qi = ones([numpoints, num_mics])
|
|
2280
|
+
qi, yval, dicts = fmin_l_bfgs_b(
|
|
2281
|
+
function,
|
|
2282
|
+
D0,
|
|
2283
|
+
fprime=None,
|
|
2284
|
+
args=(),
|
|
2285
|
+
approx_grad=0,
|
|
2286
|
+
bounds=boundarys,
|
|
2287
|
+
factr=100.0,
|
|
2288
|
+
pgtol=1e-12,
|
|
2289
|
+
epsilon=1e-08,
|
|
2290
|
+
iprint=-1,
|
|
2291
|
+
maxfun=1500000,
|
|
2292
|
+
maxiter=self.max_iter,
|
|
2293
|
+
disp=-1,
|
|
2294
|
+
callback=None,
|
|
2295
|
+
maxls=20,
|
|
2296
|
+
)
|
|
2297
|
+
# squared pressure
|
|
2298
|
+
ac[i] = qi**2
|
|
2249
2299
|
else:
|
|
2250
2300
|
pass
|
|
2251
2301
|
fr[i] = 1
|
|
2252
2302
|
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
Beamforming GIB methods with different normalizations,
|
|
2260
|
-
"""
|
|
2261
|
-
|
|
2262
|
-
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
2263
|
-
#: Values are converted back before returning.
|
|
2303
|
+
|
|
2304
|
+
class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
2305
|
+
"""Beamforming GIB methods with different normalizations,."""
|
|
2306
|
+
|
|
2307
|
+
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
2308
|
+
#: Values are converted back before returning.
|
|
2264
2309
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
2265
2310
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
2266
|
-
unit_mult = Float(1e9,
|
|
2267
|
-
desc = "unit multiplier")
|
|
2311
|
+
unit_mult = Float(1e9, desc='unit multiplier')
|
|
2268
2312
|
|
|
2269
2313
|
#: Maximum number of iterations,
|
|
2270
2314
|
#: tradeoff between speed and precision;
|
|
2271
2315
|
#: defaults to 10
|
|
2272
|
-
max_iter = Int(10,
|
|
2273
|
-
desc="maximum number of iterations")
|
|
2316
|
+
max_iter = Int(10, desc='maximum number of iterations')
|
|
2274
2317
|
|
|
2275
|
-
#: Type of fit method to be used ('Suzuki', 'LassoLars', 'LassoLarsCV', 'LassoLarsBIC',
|
|
2318
|
+
#: Type of fit method to be used ('Suzuki', 'LassoLars', 'LassoLarsCV', 'LassoLarsBIC',
|
|
2276
2319
|
#: 'OMPCV' or 'NNLS', defaults to 'Suzuki').
|
|
2277
|
-
#: These methods are implemented in
|
|
2278
|
-
#: the `scikit-learn <http://scikit-learn.org/stable/user_guide.html>`_
|
|
2320
|
+
#: These methods are implemented in
|
|
2321
|
+
#: the `scikit-learn <http://scikit-learn.org/stable/user_guide.html>`_
|
|
2279
2322
|
#: module.
|
|
2280
|
-
method = Trait(
|
|
2281
|
-
'
|
|
2323
|
+
method = Trait(
|
|
2324
|
+
'Suzuki',
|
|
2325
|
+
'InverseIRLS',
|
|
2326
|
+
'LassoLars',
|
|
2327
|
+
'LassoLarsBIC',
|
|
2328
|
+
'LassoLarsCV',
|
|
2329
|
+
'OMPCV',
|
|
2330
|
+
'NNLS',
|
|
2331
|
+
desc='fit method used',
|
|
2332
|
+
)
|
|
2282
2333
|
|
|
2283
2334
|
#: Weight factor for LassoLars method,
|
|
2284
2335
|
#: defaults to 0.0.
|
|
2285
|
-
alpha = Range(0.0, 1.0, 0.0,
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
#: Norm to consider for the regularization in InverseIRLS and Suzuki methods
|
|
2336
|
+
alpha = Range(0.0, 1.0, 0.0, desc='Lasso weight factor')
|
|
2337
|
+
# (use values in the order of 10^⁻9 for good results)
|
|
2338
|
+
|
|
2339
|
+
#: Norm to consider for the regularization in InverseIRLS and Suzuki methods
|
|
2290
2340
|
#: defaults to L-1 Norm
|
|
2291
|
-
pnorm= Float(1,desc=
|
|
2341
|
+
pnorm = Float(1, desc='Norm for regularization')
|
|
2292
2342
|
|
|
2293
2343
|
#: Beta - Fraction of sources maintained after each iteration
|
|
2294
|
-
#: defaults to 0.9
|
|
2295
|
-
beta =
|
|
2296
|
-
|
|
2344
|
+
#: defaults to 0.9
|
|
2345
|
+
beta = Float(0.9, desc='fraction of sources maintained')
|
|
2346
|
+
|
|
2297
2347
|
#: eps - Regularization parameter for Suzuki algorithm
|
|
2298
|
-
#: defaults to 0.05.
|
|
2299
|
-
eps_perc =
|
|
2348
|
+
#: defaults to 0.05.
|
|
2349
|
+
eps_perc = Float(0.05, desc='regularization parameter')
|
|
2300
2350
|
|
|
2301
|
-
# This feature is not fully supported may be changed in the next release
|
|
2351
|
+
# This feature is not fully supported may be changed in the next release
|
|
2302
2352
|
# First eigenvalue to consider. Defaults to 0.
|
|
2303
|
-
m = Int(0,
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2353
|
+
m = Int(0, desc='First eigenvalue to consider')
|
|
2354
|
+
|
|
2307
2355
|
# internal identifier++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
2308
|
-
digest = Property(
|
|
2309
|
-
depends_on
|
|
2310
|
-
'
|
|
2311
|
-
'
|
|
2312
|
-
|
|
2356
|
+
digest = Property(
|
|
2357
|
+
depends_on=[
|
|
2358
|
+
'steer.inv_digest',
|
|
2359
|
+
'freq_data.digest',
|
|
2360
|
+
'alpha',
|
|
2361
|
+
'method',
|
|
2362
|
+
'max_iter',
|
|
2363
|
+
'unit_mult',
|
|
2364
|
+
'eps_perc',
|
|
2365
|
+
'pnorm',
|
|
2366
|
+
'beta',
|
|
2367
|
+
'n',
|
|
2368
|
+
'm',
|
|
2369
|
+
],
|
|
2370
|
+
)
|
|
2313
2371
|
|
|
2314
2372
|
@cached_property
|
|
2315
|
-
def _get_digest(
|
|
2316
|
-
return digest(
|
|
2317
|
-
|
|
2373
|
+
def _get_digest(self):
|
|
2374
|
+
return digest(self)
|
|
2375
|
+
|
|
2318
2376
|
@property_depends_on('n')
|
|
2319
|
-
def _get_na(
|
|
2377
|
+
def _get_na(self):
|
|
2320
2378
|
na = self.n
|
|
2321
2379
|
nm = self.steer.mics.num_mics
|
|
2322
2380
|
if na < 0:
|
|
@@ -2324,115 +2382,120 @@ class BeamformerGIB(BeamformerEig): #BeamformerEig #BeamformerBase
|
|
|
2324
2382
|
return min(nm - 1, na)
|
|
2325
2383
|
|
|
2326
2384
|
def calc(self, ac, fr):
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
This is an internal helper function that is automatically called when
|
|
2385
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2386
|
+
|
|
2387
|
+
This is an internal helper function that is automatically called when
|
|
2332
2388
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
2333
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
2334
|
-
|
|
2389
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
2390
|
+
|
|
2335
2391
|
Parameters
|
|
2336
2392
|
----------
|
|
2337
2393
|
ac : array of floats
|
|
2338
2394
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
2339
2395
|
is used as call-by-reference parameter and contains the calculated
|
|
2340
|
-
value after calling this method.
|
|
2396
|
+
value after calling this method.
|
|
2341
2397
|
fr : array of booleans
|
|
2342
|
-
The entries of this [number of frequencies]-sized array are either
|
|
2398
|
+
The entries of this [number of frequencies]-sized array are either
|
|
2343
2399
|
'True' (if the result for this frequency has already been calculated)
|
|
2344
2400
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
2345
2401
|
After the calculation at a certain frequency the value will be set
|
|
2346
2402
|
to 'True'
|
|
2347
|
-
|
|
2403
|
+
|
|
2348
2404
|
Returns
|
|
2349
2405
|
-------
|
|
2350
2406
|
This method only returns values through the *ac* and *fr* parameters
|
|
2351
2407
|
|
|
2352
|
-
"""
|
|
2408
|
+
"""
|
|
2353
2409
|
# prepare calculation
|
|
2354
2410
|
f = self.freq_data.fftfreq()
|
|
2355
|
-
n = int(self.na)
|
|
2356
|
-
m = int(self.m)
|
|
2357
|
-
numchannels = self.freq_data.numchannels
|
|
2411
|
+
n = int(self.na) # number of eigenvalues
|
|
2412
|
+
m = int(self.m) # number of first eigenvalue
|
|
2413
|
+
numchannels = self.freq_data.numchannels # number of channels
|
|
2358
2414
|
numpoints = self.steer.grid.size
|
|
2359
2415
|
hh = zeros((1, numpoints, numchannels), dtype='D')
|
|
2360
|
-
|
|
2361
|
-
#Generate a cross spectral matrix, and perform the eigenvalue decomposition
|
|
2362
|
-
for i in self.freq_data.indices:
|
|
2416
|
+
|
|
2417
|
+
# Generate a cross spectral matrix, and perform the eigenvalue decomposition
|
|
2418
|
+
for i in self.freq_data.indices:
|
|
2363
2419
|
if not fr[i]:
|
|
2364
|
-
#for monopole and source strenght Q needs to define density
|
|
2365
|
-
#calculate a transfer matrix A
|
|
2420
|
+
# for monopole and source strenght Q needs to define density
|
|
2421
|
+
# calculate a transfer matrix A
|
|
2366
2422
|
hh = self.steer.transfer(f[i])
|
|
2367
|
-
A=hh.T
|
|
2368
|
-
#eigenvalues and vectors
|
|
2369
|
-
csm = array(self.freq_data.csm[i], dtype='complex128',copy=1)
|
|
2370
|
-
eva,eve=eigh(csm)
|
|
2423
|
+
A = hh.T
|
|
2424
|
+
# eigenvalues and vectors
|
|
2425
|
+
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
2426
|
+
eva, eve = eigh(csm)
|
|
2371
2427
|
eva = eva[::-1]
|
|
2372
|
-
eve = eve[:, ::-1]
|
|
2373
|
-
eva[eva < max(eva)/1e12] = 0
|
|
2374
|
-
#init sources
|
|
2375
|
-
qi=zeros([n+m,numpoints], dtype='complex128')
|
|
2376
|
-
#Select the number of coherent modes to be processed referring to the eigenvalue distribution.
|
|
2377
|
-
#for s in arange(n):
|
|
2378
|
-
for s in list(range(m,n+m)):
|
|
2379
|
-
if eva[s] > 0:
|
|
2380
|
-
#Generate the corresponding eigenmodes
|
|
2381
|
-
emode=array(sqrt(eva[s])*eve[:,s], dtype='complex128')
|
|
2428
|
+
eve = eve[:, ::-1]
|
|
2429
|
+
eva[eva < max(eva) / 1e12] = 0 # set small values zo 0, lowers numerical errors in simulated data
|
|
2430
|
+
# init sources
|
|
2431
|
+
qi = zeros([n + m, numpoints], dtype='complex128')
|
|
2432
|
+
# Select the number of coherent modes to be processed referring to the eigenvalue distribution.
|
|
2433
|
+
# for s in arange(n):
|
|
2434
|
+
for s in list(range(m, n + m)):
|
|
2435
|
+
if eva[s] > 0:
|
|
2436
|
+
# Generate the corresponding eigenmodes
|
|
2437
|
+
emode = array(sqrt(eva[s]) * eve[:, s], dtype='complex128')
|
|
2382
2438
|
# choose method for computation
|
|
2383
2439
|
if self.method == 'Suzuki':
|
|
2384
|
-
leftpoints=numpoints
|
|
2385
|
-
locpoints=arange(numpoints)
|
|
2386
|
-
weights=diag(ones(numpoints))
|
|
2387
|
-
epsilon=arange(self.max_iter)
|
|
2388
|
-
for it in arange(self.max_iter):
|
|
2389
|
-
if numchannels<=leftpoints:
|
|
2390
|
-
AWA= dot(dot(A[:,locpoints],weights),A[:,locpoints].conj().T)
|
|
2391
|
-
epsilon[it] = max(absolute(eigvals(AWA)))*self.eps_perc
|
|
2392
|
-
qi[s,locpoints]=dot(
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
locpoints=
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
qi[s
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2440
|
+
leftpoints = numpoints
|
|
2441
|
+
locpoints = arange(numpoints)
|
|
2442
|
+
weights = diag(ones(numpoints))
|
|
2443
|
+
epsilon = arange(self.max_iter)
|
|
2444
|
+
for it in arange(self.max_iter):
|
|
2445
|
+
if numchannels <= leftpoints:
|
|
2446
|
+
AWA = dot(dot(A[:, locpoints], weights), A[:, locpoints].conj().T)
|
|
2447
|
+
epsilon[it] = max(absolute(eigvals(AWA))) * self.eps_perc
|
|
2448
|
+
qi[s, locpoints] = dot(
|
|
2449
|
+
dot(
|
|
2450
|
+
dot(weights, A[:, locpoints].conj().T),
|
|
2451
|
+
inv(AWA + eye(numchannels) * epsilon[it]),
|
|
2452
|
+
),
|
|
2453
|
+
emode,
|
|
2454
|
+
)
|
|
2455
|
+
elif numchannels > leftpoints:
|
|
2456
|
+
AA = dot(A[:, locpoints].conj().T, A[:, locpoints])
|
|
2457
|
+
epsilon[it] = max(absolute(eigvals(AA))) * self.eps_perc
|
|
2458
|
+
qi[s, locpoints] = dot(
|
|
2459
|
+
dot(inv(AA + inv(weights) * epsilon[it]), A[:, locpoints].conj().T),
|
|
2460
|
+
emode,
|
|
2461
|
+
)
|
|
2462
|
+
if self.beta < 1 and it > 1:
|
|
2463
|
+
# Reorder from the greatest to smallest magnitude to define a reduced-point source distribution , and reform a reduced transfer matrix
|
|
2464
|
+
leftpoints = int(round(numpoints * self.beta ** (it + 1)))
|
|
2465
|
+
idx = argsort(abs(qi[s, locpoints]))[::-1]
|
|
2466
|
+
# print(it, leftpoints, locpoints, idx )
|
|
2467
|
+
locpoints = delete(locpoints, [idx[leftpoints::]])
|
|
2468
|
+
qix = zeros([n + m, leftpoints], dtype='complex128')
|
|
2469
|
+
qix[s, :] = qi[s, locpoints]
|
|
2470
|
+
# calc weights for next iteration
|
|
2471
|
+
weights = diag(absolute(qix[s, :]) ** (2 - self.pnorm))
|
|
2472
|
+
else:
|
|
2473
|
+
weights = diag(absolute(qi[s, :]) ** (2 - self.pnorm))
|
|
2474
|
+
|
|
2475
|
+
elif self.method == 'InverseIRLS':
|
|
2476
|
+
weights = eye(numpoints)
|
|
2477
|
+
locpoints = arange(numpoints)
|
|
2478
|
+
for _it in arange(self.max_iter):
|
|
2479
|
+
if numchannels <= numpoints:
|
|
2480
|
+
wtwi = inv(dot(weights.T, weights))
|
|
2481
|
+
aH = A.conj().T
|
|
2482
|
+
qi[s, :] = dot(dot(wtwi, aH), dot(inv(dot(A, dot(wtwi, aH))), emode))
|
|
2483
|
+
weights = diag(absolute(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2484
|
+
weights = weights / sum(absolute(weights))
|
|
2485
|
+
elif numchannels > numpoints:
|
|
2486
|
+
wtw = dot(weights.T, weights)
|
|
2487
|
+
qi[s, :] = dot(dot(inv(dot(dot(A.conj.T, wtw), A)), dot(A.conj().T, wtw)), emode)
|
|
2488
|
+
weights = diag(absolute(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2489
|
+
weights = weights / sum(absolute(weights))
|
|
2425
2490
|
else:
|
|
2426
|
-
locpoints=arange(numpoints)
|
|
2491
|
+
locpoints = arange(numpoints)
|
|
2427
2492
|
unit = self.unit_mult
|
|
2428
|
-
AB = vstack([hstack([A.real
|
|
2429
|
-
R
|
|
2493
|
+
AB = vstack([hstack([A.real, -A.imag]), hstack([A.imag, A.real])])
|
|
2494
|
+
R = hstack([emode.real.T, emode.imag.T]) * unit
|
|
2430
2495
|
if self.method == 'LassoLars':
|
|
2431
|
-
model = LassoLars(alpha=self.alpha * unit,
|
|
2432
|
-
max_iter=self.max_iter)
|
|
2496
|
+
model = LassoLars(alpha=self.alpha * unit, max_iter=self.max_iter)
|
|
2433
2497
|
elif self.method == 'LassoLarsBIC':
|
|
2434
|
-
model = LassoLarsIC(criterion='bic',
|
|
2435
|
-
max_iter=self.max_iter)
|
|
2498
|
+
model = LassoLarsIC(criterion='bic', max_iter=self.max_iter)
|
|
2436
2499
|
elif self.method == 'OMPCV':
|
|
2437
2500
|
model = OrthogonalMatchingPursuitCV()
|
|
2438
2501
|
elif self.method == 'LassoLarsCV':
|
|
@@ -2440,9 +2503,9 @@ class BeamformerGIB(BeamformerEig): #BeamformerEig #BeamformerBase
|
|
|
2440
2503
|
elif self.method == 'NNLS':
|
|
2441
2504
|
model = LinearRegression(positive=True)
|
|
2442
2505
|
model.normalize = False
|
|
2443
|
-
# from sklearn 1.2, normalize=True does not work
|
|
2444
|
-
# the same way anymore and the pipeline approach
|
|
2445
|
-
# with StandardScaler does scale in a different
|
|
2506
|
+
# from sklearn 1.2, normalize=True does not work
|
|
2507
|
+
# the same way anymore and the pipeline approach
|
|
2508
|
+
# with StandardScaler does scale in a different
|
|
2446
2509
|
# way, thus we monkeypatch the code and normalize
|
|
2447
2510
|
# ourselves to make results the same over different
|
|
2448
2511
|
# sklearn versions
|
|
@@ -2450,54 +2513,59 @@ class BeamformerGIB(BeamformerEig): #BeamformerEig #BeamformerBase
|
|
|
2450
2513
|
# get rid of annoying sklearn warnings that appear
|
|
2451
2514
|
# for sklearn<1.2 despite any settings
|
|
2452
2515
|
with warnings.catch_warnings():
|
|
2453
|
-
warnings.simplefilter(
|
|
2454
|
-
category=FutureWarning)
|
|
2516
|
+
warnings.simplefilter('ignore', category=FutureWarning)
|
|
2455
2517
|
# normalized A
|
|
2456
|
-
model.fit(AB/norms,R)
|
|
2518
|
+
model.fit(AB / norms, R)
|
|
2457
2519
|
# recover normalization in the coef's
|
|
2458
|
-
qi_real,qi_imag = hsplit(model.coef_[:]/norms/unit, 2)
|
|
2459
|
-
#print(s,qi.size)
|
|
2460
|
-
qi[s,locpoints] = qi_real+qi_imag*1j
|
|
2520
|
+
qi_real, qi_imag = hsplit(model.coef_[:] / norms / unit, 2)
|
|
2521
|
+
# print(s,qi.size)
|
|
2522
|
+
qi[s, locpoints] = qi_real + qi_imag * 1j
|
|
2461
2523
|
else:
|
|
2462
|
-
warn(
|
|
2463
|
-
|
|
2524
|
+
warn(
|
|
2525
|
+
f'Eigenvalue {s:g} <= 0 for frequency index {i:g}. Will not be calculated!',
|
|
2526
|
+
Warning,
|
|
2527
|
+
stacklevel=2,
|
|
2528
|
+
)
|
|
2529
|
+
# Generate source maps of all selected eigenmodes, and superpose source intensity for each source type.
|
|
2464
2530
|
temp = zeros(numpoints)
|
|
2465
|
-
temp[locpoints] = sum(absolute(qi[:,locpoints])**2,axis=0)
|
|
2531
|
+
temp[locpoints] = sum(absolute(qi[:, locpoints]) ** 2, axis=0)
|
|
2466
2532
|
ac[i] = temp
|
|
2467
|
-
fr[i] = 1
|
|
2533
|
+
fr[i] = 1
|
|
2534
|
+
|
|
2535
|
+
|
|
2536
|
+
class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
2537
|
+
"""Base class for array methods without predefined grid."""
|
|
2468
2538
|
|
|
2469
|
-
class BeamformerAdaptiveGrid(BeamformerBase,Grid):
|
|
2470
|
-
"""
|
|
2471
|
-
Base class for array methods without predefined grid
|
|
2472
|
-
"""
|
|
2473
|
-
|
|
2474
2539
|
# the grid positions live in a shadow trait
|
|
2475
2540
|
_gpos = Any
|
|
2476
2541
|
|
|
2477
|
-
def _get_shape
|
|
2542
|
+
def _get_shape(self):
|
|
2478
2543
|
return (self.size,)
|
|
2479
2544
|
|
|
2480
|
-
def _get_gpos(
|
|
2545
|
+
def _get_gpos(self):
|
|
2481
2546
|
return self._gpos
|
|
2482
2547
|
|
|
2483
2548
|
def integrate(self, sector):
|
|
2484
|
-
"""
|
|
2485
|
-
|
|
2486
|
-
|
|
2549
|
+
"""Integrates result map over a given sector.
|
|
2550
|
+
|
|
2487
2551
|
Parameters
|
|
2488
2552
|
----------
|
|
2489
2553
|
sector: :class:`~acoular.grids.Sector` or derived
|
|
2490
2554
|
Gives the sector over which to integrate
|
|
2491
|
-
|
|
2555
|
+
|
|
2492
2556
|
Returns
|
|
2493
2557
|
-------
|
|
2494
2558
|
array of floats
|
|
2495
2559
|
The spectrum (all calculated frequency bands) for the integrated sector.
|
|
2560
|
+
|
|
2496
2561
|
"""
|
|
2497
2562
|
if not isinstance(sector, Sector):
|
|
2563
|
+
msg = (
|
|
2564
|
+
f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
|
|
2565
|
+
f'instead of type {type(sector)}.'
|
|
2566
|
+
)
|
|
2498
2567
|
raise NotImplementedError(
|
|
2499
|
-
|
|
2500
|
-
f'instead of type {type(sector)}.'
|
|
2568
|
+
msg,
|
|
2501
2569
|
)
|
|
2502
2570
|
|
|
2503
2571
|
ind = self.subdomain(sector)
|
|
@@ -2507,213 +2575,216 @@ class BeamformerAdaptiveGrid(BeamformerBase,Grid):
|
|
|
2507
2575
|
h[i] = r[i][ind].sum()
|
|
2508
2576
|
return h
|
|
2509
2577
|
|
|
2578
|
+
|
|
2510
2579
|
class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
2511
|
-
"""
|
|
2512
|
-
Orthogonal beamforming without predefined grid
|
|
2513
|
-
"""
|
|
2580
|
+
"""Orthogonal beamforming without predefined grid."""
|
|
2514
2581
|
|
|
2515
2582
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
2516
2583
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
2517
|
-
eva_list = CArray(dtype=int,
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
#: Number of components to consider, defaults to 1. If set,
|
|
2584
|
+
eva_list = CArray(dtype=int, desc='components')
|
|
2585
|
+
|
|
2586
|
+
#: Number of components to consider, defaults to 1. If set,
|
|
2521
2587
|
#: :attr:`eva_list` will contain
|
|
2522
|
-
#: the indices of the n largest eigenvalues. Setting :attr:`eva_list`
|
|
2588
|
+
#: the indices of the n largest eigenvalues. Setting :attr:`eva_list`
|
|
2523
2589
|
#: afterwards will override this value.
|
|
2524
2590
|
n = Int(1)
|
|
2525
2591
|
|
|
2526
2592
|
#: Geometrical bounds of the search domain to consider.
|
|
2527
|
-
#: :attr:`bound` ist a list that contains exactly three tuple of
|
|
2528
|
-
#: (min,max) for each of the coordinates x, y, z.
|
|
2593
|
+
#: :attr:`bound` ist a list that contains exactly three tuple of
|
|
2594
|
+
#: (min,max) for each of the coordinates x, y, z.
|
|
2529
2595
|
#: Defaults to [(-1.,1.),(-1.,1.),(0.01,1.)]
|
|
2530
|
-
bounds = List(
|
|
2531
|
-
value = [(-1.,1.),(-1.,1.),(0.01,1.)])
|
|
2596
|
+
bounds = List(Tuple(Float, Float), minlen=3, maxlen=3, value=[(-1.0, 1.0), (-1.0, 1.0), (0.01, 1.0)])
|
|
2532
2597
|
|
|
2533
|
-
#: options dictionary for the SHGO solver, see
|
|
2598
|
+
#: options dictionary for the SHGO solver, see
|
|
2534
2599
|
#: `scipy docs <https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.shgo.html>`_.
|
|
2535
|
-
#: Default is Sobol sampling Nelder-Mead local minimizer, 256 initial sampling points
|
|
2600
|
+
#: Default is Sobol sampling Nelder-Mead local minimizer, 256 initial sampling points
|
|
2536
2601
|
#: and 1 iteration
|
|
2537
2602
|
shgo = Dict
|
|
2538
2603
|
|
|
2539
2604
|
# internal identifier
|
|
2540
|
-
digest = Property(
|
|
2541
|
-
depends_on
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2605
|
+
digest = Property(
|
|
2606
|
+
depends_on=['freq_data.digest', '_steer_obj.digest', 'r_diag', 'eva_list', 'bounds', 'shgo'],
|
|
2607
|
+
)
|
|
2608
|
+
|
|
2545
2609
|
@cached_property
|
|
2546
|
-
def _get_digest(
|
|
2547
|
-
return digest(
|
|
2610
|
+
def _get_digest(self):
|
|
2611
|
+
return digest(self)
|
|
2548
2612
|
|
|
2549
2613
|
@on_trait_change('n')
|
|
2550
2614
|
def set_eva_list(self):
|
|
2551
|
-
"""
|
|
2552
|
-
self.eva_list = arange(-1, -1-self.n, -1)
|
|
2615
|
+
"""Sets the list of eigenvalues to consider."""
|
|
2616
|
+
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
2553
2617
|
|
|
2554
2618
|
@on_trait_change('eva_list')
|
|
2555
2619
|
def set_n(self):
|
|
2556
|
-
"""
|
|
2620
|
+
"""Sets the list of eigenvalues to consider."""
|
|
2557
2621
|
self.n = self.eva_list.shape[0]
|
|
2558
|
-
|
|
2622
|
+
|
|
2559
2623
|
@property_depends_on('n')
|
|
2560
|
-
def _get_size
|
|
2561
|
-
return self.n*self.freq_data.fftfreq().shape[0]
|
|
2624
|
+
def _get_size(self):
|
|
2625
|
+
return self.n * self.freq_data.fftfreq().shape[0]
|
|
2562
2626
|
|
|
2563
2627
|
def calc(self, ac, fr):
|
|
2564
|
-
"""
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
This is an internal helper function that is automatically called when
|
|
2628
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2629
|
+
|
|
2630
|
+
This is an internal helper function that is automatically called when
|
|
2568
2631
|
accessing the beamformer's :attr:`~BeamformerBase.result` or calling
|
|
2569
|
-
its :meth:`~BeamformerBase.synthetic` method.
|
|
2570
|
-
|
|
2632
|
+
its :meth:`~BeamformerBase.synthetic` method.
|
|
2633
|
+
|
|
2571
2634
|
Parameters
|
|
2572
2635
|
----------
|
|
2573
2636
|
ac : array of floats
|
|
2574
2637
|
This array of dimension ([number of frequencies]x[number of gridpoints])
|
|
2575
2638
|
is used as call-by-reference parameter and contains the calculated
|
|
2576
|
-
value after calling this method.
|
|
2639
|
+
value after calling this method.
|
|
2577
2640
|
fr : array of booleans
|
|
2578
|
-
The entries of this [number of frequencies]-sized array are either
|
|
2641
|
+
The entries of this [number of frequencies]-sized array are either
|
|
2579
2642
|
'True' (if the result for this frequency has already been calculated)
|
|
2580
2643
|
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
2581
2644
|
After the calculation at a certain frequency the value will be set
|
|
2582
2645
|
to 'True'
|
|
2583
|
-
|
|
2646
|
+
|
|
2584
2647
|
Returns
|
|
2585
2648
|
-------
|
|
2586
2649
|
This method only returns values through the *ac* and *fr* parameters
|
|
2650
|
+
|
|
2587
2651
|
"""
|
|
2588
2652
|
f = self.freq_data.fftfreq()
|
|
2589
2653
|
numchannels = self.freq_data.numchannels
|
|
2590
|
-
# eigenvalue number list in standard form from largest to smallest
|
|
2654
|
+
# eigenvalue number list in standard form from largest to smallest
|
|
2591
2655
|
eva_list = unique(self.eva_list % self.steer.mics.num_mics)[::-1]
|
|
2592
2656
|
steer_type = self.steer.steer_type
|
|
2593
2657
|
if steer_type == 'custom':
|
|
2594
|
-
|
|
2658
|
+
msg = 'custom steer_type is not implemented'
|
|
2659
|
+
raise NotImplementedError(msg)
|
|
2595
2660
|
mpos = self.steer.mics.mpos
|
|
2596
2661
|
env = self.steer.env
|
|
2597
|
-
shgo_opts = {
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2662
|
+
shgo_opts = {
|
|
2663
|
+
'n': 256,
|
|
2664
|
+
'iters': 1,
|
|
2665
|
+
'sampling_method': 'sobol',
|
|
2666
|
+
'options': {'local_iter': 1},
|
|
2667
|
+
'minimizer_kwargs': {'method': 'Nelder-Mead'},
|
|
2668
|
+
}
|
|
2601
2669
|
shgo_opts.update(self.shgo)
|
|
2602
2670
|
roi = []
|
|
2603
2671
|
for x in self.bounds[0]:
|
|
2604
2672
|
for y in self.bounds[1]:
|
|
2605
2673
|
for z in self.bounds[2]:
|
|
2606
|
-
roi.append((x,y,z))
|
|
2674
|
+
roi.append((x, y, z))
|
|
2607
2675
|
self.steer.env.roi = array(roi).T
|
|
2608
|
-
bmin = array(tuple(map(min,self.bounds)))
|
|
2609
|
-
bmax = array(tuple(map(max,self.bounds)))
|
|
2676
|
+
bmin = array(tuple(map(min, self.bounds)))
|
|
2677
|
+
bmax = array(tuple(map(max, self.bounds)))
|
|
2610
2678
|
for i in self.freq_data.indices:
|
|
2611
2679
|
if not fr[i]:
|
|
2612
2680
|
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
2613
2681
|
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
2614
|
-
k = 2*pi*f[i]/env.c
|
|
2615
|
-
for j,n in enumerate(eva_list):
|
|
2616
|
-
#print(f[i],n)
|
|
2682
|
+
k = 2 * pi * f[i] / env.c
|
|
2683
|
+
for j, n in enumerate(eva_list):
|
|
2684
|
+
# print(f[i],n)
|
|
2617
2685
|
|
|
2618
2686
|
def func(xy):
|
|
2619
2687
|
# function to minimize globally
|
|
2620
|
-
xy = clip(xy,bmin,bmax)
|
|
2621
|
-
r0 = env._r(xy[:,newaxis])
|
|
2622
|
-
rm = env._r(xy[:,newaxis],mpos)
|
|
2623
|
-
return -beamformerFreq(steer_type,
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
(r0, rm, k),
|
|
2627
|
-
(ones(1), eve[:,n:n+1]))[0][0]
|
|
2688
|
+
xy = clip(xy, bmin, bmax)
|
|
2689
|
+
r0 = env._r(xy[:, newaxis])
|
|
2690
|
+
rm = env._r(xy[:, newaxis], mpos)
|
|
2691
|
+
return -beamformerFreq(steer_type, self.r_diag, 1.0, (r0, rm, k), (ones(1), eve[:, n : n + 1]))[
|
|
2692
|
+
0
|
|
2693
|
+
][0] # noqa: B023
|
|
2628
2694
|
|
|
2629
2695
|
# simplical global homotopy optimizer
|
|
2630
|
-
oR = shgo(func,self.bounds
|
|
2696
|
+
oR = shgo(func, self.bounds, **shgo_opts)
|
|
2631
2697
|
# index in grid
|
|
2632
|
-
ind = i*self.n+j
|
|
2698
|
+
ind = i * self.n + j
|
|
2633
2699
|
# store result for position
|
|
2634
|
-
self._gpos[:,ind] = oR['x']
|
|
2700
|
+
self._gpos[:, ind] = oR['x']
|
|
2635
2701
|
# store result for level
|
|
2636
|
-
ac[i,ind] = eva[n]/numchannels
|
|
2637
|
-
#print(oR['x'],eva[n]/numchannels,oR)
|
|
2702
|
+
ac[i, ind] = eva[n] / numchannels
|
|
2703
|
+
# print(oR['x'],eva[n]/numchannels,oR)
|
|
2638
2704
|
fr[i] = 1
|
|
2639
2705
|
|
|
2640
2706
|
|
|
2641
|
-
def L_p
|
|
2642
|
-
"""
|
|
2643
|
-
|
|
2644
|
-
|
|
2707
|
+
def L_p(x):
|
|
2708
|
+
r"""Calculates the sound pressure level from the squared sound pressure.
|
|
2709
|
+
|
|
2645
2710
|
:math:`L_p = 10 \lg ( x / 4\cdot 10^{-10})`
|
|
2646
|
-
|
|
2711
|
+
|
|
2647
2712
|
Parameters
|
|
2648
2713
|
----------
|
|
2649
2714
|
x: array of floats
|
|
2650
2715
|
The squared sound pressure values
|
|
2651
|
-
|
|
2716
|
+
|
|
2652
2717
|
Returns
|
|
2653
2718
|
-------
|
|
2654
2719
|
array of floats
|
|
2655
|
-
The corresponding sound pressure levels in dB.
|
|
2720
|
+
The corresponding sound pressure levels in dB.
|
|
2656
2721
|
If `x<0`, -350.0 dB is returned.
|
|
2722
|
+
|
|
2657
2723
|
"""
|
|
2658
2724
|
# new version to prevent division by zero warning for float32 arguments
|
|
2659
|
-
return 10*log10(clip(x/4e-10,1e-35,None))
|
|
2725
|
+
return 10 * log10(clip(x / 4e-10, 1e-35, None))
|
|
2726
|
+
|
|
2727
|
+
|
|
2660
2728
|
# return where(x>0, 10*log10(x/4e-10), -1000.)
|
|
2661
2729
|
|
|
2730
|
+
|
|
2662
2731
|
def integrate(data, grid, sector):
|
|
2663
|
-
"""
|
|
2664
|
-
|
|
2665
|
-
|
|
2732
|
+
"""Integrates a sound pressure map over a given sector.
|
|
2733
|
+
|
|
2666
2734
|
This function can be applied on beamforming results to
|
|
2667
2735
|
quantitatively analyze the sound pressure in a given sector.
|
|
2668
|
-
If used with :meth:`Beamformer.result()<acoular.fbeamform.BeamformerBase.result>`,
|
|
2669
|
-
the output is identical to the result of the intrinsic
|
|
2736
|
+
If used with :meth:`Beamformer.result()<acoular.fbeamform.BeamformerBase.result>`,
|
|
2737
|
+
the output is identical to the result of the intrinsic
|
|
2670
2738
|
:meth:`Beamformer.integrate<acoular.fbeamform.BeamformerBase.integrate>` method.
|
|
2671
|
-
It can, however, also be used with the
|
|
2739
|
+
It can, however, also be used with the
|
|
2672
2740
|
:meth:`Beamformer.synthetic<acoular.fbeamform.BeamformerBase.synthetic>`
|
|
2673
2741
|
output.
|
|
2674
|
-
|
|
2742
|
+
|
|
2675
2743
|
Parameters
|
|
2676
2744
|
----------
|
|
2677
2745
|
data: array of floats
|
|
2678
|
-
Contains the calculated squared sound pressure values in Pa**2.
|
|
2746
|
+
Contains the calculated squared sound pressure values in Pa**2.
|
|
2679
2747
|
If data has the same number of entries than the number of grid points
|
|
2680
2748
|
only one value is returned.
|
|
2681
|
-
In case of a 2-D array with the second dimension identical
|
|
2749
|
+
In case of a 2-D array with the second dimension identical
|
|
2682
2750
|
to the number of grid points an array containing as many entries as
|
|
2683
2751
|
the first dimension is returned.
|
|
2684
|
-
grid: Grid object
|
|
2685
|
-
Object of a :class:`~acoular.grids.Grid`-derived class
|
|
2686
|
-
that provides the grid locations.
|
|
2752
|
+
grid: Grid object
|
|
2753
|
+
Object of a :class:`~acoular.grids.Grid`-derived class
|
|
2754
|
+
that provides the grid locations.
|
|
2687
2755
|
sector: array of floats or :class:`~acoular.grids.Sector`-derived object
|
|
2688
|
-
Tuple with arguments for the `indices` method
|
|
2689
|
-
of a :class:`~acoular.grids.Grid`-derived class
|
|
2690
|
-
(e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
|
|
2756
|
+
Tuple with arguments for the `indices` method
|
|
2757
|
+
of a :class:`~acoular.grids.Grid`-derived class
|
|
2758
|
+
(e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
|
|
2691
2759
|
or :meth:`RectGrid3D.indices<acoular.grids.RectGrid3D.indices>`).
|
|
2692
2760
|
Possible sectors would be `array([xmin, ymin, xmax, ymax])`
|
|
2693
2761
|
or `array([x, y, radius])`.
|
|
2694
2762
|
Alternatively, a :class:`~acoular.grids.Sector`-derived object
|
|
2695
2763
|
can be used.
|
|
2696
|
-
|
|
2764
|
+
|
|
2697
2765
|
Returns
|
|
2698
2766
|
-------
|
|
2699
2767
|
array of floats
|
|
2700
2768
|
The spectrum (all calculated frequency bands) for the integrated sector.
|
|
2769
|
+
|
|
2701
2770
|
"""
|
|
2702
|
-
|
|
2703
2771
|
if isinstance(sector, Sector):
|
|
2704
2772
|
ind = grid.subdomain(sector)
|
|
2705
2773
|
elif hasattr(grid, 'indices'):
|
|
2706
2774
|
ind = grid.indices(*sector)
|
|
2707
2775
|
else:
|
|
2776
|
+
msg = (
|
|
2777
|
+
f'Grid of type {grid.__class__.__name__} does not have an indices method! '
|
|
2778
|
+
f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
|
|
2779
|
+
'instead of type numpy.array.'
|
|
2780
|
+
)
|
|
2708
2781
|
raise NotImplementedError(
|
|
2709
|
-
|
|
2710
|
-
f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
|
|
2711
|
-
'instead of type numpy.array.'
|
|
2782
|
+
msg,
|
|
2712
2783
|
)
|
|
2713
|
-
|
|
2784
|
+
|
|
2714
2785
|
gshape = grid.shape
|
|
2715
2786
|
gsize = grid.size
|
|
2716
|
-
if size(data) == gsize:
|
|
2787
|
+
if size(data) == gsize: # one value per grid point
|
|
2717
2788
|
h = data.reshape(gshape)[ind].sum()
|
|
2718
2789
|
elif data.ndim == 2 and data.shape[1] == gsize:
|
|
2719
2790
|
h = zeros(data.shape[0])
|