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