acoular 25.4__py3-none-any.whl → 25.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 +2 -4
- acoular/aiaa/aiaa.py +10 -10
- acoular/base.py +12 -34
- acoular/calib.py +20 -19
- acoular/configuration.py +3 -3
- acoular/demo/__init__.py +6 -1
- acoular/demo/acoular_demo.py +34 -10
- acoular/deprecation.py +10 -1
- acoular/environments.py +107 -117
- acoular/fastFuncs.py +16 -10
- acoular/fbeamform.py +300 -402
- acoular/fprocess.py +7 -33
- acoular/grids.py +228 -134
- acoular/h5cache.py +10 -4
- acoular/h5files.py +106 -9
- acoular/internal.py +4 -0
- acoular/microphones.py +22 -10
- acoular/process.py +7 -53
- acoular/sdinput.py +8 -5
- acoular/signals.py +29 -27
- acoular/sources.py +205 -335
- acoular/spectra.py +33 -43
- acoular/tbeamform.py +220 -199
- acoular/tools/helpers.py +52 -33
- acoular/tools/metrics.py +5 -10
- acoular/tprocess.py +1392 -647
- acoular/traitsviews.py +1 -3
- acoular/trajectory.py +5 -5
- acoular/version.py +4 -3
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/METADATA +8 -4
- acoular-25.10.dist-info/RECORD +56 -0
- acoular-25.4.dist-info/RECORD +0 -56
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/WHEEL +0 -0
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/licenses/LICENSE +0 -0
acoular/fbeamform.py
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
# ------------------------------------------------------------------------------
|
|
2
2
|
# Copyright (c) Acoular Development Team.
|
|
3
3
|
# ------------------------------------------------------------------------------
|
|
4
|
-
"""
|
|
4
|
+
"""
|
|
5
|
+
Implements beamformers in the frequency domain.
|
|
6
|
+
|
|
7
|
+
.. inheritance-diagram::
|
|
8
|
+
acoular.fbeamform.BeamformerBase
|
|
9
|
+
:include-subclasses:
|
|
10
|
+
:top-classes:
|
|
11
|
+
acoular.grids.Grid,
|
|
12
|
+
acoular.fbeamform.BeamformerBase
|
|
13
|
+
:parts: 1
|
|
5
14
|
|
|
6
15
|
.. autosummary::
|
|
7
16
|
:toctree: generated/
|
|
@@ -28,7 +37,6 @@
|
|
|
28
37
|
PointSpreadFunction
|
|
29
38
|
L_p
|
|
30
39
|
integrate
|
|
31
|
-
|
|
32
40
|
"""
|
|
33
41
|
|
|
34
42
|
# imports from other packages
|
|
@@ -36,53 +44,12 @@
|
|
|
36
44
|
import warnings
|
|
37
45
|
from warnings import warn
|
|
38
46
|
|
|
47
|
+
import numpy as np
|
|
48
|
+
import scipy.linalg as spla
|
|
49
|
+
|
|
39
50
|
# check for sklearn version to account for incompatible behavior
|
|
40
51
|
import sklearn
|
|
41
|
-
from numpy import (
|
|
42
|
-
absolute,
|
|
43
|
-
arange,
|
|
44
|
-
argsort,
|
|
45
|
-
array,
|
|
46
|
-
atleast_2d,
|
|
47
|
-
clip,
|
|
48
|
-
delete,
|
|
49
|
-
diag,
|
|
50
|
-
dot,
|
|
51
|
-
einsum,
|
|
52
|
-
einsum_path,
|
|
53
|
-
eye,
|
|
54
|
-
fill_diagonal,
|
|
55
|
-
full,
|
|
56
|
-
hsplit,
|
|
57
|
-
hstack,
|
|
58
|
-
index_exp,
|
|
59
|
-
inf,
|
|
60
|
-
integer,
|
|
61
|
-
invert,
|
|
62
|
-
isscalar,
|
|
63
|
-
log10,
|
|
64
|
-
ndarray,
|
|
65
|
-
newaxis,
|
|
66
|
-
ones,
|
|
67
|
-
pi,
|
|
68
|
-
real,
|
|
69
|
-
reshape,
|
|
70
|
-
round, # noqa: A004
|
|
71
|
-
searchsorted,
|
|
72
|
-
sign,
|
|
73
|
-
size,
|
|
74
|
-
sqrt,
|
|
75
|
-
sum, # noqa: A004
|
|
76
|
-
tile,
|
|
77
|
-
trace,
|
|
78
|
-
tril,
|
|
79
|
-
unique,
|
|
80
|
-
vstack,
|
|
81
|
-
zeros,
|
|
82
|
-
zeros_like,
|
|
83
|
-
)
|
|
84
52
|
from packaging.version import parse
|
|
85
|
-
from scipy.linalg import eigh, eigvals, fractional_matrix_power, inv, norm
|
|
86
53
|
from scipy.optimize import fmin_l_bfgs_b, linprog, nnls, shgo
|
|
87
54
|
from sklearn.linear_model import LassoLars, LassoLarsCV, LassoLarsIC, LinearRegression, OrthogonalMatchingPursuitCV
|
|
88
55
|
from traits.api import (
|
|
@@ -100,14 +67,13 @@ from traits.api import (
|
|
|
100
67
|
Range,
|
|
101
68
|
Tuple,
|
|
102
69
|
cached_property,
|
|
103
|
-
|
|
70
|
+
observe,
|
|
104
71
|
property_depends_on,
|
|
105
72
|
)
|
|
106
73
|
from traits.trait_errors import TraitError
|
|
107
74
|
|
|
108
75
|
# acoular imports
|
|
109
76
|
from .configuration import config
|
|
110
|
-
from .deprecation import deprecated_alias
|
|
111
77
|
from .environments import Environment
|
|
112
78
|
from .fastFuncs import beamformerFreq, calcPointSpreadFunction, calcTransfer, damasSolverGaussSeidel
|
|
113
79
|
from .grids import Grid, Sector
|
|
@@ -126,7 +92,8 @@ BEAMFORMER_BASE_DIGEST_DEPENDENCIES = ['freq_data.digest', 'r_diag', 'r_diag_nor
|
|
|
126
92
|
|
|
127
93
|
|
|
128
94
|
class SteeringVector(HasStrictTraits):
|
|
129
|
-
"""
|
|
95
|
+
"""
|
|
96
|
+
Basic class for implementing steering vectors with monopole source transfer models.
|
|
130
97
|
|
|
131
98
|
Handles four different steering vector formulations. See :cite:`Sarradj2012` for details.
|
|
132
99
|
"""
|
|
@@ -154,7 +121,7 @@ class SteeringVector(HasStrictTraits):
|
|
|
154
121
|
rm = Property(desc='all array mics to grid distances')
|
|
155
122
|
|
|
156
123
|
# mirror trait for ref
|
|
157
|
-
_ref = Any(array([0.0, 0.0, 0.0]), desc='reference position or distance')
|
|
124
|
+
_ref = Any(np.array([0.0, 0.0, 0.0]), desc='reference position or distance')
|
|
158
125
|
|
|
159
126
|
#: Reference position or distance at which to evaluate the sound pressure
|
|
160
127
|
#: of a grid point.
|
|
@@ -165,10 +132,10 @@ class SteeringVector(HasStrictTraits):
|
|
|
165
132
|
|
|
166
133
|
_steer_funcs_freq = Dict(
|
|
167
134
|
{
|
|
168
|
-
'classic': lambda x: x /
|
|
135
|
+
'classic': lambda x: x / np.abs(x) / x.shape[-1],
|
|
169
136
|
'inverse': lambda x: 1.0 / x.conj() / x.shape[-1],
|
|
170
|
-
'true level': lambda x: x / einsum('ij,ij->i', x, x.conj())[:, newaxis],
|
|
171
|
-
'true location': lambda x: x / sqrt(einsum('ij,ij->i', x, x.conj()) * x.shape[-1])[:, newaxis],
|
|
137
|
+
'true level': lambda x: x / np.einsum('ij,ij->i', x, x.conj())[:, np.newaxis],
|
|
138
|
+
'true location': lambda x: x / np.sqrt(np.einsum('ij,ij->i', x, x.conj()) * x.shape[-1])[:, np.newaxis],
|
|
172
139
|
},
|
|
173
140
|
transient=True,
|
|
174
141
|
desc='dictionary of frequency domain steering vector functions',
|
|
@@ -186,13 +153,13 @@ class SteeringVector(HasStrictTraits):
|
|
|
186
153
|
)
|
|
187
154
|
|
|
188
155
|
def _set_ref(self, ref):
|
|
189
|
-
if isscalar(ref):
|
|
156
|
+
if np.isscalar(ref):
|
|
190
157
|
try:
|
|
191
|
-
self._ref =
|
|
158
|
+
self._ref = np.abs(float(ref))
|
|
192
159
|
except ValueError as ve:
|
|
193
160
|
raise TraitError(args=self, name='ref', info='Float or CArray(3,)', value=ref) from ve
|
|
194
161
|
elif len(ref) == 3:
|
|
195
|
-
self._ref = array(ref, dtype=float)
|
|
162
|
+
self._ref = np.array(ref, dtype=float)
|
|
196
163
|
else:
|
|
197
164
|
raise TraitError(args=self, name='ref', info='Float or CArray(3,)', value=ref)
|
|
198
165
|
|
|
@@ -207,15 +174,15 @@ class SteeringVector(HasStrictTraits):
|
|
|
207
174
|
|
|
208
175
|
@property_depends_on(['grid.digest', 'env.digest', '_ref'])
|
|
209
176
|
def _get_r0(self):
|
|
210
|
-
if isscalar(self.ref):
|
|
177
|
+
if np.isscalar(self.ref):
|
|
211
178
|
if self.ref > 0:
|
|
212
|
-
return full((self.grid.size,), self.ref)
|
|
179
|
+
return np.full((self.grid.size,), self.ref)
|
|
213
180
|
return self.env._r(self.grid.pos())
|
|
214
|
-
return self.env._r(self.grid.pos, self.ref[:, newaxis])
|
|
181
|
+
return self.env._r(self.grid.pos, self.ref[:, np.newaxis])
|
|
215
182
|
|
|
216
183
|
@property_depends_on(['grid.digest', 'mics.digest', 'env.digest'])
|
|
217
184
|
def _get_rm(self):
|
|
218
|
-
return atleast_2d(self.env._r(self.grid.pos, self.mics.pos))
|
|
185
|
+
return np.atleast_2d(self.env._r(self.grid.pos, self.mics.pos))
|
|
219
186
|
|
|
220
187
|
@cached_property
|
|
221
188
|
def _get_digest(self):
|
|
@@ -226,7 +193,8 @@ class SteeringVector(HasStrictTraits):
|
|
|
226
193
|
return digest(self)
|
|
227
194
|
|
|
228
195
|
def transfer(self, f, ind=None):
|
|
229
|
-
"""
|
|
196
|
+
"""
|
|
197
|
+
Calculates the transfer matrix for one frequency.
|
|
230
198
|
|
|
231
199
|
Parameters
|
|
232
200
|
----------
|
|
@@ -241,22 +209,22 @@ class SteeringVector(HasStrictTraits):
|
|
|
241
209
|
-------
|
|
242
210
|
array of complex128
|
|
243
211
|
array of shape (ngridpts, nmics) containing the transfer matrix for the given frequency
|
|
244
|
-
|
|
245
212
|
"""
|
|
246
213
|
# if self.cached:
|
|
247
214
|
# warn('Caching of transfer function is not yet supported!', Warning)
|
|
248
215
|
# self.cached = False
|
|
249
216
|
|
|
250
217
|
if ind is None:
|
|
251
|
-
trans = calcTransfer(self.r0, self.rm, array(2 * pi * f / self.env.c))
|
|
252
|
-
elif not isinstance(ind, ndarray):
|
|
253
|
-
trans = calcTransfer(self.r0[ind], self.rm[ind, :][newaxis], array(2 * pi * f / self.env.c))
|
|
218
|
+
trans = calcTransfer(self.r0, self.rm, np.array(2 * np.pi * f / self.env.c))
|
|
219
|
+
elif not isinstance(ind, np.ndarray):
|
|
220
|
+
trans = calcTransfer(self.r0[ind], self.rm[ind, :][np.newaxis], np.array(2 * np.pi * f / self.env.c))
|
|
254
221
|
else:
|
|
255
|
-
trans = calcTransfer(self.r0[ind], self.rm[ind, :], array(2 * pi * f / self.env.c))
|
|
222
|
+
trans = calcTransfer(self.r0[ind], self.rm[ind, :], np.array(2 * np.pi * f / self.env.c))
|
|
256
223
|
return trans
|
|
257
224
|
|
|
258
225
|
def steer_vector(self, f, ind=None):
|
|
259
|
-
"""
|
|
226
|
+
"""
|
|
227
|
+
Calculates the steering vectors based on the transfer function.
|
|
260
228
|
|
|
261
229
|
See also :cite:`Sarradj2012`.
|
|
262
230
|
|
|
@@ -273,7 +241,6 @@ class SteeringVector(HasStrictTraits):
|
|
|
273
241
|
-------
|
|
274
242
|
array of complex128
|
|
275
243
|
array of shape (ngridpts, nmics) containing the steering vectors for the given frequency
|
|
276
|
-
|
|
277
244
|
"""
|
|
278
245
|
func = self._steer_funcs_freq[self.steer_type]
|
|
279
246
|
return func(self.transfer(f, ind))
|
|
@@ -292,12 +259,12 @@ class LazyBfResult:
|
|
|
292
259
|
self.bf = bf
|
|
293
260
|
|
|
294
261
|
def __getitem__(self, key):
|
|
295
|
-
|
|
296
|
-
sl = index_exp[key][0]
|
|
297
|
-
if isinstance(sl, (int, integer)):
|
|
262
|
+
"""'intelligent' [] operator, checks if results are available and triggers calculation."""
|
|
263
|
+
sl = np.index_exp[key][0]
|
|
264
|
+
if isinstance(sl, (int, np.integer)):
|
|
298
265
|
sl = slice(sl, sl + 1)
|
|
299
266
|
# indices which are missing
|
|
300
|
-
missingind = arange(*sl.indices(self.bf._numfreq))[self.bf._fr[sl] == 0]
|
|
267
|
+
missingind = np.arange(*sl.indices(self.bf._numfreq))[self.bf._fr[sl] == 0]
|
|
301
268
|
# calc if needed
|
|
302
269
|
if missingind.size:
|
|
303
270
|
self.bf._calc(missingind)
|
|
@@ -362,9 +329,10 @@ class BeamformerBase(HasStrictTraits):
|
|
|
362
329
|
return digest(self)
|
|
363
330
|
|
|
364
331
|
def _get_filecache(self):
|
|
365
|
-
"""
|
|
366
|
-
|
|
367
|
-
|
|
332
|
+
"""
|
|
333
|
+
Function collects cached results from file depending on global/local caching behaviour.
|
|
334
|
+
|
|
335
|
+
Returns (None, None) if no cachefile/data exist and global caching mode is 'readonly'.
|
|
368
336
|
"""
|
|
369
337
|
# print("get cachefile:", self.freq_data.basename)
|
|
370
338
|
H5cache.get_cache_file(self, self.freq_data.basename)
|
|
@@ -419,7 +387,9 @@ class BeamformerBase(HasStrictTraits):
|
|
|
419
387
|
|
|
420
388
|
@property_depends_on(['digest'])
|
|
421
389
|
def _get_result(self):
|
|
422
|
-
"""
|
|
390
|
+
"""
|
|
391
|
+
Implements the :attr:`result` getter routine.
|
|
392
|
+
|
|
423
393
|
The beamforming result is either loaded or calculated.
|
|
424
394
|
"""
|
|
425
395
|
# store locally for performance
|
|
@@ -439,20 +409,22 @@ class BeamformerBase(HasStrictTraits):
|
|
|
439
409
|
else:
|
|
440
410
|
# no caching or not activated, init numpy arrays
|
|
441
411
|
if isinstance(self, BeamformerAdaptiveGrid):
|
|
442
|
-
self._gpos = zeros((3, self.size), dtype=self.precision)
|
|
443
|
-
ac = zeros((self._numfreq, self.size), dtype=self.precision)
|
|
412
|
+
self._gpos = np.zeros((3, self.size), dtype=self.precision)
|
|
413
|
+
ac = np.zeros((self._numfreq, self.size), dtype=self.precision)
|
|
444
414
|
elif isinstance(self, BeamformerSODIX):
|
|
445
|
-
ac = zeros((self._numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
415
|
+
ac = np.zeros((self._numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
446
416
|
else:
|
|
447
|
-
ac = zeros((self._numfreq, self.steer.grid.size), dtype=self.precision)
|
|
448
|
-
fr = zeros(self._numfreq, dtype='int8')
|
|
417
|
+
ac = np.zeros((self._numfreq, self.steer.grid.size), dtype=self.precision)
|
|
418
|
+
fr = np.zeros(self._numfreq, dtype='int8')
|
|
449
419
|
self._ac = ac
|
|
450
420
|
self._fr = fr
|
|
451
421
|
return LazyBfResult(self)
|
|
452
422
|
|
|
453
423
|
def sig_loss_norm(self):
|
|
454
|
-
"""
|
|
455
|
-
of
|
|
424
|
+
"""
|
|
425
|
+
If the diagonal of the CSM is removed one has to handle the loss of signal energy.
|
|
426
|
+
|
|
427
|
+
Done via a normalization factor.
|
|
456
428
|
"""
|
|
457
429
|
if not self.r_diag: # Full CSM --> no normalization needed
|
|
458
430
|
normfactor = 1.0
|
|
@@ -464,28 +436,29 @@ class BeamformerBase(HasStrictTraits):
|
|
|
464
436
|
return normfactor
|
|
465
437
|
|
|
466
438
|
def _beamformer_params(self):
|
|
467
|
-
"""
|
|
468
|
-
|
|
469
|
-
|
|
439
|
+
"""
|
|
440
|
+
Manages the parameters for calling of the core beamformer functionality.
|
|
441
|
+
|
|
442
|
+
This is a workaround to allow faster calculation and may change in the future.
|
|
470
443
|
|
|
471
444
|
Returns
|
|
472
445
|
-------
|
|
473
446
|
- String containing the steering vector type
|
|
474
447
|
- Function for frequency-dependent steering vector calculation
|
|
475
|
-
|
|
476
448
|
"""
|
|
477
449
|
if type(self.steer) is SteeringVector: # for simple steering vector, use faster method
|
|
478
450
|
param_type = self.steer.steer_type
|
|
479
451
|
|
|
480
452
|
def param_steer_func(f):
|
|
481
|
-
return (self.steer.r0, self.steer.rm, 2 * pi * f / self.steer.env.c)
|
|
453
|
+
return (self.steer.r0, self.steer.rm, 2 * np.pi * f / self.steer.env.c)
|
|
482
454
|
else:
|
|
483
455
|
param_type = 'custom'
|
|
484
456
|
param_steer_func = self.steer.steer_vector
|
|
485
457
|
return param_type, param_steer_func
|
|
486
458
|
|
|
487
459
|
def _calc(self, ind):
|
|
488
|
-
"""
|
|
460
|
+
"""
|
|
461
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
489
462
|
|
|
490
463
|
This is an internal helper function that is automatically called when
|
|
491
464
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -500,14 +473,13 @@ class BeamformerBase(HasStrictTraits):
|
|
|
500
473
|
Returns
|
|
501
474
|
-------
|
|
502
475
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
503
|
-
|
|
504
476
|
"""
|
|
505
477
|
f = self._f
|
|
506
478
|
normfactor = self.sig_loss_norm()
|
|
507
479
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
508
480
|
for i in ind:
|
|
509
481
|
# print(f'compute{i}')
|
|
510
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
482
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
511
483
|
beamformerOutput = beamformerFreq(
|
|
512
484
|
param_steer_type,
|
|
513
485
|
self.r_diag,
|
|
@@ -516,13 +488,14 @@ class BeamformerBase(HasStrictTraits):
|
|
|
516
488
|
csm,
|
|
517
489
|
)[0]
|
|
518
490
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
519
|
-
indNegSign = sign(beamformerOutput) < 0
|
|
491
|
+
indNegSign = np.sign(beamformerOutput) < 0
|
|
520
492
|
beamformerOutput[indNegSign] = 0.0
|
|
521
493
|
self._ac[i] = beamformerOutput
|
|
522
494
|
self._fr[i] = 1
|
|
523
495
|
|
|
524
496
|
def synthetic(self, f, num=0):
|
|
525
|
-
"""
|
|
497
|
+
"""
|
|
498
|
+
Evaluates the beamforming result for an arbitrary frequency band.
|
|
526
499
|
|
|
527
500
|
Parameters
|
|
528
501
|
----------
|
|
@@ -550,7 +523,6 @@ class BeamformerBase(HasStrictTraits):
|
|
|
550
523
|
represented by a single frequency line depends on
|
|
551
524
|
the :attr:`sampling frequency<acoular.base.SamplesGenerator.sample_freq>` and
|
|
552
525
|
used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
553
|
-
|
|
554
526
|
"""
|
|
555
527
|
res = self.result # trigger calculation
|
|
556
528
|
freq = self.freq_data.fftfreq()
|
|
@@ -559,14 +531,14 @@ class BeamformerBase(HasStrictTraits):
|
|
|
559
531
|
|
|
560
532
|
if num == 0:
|
|
561
533
|
# single frequency line
|
|
562
|
-
ind = searchsorted(freq, f)
|
|
534
|
+
ind = np.searchsorted(freq, f)
|
|
563
535
|
if ind >= len(freq):
|
|
564
536
|
warn(
|
|
565
537
|
f'Queried frequency ({f:g} Hz) not in resolved frequency range. Returning zeros.',
|
|
566
538
|
Warning,
|
|
567
539
|
stacklevel=2,
|
|
568
540
|
)
|
|
569
|
-
h = zeros_like(res[0])
|
|
541
|
+
h = np.zeros_like(res[0])
|
|
570
542
|
else:
|
|
571
543
|
if freq[ind] != f:
|
|
572
544
|
warn(
|
|
@@ -585,8 +557,8 @@ class BeamformerBase(HasStrictTraits):
|
|
|
585
557
|
else:
|
|
586
558
|
f1 = f * 2.0 ** (-0.5 / num)
|
|
587
559
|
f2 = f * 2.0 ** (+0.5 / num)
|
|
588
|
-
ind1 = searchsorted(freq, f1)
|
|
589
|
-
ind2 = searchsorted(freq, f2)
|
|
560
|
+
ind1 = np.searchsorted(freq, f1)
|
|
561
|
+
ind2 = np.searchsorted(freq, f2)
|
|
590
562
|
if ind1 == ind2:
|
|
591
563
|
warn(
|
|
592
564
|
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
@@ -595,9 +567,9 @@ class BeamformerBase(HasStrictTraits):
|
|
|
595
567
|
Warning,
|
|
596
568
|
stacklevel=2,
|
|
597
569
|
)
|
|
598
|
-
h = zeros_like(res[0])
|
|
570
|
+
h = np.zeros_like(res[0])
|
|
599
571
|
else:
|
|
600
|
-
h = sum(res[ind1:ind2], 0)
|
|
572
|
+
h = np.sum(res[ind1:ind2], 0)
|
|
601
573
|
if isinstance(self, BeamformerAdaptiveGrid):
|
|
602
574
|
return h
|
|
603
575
|
if isinstance(self, BeamformerSODIX):
|
|
@@ -605,7 +577,8 @@ class BeamformerBase(HasStrictTraits):
|
|
|
605
577
|
return h.reshape(self.steer.grid.shape)
|
|
606
578
|
|
|
607
579
|
def integrate(self, sector, frange=None, num=0):
|
|
608
|
-
"""
|
|
580
|
+
"""
|
|
581
|
+
Integrates result map over a given sector.
|
|
609
582
|
|
|
610
583
|
Parameters
|
|
611
584
|
----------
|
|
@@ -662,45 +635,43 @@ class BeamformerBase(HasStrictTraits):
|
|
|
662
635
|
msg,
|
|
663
636
|
)
|
|
664
637
|
gshape = self.steer.grid.shape
|
|
638
|
+
num_freqs = self.freq_data.fftfreq().shape[0]
|
|
665
639
|
if num == 0 or frange is None:
|
|
666
640
|
if frange is None:
|
|
667
641
|
ind_low = self.freq_data.ind_low
|
|
668
642
|
ind_high = self.freq_data.ind_high
|
|
669
643
|
if ind_low is None:
|
|
670
644
|
ind_low = 0
|
|
671
|
-
if ind_low < 0:
|
|
672
|
-
ind_low += self._numfreq
|
|
673
645
|
if ind_high is None:
|
|
674
|
-
ind_high =
|
|
675
|
-
if ind_high < 0:
|
|
676
|
-
ind_high += self._numfreq
|
|
646
|
+
ind_high = num_freqs
|
|
677
647
|
irange = (ind_low, ind_high)
|
|
678
648
|
num = 0
|
|
679
649
|
elif len(frange) == 2:
|
|
680
|
-
irange = (searchsorted(self._f, frange[0]), searchsorted(self._f, frange[1]))
|
|
650
|
+
irange = (np.searchsorted(self._f, frange[0]), np.searchsorted(self._f, frange[1]))
|
|
681
651
|
else:
|
|
682
652
|
msg = 'Only a tuple of length 2 is allowed for frange if num==0'
|
|
683
653
|
raise TypeError(
|
|
684
654
|
msg,
|
|
685
655
|
)
|
|
686
|
-
h = zeros(
|
|
656
|
+
h = np.zeros(num_freqs, dtype=float)
|
|
687
657
|
sl = slice(*irange)
|
|
688
658
|
r = self.result[sl]
|
|
689
|
-
for i in range(
|
|
659
|
+
for i in range(num_freqs)[sl]:
|
|
690
660
|
# we do this per frequency because r might not have fancy indexing
|
|
691
661
|
h[i] = r[i - sl.start].reshape(gshape)[ind].sum()
|
|
692
662
|
if frange is None:
|
|
693
663
|
return h
|
|
694
664
|
return self._f[sl], h[sl]
|
|
695
665
|
|
|
696
|
-
h = zeros(len(frange), dtype=float)
|
|
666
|
+
h = np.zeros(len(frange), dtype=float)
|
|
697
667
|
for i, f in enumerate(frange):
|
|
698
668
|
h[i] = self.synthetic(f, num).reshape(gshape)[ind].sum()
|
|
699
669
|
return h
|
|
700
670
|
|
|
701
671
|
|
|
702
672
|
class BeamformerFunctional(BeamformerBase):
|
|
703
|
-
"""
|
|
673
|
+
"""
|
|
674
|
+
Functional beamforming algorithm.
|
|
704
675
|
|
|
705
676
|
See :cite:`Dougherty2014` for details.
|
|
706
677
|
"""
|
|
@@ -726,7 +697,8 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
726
697
|
return digest(self)
|
|
727
698
|
|
|
728
699
|
def _calc(self, ind):
|
|
729
|
-
"""
|
|
700
|
+
"""
|
|
701
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
730
702
|
|
|
731
703
|
This is an internal helper function that is automatically called when
|
|
732
704
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -741,7 +713,6 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
741
713
|
Returns
|
|
742
714
|
-------
|
|
743
715
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
744
|
-
|
|
745
716
|
"""
|
|
746
717
|
f = self._f
|
|
747
718
|
normfactor = self.sig_loss_norm()
|
|
@@ -759,8 +730,8 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
759
730
|
# WATCH OUT: This doesn't really produce good results.
|
|
760
731
|
# ==============================================================================
|
|
761
732
|
csm = self.freq_data.csm[i]
|
|
762
|
-
fill_diagonal(csm, 0)
|
|
763
|
-
csmRoot = fractional_matrix_power(csm, 1.0 / self.gamma)
|
|
733
|
+
np.fill_diagonal(csm, 0)
|
|
734
|
+
csmRoot = spla.fractional_matrix_power(csm, 1.0 / self.gamma)
|
|
764
735
|
beamformerOutput, steerNorm = beamformerFreq(
|
|
765
736
|
param_steer_type,
|
|
766
737
|
self.r_diag,
|
|
@@ -771,11 +742,11 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
771
742
|
beamformerOutput /= steerNorm # take normalized steering vec
|
|
772
743
|
|
|
773
744
|
# set (unphysical) negative output values to 0
|
|
774
|
-
indNegSign = sign(beamformerOutput) < 0
|
|
745
|
+
indNegSign = np.sign(beamformerOutput) < 0
|
|
775
746
|
beamformerOutput[indNegSign] = 0.0
|
|
776
747
|
else:
|
|
777
|
-
eva = array(self.freq_data.eva[i], dtype='float64') ** (1.0 / self.gamma)
|
|
778
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
748
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64') ** (1.0 / self.gamma)
|
|
749
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
779
750
|
beamformerOutput, steerNorm = beamformerFreq(
|
|
780
751
|
param_steer_type,
|
|
781
752
|
self.r_diag,
|
|
@@ -791,7 +762,8 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
791
762
|
|
|
792
763
|
|
|
793
764
|
class BeamformerCapon(BeamformerBase):
|
|
794
|
-
"""
|
|
765
|
+
"""
|
|
766
|
+
Beamforming using the Capon (Mininimum Variance) algorithm.
|
|
795
767
|
|
|
796
768
|
See :cite:`Capon1969` for details.
|
|
797
769
|
"""
|
|
@@ -808,7 +780,8 @@ class BeamformerCapon(BeamformerBase):
|
|
|
808
780
|
)
|
|
809
781
|
|
|
810
782
|
def _calc(self, ind):
|
|
811
|
-
"""
|
|
783
|
+
"""
|
|
784
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
812
785
|
|
|
813
786
|
This is an internal helper function that is automatically called when
|
|
814
787
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -823,21 +796,21 @@ class BeamformerCapon(BeamformerBase):
|
|
|
823
796
|
Returns
|
|
824
797
|
-------
|
|
825
798
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
826
|
-
|
|
827
799
|
"""
|
|
828
800
|
f = self._f
|
|
829
801
|
nMics = self.freq_data.num_channels
|
|
830
802
|
normfactor = self.sig_loss_norm() * nMics**2
|
|
831
803
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
832
804
|
for i in ind:
|
|
833
|
-
csm = array(inv(array(self.freq_data.csm[i], dtype='complex128')), order='C')
|
|
805
|
+
csm = np.array(spla.inv(np.array(self.freq_data.csm[i], dtype='complex128')), order='C')
|
|
834
806
|
beamformerOutput = beamformerFreq(param_steer_type, self.r_diag, normfactor, steer_vector(f[i]), csm)[0]
|
|
835
807
|
self._ac[i] = 1.0 / beamformerOutput
|
|
836
808
|
self._fr[i] = 1
|
|
837
809
|
|
|
838
810
|
|
|
839
811
|
class BeamformerEig(BeamformerBase):
|
|
840
|
-
"""
|
|
812
|
+
"""
|
|
813
|
+
Beamforming using eigenvalue and eigenvector techniques.
|
|
841
814
|
|
|
842
815
|
See :cite:`Sarradj2005` for details.
|
|
843
816
|
"""
|
|
@@ -866,7 +839,8 @@ class BeamformerEig(BeamformerBase):
|
|
|
866
839
|
return min(nm - 1, na)
|
|
867
840
|
|
|
868
841
|
def _calc(self, ind):
|
|
869
|
-
"""
|
|
842
|
+
"""
|
|
843
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
870
844
|
|
|
871
845
|
This is an internal helper function that is automatically called when
|
|
872
846
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -881,15 +855,14 @@ class BeamformerEig(BeamformerBase):
|
|
|
881
855
|
Returns
|
|
882
856
|
-------
|
|
883
857
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
884
|
-
|
|
885
858
|
"""
|
|
886
859
|
f = self._f
|
|
887
860
|
na = int(self.na) # eigenvalue taken into account
|
|
888
861
|
normfactor = self.sig_loss_norm()
|
|
889
862
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
890
863
|
for i in ind:
|
|
891
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
892
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
864
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
865
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
893
866
|
beamformerOutput = beamformerFreq(
|
|
894
867
|
param_steer_type,
|
|
895
868
|
self.r_diag,
|
|
@@ -898,14 +871,15 @@ class BeamformerEig(BeamformerBase):
|
|
|
898
871
|
(eva[na : na + 1], eve[:, na : na + 1]),
|
|
899
872
|
)[0]
|
|
900
873
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
901
|
-
indNegSign = sign(beamformerOutput) < 0
|
|
874
|
+
indNegSign = np.sign(beamformerOutput) < 0
|
|
902
875
|
beamformerOutput[indNegSign] = 0
|
|
903
876
|
self._ac[i] = beamformerOutput
|
|
904
877
|
self._fr[i] = 1
|
|
905
878
|
|
|
906
879
|
|
|
907
880
|
class BeamformerMusic(BeamformerEig):
|
|
908
|
-
"""
|
|
881
|
+
"""
|
|
882
|
+
Beamforming using the MUSIC algorithm.
|
|
909
883
|
|
|
910
884
|
See :cite:`Schmidt1986` for details.
|
|
911
885
|
"""
|
|
@@ -926,7 +900,8 @@ class BeamformerMusic(BeamformerEig):
|
|
|
926
900
|
n = Int(1, desc='assumed number of sources')
|
|
927
901
|
|
|
928
902
|
def _calc(self, ind):
|
|
929
|
-
"""
|
|
903
|
+
"""
|
|
904
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
930
905
|
|
|
931
906
|
This is an internal helper function that is automatically called when
|
|
932
907
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -941,7 +916,6 @@ class BeamformerMusic(BeamformerEig):
|
|
|
941
916
|
Returns
|
|
942
917
|
-------
|
|
943
918
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
944
|
-
|
|
945
919
|
"""
|
|
946
920
|
f = self._f
|
|
947
921
|
nMics = self.freq_data.num_channels
|
|
@@ -949,8 +923,8 @@ class BeamformerMusic(BeamformerEig):
|
|
|
949
923
|
normfactor = self.sig_loss_norm() * nMics**2
|
|
950
924
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
951
925
|
for i in ind:
|
|
952
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
953
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
926
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
927
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
954
928
|
beamformerOutput = beamformerFreq(
|
|
955
929
|
param_steer_type,
|
|
956
930
|
self.r_diag,
|
|
@@ -963,7 +937,8 @@ class BeamformerMusic(BeamformerEig):
|
|
|
963
937
|
|
|
964
938
|
|
|
965
939
|
class PointSpreadFunction(HasStrictTraits):
|
|
966
|
-
"""
|
|
940
|
+
"""
|
|
941
|
+
The point spread function.
|
|
967
942
|
|
|
968
943
|
This class provides tools to calculate the PSF depending on the used
|
|
969
944
|
microphone geometry, focus grid, flow environment, etc.
|
|
@@ -979,7 +954,7 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
979
954
|
#: Indices of grid points to calculate the PSF for.
|
|
980
955
|
grid_indices = CArray(
|
|
981
956
|
dtype=int,
|
|
982
|
-
value=array([]),
|
|
957
|
+
value=np.array([]),
|
|
983
958
|
desc='indices of grid points for psf',
|
|
984
959
|
) # value=array([]), value=self.steer.grid.pos(),
|
|
985
960
|
|
|
@@ -1016,9 +991,10 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1016
991
|
return digest(self)
|
|
1017
992
|
|
|
1018
993
|
def _get_filecache(self):
|
|
1019
|
-
"""
|
|
1020
|
-
|
|
1021
|
-
|
|
994
|
+
"""
|
|
995
|
+
Function collects cached results from file depending on global/local caching behaviour.
|
|
996
|
+
|
|
997
|
+
Returns (None, None) if no cachefile/data exist and global caching mode is 'readonly'.
|
|
1022
998
|
"""
|
|
1023
999
|
filename = 'psf' + self.digest
|
|
1024
1000
|
nodename = (f'Hz_{self.freq:.2f}').replace('.', '_')
|
|
@@ -1050,12 +1026,14 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1050
1026
|
return (ac, gp)
|
|
1051
1027
|
|
|
1052
1028
|
def _get_psf(self):
|
|
1053
|
-
"""
|
|
1029
|
+
"""
|
|
1030
|
+
Implements the :attr:`psf` getter routine.
|
|
1031
|
+
|
|
1054
1032
|
The point spread function is either loaded or calculated.
|
|
1055
1033
|
"""
|
|
1056
1034
|
gs = self.steer.grid.size
|
|
1057
1035
|
if not self.grid_indices.size:
|
|
1058
|
-
self.grid_indices = arange(gs)
|
|
1036
|
+
self.grid_indices = np.arange(gs)
|
|
1059
1037
|
|
|
1060
1038
|
if config.global_caching != 'none':
|
|
1061
1039
|
# print("get filecache..")
|
|
@@ -1078,13 +1056,13 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1078
1056
|
# print("cached results are complete! return.")
|
|
1079
1057
|
return ac[:, self.grid_indices]
|
|
1080
1058
|
# print("no caching, calculate result")
|
|
1081
|
-
ac = zeros((gs, gs), dtype=self.precision)
|
|
1082
|
-
gp = zeros((gs,), dtype='int8')
|
|
1059
|
+
ac = np.zeros((gs, gs), dtype=self.precision)
|
|
1060
|
+
gp = np.zeros((gs,), dtype='int8')
|
|
1083
1061
|
self.calc_psf(ac, gp)
|
|
1084
1062
|
else: # no caching activated
|
|
1085
1063
|
# print("no caching activated, calculate result")
|
|
1086
|
-
ac = zeros((gs, gs), dtype=self.precision)
|
|
1087
|
-
gp = zeros((gs,), dtype='int8')
|
|
1064
|
+
ac = np.zeros((gs, gs), dtype=self.precision)
|
|
1065
|
+
gp = np.zeros((gs,), dtype='int8')
|
|
1088
1066
|
self.calc_psf(ac, gp)
|
|
1089
1067
|
return ac[:, self.grid_indices]
|
|
1090
1068
|
|
|
@@ -1093,7 +1071,7 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1093
1071
|
if self.calcmode != 'full':
|
|
1094
1072
|
# calc_ind has the form [True, True, False, True], except
|
|
1095
1073
|
# when it has only 1 entry (value True/1 would be ambiguous)
|
|
1096
|
-
calc_ind = [0] if self.grid_indices.size == 1 else invert(gp[:][self.grid_indices])
|
|
1074
|
+
calc_ind = [0] if self.grid_indices.size == 1 else np.invert(gp[:][self.grid_indices])
|
|
1097
1075
|
|
|
1098
1076
|
# get indices which have the value True = not yet calculated
|
|
1099
1077
|
g_ind_calc = self.grid_indices[calc_ind]
|
|
@@ -1104,7 +1082,7 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1104
1082
|
gp[ind] = 1
|
|
1105
1083
|
elif self.calcmode == 'full': # calculate all psfs in one go
|
|
1106
1084
|
gp[:] = 1
|
|
1107
|
-
ac[:] = self._psf_call(arange(self.steer.grid.size))
|
|
1085
|
+
ac[:] = self._psf_call(np.arange(self.steer.grid.size))
|
|
1108
1086
|
else: # 'block' # calculate selected psfs in one go
|
|
1109
1087
|
hh = self._psf_call(g_ind_calc)
|
|
1110
1088
|
for indh, ind in enumerate(g_ind_calc):
|
|
@@ -1113,7 +1091,8 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1113
1091
|
indh += 1
|
|
1114
1092
|
|
|
1115
1093
|
def _psf_call(self, ind):
|
|
1116
|
-
"""
|
|
1094
|
+
"""
|
|
1095
|
+
Manages the calling of the core psf functionality.
|
|
1117
1096
|
|
|
1118
1097
|
Parameters
|
|
1119
1098
|
----------
|
|
@@ -1130,32 +1109,25 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1130
1109
|
self.steer.steer_type,
|
|
1131
1110
|
self.steer.r0,
|
|
1132
1111
|
self.steer.rm,
|
|
1133
|
-
2 * pi * self.freq / self.steer.env.c,
|
|
1112
|
+
2 * np.pi * self.freq / self.steer.env.c,
|
|
1134
1113
|
ind,
|
|
1135
1114
|
self.precision,
|
|
1136
1115
|
)
|
|
1137
1116
|
else:
|
|
1138
1117
|
# for arbitrary steering sectors, use general calculation. there is a version of this in
|
|
1139
1118
|
# fastFuncs, may be used later after runtime testing and debugging
|
|
1140
|
-
product = dot(self.steer.steer_vector(self.freq).conj(), self.steer.transfer(self.freq, ind).T)
|
|
1119
|
+
product = np.dot(self.steer.steer_vector(self.freq).conj(), self.steer.transfer(self.freq, ind).T)
|
|
1141
1120
|
result = (product * product.conj()).real
|
|
1142
1121
|
return result
|
|
1143
1122
|
|
|
1144
1123
|
|
|
1145
1124
|
class BeamformerDamas(BeamformerBase):
|
|
1146
|
-
"""
|
|
1125
|
+
"""
|
|
1126
|
+
DAMAS deconvolution algorithm.
|
|
1147
1127
|
|
|
1148
1128
|
See :cite:`Brooks2006` for details.
|
|
1149
1129
|
"""
|
|
1150
1130
|
|
|
1151
|
-
#: (only for backward compatibility) :class:`BeamformerBase` object
|
|
1152
|
-
#: if set, provides :attr:`freq_data`, :attr:`steer`, :attr:`r_diag`
|
|
1153
|
-
#: if not set, these have to be set explicitly.
|
|
1154
|
-
beamformer = Property(transient=True)
|
|
1155
|
-
|
|
1156
|
-
# private storage of beamformer instance
|
|
1157
|
-
_beamformer = Instance(BeamformerBase)
|
|
1158
|
-
|
|
1159
1131
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1160
1132
|
psf_precision = Enum('float64', 'float32', desc='precision of PSF')
|
|
1161
1133
|
|
|
@@ -1174,34 +1146,13 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1174
1146
|
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n_iter', 'damp', 'psf_precision'],
|
|
1175
1147
|
)
|
|
1176
1148
|
|
|
1177
|
-
def _get_beamformer(self):
|
|
1178
|
-
return self._beamformer
|
|
1179
|
-
|
|
1180
|
-
def _set_beamformer(self, beamformer):
|
|
1181
|
-
msg = (
|
|
1182
|
-
f"Deprecated use of 'beamformer' trait in class {self.__class__.__name__}. "
|
|
1183
|
-
'Please set :attr:`freq_data`, :attr:`steer`, :attr:`r_diag` directly. '
|
|
1184
|
-
"Using the 'beamformer' trait will be removed in version 25.07."
|
|
1185
|
-
)
|
|
1186
|
-
warn(
|
|
1187
|
-
msg,
|
|
1188
|
-
DeprecationWarning,
|
|
1189
|
-
stacklevel=2,
|
|
1190
|
-
)
|
|
1191
|
-
self._beamformer = beamformer
|
|
1192
|
-
|
|
1193
1149
|
@cached_property
|
|
1194
1150
|
def _get_digest(self):
|
|
1195
1151
|
return digest(self)
|
|
1196
1152
|
|
|
1197
|
-
@on_trait_change('_beamformer.digest')
|
|
1198
|
-
def delegate_beamformer_traits(self):
|
|
1199
|
-
self.freq_data = self.beamformer.freq_data
|
|
1200
|
-
self.r_diag = self.beamformer.r_diag
|
|
1201
|
-
self.steer = self.beamformer.steer
|
|
1202
|
-
|
|
1203
1153
|
def _calc(self, ind):
|
|
1204
|
-
"""
|
|
1154
|
+
"""
|
|
1155
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1205
1156
|
|
|
1206
1157
|
This is an internal helper function that is automatically called when
|
|
1207
1158
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1216,14 +1167,13 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1216
1167
|
Returns
|
|
1217
1168
|
-------
|
|
1218
1169
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1219
|
-
|
|
1220
1170
|
"""
|
|
1221
1171
|
f = self._f
|
|
1222
1172
|
normfactor = self.sig_loss_norm()
|
|
1223
1173
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
1224
1174
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1225
1175
|
for i in ind:
|
|
1226
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1176
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
1227
1177
|
y = beamformerFreq(
|
|
1228
1178
|
param_steer_type,
|
|
1229
1179
|
self.r_diag,
|
|
@@ -1232,7 +1182,7 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1232
1182
|
csm,
|
|
1233
1183
|
)[0]
|
|
1234
1184
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1235
|
-
indNegSign = sign(y) < 0
|
|
1185
|
+
indNegSign = np.sign(y) < 0
|
|
1236
1186
|
y[indNegSign] = 0.0
|
|
1237
1187
|
x = y.copy()
|
|
1238
1188
|
p.freq = f[i]
|
|
@@ -1242,12 +1192,13 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1242
1192
|
self._fr[i] = 1
|
|
1243
1193
|
|
|
1244
1194
|
|
|
1245
|
-
@deprecated_alias({'max_iter': 'n_iter'})
|
|
1246
1195
|
class BeamformerDamasPlus(BeamformerDamas):
|
|
1247
|
-
"""
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1196
|
+
"""
|
|
1197
|
+
DAMAS deconvolution :cite:`Brooks2006` for solving the system of equations.
|
|
1198
|
+
|
|
1199
|
+
Instead of the original Gauss-Seidel iterations, this class employs the NNLS or linear
|
|
1200
|
+
programming solvers from scipy.optimize or one of several optimization algorithms from the
|
|
1201
|
+
scikit-learn module. Needs a-priori delay-and-sum beamforming (:class:`BeamformerBase`).
|
|
1251
1202
|
"""
|
|
1252
1203
|
|
|
1253
1204
|
#: Type of fit method to be used ('LassoLars',
|
|
@@ -1283,7 +1234,8 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1283
1234
|
return digest(self)
|
|
1284
1235
|
|
|
1285
1236
|
def _calc(self, ind):
|
|
1286
|
-
"""
|
|
1237
|
+
"""
|
|
1238
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1287
1239
|
|
|
1288
1240
|
This is an internal helper function that is automatically called when
|
|
1289
1241
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1298,7 +1250,6 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1298
1250
|
Returns
|
|
1299
1251
|
-------
|
|
1300
1252
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1301
|
-
|
|
1302
1253
|
"""
|
|
1303
1254
|
f = self._f
|
|
1304
1255
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
@@ -1306,7 +1257,7 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1306
1257
|
normfactor = self.sig_loss_norm()
|
|
1307
1258
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1308
1259
|
for i in ind:
|
|
1309
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1260
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
1310
1261
|
y = beamformerFreq(
|
|
1311
1262
|
param_steer_type,
|
|
1312
1263
|
self.r_diag,
|
|
@@ -1315,7 +1266,7 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1315
1266
|
csm,
|
|
1316
1267
|
)[0]
|
|
1317
1268
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1318
|
-
indNegSign = sign(y) < 0
|
|
1269
|
+
indNegSign = np.sign(y) < 0
|
|
1319
1270
|
y[indNegSign] = 0.0
|
|
1320
1271
|
y *= unit
|
|
1321
1272
|
p.freq = f[i]
|
|
@@ -1346,7 +1297,7 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1346
1297
|
# pipeline approach with StandardScaler does scale in a different way, thus we
|
|
1347
1298
|
# monkeypatch the code and normalize ourselves to make results the same over
|
|
1348
1299
|
# different sklearn versions
|
|
1349
|
-
norms = norm(psf, axis=0)
|
|
1300
|
+
norms = spla.norm(psf, axis=0)
|
|
1350
1301
|
# get rid of annoying sklearn warnings that appear
|
|
1351
1302
|
# for sklearn<1.2 despite any settings
|
|
1352
1303
|
with warnings.catch_warnings():
|
|
@@ -1359,23 +1310,16 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1359
1310
|
|
|
1360
1311
|
|
|
1361
1312
|
class BeamformerOrth(BeamformerBase):
|
|
1362
|
-
"""
|
|
1313
|
+
"""
|
|
1314
|
+
Orthogonal deconvolution algorithm.
|
|
1363
1315
|
|
|
1364
1316
|
See :cite:`Sarradj2010` for details.
|
|
1365
1317
|
New faster implementation without explicit (:class:`BeamformerEig`).
|
|
1366
1318
|
"""
|
|
1367
1319
|
|
|
1368
|
-
#: (only for backward compatibility) :class:`BeamformerEig` object
|
|
1369
|
-
#: if set, provides :attr:`freq_data`, :attr:`steer`, :attr:`r_diag`
|
|
1370
|
-
#: if not set, these have to be set explicitly.
|
|
1371
|
-
beamformer = Property(transient=True)
|
|
1372
|
-
|
|
1373
|
-
# private storage of beamformer instance
|
|
1374
|
-
_beamformer = Instance(BeamformerEig)
|
|
1375
|
-
|
|
1376
1320
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
1377
1321
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
1378
|
-
eva_list = CArray(dtype=int, value=array([-1]), desc='components')
|
|
1322
|
+
eva_list = CArray(dtype=int, value=np.array([-1]), desc='components')
|
|
1379
1323
|
|
|
1380
1324
|
#: Number of components to consider, defaults to 1. If set,
|
|
1381
1325
|
#: :attr:`eva_list` will contain
|
|
@@ -1388,39 +1332,18 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1388
1332
|
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['eva_list'],
|
|
1389
1333
|
)
|
|
1390
1334
|
|
|
1391
|
-
def _get_beamformer(self):
|
|
1392
|
-
return self._beamformer
|
|
1393
|
-
|
|
1394
|
-
def _set_beamformer(self, beamformer):
|
|
1395
|
-
msg = (
|
|
1396
|
-
f"Deprecated use of 'beamformer' trait in class {self.__class__.__name__}. "
|
|
1397
|
-
'Please set :attr:`freq_data`, :attr:`steer`, :attr:`r_diag` directly. '
|
|
1398
|
-
"Using the 'beamformer' trait will be removed in version 25.07."
|
|
1399
|
-
)
|
|
1400
|
-
warn(
|
|
1401
|
-
msg,
|
|
1402
|
-
DeprecationWarning,
|
|
1403
|
-
stacklevel=2,
|
|
1404
|
-
)
|
|
1405
|
-
self._beamformer = beamformer
|
|
1406
|
-
|
|
1407
1335
|
@cached_property
|
|
1408
1336
|
def _get_digest(self):
|
|
1409
1337
|
return digest(self)
|
|
1410
1338
|
|
|
1411
|
-
@
|
|
1412
|
-
def
|
|
1413
|
-
self.freq_data = self.beamformer.freq_data
|
|
1414
|
-
self.r_diag = self.beamformer.r_diag
|
|
1415
|
-
self.steer = self.beamformer.steer
|
|
1416
|
-
|
|
1417
|
-
@on_trait_change('n')
|
|
1418
|
-
def set_eva_list(self):
|
|
1339
|
+
@observe('n')
|
|
1340
|
+
def _update_eva_list(self, event): # noqa ARG002
|
|
1419
1341
|
"""Sets the list of eigenvalues to consider."""
|
|
1420
|
-
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
1342
|
+
self.eva_list = np.arange(-1, -1 - self.n, -1)
|
|
1421
1343
|
|
|
1422
1344
|
def _calc(self, ind):
|
|
1423
|
-
"""
|
|
1345
|
+
"""
|
|
1346
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1424
1347
|
|
|
1425
1348
|
This is an internal helper function that is automatically called when
|
|
1426
1349
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1435,30 +1358,29 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1435
1358
|
Returns
|
|
1436
1359
|
-------
|
|
1437
1360
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1438
|
-
|
|
1439
1361
|
"""
|
|
1440
1362
|
f = self._f
|
|
1441
1363
|
num_channels = self.freq_data.num_channels
|
|
1442
1364
|
normfactor = self.sig_loss_norm()
|
|
1443
1365
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1444
1366
|
for i in ind:
|
|
1445
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
1446
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
1367
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
1368
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
1447
1369
|
for n in self.eva_list:
|
|
1448
1370
|
beamformerOutput = beamformerFreq(
|
|
1449
1371
|
param_steer_type,
|
|
1450
1372
|
self.r_diag,
|
|
1451
1373
|
normfactor,
|
|
1452
1374
|
steer_vector(f[i]),
|
|
1453
|
-
(ones(1), eve[:, n].reshape((-1, 1))),
|
|
1375
|
+
(np.ones(1), eve[:, n].reshape((-1, 1))),
|
|
1454
1376
|
)[0]
|
|
1455
1377
|
self._ac[i, beamformerOutput.argmax()] += eva[n] / num_channels
|
|
1456
1378
|
self._fr[i] = 1
|
|
1457
1379
|
|
|
1458
1380
|
|
|
1459
|
-
@deprecated_alias({'n': 'n_iter'})
|
|
1460
1381
|
class BeamformerCleansc(BeamformerBase):
|
|
1461
|
-
"""
|
|
1382
|
+
"""
|
|
1383
|
+
CLEAN-SC deconvolution algorithm.
|
|
1462
1384
|
|
|
1463
1385
|
See :cite:`Sijtsma2007` for details.
|
|
1464
1386
|
Classic delay-and-sum beamforming is already included.
|
|
@@ -1485,7 +1407,8 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1485
1407
|
return digest(self)
|
|
1486
1408
|
|
|
1487
1409
|
def _calc(self, ind):
|
|
1488
|
-
"""
|
|
1410
|
+
"""
|
|
1411
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1489
1412
|
|
|
1490
1413
|
This is an internal helper function that is automatically called when
|
|
1491
1414
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1500,18 +1423,17 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1500
1423
|
Returns
|
|
1501
1424
|
-------
|
|
1502
1425
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1503
|
-
|
|
1504
1426
|
"""
|
|
1505
1427
|
f = self._f
|
|
1506
1428
|
normfactor = self.sig_loss_norm()
|
|
1507
1429
|
num_channels = self.freq_data.num_channels
|
|
1508
|
-
result = zeros((self.steer.grid.size), 'f')
|
|
1430
|
+
result = np.zeros((self.steer.grid.size), 'f')
|
|
1509
1431
|
J = num_channels * 2 if not self.n_iter else self.n_iter
|
|
1510
|
-
powers = zeros(J, 'd')
|
|
1432
|
+
powers = np.zeros(J, 'd')
|
|
1511
1433
|
|
|
1512
1434
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1513
1435
|
for i in ind:
|
|
1514
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
1436
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
1515
1437
|
# h = self.steer._beamformerCall(f[i], self.r_diag, normfactor, (csm,))[0]
|
|
1516
1438
|
h = beamformerFreq(param_steer_type, self.r_diag, normfactor, steer_vector(f[i]), csm)[0]
|
|
1517
1439
|
# CLEANSC Iteration
|
|
@@ -1522,25 +1444,25 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1522
1444
|
result[xi_max] += self.damp * hmax
|
|
1523
1445
|
if j > self.stopn and hmax > powers[j - self.stopn]:
|
|
1524
1446
|
break
|
|
1525
|
-
wmax = self.steer.steer_vector(f[i], xi_max) * sqrt(normfactor)
|
|
1447
|
+
wmax = self.steer.steer_vector(f[i], xi_max) * np.sqrt(normfactor)
|
|
1526
1448
|
wmax = wmax[0].conj() # as old code worked with conjugated csm..should be updated
|
|
1527
1449
|
hh = wmax.copy()
|
|
1528
|
-
D1 = dot(csm.T - diag(diag(csm)), wmax) / hmax
|
|
1450
|
+
D1 = np.dot(csm.T - np.diag(np.diag(csm)), wmax) / hmax
|
|
1529
1451
|
ww = wmax.conj() * wmax
|
|
1530
|
-
for
|
|
1452
|
+
for _ in range(20):
|
|
1531
1453
|
H = hh.conj() * hh
|
|
1532
|
-
hh = (D1 + H * wmax) / sqrt(1 + dot(ww, H))
|
|
1533
|
-
hh = hh[:, newaxis]
|
|
1454
|
+
hh = (D1 + H * wmax) / np.sqrt(1 + np.dot(ww, H))
|
|
1455
|
+
hh = hh[:, np.newaxis]
|
|
1534
1456
|
csm1 = hmax * (hh * hh.conj().T)
|
|
1535
1457
|
|
|
1536
1458
|
# h1 = self.steer._beamformerCall(f[i], self.r_diag, normfactor, \
|
|
1537
|
-
# (array((hmax, ))[newaxis, :], hh[newaxis, :].conjugate()))[0]
|
|
1459
|
+
# (np.array((hmax, ))[np.newaxis, :], hh[np.newaxis, :].conjugate()))[0]
|
|
1538
1460
|
h1 = beamformerFreq(
|
|
1539
1461
|
param_steer_type,
|
|
1540
1462
|
self.r_diag,
|
|
1541
1463
|
normfactor,
|
|
1542
1464
|
steer_vector(f[i]),
|
|
1543
|
-
(array((hmax,)), hh.conj()),
|
|
1465
|
+
(np.array((hmax,)), hh.conj()),
|
|
1544
1466
|
)[0]
|
|
1545
1467
|
h -= self.damp * h1
|
|
1546
1468
|
csm -= self.damp * csm1.T # transpose(0,2,1)
|
|
@@ -1549,19 +1471,12 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1549
1471
|
|
|
1550
1472
|
|
|
1551
1473
|
class BeamformerClean(BeamformerBase):
|
|
1552
|
-
"""
|
|
1474
|
+
"""
|
|
1475
|
+
CLEAN deconvolution algorithm.
|
|
1553
1476
|
|
|
1554
1477
|
See :cite:`Hoegbom1974` for details.
|
|
1555
1478
|
"""
|
|
1556
1479
|
|
|
1557
|
-
#: (only for backward compatibility) :class:`BeamformerBase` object
|
|
1558
|
-
#: if set, provides :attr:`freq_data`, :attr:`steer`, :attr:`r_diag`
|
|
1559
|
-
#: if not set, these have to be set explicitly.
|
|
1560
|
-
beamformer = Property(transient=True)
|
|
1561
|
-
|
|
1562
|
-
# private storage of beamformer instance
|
|
1563
|
-
_beamformer = Instance(BeamformerBase)
|
|
1564
|
-
|
|
1565
1480
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1566
1481
|
psf_precision = Enum('float64', 'float32', desc='precision of PSF.')
|
|
1567
1482
|
|
|
@@ -1584,28 +1499,6 @@ class BeamformerClean(BeamformerBase):
|
|
|
1584
1499
|
def _get_digest(self):
|
|
1585
1500
|
return digest(self)
|
|
1586
1501
|
|
|
1587
|
-
def _get_beamformer(self):
|
|
1588
|
-
return self._beamformer
|
|
1589
|
-
|
|
1590
|
-
def _set_beamformer(self, beamformer):
|
|
1591
|
-
msg = (
|
|
1592
|
-
f"Deprecated use of 'beamformer' trait in class {self.__class__.__name__}. "
|
|
1593
|
-
'Please set :attr:`freq_data`, :attr:`steer`, :attr:`r_diag` directly. '
|
|
1594
|
-
"Using the 'beamformer' trait will be removed in version 25.07."
|
|
1595
|
-
)
|
|
1596
|
-
warn(
|
|
1597
|
-
msg,
|
|
1598
|
-
DeprecationWarning,
|
|
1599
|
-
stacklevel=2,
|
|
1600
|
-
)
|
|
1601
|
-
self._beamformer = beamformer
|
|
1602
|
-
|
|
1603
|
-
@on_trait_change('_beamformer.digest')
|
|
1604
|
-
def delegate_beamformer_traits(self):
|
|
1605
|
-
self.freq_data = self.beamformer.freq_data
|
|
1606
|
-
self.r_diag = self.beamformer.r_diag
|
|
1607
|
-
self.steer = self.beamformer.steer
|
|
1608
|
-
|
|
1609
1502
|
def _calc(self, ind):
|
|
1610
1503
|
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1611
1504
|
|
|
@@ -1622,7 +1515,6 @@ class BeamformerClean(BeamformerBase):
|
|
|
1622
1515
|
Returns
|
|
1623
1516
|
-------
|
|
1624
1517
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1625
|
-
|
|
1626
1518
|
"""
|
|
1627
1519
|
f = self._f
|
|
1628
1520
|
gs = self.steer.grid.size
|
|
@@ -1638,7 +1530,7 @@ class BeamformerClean(BeamformerBase):
|
|
|
1638
1530
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1639
1531
|
for i in ind:
|
|
1640
1532
|
p.freq = f[i]
|
|
1641
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1533
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
1642
1534
|
dirty = beamformerFreq(
|
|
1643
1535
|
param_steer_type,
|
|
1644
1536
|
self.r_diag,
|
|
@@ -1647,16 +1539,16 @@ class BeamformerClean(BeamformerBase):
|
|
|
1647
1539
|
csm,
|
|
1648
1540
|
)[0]
|
|
1649
1541
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1650
|
-
indNegSign = sign(dirty) < 0
|
|
1542
|
+
indNegSign = np.sign(dirty) < 0
|
|
1651
1543
|
dirty[indNegSign] = 0.0
|
|
1652
1544
|
|
|
1653
|
-
clean = zeros(gs, dtype=dirty.dtype)
|
|
1545
|
+
clean = np.zeros(gs, dtype=dirty.dtype)
|
|
1654
1546
|
i_iter = 0
|
|
1655
1547
|
flag = True
|
|
1656
1548
|
while flag:
|
|
1657
1549
|
dirty_sum = abs(dirty).sum(0)
|
|
1658
1550
|
next_max = dirty.argmax(0)
|
|
1659
|
-
p.grid_indices = array([next_max])
|
|
1551
|
+
p.grid_indices = np.array([next_max])
|
|
1660
1552
|
psf = p.psf.reshape(gs)
|
|
1661
1553
|
new_amp = self.damp * dirty[next_max] # / psf[next_max]
|
|
1662
1554
|
clean[next_max] += new_amp
|
|
@@ -1668,9 +1560,9 @@ class BeamformerClean(BeamformerBase):
|
|
|
1668
1560
|
self._fr[i] = 1
|
|
1669
1561
|
|
|
1670
1562
|
|
|
1671
|
-
@deprecated_alias({'max_iter': 'n_iter'})
|
|
1672
1563
|
class BeamformerCMF(BeamformerBase):
|
|
1673
|
-
"""
|
|
1564
|
+
"""
|
|
1565
|
+
Covariance Matrix Fitting algorithm.
|
|
1674
1566
|
|
|
1675
1567
|
This is not really a beamformer, but an inverse method.
|
|
1676
1568
|
See :cite:`Yardibi2008` for details.
|
|
@@ -1737,8 +1629,8 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1737
1629
|
def _get_digest(self):
|
|
1738
1630
|
return digest(self)
|
|
1739
1631
|
|
|
1740
|
-
@
|
|
1741
|
-
def _validate(self):
|
|
1632
|
+
@observe('method')
|
|
1633
|
+
def _validate(self, event): # noqa ARG002
|
|
1742
1634
|
if self.method in ['FISTA', 'Split_Bregman'] and not config.have_pylops:
|
|
1743
1635
|
msg = (
|
|
1744
1636
|
'Cannot import Pylops package. No Pylops installed.'
|
|
@@ -1747,7 +1639,8 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1747
1639
|
raise ImportError(msg)
|
|
1748
1640
|
|
|
1749
1641
|
def _calc(self, ind):
|
|
1750
|
-
"""
|
|
1642
|
+
"""
|
|
1643
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1751
1644
|
|
|
1752
1645
|
This is an internal helper function that is automatically called when
|
|
1753
1646
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1762,13 +1655,12 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1762
1655
|
Returns
|
|
1763
1656
|
-------
|
|
1764
1657
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1765
|
-
|
|
1766
1658
|
"""
|
|
1767
1659
|
f = self._f
|
|
1768
1660
|
|
|
1769
1661
|
# function to repack complex matrices to deal with them in real number space
|
|
1770
1662
|
def realify(matrix):
|
|
1771
|
-
return vstack([matrix.real, matrix.imag])
|
|
1663
|
+
return np.vstack([matrix.real, matrix.imag])
|
|
1772
1664
|
|
|
1773
1665
|
# prepare calculation
|
|
1774
1666
|
nc = self.freq_data.num_channels
|
|
@@ -1776,29 +1668,29 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1776
1668
|
unit = self.unit_mult
|
|
1777
1669
|
|
|
1778
1670
|
for i in ind:
|
|
1779
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
1671
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
1780
1672
|
|
|
1781
1673
|
h = self.steer.transfer(f[i]).T
|
|
1782
1674
|
|
|
1783
1675
|
# reduced Kronecker product (only where solution matrix != 0)
|
|
1784
|
-
Bc = (h[:, :, newaxis] * h.conjugate().T[newaxis, :, :]).transpose(2, 0, 1)
|
|
1676
|
+
Bc = (h[:, :, np.newaxis] * h.conjugate().T[np.newaxis, :, :]).transpose(2, 0, 1)
|
|
1785
1677
|
Ac = Bc.reshape(nc * nc, num_points)
|
|
1786
1678
|
|
|
1787
|
-
# get indices for upper triangular matrices (use tril b/c transposed)
|
|
1788
|
-
ind = reshape(tril(ones((nc, nc))), (nc * nc,)) > 0
|
|
1679
|
+
# get indices for upper triangular matrices (use np.tril b/c transposed)
|
|
1680
|
+
ind = np.reshape(np.tril(np.ones((nc, nc))), (nc * nc,)) > 0
|
|
1789
1681
|
|
|
1790
|
-
ind_im0 = (reshape(eye(nc), (nc * nc,)) == 0)[ind]
|
|
1682
|
+
ind_im0 = (np.reshape(np.eye(nc), (nc * nc,)) == 0)[ind]
|
|
1791
1683
|
if self.r_diag:
|
|
1792
1684
|
# omit main diagonal for noise reduction
|
|
1793
|
-
ind_reim = hstack([ind_im0, ind_im0])
|
|
1685
|
+
ind_reim = np.hstack([ind_im0, ind_im0])
|
|
1794
1686
|
else:
|
|
1795
1687
|
# take all real parts -- also main diagonal
|
|
1796
|
-
ind_reim = hstack([ones(size(ind_im0)) > 0, ind_im0])
|
|
1688
|
+
ind_reim = np.hstack([np.ones(np.size(ind_im0)) > 0, ind_im0])
|
|
1797
1689
|
ind_reim[0] = True # why this ?
|
|
1798
1690
|
|
|
1799
1691
|
A = realify(Ac[ind, :])[ind_reim, :]
|
|
1800
1692
|
# use csm.T for column stacking reshape!
|
|
1801
|
-
R = realify(reshape(csm.T, (nc * nc, 1))[ind, :])[ind_reim, :] * unit
|
|
1693
|
+
R = realify(np.reshape(csm.T, (nc * nc, 1))[ind, :])[ind_reim, :] * unit
|
|
1802
1694
|
# choose method
|
|
1803
1695
|
if self.method == 'LassoLars':
|
|
1804
1696
|
model = LassoLars(alpha=self.alpha * unit, max_iter=self.n_iter, positive=True, **sklearn_ndict)
|
|
@@ -1852,13 +1744,13 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1852
1744
|
# function
|
|
1853
1745
|
func = x.T @ A.T @ A @ x - 2 * R.T @ A @ x + R.T @ R
|
|
1854
1746
|
# derivitaive
|
|
1855
|
-
der = 2 * A.T @ A @ x.T[:, newaxis] - 2 * A.T @ R
|
|
1747
|
+
der = 2 * A.T @ A @ x.T[:, np.newaxis] - 2 * A.T @ R
|
|
1856
1748
|
return func[0].T, der[:, 0]
|
|
1857
1749
|
|
|
1858
1750
|
# initial guess
|
|
1859
|
-
x0 = ones([num_points])
|
|
1751
|
+
x0 = np.ones([num_points])
|
|
1860
1752
|
# boundaries - set to non negative
|
|
1861
|
-
boundaries = tile((0,
|
|
1753
|
+
boundaries = np.tile((0, np.inf), (len(x0), 1))
|
|
1862
1754
|
|
|
1863
1755
|
# optimize
|
|
1864
1756
|
self._ac[i], yval, dicts = fmin_l_bfgs_b(
|
|
@@ -1872,10 +1764,8 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1872
1764
|
factr=10000000.0,
|
|
1873
1765
|
pgtol=1e-05,
|
|
1874
1766
|
epsilon=1e-08,
|
|
1875
|
-
iprint=-1,
|
|
1876
1767
|
maxfun=15000,
|
|
1877
1768
|
maxiter=self.n_iter,
|
|
1878
|
-
disp=None,
|
|
1879
1769
|
callback=None,
|
|
1880
1770
|
maxls=20,
|
|
1881
1771
|
)
|
|
@@ -1886,7 +1776,7 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1886
1776
|
# pipeline approach with StandardScaler does scale in a different way, thus we
|
|
1887
1777
|
# monkeypatch the code and normalize ourselves to make results the same over
|
|
1888
1778
|
# different sklearn versions
|
|
1889
|
-
norms = norm(A, axis=0)
|
|
1779
|
+
norms = spla.norm(A, axis=0)
|
|
1890
1780
|
# get rid of sklearn warnings that appear for sklearn<1.2 despite any settings
|
|
1891
1781
|
with warnings.catch_warnings():
|
|
1892
1782
|
warnings.simplefilter('ignore', category=FutureWarning)
|
|
@@ -1897,9 +1787,9 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1897
1787
|
self._fr[i] = 1
|
|
1898
1788
|
|
|
1899
1789
|
|
|
1900
|
-
@deprecated_alias({'max_iter': 'n_iter'})
|
|
1901
1790
|
class BeamformerSODIX(BeamformerBase):
|
|
1902
|
-
"""
|
|
1791
|
+
"""
|
|
1792
|
+
Source directivity modeling in the cross-spectral matrix (SODIX) algorithm.
|
|
1903
1793
|
|
|
1904
1794
|
See :cite:`Funke2017` and :cite:`Oertwig2019` for details.
|
|
1905
1795
|
"""
|
|
@@ -1949,7 +1839,8 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1949
1839
|
return digest(self)
|
|
1950
1840
|
|
|
1951
1841
|
def _calc(self, ind):
|
|
1952
|
-
"""
|
|
1842
|
+
"""
|
|
1843
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1953
1844
|
|
|
1954
1845
|
This is an internal helper function that is automatically called when
|
|
1955
1846
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1964,7 +1855,6 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1964
1855
|
Returns
|
|
1965
1856
|
-------
|
|
1966
1857
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1967
|
-
|
|
1968
1858
|
"""
|
|
1969
1859
|
# prepare calculation
|
|
1970
1860
|
f = self._f
|
|
@@ -1979,14 +1869,17 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1979
1869
|
for i in range(1, ind.max() + 1):
|
|
1980
1870
|
if not self._fr[i]:
|
|
1981
1871
|
# measured csm
|
|
1982
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
1872
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
1983
1873
|
# transfer function
|
|
1984
1874
|
h = self.steer.transfer(f[i]).T
|
|
1985
1875
|
|
|
1986
1876
|
if self.method == 'fmin_l_bfgs_b':
|
|
1987
1877
|
# function to minimize
|
|
1988
1878
|
def function(directions):
|
|
1989
|
-
"""
|
|
1879
|
+
"""
|
|
1880
|
+
Calculates SODIX objective function and derivatives for optimization.
|
|
1881
|
+
|
|
1882
|
+
Parameters
|
|
1990
1883
|
----------
|
|
1991
1884
|
directions
|
|
1992
1885
|
[num_points*num_mics]
|
|
@@ -1997,41 +1890,43 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1997
1890
|
[1]
|
|
1998
1891
|
derdrl - derivitaives in direction of D
|
|
1999
1892
|
[num_mics*num_points].
|
|
2000
|
-
|
|
2001
1893
|
"""
|
|
2002
1894
|
#### the sodix function ####
|
|
2003
1895
|
Djm = directions.reshape([num_points, num_mics])
|
|
2004
1896
|
p = h.T * Djm
|
|
2005
|
-
csm_mod = dot(p.T, p.conj())
|
|
1897
|
+
csm_mod = np.dot(p.T, p.conj())
|
|
2006
1898
|
Q = csm - csm_mod
|
|
2007
|
-
func = sum((
|
|
1899
|
+
func = np.sum((np.abs(Q)) ** 2)
|
|
2008
1900
|
|
|
2009
1901
|
# subscripts and operands for numpy einsum and einsum_path
|
|
2010
1902
|
subscripts = 'rl,rm,ml->rl'
|
|
2011
1903
|
operands = (h.T, h.T.conj() * Djm, Q)
|
|
2012
|
-
es_path = einsum_path(subscripts, *operands, optimize='greedy')[0]
|
|
1904
|
+
es_path = np.einsum_path(subscripts, *operands, optimize='greedy')[0]
|
|
2013
1905
|
|
|
2014
1906
|
#### the sodix derivative ####
|
|
2015
|
-
derdrl = einsum(subscripts, *operands, optimize=es_path)
|
|
2016
|
-
derdrl = -4 * real(derdrl)
|
|
1907
|
+
derdrl = np.einsum(subscripts, *operands, optimize=es_path)
|
|
1908
|
+
derdrl = -4 * np.real(derdrl)
|
|
2017
1909
|
return func, derdrl.ravel()
|
|
2018
1910
|
|
|
2019
1911
|
##### initial guess ####
|
|
2020
1912
|
if not self._fr[(i - 1)]:
|
|
2021
|
-
D0 = ones([num_points, num_mics])
|
|
1913
|
+
D0 = np.ones([num_points, num_mics])
|
|
2022
1914
|
else:
|
|
2023
|
-
D0 = sqrt(
|
|
1915
|
+
D0 = np.sqrt(
|
|
2024
1916
|
self._ac[(i - 1)]
|
|
2025
|
-
* real(
|
|
1917
|
+
* np.real(
|
|
1918
|
+
np.trace(csm)
|
|
1919
|
+
/ np.trace(np.array(self.freq_data.csm[i - 1], dtype='complex128', copy=True))
|
|
1920
|
+
),
|
|
2026
1921
|
)
|
|
2027
1922
|
|
|
2028
1923
|
# boundaries - set to non negative [2*(num_points*num_mics)]
|
|
2029
|
-
boundaries = tile((0,
|
|
1924
|
+
boundaries = np.tile((0, np.inf), (num_points * num_mics, 1))
|
|
2030
1925
|
|
|
2031
1926
|
# optimize with gradient solver
|
|
2032
1927
|
# see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html
|
|
2033
1928
|
|
|
2034
|
-
qi = ones([num_points, num_mics])
|
|
1929
|
+
qi = np.ones([num_points, num_mics])
|
|
2035
1930
|
qi, yval, dicts = fmin_l_bfgs_b(
|
|
2036
1931
|
function,
|
|
2037
1932
|
D0,
|
|
@@ -2042,10 +1937,8 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2042
1937
|
factr=100.0,
|
|
2043
1938
|
pgtol=1e-12,
|
|
2044
1939
|
epsilon=1e-08,
|
|
2045
|
-
iprint=-1,
|
|
2046
1940
|
maxfun=1500000,
|
|
2047
1941
|
maxiter=self.n_iter,
|
|
2048
|
-
disp=-1,
|
|
2049
1942
|
callback=None,
|
|
2050
1943
|
maxls=20,
|
|
2051
1944
|
)
|
|
@@ -2056,9 +1949,9 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2056
1949
|
self._fr[i] = 1
|
|
2057
1950
|
|
|
2058
1951
|
|
|
2059
|
-
@deprecated_alias({'max_iter': 'n_iter'})
|
|
2060
1952
|
class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
2061
|
-
"""
|
|
1953
|
+
"""
|
|
1954
|
+
Beamforming GIB methods with different normalizations.
|
|
2062
1955
|
|
|
2063
1956
|
See :cite:`Suzuki2011` for details.
|
|
2064
1957
|
"""
|
|
@@ -2149,7 +2042,8 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2149
2042
|
return min(nm - 1, na)
|
|
2150
2043
|
|
|
2151
2044
|
def _calc(self, ind):
|
|
2152
|
-
"""
|
|
2045
|
+
"""
|
|
2046
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2153
2047
|
|
|
2154
2048
|
This is an internal helper function that is automatically called when
|
|
2155
2049
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -2164,14 +2058,13 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2164
2058
|
Returns
|
|
2165
2059
|
-------
|
|
2166
2060
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
2167
|
-
|
|
2168
2061
|
"""
|
|
2169
2062
|
f = self._f
|
|
2170
2063
|
n = int(self.na) # number of eigenvalues
|
|
2171
2064
|
m = int(self.m) # number of first eigenvalue
|
|
2172
2065
|
num_channels = self.freq_data.num_channels # number of channels
|
|
2173
2066
|
num_points = self.steer.grid.size
|
|
2174
|
-
hh = zeros((1, num_points, num_channels), dtype='D')
|
|
2067
|
+
hh = np.zeros((1, num_points, num_channels), dtype='D')
|
|
2175
2068
|
|
|
2176
2069
|
# Generate a cross spectral matrix, and perform the eigenvalue decomposition
|
|
2177
2070
|
for i in ind:
|
|
@@ -2180,79 +2073,83 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2180
2073
|
hh = self.steer.transfer(f[i])
|
|
2181
2074
|
A = hh.T
|
|
2182
2075
|
# eigenvalues and vectors
|
|
2183
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
2184
|
-
eva, eve = eigh(csm)
|
|
2076
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
2077
|
+
eva, eve = spla.eigh(csm)
|
|
2185
2078
|
eva = eva[::-1]
|
|
2186
2079
|
eve = eve[:, ::-1]
|
|
2187
2080
|
# set small values zo 0, lowers numerical errors in simulated data
|
|
2188
2081
|
eva[eva < max(eva) / 1e12] = 0
|
|
2189
2082
|
# init sources
|
|
2190
|
-
qi = zeros([n + m, num_points], dtype='complex128')
|
|
2083
|
+
qi = np.zeros([n + m, num_points], dtype='complex128')
|
|
2191
2084
|
# Select the number of coherent modes to be processed referring to the eigenvalue
|
|
2192
2085
|
# distribution.
|
|
2193
2086
|
for s in list(range(m, n + m)):
|
|
2194
2087
|
if eva[s] > 0:
|
|
2195
2088
|
# Generate the corresponding eigenmodes
|
|
2196
|
-
emode = array(sqrt(eva[s]) * eve[:, s], dtype='complex128')
|
|
2089
|
+
emode = np.array(np.sqrt(eva[s]) * eve[:, s], dtype='complex128')
|
|
2197
2090
|
# choose method for computation
|
|
2198
2091
|
if self.method == 'Suzuki':
|
|
2199
2092
|
leftpoints = num_points
|
|
2200
|
-
locpoints = arange(num_points)
|
|
2201
|
-
weights = diag(ones(num_points))
|
|
2202
|
-
epsilon = arange(self.n_iter)
|
|
2203
|
-
for it in arange(self.n_iter):
|
|
2093
|
+
locpoints = np.arange(num_points)
|
|
2094
|
+
weights = np.diag(np.ones(num_points))
|
|
2095
|
+
epsilon = np.arange(self.n_iter)
|
|
2096
|
+
for it in np.arange(self.n_iter):
|
|
2204
2097
|
if num_channels <= leftpoints:
|
|
2205
|
-
AWA = dot(dot(A[:, locpoints], weights), A[:, locpoints].conj().T)
|
|
2206
|
-
epsilon[it] = max(
|
|
2207
|
-
qi[s, locpoints] = dot(
|
|
2208
|
-
dot(
|
|
2209
|
-
dot(weights, A[:, locpoints].conj().T),
|
|
2210
|
-
inv(AWA + eye(num_channels) * epsilon[it]),
|
|
2098
|
+
AWA = np.dot(np.dot(A[:, locpoints], weights), A[:, locpoints].conj().T)
|
|
2099
|
+
epsilon[it] = max(np.abs(spla.eigvals(AWA))) * self.eps_perc
|
|
2100
|
+
qi[s, locpoints] = np.dot(
|
|
2101
|
+
np.dot(
|
|
2102
|
+
np.dot(weights, A[:, locpoints].conj().T),
|
|
2103
|
+
spla.inv(AWA + np.eye(num_channels) * epsilon[it]),
|
|
2211
2104
|
),
|
|
2212
2105
|
emode,
|
|
2213
2106
|
)
|
|
2214
2107
|
elif num_channels > leftpoints:
|
|
2215
|
-
AA = dot(A[:, locpoints].conj().T, A[:, locpoints])
|
|
2216
|
-
epsilon[it] = max(
|
|
2217
|
-
qi[s, locpoints] = dot(
|
|
2218
|
-
dot(inv(AA + inv(weights) * epsilon[it]), A[:, locpoints].conj().T),
|
|
2108
|
+
AA = np.dot(A[:, locpoints].conj().T, A[:, locpoints])
|
|
2109
|
+
epsilon[it] = max(np.abs(spla.eigvals(AA))) * self.eps_perc
|
|
2110
|
+
qi[s, locpoints] = np.dot(
|
|
2111
|
+
np.dot(spla.inv(AA + spla.inv(weights) * epsilon[it]), A[:, locpoints].conj().T),
|
|
2219
2112
|
emode,
|
|
2220
2113
|
)
|
|
2221
2114
|
if self.beta < 1 and it > 1:
|
|
2222
2115
|
# Reorder from the greatest to smallest magnitude to define a
|
|
2223
2116
|
# reduced-point source distribution, and reform a reduced transfer
|
|
2224
2117
|
# matrix
|
|
2225
|
-
leftpoints = int(round(num_points * self.beta ** (it + 1)))
|
|
2226
|
-
idx = argsort(abs(qi[s, locpoints]))[::-1]
|
|
2118
|
+
leftpoints = int(np.round(num_points * self.beta ** (it + 1)))
|
|
2119
|
+
idx = np.argsort(abs(qi[s, locpoints]))[::-1]
|
|
2227
2120
|
# print(it, leftpoints, locpoints, idx )
|
|
2228
|
-
locpoints = delete(locpoints, [idx[leftpoints::]])
|
|
2229
|
-
qix = zeros([n + m, leftpoints], dtype='complex128')
|
|
2121
|
+
locpoints = np.delete(locpoints, [idx[leftpoints::]])
|
|
2122
|
+
qix = np.zeros([n + m, leftpoints], dtype='complex128')
|
|
2230
2123
|
qix[s, :] = qi[s, locpoints]
|
|
2231
2124
|
# calc weights for next iteration
|
|
2232
|
-
weights = diag(
|
|
2125
|
+
weights = np.diag(np.abs(qix[s, :]) ** (2 - self.pnorm))
|
|
2233
2126
|
else:
|
|
2234
|
-
weights = diag(
|
|
2127
|
+
weights = np.diag(np.abs(qi[s, :]) ** (2 - self.pnorm))
|
|
2235
2128
|
|
|
2236
2129
|
elif self.method == 'InverseIRLS':
|
|
2237
|
-
weights = eye(num_points)
|
|
2238
|
-
locpoints = arange(num_points)
|
|
2239
|
-
for _it in arange(self.n_iter):
|
|
2130
|
+
weights = np.eye(num_points)
|
|
2131
|
+
locpoints = np.arange(num_points)
|
|
2132
|
+
for _it in np.arange(self.n_iter):
|
|
2240
2133
|
if num_channels <= num_points:
|
|
2241
|
-
wtwi = inv(dot(weights.T, weights))
|
|
2134
|
+
wtwi = spla.inv(np.dot(weights.T, weights))
|
|
2242
2135
|
aH = A.conj().T
|
|
2243
|
-
qi[s, :] = dot(
|
|
2244
|
-
|
|
2245
|
-
|
|
2136
|
+
qi[s, :] = np.dot(
|
|
2137
|
+
np.dot(wtwi, aH), np.dot(spla.inv(np.dot(A, np.dot(wtwi, aH))), emode)
|
|
2138
|
+
)
|
|
2139
|
+
weights = np.diag(np.abs(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2140
|
+
weights = weights / np.sum(np.abs(weights))
|
|
2246
2141
|
elif num_channels > num_points:
|
|
2247
|
-
wtw = dot(weights.T, weights)
|
|
2248
|
-
qi[s, :] =
|
|
2249
|
-
|
|
2250
|
-
|
|
2142
|
+
wtw = np.dot(weights.T, weights)
|
|
2143
|
+
qi[s, :] = np.dot(
|
|
2144
|
+
np.dot(spla.inv(np.dot(np.dot(A.conj.T, wtw), A)), np.dot(A.conj().T, wtw)), emode
|
|
2145
|
+
)
|
|
2146
|
+
weights = np.diag(np.abs(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2147
|
+
weights = weights / np.sum(np.abs(weights))
|
|
2251
2148
|
else:
|
|
2252
|
-
locpoints = arange(num_points)
|
|
2149
|
+
locpoints = np.arange(num_points)
|
|
2253
2150
|
unit = self.unit_mult
|
|
2254
|
-
AB = vstack([hstack([A.real, -A.imag]), hstack([A.imag, A.real])])
|
|
2255
|
-
R = hstack([emode.real.T, emode.imag.T]) * unit
|
|
2151
|
+
AB = np.vstack([np.hstack([A.real, -A.imag]), np.hstack([A.imag, A.real])])
|
|
2152
|
+
R = np.hstack([emode.real.T, emode.imag.T]) * unit
|
|
2256
2153
|
if self.method == 'LassoLars':
|
|
2257
2154
|
model = LassoLars(alpha=self.alpha * unit, max_iter=self.n_iter, positive=True)
|
|
2258
2155
|
elif self.method == 'LassoLarsBIC':
|
|
@@ -2270,7 +2167,7 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2270
2167
|
# way, thus we monkeypatch the code and normalize
|
|
2271
2168
|
# ourselves to make results the same over different
|
|
2272
2169
|
# sklearn versions
|
|
2273
|
-
norms = norm(AB, axis=0)
|
|
2170
|
+
norms = spla.norm(AB, axis=0)
|
|
2274
2171
|
# get rid of annoying sklearn warnings that appear
|
|
2275
2172
|
# for sklearn<1.2 despite any settings
|
|
2276
2173
|
with warnings.catch_warnings():
|
|
@@ -2278,7 +2175,7 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2278
2175
|
# normalized A
|
|
2279
2176
|
model.fit(AB / norms, R)
|
|
2280
2177
|
# recover normalization in the coef's
|
|
2281
|
-
qi_real, qi_imag = hsplit(model.coef_[:] / norms / unit, 2)
|
|
2178
|
+
qi_real, qi_imag = np.hsplit(model.coef_[:] / norms / unit, 2)
|
|
2282
2179
|
# print(s,qi.size)
|
|
2283
2180
|
qi[s, locpoints] = qi_real + qi_imag * 1j
|
|
2284
2181
|
else:
|
|
@@ -2289,8 +2186,8 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2289
2186
|
)
|
|
2290
2187
|
# Generate source maps of all selected eigenmodes, and superpose source intensity
|
|
2291
2188
|
# for each source type.
|
|
2292
|
-
temp = zeros(num_points)
|
|
2293
|
-
temp[locpoints] = sum(
|
|
2189
|
+
temp = np.zeros(num_points)
|
|
2190
|
+
temp[locpoints] = np.sum(np.abs(qi[:, locpoints]) ** 2, axis=0)
|
|
2294
2191
|
self._ac[i] = temp
|
|
2295
2192
|
self._fr[i] = 1
|
|
2296
2193
|
|
|
@@ -2308,7 +2205,8 @@ class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
|
2308
2205
|
return self._gpos
|
|
2309
2206
|
|
|
2310
2207
|
def integrate(self, sector):
|
|
2311
|
-
"""
|
|
2208
|
+
"""
|
|
2209
|
+
Integrates result map over a given sector.
|
|
2312
2210
|
|
|
2313
2211
|
Parameters
|
|
2314
2212
|
----------
|
|
@@ -2319,7 +2217,6 @@ class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
|
2319
2217
|
-------
|
|
2320
2218
|
array of floats
|
|
2321
2219
|
The spectrum (all calculated frequency bands) for the integrated sector.
|
|
2322
|
-
|
|
2323
2220
|
"""
|
|
2324
2221
|
if not isinstance(sector, Sector):
|
|
2325
2222
|
msg = (
|
|
@@ -2332,21 +2229,22 @@ class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
|
2332
2229
|
|
|
2333
2230
|
ind = self.subdomain(sector)
|
|
2334
2231
|
r = self.result
|
|
2335
|
-
h = zeros(r.shape[0])
|
|
2232
|
+
h = np.zeros(r.shape[0])
|
|
2336
2233
|
for i in range(r.shape[0]):
|
|
2337
2234
|
h[i] = r[i][ind].sum()
|
|
2338
2235
|
return h
|
|
2339
2236
|
|
|
2340
2237
|
|
|
2341
2238
|
class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
2342
|
-
"""
|
|
2239
|
+
"""
|
|
2240
|
+
Orthogonal beamforming without predefined grid.
|
|
2343
2241
|
|
|
2344
2242
|
See :cite:`Sarradj2022` for details.
|
|
2345
2243
|
"""
|
|
2346
2244
|
|
|
2347
2245
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
2348
2246
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
2349
|
-
eva_list = CArray(dtype=int, value=array([-1]), desc='components')
|
|
2247
|
+
eva_list = CArray(dtype=int, value=np.array([-1]), desc='components')
|
|
2350
2248
|
|
|
2351
2249
|
#: Number of components to consider, defaults to 1. If set,
|
|
2352
2250
|
#: :attr:`eva_list` will contain
|
|
@@ -2383,17 +2281,18 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2383
2281
|
def _get_digest(self):
|
|
2384
2282
|
return digest(self)
|
|
2385
2283
|
|
|
2386
|
-
@
|
|
2387
|
-
def
|
|
2284
|
+
@observe('n')
|
|
2285
|
+
def _update_eva_list(self, event): # noqa ARG002
|
|
2388
2286
|
"""Sets the list of eigenvalues to consider."""
|
|
2389
|
-
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
2287
|
+
self.eva_list = np.arange(-1, -1 - self.n, -1)
|
|
2390
2288
|
|
|
2391
2289
|
@property_depends_on('n')
|
|
2392
2290
|
def _get_size(self):
|
|
2393
2291
|
return self.n * self.freq_data.fftfreq().shape[0]
|
|
2394
2292
|
|
|
2395
2293
|
def _calc(self, ind):
|
|
2396
|
-
"""
|
|
2294
|
+
"""
|
|
2295
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2397
2296
|
|
|
2398
2297
|
This is an internal helper function that is automatically called when
|
|
2399
2298
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -2408,13 +2307,12 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2408
2307
|
Returns
|
|
2409
2308
|
-------
|
|
2410
2309
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
2411
|
-
|
|
2412
2310
|
"""
|
|
2413
2311
|
f = self._f
|
|
2414
2312
|
normfactor = self.sig_loss_norm()
|
|
2415
2313
|
num_channels = self.freq_data.num_channels
|
|
2416
2314
|
# eigenvalue number list in standard form from largest to smallest
|
|
2417
|
-
eva_list = unique(self.eva_list % self.steer.mics.num_mics)[::-1]
|
|
2315
|
+
eva_list = np.unique(self.eva_list % self.steer.mics.num_mics)[::-1]
|
|
2418
2316
|
steer_type = self.steer.steer_type
|
|
2419
2317
|
if steer_type == 'custom':
|
|
2420
2318
|
msg = 'custom steer_type is not implemented'
|
|
@@ -2433,27 +2331,27 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2433
2331
|
for y in self.bounds[1]:
|
|
2434
2332
|
for z in self.bounds[2]:
|
|
2435
2333
|
roi.append((x, y, z))
|
|
2436
|
-
self.steer.env.roi = array(roi).T
|
|
2437
|
-
bmin = array(tuple(map(min, self.bounds)))
|
|
2438
|
-
bmax = array(tuple(map(max, self.bounds)))
|
|
2334
|
+
self.steer.env.roi = np.array(roi).T
|
|
2335
|
+
bmin = np.array(tuple(map(min, self.bounds)))
|
|
2336
|
+
bmax = np.array(tuple(map(max, self.bounds)))
|
|
2439
2337
|
for i in ind:
|
|
2440
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
2441
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
2442
|
-
k = 2 * pi * f[i] / env.c
|
|
2338
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
2339
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
2340
|
+
k = 2 * np.pi * f[i] / env.c
|
|
2443
2341
|
for j, n in enumerate(eva_list):
|
|
2444
2342
|
# print(f[i],n)
|
|
2445
2343
|
|
|
2446
2344
|
def func(xy):
|
|
2447
2345
|
# function to minimize globally
|
|
2448
|
-
xy = clip(xy, bmin, bmax)
|
|
2449
|
-
r0 = env._r(xy[:, newaxis])
|
|
2450
|
-
rm = env._r(xy[:, newaxis], mpos)
|
|
2346
|
+
xy = np.clip(xy, bmin, bmax)
|
|
2347
|
+
r0 = env._r(xy[:, np.newaxis])
|
|
2348
|
+
rm = env._r(xy[:, np.newaxis], mpos)
|
|
2451
2349
|
return -beamformerFreq(
|
|
2452
2350
|
steer_type,
|
|
2453
2351
|
self.r_diag,
|
|
2454
2352
|
normfactor,
|
|
2455
2353
|
(r0, rm, k),
|
|
2456
|
-
(ones(1), eve[:, n : n + 1]),
|
|
2354
|
+
(np.ones(1), eve[:, n : n + 1]),
|
|
2457
2355
|
)[0][0] # noqa: B023
|
|
2458
2356
|
|
|
2459
2357
|
# simplical global homotopy optimizer
|
|
@@ -2469,7 +2367,8 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2469
2367
|
|
|
2470
2368
|
|
|
2471
2369
|
def L_p(x): # noqa: N802
|
|
2472
|
-
r"""
|
|
2370
|
+
r"""
|
|
2371
|
+
Calculates the sound pressure level from the squared sound pressure.
|
|
2473
2372
|
|
|
2474
2373
|
:math:`L_p = 10 \lg ( x / 4\cdot 10^{-10})`
|
|
2475
2374
|
|
|
@@ -2483,17 +2382,17 @@ def L_p(x): # noqa: N802
|
|
|
2483
2382
|
array of floats
|
|
2484
2383
|
The corresponding sound pressure levels in dB.
|
|
2485
2384
|
If `x<0`, -350.0 dB is returned.
|
|
2486
|
-
|
|
2487
2385
|
"""
|
|
2488
2386
|
# new version to prevent division by zero warning for float32 arguments
|
|
2489
|
-
return 10 * log10(clip(x / 4e-10, 1e-35, None))
|
|
2387
|
+
return 10 * np.log10(np.clip(x / 4e-10, 1e-35, None))
|
|
2490
2388
|
|
|
2491
2389
|
|
|
2492
|
-
# return where(x>0, 10*log10(x/4e-10), -1000.)
|
|
2390
|
+
# return where(x>0, 10*np.log10(x/4e-10), -1000.)
|
|
2493
2391
|
|
|
2494
2392
|
|
|
2495
2393
|
def integrate(data, grid, sector):
|
|
2496
|
-
"""
|
|
2394
|
+
"""
|
|
2395
|
+
Integrates a sound pressure map over a given sector.
|
|
2497
2396
|
|
|
2498
2397
|
This function can be applied on beamforming results to
|
|
2499
2398
|
quantitatively analyze the sound pressure in a given sector.
|
|
@@ -2521,8 +2420,8 @@ def integrate(data, grid, sector):
|
|
|
2521
2420
|
of a :class:`~acoular.grids.Grid`-derived class
|
|
2522
2421
|
(e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
|
|
2523
2422
|
or :meth:`RectGrid3D.indices<acoular.grids.RectGrid3D.indices>`).
|
|
2524
|
-
Possible sectors would be `array([xmin, ymin, xmax, ymax])`
|
|
2525
|
-
or `array([x, y, radius])`.
|
|
2423
|
+
Possible sectors would be `np.array([xmin, ymin, xmax, ymax])`
|
|
2424
|
+
or `np.array([x, y, radius])`.
|
|
2526
2425
|
Alternatively, a :class:`~acoular.grids.Sector`-derived object
|
|
2527
2426
|
can be used.
|
|
2528
2427
|
|
|
@@ -2530,7 +2429,6 @@ def integrate(data, grid, sector):
|
|
|
2530
2429
|
-------
|
|
2531
2430
|
array of floats
|
|
2532
2431
|
The spectrum (all calculated frequency bands) for the integrated sector.
|
|
2533
|
-
|
|
2534
2432
|
"""
|
|
2535
2433
|
if isinstance(sector, Sector):
|
|
2536
2434
|
ind = grid.subdomain(sector)
|
|
@@ -2548,10 +2446,10 @@ def integrate(data, grid, sector):
|
|
|
2548
2446
|
|
|
2549
2447
|
gshape = grid.shape
|
|
2550
2448
|
gsize = grid.size
|
|
2551
|
-
if size(data) == gsize: # one value per grid point
|
|
2449
|
+
if np.size(data) == gsize: # one value per grid point
|
|
2552
2450
|
h = data.reshape(gshape)[ind].sum()
|
|
2553
2451
|
elif data.ndim == 2 and data.shape[1] == gsize:
|
|
2554
|
-
h = zeros(data.shape[0])
|
|
2452
|
+
h = np.zeros(data.shape[0])
|
|
2555
2453
|
for i in range(data.shape[0]):
|
|
2556
2454
|
h[i] = data[i].reshape(gshape)[ind].sum()
|
|
2557
2455
|
return h
|