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