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