acoular 25.7__py3-none-any.whl → 26.1__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/aiaa/aiaa.py +8 -10
- acoular/base.py +13 -16
- acoular/calib.py +25 -24
- acoular/configuration.py +2 -2
- acoular/demo/__init__.py +97 -9
- acoular/demo/__main__.py +37 -0
- acoular/environments.py +119 -130
- acoular/fbeamform.py +438 -440
- acoular/fprocess.py +18 -13
- acoular/grids.py +122 -301
- acoular/h5cache.py +5 -1
- acoular/h5files.py +96 -9
- acoular/microphones.py +30 -35
- acoular/process.py +14 -25
- acoular/sdinput.py +9 -14
- acoular/signals.py +36 -34
- acoular/sources.py +263 -380
- acoular/spectra.py +60 -80
- acoular/tbeamform.py +242 -224
- acoular/tools/helpers.py +25 -33
- acoular/tools/metrics.py +5 -10
- acoular/tools/utils.py +168 -0
- acoular/tprocess.py +248 -271
- acoular/trajectory.py +5 -6
- acoular/version.py +2 -2
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/METADATA +54 -105
- acoular-26.1.dist-info/RECORD +56 -0
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/WHEEL +1 -1
- acoular/demo/acoular_demo.py +0 -135
- acoular-25.7.dist-info/RECORD +0 -56
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.7.dist-info → acoular-26.1.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,54 +92,56 @@ 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
|
"""
|
|
133
100
|
|
|
134
101
|
#: :class:`~acoular.grids.Grid`-derived object that provides the grid locations.
|
|
135
|
-
grid = Instance(Grid
|
|
102
|
+
grid = Instance(Grid)
|
|
136
103
|
|
|
137
104
|
#: :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
138
|
-
mics = Instance(MicGeom
|
|
105
|
+
mics = Instance(MicGeom)
|
|
139
106
|
|
|
140
107
|
#: Type of steering vectors, see also :cite:`Sarradj2012`. Defaults to 'true level'.
|
|
141
|
-
steer_type = Enum('true level', 'true location', 'classic', 'inverse'
|
|
108
|
+
steer_type = Enum('true level', 'true location', 'classic', 'inverse')
|
|
142
109
|
|
|
143
110
|
#: :class:`~acoular.environments.Environment` or derived object,
|
|
144
111
|
#: which provides information about the sound propagation in the medium.
|
|
145
112
|
#: Defaults to standard :class:`~acoular.environments.Environment` object.
|
|
146
113
|
env = Instance(Environment(), Environment)
|
|
147
114
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
r0 = Property(
|
|
115
|
+
#: Sound travel distances from microphone array center to grid : points or reference position
|
|
116
|
+
#: (readonly). Feature may change.
|
|
117
|
+
r0 = Property()
|
|
151
118
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
rm = Property(
|
|
119
|
+
#: Sound travel distances from array microphones to grid : points (readonly). Feature may
|
|
120
|
+
#: change.
|
|
121
|
+
rm = Property()
|
|
155
122
|
|
|
156
|
-
#
|
|
157
|
-
_ref = Any(array([0.0, 0.0, 0.0])
|
|
123
|
+
# reference position or distance
|
|
124
|
+
_ref = Any(np.array([0.0, 0.0, 0.0]))
|
|
158
125
|
|
|
159
126
|
#: Reference position or distance at which to evaluate the sound pressure
|
|
160
127
|
#: of a grid point.
|
|
161
128
|
#: If set to a scalar, this is used as reference distance to the grid points.
|
|
162
129
|
#: If set to a vector, this is interpreted as x,y,z coordinates of the reference position.
|
|
163
130
|
#: Defaults to [0.,0.,0.].
|
|
164
|
-
ref = Property(
|
|
131
|
+
ref = Property()
|
|
165
132
|
|
|
133
|
+
#: dictionary of frequency domain steering vector functions
|
|
166
134
|
_steer_funcs_freq = Dict(
|
|
167
135
|
{
|
|
168
|
-
'classic': lambda x: x /
|
|
136
|
+
'classic': lambda x: x / np.abs(x) / x.shape[-1],
|
|
169
137
|
'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],
|
|
138
|
+
'true level': lambda x: x / np.einsum('ij,ij->i', x, x.conj())[:, np.newaxis],
|
|
139
|
+
'true location': lambda x: x / np.sqrt(np.einsum('ij,ij->i', x, x.conj()) * x.shape[-1])[:, np.newaxis],
|
|
172
140
|
},
|
|
173
141
|
transient=True,
|
|
174
|
-
desc='dictionary of frequency domain steering vector functions',
|
|
175
142
|
)
|
|
176
143
|
|
|
144
|
+
#: dictionary of time domain steering vector functions
|
|
177
145
|
_steer_funcs_time = Dict(
|
|
178
146
|
{
|
|
179
147
|
'classic': _steer_I,
|
|
@@ -182,40 +150,40 @@ class SteeringVector(HasStrictTraits):
|
|
|
182
150
|
'true location': _steer_IV,
|
|
183
151
|
},
|
|
184
152
|
transient=True,
|
|
185
|
-
desc='dictionary of time domain steering vector functions',
|
|
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
|
|
|
199
166
|
def _get_ref(self):
|
|
200
167
|
return self._ref
|
|
201
168
|
|
|
202
|
-
|
|
169
|
+
#: A unique identifier for the steering vector, based on its properties. (read-only)
|
|
203
170
|
digest = Property(depends_on=['steer_type', 'env.digest', 'grid.digest', 'mics.digest', '_ref'])
|
|
204
171
|
|
|
205
|
-
|
|
172
|
+
#: A unique identifier for the grid, excluding :attr:`steer_type`.
|
|
173
|
+
#: Use for inverse methods. (read-only)
|
|
206
174
|
inv_digest = Property(depends_on=['env.digest', 'grid.digest', 'mics.digest', '_ref'])
|
|
207
175
|
|
|
208
176
|
@property_depends_on(['grid.digest', 'env.digest', '_ref'])
|
|
209
177
|
def _get_r0(self):
|
|
210
|
-
if isscalar(self.ref):
|
|
178
|
+
if np.isscalar(self.ref):
|
|
211
179
|
if self.ref > 0:
|
|
212
|
-
return full((self.grid.size,), self.ref)
|
|
180
|
+
return np.full((self.grid.size,), self.ref)
|
|
213
181
|
return self.env._r(self.grid.pos())
|
|
214
|
-
return self.env._r(self.grid.pos, self.ref[:, newaxis])
|
|
182
|
+
return self.env._r(self.grid.pos, self.ref[:, np.newaxis])
|
|
215
183
|
|
|
216
184
|
@property_depends_on(['grid.digest', 'mics.digest', 'env.digest'])
|
|
217
185
|
def _get_rm(self):
|
|
218
|
-
return atleast_2d(self.env._r(self.grid.pos, self.mics.pos))
|
|
186
|
+
return np.atleast_2d(self.env._r(self.grid.pos, self.mics.pos))
|
|
219
187
|
|
|
220
188
|
@cached_property
|
|
221
189
|
def _get_digest(self):
|
|
@@ -226,7 +194,8 @@ class SteeringVector(HasStrictTraits):
|
|
|
226
194
|
return digest(self)
|
|
227
195
|
|
|
228
196
|
def transfer(self, f, ind=None):
|
|
229
|
-
"""
|
|
197
|
+
"""
|
|
198
|
+
Calculates the transfer matrix for one frequency.
|
|
230
199
|
|
|
231
200
|
Parameters
|
|
232
201
|
----------
|
|
@@ -241,22 +210,22 @@ class SteeringVector(HasStrictTraits):
|
|
|
241
210
|
-------
|
|
242
211
|
array of complex128
|
|
243
212
|
array of shape (ngridpts, nmics) containing the transfer matrix for the given frequency
|
|
244
|
-
|
|
245
213
|
"""
|
|
246
214
|
# if self.cached:
|
|
247
215
|
# warn('Caching of transfer function is not yet supported!', Warning)
|
|
248
216
|
# self.cached = False
|
|
249
217
|
|
|
250
218
|
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))
|
|
219
|
+
trans = calcTransfer(self.r0, self.rm, np.array(2 * np.pi * f / self.env.c))
|
|
220
|
+
elif not isinstance(ind, np.ndarray):
|
|
221
|
+
trans = calcTransfer(self.r0[ind], self.rm[ind, :][np.newaxis], np.array(2 * np.pi * f / self.env.c))
|
|
254
222
|
else:
|
|
255
|
-
trans = calcTransfer(self.r0[ind], self.rm[ind, :], array(2 * pi * f / self.env.c))
|
|
223
|
+
trans = calcTransfer(self.r0[ind], self.rm[ind, :], np.array(2 * np.pi * f / self.env.c))
|
|
256
224
|
return trans
|
|
257
225
|
|
|
258
226
|
def steer_vector(self, f, ind=None):
|
|
259
|
-
"""
|
|
227
|
+
"""
|
|
228
|
+
Calculates the steering vectors based on the transfer function.
|
|
260
229
|
|
|
261
230
|
See also :cite:`Sarradj2012`.
|
|
262
231
|
|
|
@@ -273,7 +242,6 @@ class SteeringVector(HasStrictTraits):
|
|
|
273
242
|
-------
|
|
274
243
|
array of complex128
|
|
275
244
|
array of shape (ngridpts, nmics) containing the steering vectors for the given frequency
|
|
276
|
-
|
|
277
245
|
"""
|
|
278
246
|
func = self._steer_funcs_freq[self.steer_type]
|
|
279
247
|
return func(self.transfer(f, ind))
|
|
@@ -293,11 +261,11 @@ class LazyBfResult:
|
|
|
293
261
|
|
|
294
262
|
def __getitem__(self, key):
|
|
295
263
|
"""'intelligent' [] operator, checks if results are available and triggers calculation."""
|
|
296
|
-
sl = index_exp[key][0]
|
|
297
|
-
if isinstance(sl, (int, integer)):
|
|
264
|
+
sl = np.index_exp[key][0]
|
|
265
|
+
if isinstance(sl, (int, np.integer)):
|
|
298
266
|
sl = slice(sl, sl + 1)
|
|
299
267
|
# indices which are missing
|
|
300
|
-
missingind = arange(*sl.indices(self.bf._numfreq))[self.bf._fr[sl] == 0]
|
|
268
|
+
missingind = np.arange(*sl.indices(self.bf._numfreq))[self.bf._fr[sl] == 0]
|
|
301
269
|
# calc if needed
|
|
302
270
|
if missingind.size:
|
|
303
271
|
self.bf._calc(missingind)
|
|
@@ -310,61 +278,65 @@ class LazyBfResult:
|
|
|
310
278
|
class BeamformerBase(HasStrictTraits):
|
|
311
279
|
"""Beamforming using the basic delay-and-sum algorithm in the frequency domain."""
|
|
312
280
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
281
|
+
#: Instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes
|
|
282
|
+
#: that contains information about the steering vector. This is a private trait.
|
|
283
|
+
#: Do not set this directly, use `steer` trait instead.
|
|
316
284
|
steer = Instance(SteeringVector, args=())
|
|
317
285
|
|
|
318
286
|
#: :class:`~acoular.spectra.PowerSpectra` object that provides the
|
|
319
287
|
#: cross spectral matrix and eigenvalues
|
|
320
|
-
freq_data = Instance(PowerSpectra
|
|
288
|
+
freq_data = Instance(PowerSpectra)
|
|
321
289
|
|
|
322
290
|
#: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
|
|
323
|
-
r_diag = Bool(True
|
|
291
|
+
r_diag = Bool(True)
|
|
324
292
|
|
|
325
|
-
#: If
|
|
293
|
+
#: If diagonal of the CSM is removed, some signal energy is lost.
|
|
294
|
+
#: This is handled via this normalization factor.
|
|
295
|
+
#: Internally, the default is: num_mics / (num_mics - 1).
|
|
296
|
+
#:
|
|
297
|
+
#: If r_diag==True: if r_diag_norm==0.0, the default
|
|
326
298
|
#: normalization = num_mics/(num_mics-1) is used.
|
|
327
299
|
#: If r_diag_norm !=0.0, the user input is used instead.
|
|
328
300
|
#: If r_diag==False, the normalization is 1.0 either way.
|
|
329
|
-
r_diag_norm = Float(
|
|
330
|
-
0.0,
|
|
331
|
-
desc='If diagonal of the csm is removed, some signal energy is lost.'
|
|
332
|
-
'This is handled via this normalization factor.'
|
|
333
|
-
'Internally, the default is: num_mics / (num_mics - 1).',
|
|
334
|
-
)
|
|
301
|
+
r_diag_norm = Float(0.0)
|
|
335
302
|
|
|
336
303
|
#: Floating point precision of property result. Corresponding to numpy dtypes. Default = 64 Bit.
|
|
337
|
-
precision = Enum('float64', 'float32'
|
|
304
|
+
precision = Enum('float64', 'float32')
|
|
338
305
|
|
|
339
306
|
#: Boolean flag, if 'True' (default), the result is cached in h5 files.
|
|
340
|
-
cached = Bool(True
|
|
307
|
+
cached = Bool(True)
|
|
341
308
|
|
|
342
|
-
|
|
309
|
+
#: hdf5 cache file
|
|
343
310
|
h5f = Instance(H5CacheFileBase, transient=True)
|
|
344
311
|
|
|
345
312
|
#: The beamforming result as squared sound pressure values
|
|
346
313
|
#: at all grid point locations (readonly).
|
|
347
314
|
#: Returns a (number of frequencies, number of gridpoints) array-like
|
|
348
315
|
#: of floats. Values can only be accessed via the index operator [].
|
|
349
|
-
result = Property(
|
|
316
|
+
result = Property()
|
|
350
317
|
|
|
351
|
-
|
|
318
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
352
319
|
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES)
|
|
353
320
|
|
|
354
321
|
# private traits
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
322
|
+
#: beamforming result
|
|
323
|
+
_ac = Any()
|
|
324
|
+
#: flag for beamforming result at frequency index
|
|
325
|
+
_fr = Any()
|
|
326
|
+
#: frequencies
|
|
327
|
+
_f = CArray(dtype='float64')
|
|
328
|
+
#: number of frequencies
|
|
329
|
+
_numfreq = Int()
|
|
359
330
|
|
|
360
331
|
@cached_property
|
|
361
332
|
def _get_digest(self):
|
|
362
333
|
return digest(self)
|
|
363
334
|
|
|
364
335
|
def _get_filecache(self):
|
|
365
|
-
"""
|
|
366
|
-
|
|
367
|
-
|
|
336
|
+
"""
|
|
337
|
+
Function collects cached results from file depending on global/local caching behaviour.
|
|
338
|
+
|
|
339
|
+
Returns (None, None) if no cachefile/data exist and global caching mode is 'readonly'.
|
|
368
340
|
"""
|
|
369
341
|
# print("get cachefile:", self.freq_data.basename)
|
|
370
342
|
H5cache.get_cache_file(self, self.freq_data.basename)
|
|
@@ -419,7 +391,9 @@ class BeamformerBase(HasStrictTraits):
|
|
|
419
391
|
|
|
420
392
|
@property_depends_on(['digest'])
|
|
421
393
|
def _get_result(self):
|
|
422
|
-
"""
|
|
394
|
+
"""
|
|
395
|
+
Implements the :attr:`result` getter routine.
|
|
396
|
+
|
|
423
397
|
The beamforming result is either loaded or calculated.
|
|
424
398
|
"""
|
|
425
399
|
# store locally for performance
|
|
@@ -439,20 +413,22 @@ class BeamformerBase(HasStrictTraits):
|
|
|
439
413
|
else:
|
|
440
414
|
# no caching or not activated, init numpy arrays
|
|
441
415
|
if isinstance(self, BeamformerAdaptiveGrid):
|
|
442
|
-
self._gpos = zeros((3, self.size), dtype=self.precision)
|
|
443
|
-
ac = zeros((self._numfreq, self.size), dtype=self.precision)
|
|
416
|
+
self._gpos = np.zeros((3, self.size), dtype=self.precision)
|
|
417
|
+
ac = np.zeros((self._numfreq, self.size), dtype=self.precision)
|
|
444
418
|
elif isinstance(self, BeamformerSODIX):
|
|
445
|
-
ac = zeros((self._numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
419
|
+
ac = np.zeros((self._numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
446
420
|
else:
|
|
447
|
-
ac = zeros((self._numfreq, self.steer.grid.size), dtype=self.precision)
|
|
448
|
-
fr = zeros(self._numfreq, dtype='int8')
|
|
421
|
+
ac = np.zeros((self._numfreq, self.steer.grid.size), dtype=self.precision)
|
|
422
|
+
fr = np.zeros(self._numfreq, dtype='int8')
|
|
449
423
|
self._ac = ac
|
|
450
424
|
self._fr = fr
|
|
451
425
|
return LazyBfResult(self)
|
|
452
426
|
|
|
453
427
|
def sig_loss_norm(self):
|
|
454
|
-
"""
|
|
455
|
-
of
|
|
428
|
+
"""
|
|
429
|
+
If the diagonal of the CSM is removed one has to handle the loss of signal energy.
|
|
430
|
+
|
|
431
|
+
Done via a normalization factor.
|
|
456
432
|
"""
|
|
457
433
|
if not self.r_diag: # Full CSM --> no normalization needed
|
|
458
434
|
normfactor = 1.0
|
|
@@ -464,28 +440,29 @@ class BeamformerBase(HasStrictTraits):
|
|
|
464
440
|
return normfactor
|
|
465
441
|
|
|
466
442
|
def _beamformer_params(self):
|
|
467
|
-
"""
|
|
468
|
-
|
|
469
|
-
|
|
443
|
+
"""
|
|
444
|
+
Manages the parameters for calling of the core beamformer functionality.
|
|
445
|
+
|
|
446
|
+
This is a workaround to allow faster calculation and may change in the future.
|
|
470
447
|
|
|
471
448
|
Returns
|
|
472
449
|
-------
|
|
473
450
|
- String containing the steering vector type
|
|
474
451
|
- Function for frequency-dependent steering vector calculation
|
|
475
|
-
|
|
476
452
|
"""
|
|
477
453
|
if type(self.steer) is SteeringVector: # for simple steering vector, use faster method
|
|
478
454
|
param_type = self.steer.steer_type
|
|
479
455
|
|
|
480
456
|
def param_steer_func(f):
|
|
481
|
-
return (self.steer.r0, self.steer.rm, 2 * pi * f / self.steer.env.c)
|
|
457
|
+
return (self.steer.r0, self.steer.rm, 2 * np.pi * f / self.steer.env.c)
|
|
482
458
|
else:
|
|
483
459
|
param_type = 'custom'
|
|
484
460
|
param_steer_func = self.steer.steer_vector
|
|
485
461
|
return param_type, param_steer_func
|
|
486
462
|
|
|
487
463
|
def _calc(self, ind):
|
|
488
|
-
"""
|
|
464
|
+
"""
|
|
465
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
489
466
|
|
|
490
467
|
This is an internal helper function that is automatically called when
|
|
491
468
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -500,14 +477,13 @@ class BeamformerBase(HasStrictTraits):
|
|
|
500
477
|
Returns
|
|
501
478
|
-------
|
|
502
479
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
503
|
-
|
|
504
480
|
"""
|
|
505
481
|
f = self._f
|
|
506
482
|
normfactor = self.sig_loss_norm()
|
|
507
483
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
508
484
|
for i in ind:
|
|
509
485
|
# print(f'compute{i}')
|
|
510
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
486
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
511
487
|
beamformerOutput = beamformerFreq(
|
|
512
488
|
param_steer_type,
|
|
513
489
|
self.r_diag,
|
|
@@ -516,13 +492,14 @@ class BeamformerBase(HasStrictTraits):
|
|
|
516
492
|
csm,
|
|
517
493
|
)[0]
|
|
518
494
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
519
|
-
indNegSign = sign(beamformerOutput) < 0
|
|
495
|
+
indNegSign = np.sign(beamformerOutput) < 0
|
|
520
496
|
beamformerOutput[indNegSign] = 0.0
|
|
521
497
|
self._ac[i] = beamformerOutput
|
|
522
498
|
self._fr[i] = 1
|
|
523
499
|
|
|
524
500
|
def synthetic(self, f, num=0):
|
|
525
|
-
"""
|
|
501
|
+
"""
|
|
502
|
+
Evaluates the beamforming result for an arbitrary frequency band.
|
|
526
503
|
|
|
527
504
|
Parameters
|
|
528
505
|
----------
|
|
@@ -550,7 +527,6 @@ class BeamformerBase(HasStrictTraits):
|
|
|
550
527
|
represented by a single frequency line depends on
|
|
551
528
|
the :attr:`sampling frequency<acoular.base.SamplesGenerator.sample_freq>` and
|
|
552
529
|
used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
553
|
-
|
|
554
530
|
"""
|
|
555
531
|
res = self.result # trigger calculation
|
|
556
532
|
freq = self.freq_data.fftfreq()
|
|
@@ -559,14 +535,14 @@ class BeamformerBase(HasStrictTraits):
|
|
|
559
535
|
|
|
560
536
|
if num == 0:
|
|
561
537
|
# single frequency line
|
|
562
|
-
ind = searchsorted(freq, f)
|
|
538
|
+
ind = np.searchsorted(freq, f)
|
|
563
539
|
if ind >= len(freq):
|
|
564
540
|
warn(
|
|
565
541
|
f'Queried frequency ({f:g} Hz) not in resolved frequency range. Returning zeros.',
|
|
566
542
|
Warning,
|
|
567
543
|
stacklevel=2,
|
|
568
544
|
)
|
|
569
|
-
h = zeros_like(res[0])
|
|
545
|
+
h = np.zeros_like(res[0])
|
|
570
546
|
else:
|
|
571
547
|
if freq[ind] != f:
|
|
572
548
|
warn(
|
|
@@ -585,8 +561,8 @@ class BeamformerBase(HasStrictTraits):
|
|
|
585
561
|
else:
|
|
586
562
|
f1 = f * 2.0 ** (-0.5 / num)
|
|
587
563
|
f2 = f * 2.0 ** (+0.5 / num)
|
|
588
|
-
ind1 = searchsorted(freq, f1)
|
|
589
|
-
ind2 = searchsorted(freq, f2)
|
|
564
|
+
ind1 = np.searchsorted(freq, f1)
|
|
565
|
+
ind2 = np.searchsorted(freq, f2)
|
|
590
566
|
if ind1 == ind2:
|
|
591
567
|
warn(
|
|
592
568
|
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
@@ -595,9 +571,9 @@ class BeamformerBase(HasStrictTraits):
|
|
|
595
571
|
Warning,
|
|
596
572
|
stacklevel=2,
|
|
597
573
|
)
|
|
598
|
-
h = zeros_like(res[0])
|
|
574
|
+
h = np.zeros_like(res[0])
|
|
599
575
|
else:
|
|
600
|
-
h = sum(res[ind1:ind2], 0)
|
|
576
|
+
h = np.sum(res[ind1:ind2], 0)
|
|
601
577
|
if isinstance(self, BeamformerAdaptiveGrid):
|
|
602
578
|
return h
|
|
603
579
|
if isinstance(self, BeamformerSODIX):
|
|
@@ -605,7 +581,8 @@ class BeamformerBase(HasStrictTraits):
|
|
|
605
581
|
return h.reshape(self.steer.grid.shape)
|
|
606
582
|
|
|
607
583
|
def integrate(self, sector, frange=None, num=0):
|
|
608
|
-
"""
|
|
584
|
+
"""
|
|
585
|
+
Integrates result map over a given sector.
|
|
609
586
|
|
|
610
587
|
Parameters
|
|
611
588
|
----------
|
|
@@ -674,13 +651,13 @@ class BeamformerBase(HasStrictTraits):
|
|
|
674
651
|
irange = (ind_low, ind_high)
|
|
675
652
|
num = 0
|
|
676
653
|
elif len(frange) == 2:
|
|
677
|
-
irange = (searchsorted(self._f, frange[0]), searchsorted(self._f, frange[1]))
|
|
654
|
+
irange = (np.searchsorted(self._f, frange[0]), np.searchsorted(self._f, frange[1]))
|
|
678
655
|
else:
|
|
679
656
|
msg = 'Only a tuple of length 2 is allowed for frange if num==0'
|
|
680
657
|
raise TypeError(
|
|
681
658
|
msg,
|
|
682
659
|
)
|
|
683
|
-
h = zeros(num_freqs, dtype=float)
|
|
660
|
+
h = np.zeros(num_freqs, dtype=float)
|
|
684
661
|
sl = slice(*irange)
|
|
685
662
|
r = self.result[sl]
|
|
686
663
|
for i in range(num_freqs)[sl]:
|
|
@@ -690,32 +667,30 @@ class BeamformerBase(HasStrictTraits):
|
|
|
690
667
|
return h
|
|
691
668
|
return self._f[sl], h[sl]
|
|
692
669
|
|
|
693
|
-
h = zeros(len(frange), dtype=float)
|
|
670
|
+
h = np.zeros(len(frange), dtype=float)
|
|
694
671
|
for i, f in enumerate(frange):
|
|
695
672
|
h[i] = self.synthetic(f, num).reshape(gshape)[ind].sum()
|
|
696
673
|
return h
|
|
697
674
|
|
|
698
675
|
|
|
699
676
|
class BeamformerFunctional(BeamformerBase):
|
|
700
|
-
"""
|
|
677
|
+
"""
|
|
678
|
+
Functional beamforming algorithm.
|
|
701
679
|
|
|
702
680
|
See :cite:`Dougherty2014` for details.
|
|
703
681
|
"""
|
|
704
682
|
|
|
705
683
|
#: Functional exponent, defaults to 1 (= Classic Beamforming).
|
|
706
|
-
gamma = Float(1
|
|
684
|
+
gamma = Float(1)
|
|
707
685
|
|
|
708
686
|
#: Functional Beamforming is only well defined for full CSM
|
|
709
|
-
r_diag = Enum(False
|
|
687
|
+
r_diag = Enum(False)
|
|
710
688
|
|
|
711
689
|
#: Normalization factor in case of CSM diagonal removal. Defaults to 1.0 since Functional
|
|
712
690
|
#: Beamforming is only well defined for full CSM.
|
|
713
|
-
r_diag_norm = Enum(
|
|
714
|
-
1.0,
|
|
715
|
-
desc='No normalization needed. Functional Beamforming is only well defined for full CSM.',
|
|
716
|
-
)
|
|
691
|
+
r_diag_norm = Enum(1.0)
|
|
717
692
|
|
|
718
|
-
|
|
693
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
719
694
|
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['gamma'])
|
|
720
695
|
|
|
721
696
|
@cached_property
|
|
@@ -723,7 +698,8 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
723
698
|
return digest(self)
|
|
724
699
|
|
|
725
700
|
def _calc(self, ind):
|
|
726
|
-
"""
|
|
701
|
+
"""
|
|
702
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
727
703
|
|
|
728
704
|
This is an internal helper function that is automatically called when
|
|
729
705
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -738,7 +714,6 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
738
714
|
Returns
|
|
739
715
|
-------
|
|
740
716
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
741
|
-
|
|
742
717
|
"""
|
|
743
718
|
f = self._f
|
|
744
719
|
normfactor = self.sig_loss_norm()
|
|
@@ -756,8 +731,8 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
756
731
|
# WATCH OUT: This doesn't really produce good results.
|
|
757
732
|
# ==============================================================================
|
|
758
733
|
csm = self.freq_data.csm[i]
|
|
759
|
-
fill_diagonal(csm, 0)
|
|
760
|
-
csmRoot = fractional_matrix_power(csm, 1.0 / self.gamma)
|
|
734
|
+
np.fill_diagonal(csm, 0)
|
|
735
|
+
csmRoot = spla.fractional_matrix_power(csm, 1.0 / self.gamma)
|
|
761
736
|
beamformerOutput, steerNorm = beamformerFreq(
|
|
762
737
|
param_steer_type,
|
|
763
738
|
self.r_diag,
|
|
@@ -768,11 +743,11 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
768
743
|
beamformerOutput /= steerNorm # take normalized steering vec
|
|
769
744
|
|
|
770
745
|
# set (unphysical) negative output values to 0
|
|
771
|
-
indNegSign = sign(beamformerOutput) < 0
|
|
746
|
+
indNegSign = np.sign(beamformerOutput) < 0
|
|
772
747
|
beamformerOutput[indNegSign] = 0.0
|
|
773
748
|
else:
|
|
774
|
-
eva = array(self.freq_data.eva[i], dtype='float64') ** (1.0 / self.gamma)
|
|
775
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
749
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64') ** (1.0 / self.gamma)
|
|
750
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
776
751
|
beamformerOutput, steerNorm = beamformerFreq(
|
|
777
752
|
param_steer_type,
|
|
778
753
|
self.r_diag,
|
|
@@ -788,24 +763,23 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
788
763
|
|
|
789
764
|
|
|
790
765
|
class BeamformerCapon(BeamformerBase):
|
|
791
|
-
"""
|
|
766
|
+
"""
|
|
767
|
+
Beamforming using the Capon (Mininimum Variance) algorithm.
|
|
792
768
|
|
|
793
769
|
See :cite:`Capon1969` for details.
|
|
794
770
|
"""
|
|
795
771
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
r_diag = Enum(False
|
|
772
|
+
#: Boolean flag, if ``True``, the main diagonal is removed before beamforming;
|
|
773
|
+
#: for Capon beamforming :attr:`r_diag` is set to ``False``.
|
|
774
|
+
r_diag = Enum(False)
|
|
799
775
|
|
|
800
|
-
#: Normalization factor in case of CSM diagonal removal.
|
|
801
|
-
#: is only well defined for full CSM.
|
|
802
|
-
r_diag_norm = Enum(
|
|
803
|
-
1.0,
|
|
804
|
-
desc='No normalization. BeamformerCapon is only well defined for full CSM.',
|
|
805
|
-
)
|
|
776
|
+
#: Normalization factor in case of CSM diagonal removal.
|
|
777
|
+
#: Defaults to ``1.0`` since Beamformer Capon is only well defined for full CSM.
|
|
778
|
+
r_diag_norm = Enum(1.0)
|
|
806
779
|
|
|
807
780
|
def _calc(self, ind):
|
|
808
|
-
"""
|
|
781
|
+
"""
|
|
782
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
809
783
|
|
|
810
784
|
This is an internal helper function that is automatically called when
|
|
811
785
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -820,34 +794,35 @@ class BeamformerCapon(BeamformerBase):
|
|
|
820
794
|
Returns
|
|
821
795
|
-------
|
|
822
796
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
823
|
-
|
|
824
797
|
"""
|
|
825
798
|
f = self._f
|
|
826
799
|
nMics = self.freq_data.num_channels
|
|
827
800
|
normfactor = self.sig_loss_norm() * nMics**2
|
|
828
801
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
829
802
|
for i in ind:
|
|
830
|
-
csm = array(inv(array(self.freq_data.csm[i], dtype='complex128')), order='C')
|
|
803
|
+
csm = np.array(spla.inv(np.array(self.freq_data.csm[i], dtype='complex128')), order='C')
|
|
831
804
|
beamformerOutput = beamformerFreq(param_steer_type, self.r_diag, normfactor, steer_vector(f[i]), csm)[0]
|
|
832
805
|
self._ac[i] = 1.0 / beamformerOutput
|
|
833
806
|
self._fr[i] = 1
|
|
834
807
|
|
|
835
808
|
|
|
836
809
|
class BeamformerEig(BeamformerBase):
|
|
837
|
-
"""
|
|
810
|
+
"""
|
|
811
|
+
Beamforming using eigenvalue and eigenvector techniques.
|
|
838
812
|
|
|
839
813
|
See :cite:`Sarradj2005` for details.
|
|
840
814
|
"""
|
|
841
815
|
|
|
842
816
|
#: Number of component to calculate:
|
|
843
|
-
#: 0 (smallest) ... :attr:`~acoular.base.SamplesGenerator.num_channels`-1;
|
|
844
|
-
#: defaults to
|
|
845
|
-
n = Int(-1
|
|
817
|
+
#: ``0`` (smallest) ... :attr:`~acoular.base.SamplesGenerator.num_channels`-1;
|
|
818
|
+
#: defaults to ``-1``, i.e. :attr:`~acoular.base.SamplesGenerator.num_channels`-1.
|
|
819
|
+
n = Int(-1)
|
|
846
820
|
|
|
847
821
|
# Actual component to calculate, internal, readonly.
|
|
848
|
-
|
|
822
|
+
#: No. of eigenvalue
|
|
823
|
+
na = Property()
|
|
849
824
|
|
|
850
|
-
|
|
825
|
+
#: A uniquernal identifier for the beamformer, based on its properties. (read-only)
|
|
851
826
|
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n'])
|
|
852
827
|
|
|
853
828
|
@cached_property
|
|
@@ -863,7 +838,8 @@ class BeamformerEig(BeamformerBase):
|
|
|
863
838
|
return min(nm - 1, na)
|
|
864
839
|
|
|
865
840
|
def _calc(self, ind):
|
|
866
|
-
"""
|
|
841
|
+
"""
|
|
842
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
867
843
|
|
|
868
844
|
This is an internal helper function that is automatically called when
|
|
869
845
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -878,15 +854,14 @@ class BeamformerEig(BeamformerBase):
|
|
|
878
854
|
Returns
|
|
879
855
|
-------
|
|
880
856
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
881
|
-
|
|
882
857
|
"""
|
|
883
858
|
f = self._f
|
|
884
859
|
na = int(self.na) # eigenvalue taken into account
|
|
885
860
|
normfactor = self.sig_loss_norm()
|
|
886
861
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
887
862
|
for i in ind:
|
|
888
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
889
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
863
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
864
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
890
865
|
beamformerOutput = beamformerFreq(
|
|
891
866
|
param_steer_type,
|
|
892
867
|
self.r_diag,
|
|
@@ -895,35 +870,35 @@ class BeamformerEig(BeamformerBase):
|
|
|
895
870
|
(eva[na : na + 1], eve[:, na : na + 1]),
|
|
896
871
|
)[0]
|
|
897
872
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
898
|
-
indNegSign = sign(beamformerOutput) < 0
|
|
873
|
+
indNegSign = np.sign(beamformerOutput) < 0
|
|
899
874
|
beamformerOutput[indNegSign] = 0
|
|
900
875
|
self._ac[i] = beamformerOutput
|
|
901
876
|
self._fr[i] = 1
|
|
902
877
|
|
|
903
878
|
|
|
904
879
|
class BeamformerMusic(BeamformerEig):
|
|
905
|
-
"""
|
|
880
|
+
"""
|
|
881
|
+
Beamforming using the MUSIC algorithm.
|
|
906
882
|
|
|
907
883
|
See :cite:`Schmidt1986` for details.
|
|
908
884
|
"""
|
|
909
885
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
r_diag = Enum(False
|
|
886
|
+
#: Boolean flag, if ``True``, the main diagonal is removed before beamforming;
|
|
887
|
+
#: for MUSIC beamforming :attr:`r_diag` is set to ``False``.
|
|
888
|
+
r_diag = Enum(False)
|
|
913
889
|
|
|
914
890
|
#: Normalization factor in case of CSM diagonal removal. Defaults to 1.0 since BeamformerMusic
|
|
915
891
|
#: is only well defined for full CSM.
|
|
916
|
-
r_diag_norm = Enum(
|
|
917
|
-
1.0,
|
|
918
|
-
desc='No normalization. BeamformerMusic is only well defined for full CSM.',
|
|
919
|
-
)
|
|
892
|
+
r_diag_norm = Enum(1.0)
|
|
920
893
|
|
|
921
894
|
# assumed number of sources, should be set to a value not too small
|
|
922
895
|
# defaults to 1
|
|
923
|
-
|
|
896
|
+
#: assumed number of sources
|
|
897
|
+
n = Int(1)
|
|
924
898
|
|
|
925
899
|
def _calc(self, ind):
|
|
926
|
-
"""
|
|
900
|
+
"""
|
|
901
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
927
902
|
|
|
928
903
|
This is an internal helper function that is automatically called when
|
|
929
904
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -938,7 +913,6 @@ class BeamformerMusic(BeamformerEig):
|
|
|
938
913
|
Returns
|
|
939
914
|
-------
|
|
940
915
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
941
|
-
|
|
942
916
|
"""
|
|
943
917
|
f = self._f
|
|
944
918
|
nMics = self.freq_data.num_channels
|
|
@@ -946,8 +920,8 @@ class BeamformerMusic(BeamformerEig):
|
|
|
946
920
|
normfactor = self.sig_loss_norm() * nMics**2
|
|
947
921
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
948
922
|
for i in ind:
|
|
949
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
950
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
923
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
924
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
951
925
|
beamformerOutput = beamformerFreq(
|
|
952
926
|
param_steer_type,
|
|
953
927
|
self.r_diag,
|
|
@@ -960,7 +934,8 @@ class BeamformerMusic(BeamformerEig):
|
|
|
960
934
|
|
|
961
935
|
|
|
962
936
|
class PointSpreadFunction(HasStrictTraits):
|
|
963
|
-
"""
|
|
937
|
+
"""
|
|
938
|
+
The point spread function.
|
|
964
939
|
|
|
965
940
|
This class provides tools to calculate the PSF depending on the used
|
|
966
941
|
microphone geometry, focus grid, flow environment, etc.
|
|
@@ -968,16 +943,15 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
968
943
|
the aberrations when using simple delay-and-sum beamforming.
|
|
969
944
|
"""
|
|
970
945
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
946
|
+
#: Instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes
|
|
947
|
+
#: that contains information about the steering vector. This is a private trait.
|
|
948
|
+
#: Do not set this directly, use :attr:`steer` trait instead.
|
|
974
949
|
steer = Instance(SteeringVector, args=())
|
|
975
950
|
|
|
976
951
|
#: Indices of grid points to calculate the PSF for.
|
|
977
952
|
grid_indices = CArray(
|
|
978
953
|
dtype=int,
|
|
979
|
-
value=array([]),
|
|
980
|
-
desc='indices of grid points for psf',
|
|
954
|
+
value=np.array([]),
|
|
981
955
|
) # value=array([]), value=self.steer.grid.pos(),
|
|
982
956
|
|
|
983
957
|
#: Flag that defines how to calculate and store the point spread function
|
|
@@ -991,21 +965,21 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
991
965
|
#: (useful if not all PSFs are needed, as with :class:`CLEAN<BeamformerClean>`)
|
|
992
966
|
#: * 'readonly': Do not attempt to calculate the PSF since it should already be cached (useful
|
|
993
967
|
#: if multiple processes have to access the cache file)
|
|
994
|
-
calcmode = Enum('single', 'block', 'full', 'readonly'
|
|
968
|
+
calcmode = Enum('single', 'block', 'full', 'readonly')
|
|
995
969
|
|
|
996
970
|
#: Floating point precision of property psf. Corresponding to numpy dtypes. Default = 64 Bit.
|
|
997
|
-
precision = Enum('float64', 'float32'
|
|
971
|
+
precision = Enum('float64', 'float32')
|
|
998
972
|
|
|
999
973
|
#: The actual point spread function.
|
|
1000
|
-
psf = Property(
|
|
974
|
+
psf = Property()
|
|
1001
975
|
|
|
1002
976
|
#: Frequency to evaluate the PSF for; defaults to 1.0.
|
|
1003
|
-
freq = Float(1.0
|
|
977
|
+
freq = Float(1.0)
|
|
1004
978
|
|
|
1005
979
|
# hdf5 cache file
|
|
1006
980
|
h5f = Instance(H5CacheFileBase, transient=True)
|
|
1007
981
|
|
|
1008
|
-
|
|
982
|
+
#: A unique identifier for the object, based on its properties. (read-only)
|
|
1009
983
|
digest = Property(depends_on=['steer.digest', 'precision'], cached=True)
|
|
1010
984
|
|
|
1011
985
|
@cached_property
|
|
@@ -1013,9 +987,10 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1013
987
|
return digest(self)
|
|
1014
988
|
|
|
1015
989
|
def _get_filecache(self):
|
|
1016
|
-
"""
|
|
1017
|
-
|
|
1018
|
-
|
|
990
|
+
"""
|
|
991
|
+
Function collects cached results from file depending on global/local caching behaviour.
|
|
992
|
+
|
|
993
|
+
Returns (None, None) if no cachefile/data exist and global caching mode is 'readonly'.
|
|
1019
994
|
"""
|
|
1020
995
|
filename = 'psf' + self.digest
|
|
1021
996
|
nodename = (f'Hz_{self.freq:.2f}').replace('.', '_')
|
|
@@ -1047,12 +1022,14 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1047
1022
|
return (ac, gp)
|
|
1048
1023
|
|
|
1049
1024
|
def _get_psf(self):
|
|
1050
|
-
"""
|
|
1025
|
+
"""
|
|
1026
|
+
Implements the :attr:`psf` getter routine.
|
|
1027
|
+
|
|
1051
1028
|
The point spread function is either loaded or calculated.
|
|
1052
1029
|
"""
|
|
1053
1030
|
gs = self.steer.grid.size
|
|
1054
1031
|
if not self.grid_indices.size:
|
|
1055
|
-
self.grid_indices = arange(gs)
|
|
1032
|
+
self.grid_indices = np.arange(gs)
|
|
1056
1033
|
|
|
1057
1034
|
if config.global_caching != 'none':
|
|
1058
1035
|
# print("get filecache..")
|
|
@@ -1075,13 +1052,13 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1075
1052
|
# print("cached results are complete! return.")
|
|
1076
1053
|
return ac[:, self.grid_indices]
|
|
1077
1054
|
# print("no caching, calculate result")
|
|
1078
|
-
ac = zeros((gs, gs), dtype=self.precision)
|
|
1079
|
-
gp = zeros((gs,), dtype='int8')
|
|
1055
|
+
ac = np.zeros((gs, gs), dtype=self.precision)
|
|
1056
|
+
gp = np.zeros((gs,), dtype='int8')
|
|
1080
1057
|
self.calc_psf(ac, gp)
|
|
1081
1058
|
else: # no caching activated
|
|
1082
1059
|
# print("no caching activated, calculate result")
|
|
1083
|
-
ac = zeros((gs, gs), dtype=self.precision)
|
|
1084
|
-
gp = zeros((gs,), dtype='int8')
|
|
1060
|
+
ac = np.zeros((gs, gs), dtype=self.precision)
|
|
1061
|
+
gp = np.zeros((gs,), dtype='int8')
|
|
1085
1062
|
self.calc_psf(ac, gp)
|
|
1086
1063
|
return ac[:, self.grid_indices]
|
|
1087
1064
|
|
|
@@ -1090,7 +1067,7 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1090
1067
|
if self.calcmode != 'full':
|
|
1091
1068
|
# calc_ind has the form [True, True, False, True], except
|
|
1092
1069
|
# when it has only 1 entry (value True/1 would be ambiguous)
|
|
1093
|
-
calc_ind = [0] if self.grid_indices.size == 1 else invert(gp[:][self.grid_indices])
|
|
1070
|
+
calc_ind = [0] if self.grid_indices.size == 1 else np.invert(gp[:][self.grid_indices])
|
|
1094
1071
|
|
|
1095
1072
|
# get indices which have the value True = not yet calculated
|
|
1096
1073
|
g_ind_calc = self.grid_indices[calc_ind]
|
|
@@ -1101,7 +1078,7 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1101
1078
|
gp[ind] = 1
|
|
1102
1079
|
elif self.calcmode == 'full': # calculate all psfs in one go
|
|
1103
1080
|
gp[:] = 1
|
|
1104
|
-
ac[:] = self._psf_call(arange(self.steer.grid.size))
|
|
1081
|
+
ac[:] = self._psf_call(np.arange(self.steer.grid.size))
|
|
1105
1082
|
else: # 'block' # calculate selected psfs in one go
|
|
1106
1083
|
hh = self._psf_call(g_ind_calc)
|
|
1107
1084
|
for indh, ind in enumerate(g_ind_calc):
|
|
@@ -1110,7 +1087,8 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1110
1087
|
indh += 1
|
|
1111
1088
|
|
|
1112
1089
|
def _psf_call(self, ind):
|
|
1113
|
-
"""
|
|
1090
|
+
"""
|
|
1091
|
+
Manages the calling of the core psf functionality.
|
|
1114
1092
|
|
|
1115
1093
|
Parameters
|
|
1116
1094
|
----------
|
|
@@ -1127,38 +1105,48 @@ class PointSpreadFunction(HasStrictTraits):
|
|
|
1127
1105
|
self.steer.steer_type,
|
|
1128
1106
|
self.steer.r0,
|
|
1129
1107
|
self.steer.rm,
|
|
1130
|
-
2 * pi * self.freq / self.steer.env.c,
|
|
1108
|
+
2 * np.pi * self.freq / self.steer.env.c,
|
|
1131
1109
|
ind,
|
|
1132
1110
|
self.precision,
|
|
1133
1111
|
)
|
|
1134
1112
|
else:
|
|
1135
1113
|
# for arbitrary steering sectors, use general calculation. there is a version of this in
|
|
1136
1114
|
# fastFuncs, may be used later after runtime testing and debugging
|
|
1137
|
-
product = dot(self.steer.steer_vector(self.freq).conj(), self.steer.transfer(self.freq, ind).T)
|
|
1115
|
+
product = np.dot(self.steer.steer_vector(self.freq).conj(), self.steer.transfer(self.freq, ind).T)
|
|
1138
1116
|
result = (product * product.conj()).real
|
|
1139
1117
|
return result
|
|
1140
1118
|
|
|
1141
1119
|
|
|
1142
1120
|
class BeamformerDamas(BeamformerBase):
|
|
1143
|
-
"""
|
|
1121
|
+
"""
|
|
1122
|
+
DAMAS deconvolution algorithm.
|
|
1144
1123
|
|
|
1145
1124
|
See :cite:`Brooks2006` for details.
|
|
1146
1125
|
"""
|
|
1147
1126
|
|
|
1148
1127
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1149
|
-
psf_precision = Enum('float64', 'float32'
|
|
1128
|
+
psf_precision = Enum('float64', 'float32')
|
|
1150
1129
|
|
|
1151
1130
|
#: Number of iterations, defaults to 100.
|
|
1152
|
-
n_iter = Int(100
|
|
1131
|
+
n_iter = Int(100)
|
|
1153
1132
|
|
|
1154
1133
|
#: Damping factor in modified gauss-seidel
|
|
1155
|
-
damp = Float(1.0
|
|
1134
|
+
damp = Float(1.0)
|
|
1156
1135
|
|
|
1157
|
-
#: Flag that defines how to calculate and store the point spread function
|
|
1158
|
-
#: defaults to '
|
|
1159
|
-
|
|
1136
|
+
#: Flag that defines how to calculate and store the point spread function
|
|
1137
|
+
#: defaults to 'single'.
|
|
1138
|
+
#:
|
|
1139
|
+
#: * 'full': Calculate the full PSF (for all grid points) in one go (should be used if the PSF
|
|
1140
|
+
#: at all grid points is needed, as with :class:`DAMAS<BeamformerDamas>`)
|
|
1141
|
+
#: * 'single': Calculate the PSF for the grid points defined by :attr:`grid_indices`, one by one
|
|
1142
|
+
#: (useful if not all PSFs are needed, as with :class:`CLEAN<BeamformerClean>`)
|
|
1143
|
+
#: * 'block': Calculate the PSF for the grid points defined by :attr:`grid_indices`, in one go
|
|
1144
|
+
#: (useful if not all PSFs are needed, as with :class:`CLEAN<BeamformerClean>`)
|
|
1145
|
+
#: * 'readonly': Do not attempt to calculate the PSF since it should already be cached (useful
|
|
1146
|
+
#: if multiple processes have to access the cache file)
|
|
1147
|
+
calcmode = Enum('full', 'single', 'block', 'readonly')
|
|
1160
1148
|
|
|
1161
|
-
|
|
1149
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
1162
1150
|
digest = Property(
|
|
1163
1151
|
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n_iter', 'damp', 'psf_precision'],
|
|
1164
1152
|
)
|
|
@@ -1168,7 +1156,8 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1168
1156
|
return digest(self)
|
|
1169
1157
|
|
|
1170
1158
|
def _calc(self, ind):
|
|
1171
|
-
"""
|
|
1159
|
+
"""
|
|
1160
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1172
1161
|
|
|
1173
1162
|
This is an internal helper function that is automatically called when
|
|
1174
1163
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1183,14 +1172,13 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1183
1172
|
Returns
|
|
1184
1173
|
-------
|
|
1185
1174
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1186
|
-
|
|
1187
1175
|
"""
|
|
1188
1176
|
f = self._f
|
|
1189
1177
|
normfactor = self.sig_loss_norm()
|
|
1190
1178
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
1191
1179
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1192
1180
|
for i in ind:
|
|
1193
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1181
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
1194
1182
|
y = beamformerFreq(
|
|
1195
1183
|
param_steer_type,
|
|
1196
1184
|
self.r_diag,
|
|
@@ -1199,7 +1187,7 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1199
1187
|
csm,
|
|
1200
1188
|
)[0]
|
|
1201
1189
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1202
|
-
indNegSign = sign(y) < 0
|
|
1190
|
+
indNegSign = np.sign(y) < 0
|
|
1203
1191
|
y[indNegSign] = 0.0
|
|
1204
1192
|
x = y.copy()
|
|
1205
1193
|
p.freq = f[i]
|
|
@@ -1209,12 +1197,13 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1209
1197
|
self._fr[i] = 1
|
|
1210
1198
|
|
|
1211
1199
|
|
|
1212
|
-
@deprecated_alias({'max_iter': 'n_iter'}, removal_version='25.10')
|
|
1213
1200
|
class BeamformerDamasPlus(BeamformerDamas):
|
|
1214
|
-
"""
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1201
|
+
"""
|
|
1202
|
+
DAMAS deconvolution :cite:`Brooks2006` for solving the system of equations.
|
|
1203
|
+
|
|
1204
|
+
Instead of the original Gauss-Seidel iterations, this class employs the NNLS or linear
|
|
1205
|
+
programming solvers from scipy.optimize or one of several optimization algorithms from the
|
|
1206
|
+
scikit-learn module. Needs a-priori delay-and-sum beamforming (:class:`BeamformerBase`).
|
|
1218
1207
|
"""
|
|
1219
1208
|
|
|
1220
1209
|
#: Type of fit method to be used ('LassoLars',
|
|
@@ -1222,25 +1211,25 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1222
1211
|
#: These methods are implemented in
|
|
1223
1212
|
#: the `scikit-learn <http://scikit-learn.org/stable/user_guide.html>`_
|
|
1224
1213
|
#: module or within scipy.optimize respectively.
|
|
1225
|
-
method = Enum('NNLS', 'LP', 'LassoLars', 'OMPCV'
|
|
1214
|
+
method = Enum('NNLS', 'LP', 'LassoLars', 'OMPCV')
|
|
1226
1215
|
|
|
1227
1216
|
#: Weight factor for LassoLars method,
|
|
1228
1217
|
#: defaults to 0.0.
|
|
1229
1218
|
# (Values in the order of 10^⁻9 should produce good results.)
|
|
1230
|
-
alpha = Range(0.0, 1.0, 0.0
|
|
1219
|
+
alpha = Range(0.0, 1.0, 0.0)
|
|
1231
1220
|
|
|
1232
1221
|
#: Maximum number of iterations,
|
|
1233
1222
|
#: tradeoff between speed and precision;
|
|
1234
1223
|
#: defaults to 500
|
|
1235
|
-
n_iter = Int(500
|
|
1224
|
+
n_iter = Int(500)
|
|
1236
1225
|
|
|
1237
1226
|
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
1238
1227
|
#: Values are converted back before returning.
|
|
1239
1228
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
1240
1229
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
1241
|
-
unit_mult = Float(1e9
|
|
1230
|
+
unit_mult = Float(1e9)
|
|
1242
1231
|
|
|
1243
|
-
|
|
1232
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
1244
1233
|
digest = Property(
|
|
1245
1234
|
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['alpha', 'method', 'n_iter', 'unit_mult'],
|
|
1246
1235
|
)
|
|
@@ -1250,7 +1239,8 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1250
1239
|
return digest(self)
|
|
1251
1240
|
|
|
1252
1241
|
def _calc(self, ind):
|
|
1253
|
-
"""
|
|
1242
|
+
"""
|
|
1243
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1254
1244
|
|
|
1255
1245
|
This is an internal helper function that is automatically called when
|
|
1256
1246
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1265,7 +1255,6 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1265
1255
|
Returns
|
|
1266
1256
|
-------
|
|
1267
1257
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1268
|
-
|
|
1269
1258
|
"""
|
|
1270
1259
|
f = self._f
|
|
1271
1260
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
@@ -1273,7 +1262,7 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1273
1262
|
normfactor = self.sig_loss_norm()
|
|
1274
1263
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1275
1264
|
for i in ind:
|
|
1276
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1265
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
1277
1266
|
y = beamformerFreq(
|
|
1278
1267
|
param_steer_type,
|
|
1279
1268
|
self.r_diag,
|
|
@@ -1282,7 +1271,7 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1282
1271
|
csm,
|
|
1283
1272
|
)[0]
|
|
1284
1273
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1285
|
-
indNegSign = sign(y) < 0
|
|
1274
|
+
indNegSign = np.sign(y) < 0
|
|
1286
1275
|
y[indNegSign] = 0.0
|
|
1287
1276
|
y *= unit
|
|
1288
1277
|
p.freq = f[i]
|
|
@@ -1313,7 +1302,7 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1313
1302
|
# pipeline approach with StandardScaler does scale in a different way, thus we
|
|
1314
1303
|
# monkeypatch the code and normalize ourselves to make results the same over
|
|
1315
1304
|
# different sklearn versions
|
|
1316
|
-
norms = norm(psf, axis=0)
|
|
1305
|
+
norms = spla.norm(psf, axis=0)
|
|
1317
1306
|
# get rid of annoying sklearn warnings that appear
|
|
1318
1307
|
# for sklearn<1.2 despite any settings
|
|
1319
1308
|
with warnings.catch_warnings():
|
|
@@ -1326,7 +1315,8 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1326
1315
|
|
|
1327
1316
|
|
|
1328
1317
|
class BeamformerOrth(BeamformerBase):
|
|
1329
|
-
"""
|
|
1318
|
+
"""
|
|
1319
|
+
Orthogonal deconvolution algorithm.
|
|
1330
1320
|
|
|
1331
1321
|
See :cite:`Sarradj2010` for details.
|
|
1332
1322
|
New faster implementation without explicit (:class:`BeamformerEig`).
|
|
@@ -1334,15 +1324,15 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1334
1324
|
|
|
1335
1325
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
1336
1326
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
1337
|
-
eva_list = CArray(dtype=int, value=array([-1])
|
|
1327
|
+
eva_list = CArray(dtype=int, value=np.array([-1]))
|
|
1338
1328
|
|
|
1339
|
-
#: Number of components to consider, defaults to 1
|
|
1329
|
+
#: Number of components to consider, defaults to ``1``. If set,
|
|
1340
1330
|
#: :attr:`eva_list` will contain
|
|
1341
1331
|
#: the indices of the n largest eigenvalues. Setting :attr:`eva_list`
|
|
1342
1332
|
#: afterwards will override this value.
|
|
1343
1333
|
n = Int(1)
|
|
1344
1334
|
|
|
1345
|
-
|
|
1335
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
1346
1336
|
digest = Property(
|
|
1347
1337
|
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['eva_list'],
|
|
1348
1338
|
)
|
|
@@ -1351,13 +1341,14 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1351
1341
|
def _get_digest(self):
|
|
1352
1342
|
return digest(self)
|
|
1353
1343
|
|
|
1354
|
-
@
|
|
1355
|
-
def
|
|
1344
|
+
@observe('n')
|
|
1345
|
+
def _update_eva_list(self, event): # noqa ARG002
|
|
1356
1346
|
"""Sets the list of eigenvalues to consider."""
|
|
1357
|
-
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
1347
|
+
self.eva_list = np.arange(-1, -1 - self.n, -1)
|
|
1358
1348
|
|
|
1359
1349
|
def _calc(self, ind):
|
|
1360
|
-
"""
|
|
1350
|
+
"""
|
|
1351
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1361
1352
|
|
|
1362
1353
|
This is an internal helper function that is automatically called when
|
|
1363
1354
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1372,30 +1363,29 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1372
1363
|
Returns
|
|
1373
1364
|
-------
|
|
1374
1365
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1375
|
-
|
|
1376
1366
|
"""
|
|
1377
1367
|
f = self._f
|
|
1378
1368
|
num_channels = self.freq_data.num_channels
|
|
1379
1369
|
normfactor = self.sig_loss_norm()
|
|
1380
1370
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1381
1371
|
for i in ind:
|
|
1382
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
1383
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
1372
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
1373
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
1384
1374
|
for n in self.eva_list:
|
|
1385
1375
|
beamformerOutput = beamformerFreq(
|
|
1386
1376
|
param_steer_type,
|
|
1387
1377
|
self.r_diag,
|
|
1388
1378
|
normfactor,
|
|
1389
1379
|
steer_vector(f[i]),
|
|
1390
|
-
(ones(1), eve[:, n].reshape((-1, 1))),
|
|
1380
|
+
(np.ones(1), eve[:, n].reshape((-1, 1))),
|
|
1391
1381
|
)[0]
|
|
1392
1382
|
self._ac[i, beamformerOutput.argmax()] += eva[n] / num_channels
|
|
1393
1383
|
self._fr[i] = 1
|
|
1394
1384
|
|
|
1395
1385
|
|
|
1396
|
-
@deprecated_alias({'n': 'n_iter'}, removal_version='25.10')
|
|
1397
1386
|
class BeamformerCleansc(BeamformerBase):
|
|
1398
|
-
"""
|
|
1387
|
+
"""
|
|
1388
|
+
CLEAN-SC deconvolution algorithm.
|
|
1399
1389
|
|
|
1400
1390
|
See :cite:`Sijtsma2007` for details.
|
|
1401
1391
|
Classic delay-and-sum beamforming is already included.
|
|
@@ -1403,18 +1393,18 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1403
1393
|
|
|
1404
1394
|
#: no of CLEAN-SC iterations
|
|
1405
1395
|
#: defaults to 0, i.e. automatic (max 2*num_channels)
|
|
1406
|
-
n_iter = Int(0
|
|
1396
|
+
n_iter = Int(0)
|
|
1407
1397
|
|
|
1408
1398
|
#: iteration damping factor
|
|
1409
1399
|
#: defaults to 0.6
|
|
1410
|
-
damp = Range(0.01, 1.0, 0.6
|
|
1400
|
+
damp = Range(0.01, 1.0, 0.6)
|
|
1411
1401
|
|
|
1412
1402
|
#: iteration stop criterion for automatic detection
|
|
1413
1403
|
#: iteration stops if power[i]>power[i-stopn]
|
|
1414
1404
|
#: defaults to 3
|
|
1415
|
-
stopn = Int(3
|
|
1405
|
+
stopn = Int(3)
|
|
1416
1406
|
|
|
1417
|
-
|
|
1407
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
1418
1408
|
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n_iter', 'damp', 'stopn'])
|
|
1419
1409
|
|
|
1420
1410
|
@cached_property
|
|
@@ -1422,7 +1412,8 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1422
1412
|
return digest(self)
|
|
1423
1413
|
|
|
1424
1414
|
def _calc(self, ind):
|
|
1425
|
-
"""
|
|
1415
|
+
"""
|
|
1416
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1426
1417
|
|
|
1427
1418
|
This is an internal helper function that is automatically called when
|
|
1428
1419
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1437,18 +1428,17 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1437
1428
|
Returns
|
|
1438
1429
|
-------
|
|
1439
1430
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1440
|
-
|
|
1441
1431
|
"""
|
|
1442
1432
|
f = self._f
|
|
1443
1433
|
normfactor = self.sig_loss_norm()
|
|
1444
1434
|
num_channels = self.freq_data.num_channels
|
|
1445
|
-
result = zeros((self.steer.grid.size), 'f')
|
|
1435
|
+
result = np.zeros((self.steer.grid.size), 'f')
|
|
1446
1436
|
J = num_channels * 2 if not self.n_iter else self.n_iter
|
|
1447
|
-
powers = zeros(J, 'd')
|
|
1437
|
+
powers = np.zeros(J, 'd')
|
|
1448
1438
|
|
|
1449
1439
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1450
1440
|
for i in ind:
|
|
1451
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
1441
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
1452
1442
|
# h = self.steer._beamformerCall(f[i], self.r_diag, normfactor, (csm,))[0]
|
|
1453
1443
|
h = beamformerFreq(param_steer_type, self.r_diag, normfactor, steer_vector(f[i]), csm)[0]
|
|
1454
1444
|
# CLEANSC Iteration
|
|
@@ -1459,25 +1449,25 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1459
1449
|
result[xi_max] += self.damp * hmax
|
|
1460
1450
|
if j > self.stopn and hmax > powers[j - self.stopn]:
|
|
1461
1451
|
break
|
|
1462
|
-
wmax = self.steer.steer_vector(f[i], xi_max) * sqrt(normfactor)
|
|
1452
|
+
wmax = self.steer.steer_vector(f[i], xi_max) * np.sqrt(normfactor)
|
|
1463
1453
|
wmax = wmax[0].conj() # as old code worked with conjugated csm..should be updated
|
|
1464
1454
|
hh = wmax.copy()
|
|
1465
|
-
D1 = dot(csm.T - diag(diag(csm)), wmax) / hmax
|
|
1455
|
+
D1 = np.dot(csm.T - np.diag(np.diag(csm)), wmax) / hmax
|
|
1466
1456
|
ww = wmax.conj() * wmax
|
|
1467
|
-
for
|
|
1457
|
+
for _ in range(20):
|
|
1468
1458
|
H = hh.conj() * hh
|
|
1469
|
-
hh = (D1 + H * wmax) / sqrt(1 + dot(ww, H))
|
|
1470
|
-
hh = hh[:, newaxis]
|
|
1459
|
+
hh = (D1 + H * wmax) / np.sqrt(1 + np.dot(ww, H))
|
|
1460
|
+
hh = hh[:, np.newaxis]
|
|
1471
1461
|
csm1 = hmax * (hh * hh.conj().T)
|
|
1472
1462
|
|
|
1473
1463
|
# h1 = self.steer._beamformerCall(f[i], self.r_diag, normfactor, \
|
|
1474
|
-
# (array((hmax, ))[newaxis, :], hh[newaxis, :].conjugate()))[0]
|
|
1464
|
+
# (np.array((hmax, ))[np.newaxis, :], hh[np.newaxis, :].conjugate()))[0]
|
|
1475
1465
|
h1 = beamformerFreq(
|
|
1476
1466
|
param_steer_type,
|
|
1477
1467
|
self.r_diag,
|
|
1478
1468
|
normfactor,
|
|
1479
1469
|
steer_vector(f[i]),
|
|
1480
|
-
(array((hmax,)), hh.conj()),
|
|
1470
|
+
(np.array((hmax,)), hh.conj()),
|
|
1481
1471
|
)[0]
|
|
1482
1472
|
h -= self.damp * h1
|
|
1483
1473
|
csm -= self.damp * csm1.T # transpose(0,2,1)
|
|
@@ -1486,25 +1476,38 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1486
1476
|
|
|
1487
1477
|
|
|
1488
1478
|
class BeamformerClean(BeamformerBase):
|
|
1489
|
-
"""
|
|
1479
|
+
"""
|
|
1480
|
+
CLEAN deconvolution algorithm.
|
|
1490
1481
|
|
|
1491
1482
|
See :cite:`Hoegbom1974` for details.
|
|
1492
1483
|
"""
|
|
1493
1484
|
|
|
1494
1485
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1495
|
-
psf_precision = Enum('float64', 'float32'
|
|
1486
|
+
psf_precision = Enum('float64', 'float32')
|
|
1496
1487
|
|
|
1497
1488
|
# iteration damping factor
|
|
1498
1489
|
# defaults to 0.6
|
|
1499
|
-
|
|
1490
|
+
#: damping factor
|
|
1491
|
+
damp = Range(0.01, 1.0, 0.6)
|
|
1500
1492
|
|
|
1501
1493
|
# max number of iterations
|
|
1502
|
-
|
|
1494
|
+
#: maximum number of iterations
|
|
1495
|
+
n_iter = Int(100)
|
|
1503
1496
|
|
|
1504
|
-
|
|
1505
|
-
|
|
1497
|
+
#: Flag that defines how to calculate and store the point spread function
|
|
1498
|
+
#: defaults to 'single'.
|
|
1499
|
+
#:
|
|
1500
|
+
#: * 'full': Calculate the full PSF (for all grid points) in one go (should be used if the PSF
|
|
1501
|
+
#: at all grid points is needed, as with :class:`DAMAS<BeamformerDamas>`)
|
|
1502
|
+
#: * 'single': Calculate the PSF for the grid points defined by :attr:`grid_indices`, one by one
|
|
1503
|
+
#: (useful if not all PSFs are needed, as with :class:`CLEAN<BeamformerClean>`)
|
|
1504
|
+
#: * 'block': Calculate the PSF for the grid points defined by :attr:`grid_indices`, in one go
|
|
1505
|
+
#: (useful if not all PSFs are needed, as with :class:`CLEAN<BeamformerClean>`)
|
|
1506
|
+
#: * 'readonly': Do not attempt to calculate the PSF since it should already be cached (useful
|
|
1507
|
+
#: if multiple processes have to access the cache file)
|
|
1508
|
+
calcmode = Enum('block', 'full', 'single', 'readonly')
|
|
1506
1509
|
|
|
1507
|
-
|
|
1510
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
1508
1511
|
digest = Property(
|
|
1509
1512
|
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n_iter', 'damp', 'psf_precision'],
|
|
1510
1513
|
)
|
|
@@ -1529,7 +1532,6 @@ class BeamformerClean(BeamformerBase):
|
|
|
1529
1532
|
Returns
|
|
1530
1533
|
-------
|
|
1531
1534
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1532
|
-
|
|
1533
1535
|
"""
|
|
1534
1536
|
f = self._f
|
|
1535
1537
|
gs = self.steer.grid.size
|
|
@@ -1545,7 +1547,7 @@ class BeamformerClean(BeamformerBase):
|
|
|
1545
1547
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1546
1548
|
for i in ind:
|
|
1547
1549
|
p.freq = f[i]
|
|
1548
|
-
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1550
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128')
|
|
1549
1551
|
dirty = beamformerFreq(
|
|
1550
1552
|
param_steer_type,
|
|
1551
1553
|
self.r_diag,
|
|
@@ -1554,16 +1556,16 @@ class BeamformerClean(BeamformerBase):
|
|
|
1554
1556
|
csm,
|
|
1555
1557
|
)[0]
|
|
1556
1558
|
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1557
|
-
indNegSign = sign(dirty) < 0
|
|
1559
|
+
indNegSign = np.sign(dirty) < 0
|
|
1558
1560
|
dirty[indNegSign] = 0.0
|
|
1559
1561
|
|
|
1560
|
-
clean = zeros(gs, dtype=dirty.dtype)
|
|
1562
|
+
clean = np.zeros(gs, dtype=dirty.dtype)
|
|
1561
1563
|
i_iter = 0
|
|
1562
1564
|
flag = True
|
|
1563
1565
|
while flag:
|
|
1564
1566
|
dirty_sum = abs(dirty).sum(0)
|
|
1565
1567
|
next_max = dirty.argmax(0)
|
|
1566
|
-
p.grid_indices = array([next_max])
|
|
1568
|
+
p.grid_indices = np.array([next_max])
|
|
1567
1569
|
psf = p.psf.reshape(gs)
|
|
1568
1570
|
new_amp = self.damp * dirty[next_max] # / psf[next_max]
|
|
1569
1571
|
clean[next_max] += new_amp
|
|
@@ -1575,9 +1577,9 @@ class BeamformerClean(BeamformerBase):
|
|
|
1575
1577
|
self._fr[i] = 1
|
|
1576
1578
|
|
|
1577
1579
|
|
|
1578
|
-
@deprecated_alias({'max_iter': 'n_iter'}, removal_version='25.10')
|
|
1579
1580
|
class BeamformerCMF(BeamformerBase):
|
|
1580
|
-
"""
|
|
1581
|
+
"""
|
|
1582
|
+
Covariance Matrix Fitting algorithm.
|
|
1581
1583
|
|
|
1582
1584
|
This is not really a beamformer, but an inverse method.
|
|
1583
1585
|
See :cite:`Yardibi2008` for details.
|
|
@@ -1596,37 +1598,33 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1596
1598
|
'fmin_l_bfgs_b',
|
|
1597
1599
|
'Split_Bregman',
|
|
1598
1600
|
'FISTA',
|
|
1599
|
-
desc='fit method used',
|
|
1600
1601
|
)
|
|
1601
1602
|
|
|
1602
1603
|
#: Weight factor for LassoLars method,
|
|
1603
1604
|
#: defaults to 0.0.
|
|
1604
1605
|
#: (Use values in the order of 10^⁻9 for good results.)
|
|
1605
|
-
alpha = Range(0.0, 1.0, 0.0
|
|
1606
|
+
alpha = Range(0.0, 1.0, 0.0)
|
|
1606
1607
|
|
|
1607
1608
|
#: Total or maximum number of iterations
|
|
1608
1609
|
#: (depending on :attr:`method`),
|
|
1609
1610
|
#: tradeoff between speed and precision;
|
|
1610
1611
|
#: defaults to 500
|
|
1611
|
-
n_iter = Int(500
|
|
1612
|
+
n_iter = Int(500)
|
|
1612
1613
|
|
|
1613
1614
|
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
1614
1615
|
#: Values are converted back before returning.
|
|
1615
1616
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
1616
1617
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
1617
|
-
unit_mult = Float(1e9
|
|
1618
|
+
unit_mult = Float(1e9)
|
|
1618
1619
|
|
|
1619
1620
|
#: If True, shows the status of the PyLops solver. Only relevant in case of FISTA or
|
|
1620
1621
|
#: Split_Bregman
|
|
1621
|
-
show = Bool(False
|
|
1622
|
+
show = Bool(False)
|
|
1622
1623
|
|
|
1623
1624
|
#: Energy normalization in case of diagonal removal not implemented for inverse methods.
|
|
1624
|
-
r_diag_norm = Enum(
|
|
1625
|
-
None,
|
|
1626
|
-
desc='Energy normalization in case of diagonal removal not implemented for inverse methods',
|
|
1627
|
-
)
|
|
1625
|
+
r_diag_norm = Enum(None)
|
|
1628
1626
|
|
|
1629
|
-
|
|
1627
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
1630
1628
|
digest = Property(
|
|
1631
1629
|
depends_on=[
|
|
1632
1630
|
'freq_data.digest',
|
|
@@ -1644,8 +1642,8 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1644
1642
|
def _get_digest(self):
|
|
1645
1643
|
return digest(self)
|
|
1646
1644
|
|
|
1647
|
-
@
|
|
1648
|
-
def _validate(self):
|
|
1645
|
+
@observe('method')
|
|
1646
|
+
def _validate(self, event): # noqa ARG002
|
|
1649
1647
|
if self.method in ['FISTA', 'Split_Bregman'] and not config.have_pylops:
|
|
1650
1648
|
msg = (
|
|
1651
1649
|
'Cannot import Pylops package. No Pylops installed.'
|
|
@@ -1654,7 +1652,8 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1654
1652
|
raise ImportError(msg)
|
|
1655
1653
|
|
|
1656
1654
|
def _calc(self, ind):
|
|
1657
|
-
"""
|
|
1655
|
+
"""
|
|
1656
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1658
1657
|
|
|
1659
1658
|
This is an internal helper function that is automatically called when
|
|
1660
1659
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1669,13 +1668,12 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1669
1668
|
Returns
|
|
1670
1669
|
-------
|
|
1671
1670
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1672
|
-
|
|
1673
1671
|
"""
|
|
1674
1672
|
f = self._f
|
|
1675
1673
|
|
|
1676
1674
|
# function to repack complex matrices to deal with them in real number space
|
|
1677
1675
|
def realify(matrix):
|
|
1678
|
-
return vstack([matrix.real, matrix.imag])
|
|
1676
|
+
return np.vstack([matrix.real, matrix.imag])
|
|
1679
1677
|
|
|
1680
1678
|
# prepare calculation
|
|
1681
1679
|
nc = self.freq_data.num_channels
|
|
@@ -1683,29 +1681,29 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1683
1681
|
unit = self.unit_mult
|
|
1684
1682
|
|
|
1685
1683
|
for i in ind:
|
|
1686
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
1684
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
1687
1685
|
|
|
1688
1686
|
h = self.steer.transfer(f[i]).T
|
|
1689
1687
|
|
|
1690
1688
|
# reduced Kronecker product (only where solution matrix != 0)
|
|
1691
|
-
Bc = (h[:, :, newaxis] * h.conjugate().T[newaxis, :, :]).transpose(2, 0, 1)
|
|
1689
|
+
Bc = (h[:, :, np.newaxis] * h.conjugate().T[np.newaxis, :, :]).transpose(2, 0, 1)
|
|
1692
1690
|
Ac = Bc.reshape(nc * nc, num_points)
|
|
1693
1691
|
|
|
1694
|
-
# get indices for upper triangular matrices (use tril b/c transposed)
|
|
1695
|
-
ind = reshape(tril(ones((nc, nc))), (nc * nc,)) > 0
|
|
1692
|
+
# get indices for upper triangular matrices (use np.tril b/c transposed)
|
|
1693
|
+
ind = np.reshape(np.tril(np.ones((nc, nc))), (nc * nc,)) > 0
|
|
1696
1694
|
|
|
1697
|
-
ind_im0 = (reshape(eye(nc), (nc * nc,)) == 0)[ind]
|
|
1695
|
+
ind_im0 = (np.reshape(np.eye(nc), (nc * nc,)) == 0)[ind]
|
|
1698
1696
|
if self.r_diag:
|
|
1699
1697
|
# omit main diagonal for noise reduction
|
|
1700
|
-
ind_reim = hstack([ind_im0, ind_im0])
|
|
1698
|
+
ind_reim = np.hstack([ind_im0, ind_im0])
|
|
1701
1699
|
else:
|
|
1702
1700
|
# take all real parts -- also main diagonal
|
|
1703
|
-
ind_reim = hstack([ones(size(ind_im0)) > 0, ind_im0])
|
|
1701
|
+
ind_reim = np.hstack([np.ones(np.size(ind_im0)) > 0, ind_im0])
|
|
1704
1702
|
ind_reim[0] = True # why this ?
|
|
1705
1703
|
|
|
1706
1704
|
A = realify(Ac[ind, :])[ind_reim, :]
|
|
1707
1705
|
# use csm.T for column stacking reshape!
|
|
1708
|
-
R = realify(reshape(csm.T, (nc * nc, 1))[ind, :])[ind_reim, :] * unit
|
|
1706
|
+
R = realify(np.reshape(csm.T, (nc * nc, 1))[ind, :])[ind_reim, :] * unit
|
|
1709
1707
|
# choose method
|
|
1710
1708
|
if self.method == 'LassoLars':
|
|
1711
1709
|
model = LassoLars(alpha=self.alpha * unit, max_iter=self.n_iter, positive=True, **sklearn_ndict)
|
|
@@ -1759,13 +1757,13 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1759
1757
|
# function
|
|
1760
1758
|
func = x.T @ A.T @ A @ x - 2 * R.T @ A @ x + R.T @ R
|
|
1761
1759
|
# derivitaive
|
|
1762
|
-
der = 2 * A.T @ A @ x.T[:, newaxis] - 2 * A.T @ R
|
|
1760
|
+
der = 2 * A.T @ A @ x.T[:, np.newaxis] - 2 * A.T @ R
|
|
1763
1761
|
return func[0].T, der[:, 0]
|
|
1764
1762
|
|
|
1765
1763
|
# initial guess
|
|
1766
|
-
x0 = ones([num_points])
|
|
1764
|
+
x0 = np.ones([num_points])
|
|
1767
1765
|
# boundaries - set to non negative
|
|
1768
|
-
boundaries = tile((0,
|
|
1766
|
+
boundaries = np.tile((0, np.inf), (len(x0), 1))
|
|
1769
1767
|
|
|
1770
1768
|
# optimize
|
|
1771
1769
|
self._ac[i], yval, dicts = fmin_l_bfgs_b(
|
|
@@ -1791,7 +1789,7 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1791
1789
|
# pipeline approach with StandardScaler does scale in a different way, thus we
|
|
1792
1790
|
# monkeypatch the code and normalize ourselves to make results the same over
|
|
1793
1791
|
# different sklearn versions
|
|
1794
|
-
norms = norm(A, axis=0)
|
|
1792
|
+
norms = spla.norm(A, axis=0)
|
|
1795
1793
|
# get rid of sklearn warnings that appear for sklearn<1.2 despite any settings
|
|
1796
1794
|
with warnings.catch_warnings():
|
|
1797
1795
|
warnings.simplefilter('ignore', category=FutureWarning)
|
|
@@ -1802,9 +1800,9 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1802
1800
|
self._fr[i] = 1
|
|
1803
1801
|
|
|
1804
1802
|
|
|
1805
|
-
@deprecated_alias({'max_iter': 'n_iter'}, removal_version='25.10')
|
|
1806
1803
|
class BeamformerSODIX(BeamformerBase):
|
|
1807
|
-
"""
|
|
1804
|
+
"""
|
|
1805
|
+
Source directivity modeling in the cross-spectral matrix (SODIX) algorithm.
|
|
1808
1806
|
|
|
1809
1807
|
See :cite:`Funke2017` and :cite:`Oertwig2019` for details.
|
|
1810
1808
|
"""
|
|
@@ -1812,30 +1810,27 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1812
1810
|
#: Type of fit method to be used ('fmin_l_bfgs_b').
|
|
1813
1811
|
#: These methods are implemented in
|
|
1814
1812
|
#: the scipy module.
|
|
1815
|
-
method = Enum('fmin_l_bfgs_b'
|
|
1813
|
+
method = Enum('fmin_l_bfgs_b')
|
|
1816
1814
|
|
|
1817
1815
|
#: Maximum number of iterations,
|
|
1818
1816
|
#: tradeoff between speed and precision;
|
|
1819
1817
|
#: defaults to 200
|
|
1820
|
-
n_iter = Int(200
|
|
1818
|
+
n_iter = Int(200)
|
|
1821
1819
|
|
|
1822
1820
|
#: Weight factor for regularization,
|
|
1823
1821
|
#: defaults to 0.0.
|
|
1824
|
-
alpha = Range(0.0, 1.0, 0.0
|
|
1822
|
+
alpha = Range(0.0, 1.0, 0.0)
|
|
1825
1823
|
|
|
1826
1824
|
#: Unit multiplier for evaluating, e.g., nPa instead of Pa.
|
|
1827
1825
|
#: Values are converted back before returning.
|
|
1828
1826
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
1829
1827
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
1830
|
-
unit_mult = Float(1e9
|
|
1828
|
+
unit_mult = Float(1e9)
|
|
1831
1829
|
|
|
1832
1830
|
#: Energy normalization in case of diagonal removal not implemented for inverse methods.
|
|
1833
|
-
r_diag_norm = Enum(
|
|
1834
|
-
None,
|
|
1835
|
-
desc='Energy normalization in case of diagonal removal not implemented for inverse methods',
|
|
1836
|
-
)
|
|
1831
|
+
r_diag_norm = Enum(None)
|
|
1837
1832
|
|
|
1838
|
-
|
|
1833
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
1839
1834
|
digest = Property(
|
|
1840
1835
|
depends_on=[
|
|
1841
1836
|
'freq_data.digest',
|
|
@@ -1854,7 +1849,8 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1854
1849
|
return digest(self)
|
|
1855
1850
|
|
|
1856
1851
|
def _calc(self, ind):
|
|
1857
|
-
"""
|
|
1852
|
+
"""
|
|
1853
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1858
1854
|
|
|
1859
1855
|
This is an internal helper function that is automatically called when
|
|
1860
1856
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -1869,7 +1865,6 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1869
1865
|
Returns
|
|
1870
1866
|
-------
|
|
1871
1867
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1872
|
-
|
|
1873
1868
|
"""
|
|
1874
1869
|
# prepare calculation
|
|
1875
1870
|
f = self._f
|
|
@@ -1884,14 +1879,17 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1884
1879
|
for i in range(1, ind.max() + 1):
|
|
1885
1880
|
if not self._fr[i]:
|
|
1886
1881
|
# measured csm
|
|
1887
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
1882
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
1888
1883
|
# transfer function
|
|
1889
1884
|
h = self.steer.transfer(f[i]).T
|
|
1890
1885
|
|
|
1891
1886
|
if self.method == 'fmin_l_bfgs_b':
|
|
1892
1887
|
# function to minimize
|
|
1893
1888
|
def function(directions):
|
|
1894
|
-
"""
|
|
1889
|
+
"""
|
|
1890
|
+
Calculates SODIX objective function and derivatives for optimization.
|
|
1891
|
+
|
|
1892
|
+
Parameters
|
|
1895
1893
|
----------
|
|
1896
1894
|
directions
|
|
1897
1895
|
[num_points*num_mics]
|
|
@@ -1902,41 +1900,43 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1902
1900
|
[1]
|
|
1903
1901
|
derdrl - derivitaives in direction of D
|
|
1904
1902
|
[num_mics*num_points].
|
|
1905
|
-
|
|
1906
1903
|
"""
|
|
1907
1904
|
#### the sodix function ####
|
|
1908
1905
|
Djm = directions.reshape([num_points, num_mics])
|
|
1909
1906
|
p = h.T * Djm
|
|
1910
|
-
csm_mod = dot(p.T, p.conj())
|
|
1907
|
+
csm_mod = np.dot(p.T, p.conj())
|
|
1911
1908
|
Q = csm - csm_mod
|
|
1912
|
-
func = sum((
|
|
1909
|
+
func = np.sum((np.abs(Q)) ** 2)
|
|
1913
1910
|
|
|
1914
1911
|
# subscripts and operands for numpy einsum and einsum_path
|
|
1915
1912
|
subscripts = 'rl,rm,ml->rl'
|
|
1916
1913
|
operands = (h.T, h.T.conj() * Djm, Q)
|
|
1917
|
-
es_path = einsum_path(subscripts, *operands, optimize='greedy')[0]
|
|
1914
|
+
es_path = np.einsum_path(subscripts, *operands, optimize='greedy')[0]
|
|
1918
1915
|
|
|
1919
1916
|
#### the sodix derivative ####
|
|
1920
|
-
derdrl = einsum(subscripts, *operands, optimize=es_path)
|
|
1921
|
-
derdrl = -4 * real(derdrl)
|
|
1917
|
+
derdrl = np.einsum(subscripts, *operands, optimize=es_path)
|
|
1918
|
+
derdrl = -4 * np.real(derdrl)
|
|
1922
1919
|
return func, derdrl.ravel()
|
|
1923
1920
|
|
|
1924
1921
|
##### initial guess ####
|
|
1925
1922
|
if not self._fr[(i - 1)]:
|
|
1926
|
-
D0 = ones([num_points, num_mics])
|
|
1923
|
+
D0 = np.ones([num_points, num_mics])
|
|
1927
1924
|
else:
|
|
1928
|
-
D0 = sqrt(
|
|
1925
|
+
D0 = np.sqrt(
|
|
1929
1926
|
self._ac[(i - 1)]
|
|
1930
|
-
* real(
|
|
1927
|
+
* np.real(
|
|
1928
|
+
np.trace(csm)
|
|
1929
|
+
/ np.trace(np.array(self.freq_data.csm[i - 1], dtype='complex128', copy=True))
|
|
1930
|
+
),
|
|
1931
1931
|
)
|
|
1932
1932
|
|
|
1933
1933
|
# boundaries - set to non negative [2*(num_points*num_mics)]
|
|
1934
|
-
boundaries = tile((0,
|
|
1934
|
+
boundaries = np.tile((0, np.inf), (num_points * num_mics, 1))
|
|
1935
1935
|
|
|
1936
1936
|
# optimize with gradient solver
|
|
1937
1937
|
# see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html
|
|
1938
1938
|
|
|
1939
|
-
qi = ones([num_points, num_mics])
|
|
1939
|
+
qi = np.ones([num_points, num_mics])
|
|
1940
1940
|
qi, yval, dicts = fmin_l_bfgs_b(
|
|
1941
1941
|
function,
|
|
1942
1942
|
D0,
|
|
@@ -1959,9 +1959,9 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
1959
1959
|
self._fr[i] = 1
|
|
1960
1960
|
|
|
1961
1961
|
|
|
1962
|
-
@deprecated_alias({'max_iter': 'n_iter'}, removal_version='25.10')
|
|
1963
1962
|
class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
1964
|
-
"""
|
|
1963
|
+
"""
|
|
1964
|
+
Beamforming GIB methods with different normalizations.
|
|
1965
1965
|
|
|
1966
1966
|
See :cite:`Suzuki2011` for details.
|
|
1967
1967
|
"""
|
|
@@ -1970,13 +1970,13 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
1970
1970
|
#: Values are converted back before returning.
|
|
1971
1971
|
#: Temporary conversion may be necessary to not reach machine epsilon
|
|
1972
1972
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
1973
|
-
unit_mult = Float(1e9
|
|
1973
|
+
unit_mult = Float(1e9)
|
|
1974
1974
|
|
|
1975
1975
|
#: Total or maximum number of iterations
|
|
1976
1976
|
#: (depending on :attr:`method`),
|
|
1977
1977
|
#: tradeoff between speed and precision;
|
|
1978
1978
|
#: defaults to 10
|
|
1979
|
-
n_iter = Int(10
|
|
1979
|
+
n_iter = Int(10)
|
|
1980
1980
|
|
|
1981
1981
|
#: Type of fit method to be used ('Suzuki', 'LassoLars', 'LassoLarsCV', 'LassoLarsBIC',
|
|
1982
1982
|
#: 'OMPCV' or 'NNLS', defaults to 'Suzuki').
|
|
@@ -1991,37 +1991,33 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
1991
1991
|
'LassoLarsCV',
|
|
1992
1992
|
'OMPCV',
|
|
1993
1993
|
'NNLS',
|
|
1994
|
-
desc='fit method used',
|
|
1995
1994
|
)
|
|
1996
1995
|
|
|
1997
1996
|
#: Weight factor for LassoLars method,
|
|
1998
1997
|
#: defaults to 0.0.
|
|
1999
|
-
alpha = Range(0.0, 1.0, 0.0
|
|
1998
|
+
alpha = Range(0.0, 1.0, 0.0)
|
|
2000
1999
|
# (use values in the order of 10^⁻9 for good results)
|
|
2001
2000
|
|
|
2002
2001
|
#: Norm to consider for the regularization in InverseIRLS and Suzuki methods
|
|
2003
2002
|
#: defaults to L-1 Norm
|
|
2004
|
-
pnorm = Float(1
|
|
2003
|
+
pnorm = Float(1)
|
|
2005
2004
|
|
|
2006
2005
|
#: Beta - Fraction of sources maintained after each iteration
|
|
2007
2006
|
#: defaults to 0.9
|
|
2008
|
-
beta = Float(0.9
|
|
2007
|
+
beta = Float(0.9)
|
|
2009
2008
|
|
|
2010
2009
|
#: eps - Regularization parameter for Suzuki algorithm
|
|
2011
2010
|
#: defaults to 0.05.
|
|
2012
|
-
eps_perc = Float(0.05
|
|
2011
|
+
eps_perc = Float(0.05)
|
|
2013
2012
|
|
|
2014
2013
|
# This feature is not fully supported may be changed in the next release
|
|
2015
|
-
|
|
2016
|
-
m = Int(0
|
|
2014
|
+
#: First eigenvalue to consider. Defaults to 0.
|
|
2015
|
+
m = Int(0)
|
|
2017
2016
|
|
|
2018
2017
|
#: Energy normalization in case of diagonal removal not implemented for inverse methods.
|
|
2019
|
-
r_diag_norm = Enum(
|
|
2020
|
-
None,
|
|
2021
|
-
desc='Energy normalization in case of diagonal removal not implemented for inverse methods',
|
|
2022
|
-
)
|
|
2018
|
+
r_diag_norm = Enum(None)
|
|
2023
2019
|
|
|
2024
|
-
|
|
2020
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
2025
2021
|
digest = Property(
|
|
2026
2022
|
depends_on=[
|
|
2027
2023
|
'steer.inv_digest',
|
|
@@ -2052,7 +2048,8 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2052
2048
|
return min(nm - 1, na)
|
|
2053
2049
|
|
|
2054
2050
|
def _calc(self, ind):
|
|
2055
|
-
"""
|
|
2051
|
+
"""
|
|
2052
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2056
2053
|
|
|
2057
2054
|
This is an internal helper function that is automatically called when
|
|
2058
2055
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -2067,14 +2064,13 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2067
2064
|
Returns
|
|
2068
2065
|
-------
|
|
2069
2066
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
2070
|
-
|
|
2071
2067
|
"""
|
|
2072
2068
|
f = self._f
|
|
2073
2069
|
n = int(self.na) # number of eigenvalues
|
|
2074
2070
|
m = int(self.m) # number of first eigenvalue
|
|
2075
2071
|
num_channels = self.freq_data.num_channels # number of channels
|
|
2076
2072
|
num_points = self.steer.grid.size
|
|
2077
|
-
hh = zeros((1, num_points, num_channels), dtype='D')
|
|
2073
|
+
hh = np.zeros((1, num_points, num_channels), dtype='D')
|
|
2078
2074
|
|
|
2079
2075
|
# Generate a cross spectral matrix, and perform the eigenvalue decomposition
|
|
2080
2076
|
for i in ind:
|
|
@@ -2083,79 +2079,83 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2083
2079
|
hh = self.steer.transfer(f[i])
|
|
2084
2080
|
A = hh.T
|
|
2085
2081
|
# eigenvalues and vectors
|
|
2086
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=
|
|
2087
|
-
eva, eve = eigh(csm)
|
|
2082
|
+
csm = np.array(self.freq_data.csm[i], dtype='complex128', copy=True)
|
|
2083
|
+
eva, eve = spla.eigh(csm)
|
|
2088
2084
|
eva = eva[::-1]
|
|
2089
2085
|
eve = eve[:, ::-1]
|
|
2090
2086
|
# set small values zo 0, lowers numerical errors in simulated data
|
|
2091
2087
|
eva[eva < max(eva) / 1e12] = 0
|
|
2092
2088
|
# init sources
|
|
2093
|
-
qi = zeros([n + m, num_points], dtype='complex128')
|
|
2089
|
+
qi = np.zeros([n + m, num_points], dtype='complex128')
|
|
2094
2090
|
# Select the number of coherent modes to be processed referring to the eigenvalue
|
|
2095
2091
|
# distribution.
|
|
2096
2092
|
for s in list(range(m, n + m)):
|
|
2097
2093
|
if eva[s] > 0:
|
|
2098
2094
|
# Generate the corresponding eigenmodes
|
|
2099
|
-
emode = array(sqrt(eva[s]) * eve[:, s], dtype='complex128')
|
|
2095
|
+
emode = np.array(np.sqrt(eva[s]) * eve[:, s], dtype='complex128')
|
|
2100
2096
|
# choose method for computation
|
|
2101
2097
|
if self.method == 'Suzuki':
|
|
2102
2098
|
leftpoints = num_points
|
|
2103
|
-
locpoints = arange(num_points)
|
|
2104
|
-
weights = diag(ones(num_points))
|
|
2105
|
-
epsilon = arange(self.n_iter)
|
|
2106
|
-
for it in arange(self.n_iter):
|
|
2099
|
+
locpoints = np.arange(num_points)
|
|
2100
|
+
weights = np.diag(np.ones(num_points))
|
|
2101
|
+
epsilon = np.arange(self.n_iter)
|
|
2102
|
+
for it in np.arange(self.n_iter):
|
|
2107
2103
|
if num_channels <= leftpoints:
|
|
2108
|
-
AWA = dot(dot(A[:, locpoints], weights), A[:, locpoints].conj().T)
|
|
2109
|
-
epsilon[it] = max(
|
|
2110
|
-
qi[s, locpoints] = dot(
|
|
2111
|
-
dot(
|
|
2112
|
-
dot(weights, A[:, locpoints].conj().T),
|
|
2113
|
-
inv(AWA + eye(num_channels) * epsilon[it]),
|
|
2104
|
+
AWA = np.dot(np.dot(A[:, locpoints], weights), A[:, locpoints].conj().T)
|
|
2105
|
+
epsilon[it] = max(np.abs(spla.eigvals(AWA))) * self.eps_perc
|
|
2106
|
+
qi[s, locpoints] = np.dot(
|
|
2107
|
+
np.dot(
|
|
2108
|
+
np.dot(weights, A[:, locpoints].conj().T),
|
|
2109
|
+
spla.inv(AWA + np.eye(num_channels) * epsilon[it]),
|
|
2114
2110
|
),
|
|
2115
2111
|
emode,
|
|
2116
2112
|
)
|
|
2117
2113
|
elif num_channels > leftpoints:
|
|
2118
|
-
AA = dot(A[:, locpoints].conj().T, A[:, locpoints])
|
|
2119
|
-
epsilon[it] = max(
|
|
2120
|
-
qi[s, locpoints] = dot(
|
|
2121
|
-
dot(inv(AA + inv(weights) * epsilon[it]), A[:, locpoints].conj().T),
|
|
2114
|
+
AA = np.dot(A[:, locpoints].conj().T, A[:, locpoints])
|
|
2115
|
+
epsilon[it] = max(np.abs(spla.eigvals(AA))) * self.eps_perc
|
|
2116
|
+
qi[s, locpoints] = np.dot(
|
|
2117
|
+
np.dot(spla.inv(AA + spla.inv(weights) * epsilon[it]), A[:, locpoints].conj().T),
|
|
2122
2118
|
emode,
|
|
2123
2119
|
)
|
|
2124
2120
|
if self.beta < 1 and it > 1:
|
|
2125
2121
|
# Reorder from the greatest to smallest magnitude to define a
|
|
2126
2122
|
# reduced-point source distribution, and reform a reduced transfer
|
|
2127
2123
|
# matrix
|
|
2128
|
-
leftpoints = int(round(num_points * self.beta ** (it + 1)))
|
|
2129
|
-
idx = argsort(abs(qi[s, locpoints]))[::-1]
|
|
2124
|
+
leftpoints = int(np.round(num_points * self.beta ** (it + 1)))
|
|
2125
|
+
idx = np.argsort(abs(qi[s, locpoints]))[::-1]
|
|
2130
2126
|
# print(it, leftpoints, locpoints, idx )
|
|
2131
|
-
locpoints = delete(locpoints, [idx[leftpoints::]])
|
|
2132
|
-
qix = zeros([n + m, leftpoints], dtype='complex128')
|
|
2127
|
+
locpoints = np.delete(locpoints, [idx[leftpoints::]])
|
|
2128
|
+
qix = np.zeros([n + m, leftpoints], dtype='complex128')
|
|
2133
2129
|
qix[s, :] = qi[s, locpoints]
|
|
2134
2130
|
# calc weights for next iteration
|
|
2135
|
-
weights = diag(
|
|
2131
|
+
weights = np.diag(np.abs(qix[s, :]) ** (2 - self.pnorm))
|
|
2136
2132
|
else:
|
|
2137
|
-
weights = diag(
|
|
2133
|
+
weights = np.diag(np.abs(qi[s, :]) ** (2 - self.pnorm))
|
|
2138
2134
|
|
|
2139
2135
|
elif self.method == 'InverseIRLS':
|
|
2140
|
-
weights = eye(num_points)
|
|
2141
|
-
locpoints = arange(num_points)
|
|
2142
|
-
for _it in arange(self.n_iter):
|
|
2136
|
+
weights = np.eye(num_points)
|
|
2137
|
+
locpoints = np.arange(num_points)
|
|
2138
|
+
for _it in np.arange(self.n_iter):
|
|
2143
2139
|
if num_channels <= num_points:
|
|
2144
|
-
wtwi = inv(dot(weights.T, weights))
|
|
2140
|
+
wtwi = spla.inv(np.dot(weights.T, weights))
|
|
2145
2141
|
aH = A.conj().T
|
|
2146
|
-
qi[s, :] = dot(
|
|
2147
|
-
|
|
2148
|
-
|
|
2142
|
+
qi[s, :] = np.dot(
|
|
2143
|
+
np.dot(wtwi, aH), np.dot(spla.inv(np.dot(A, np.dot(wtwi, aH))), emode)
|
|
2144
|
+
)
|
|
2145
|
+
weights = np.diag(np.abs(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2146
|
+
weights = weights / np.sum(np.abs(weights))
|
|
2149
2147
|
elif num_channels > num_points:
|
|
2150
|
-
wtw = dot(weights.T, weights)
|
|
2151
|
-
qi[s, :] =
|
|
2152
|
-
|
|
2153
|
-
|
|
2148
|
+
wtw = np.dot(weights.T, weights)
|
|
2149
|
+
qi[s, :] = np.dot(
|
|
2150
|
+
np.dot(spla.inv(np.dot(np.dot(A.conj.T, wtw), A)), np.dot(A.conj().T, wtw)), emode
|
|
2151
|
+
)
|
|
2152
|
+
weights = np.diag(np.abs(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2153
|
+
weights = weights / np.sum(np.abs(weights))
|
|
2154
2154
|
else:
|
|
2155
|
-
locpoints = arange(num_points)
|
|
2155
|
+
locpoints = np.arange(num_points)
|
|
2156
2156
|
unit = self.unit_mult
|
|
2157
|
-
AB = vstack([hstack([A.real, -A.imag]), hstack([A.imag, A.real])])
|
|
2158
|
-
R = hstack([emode.real.T, emode.imag.T]) * unit
|
|
2157
|
+
AB = np.vstack([np.hstack([A.real, -A.imag]), np.hstack([A.imag, A.real])])
|
|
2158
|
+
R = np.hstack([emode.real.T, emode.imag.T]) * unit
|
|
2159
2159
|
if self.method == 'LassoLars':
|
|
2160
2160
|
model = LassoLars(alpha=self.alpha * unit, max_iter=self.n_iter, positive=True)
|
|
2161
2161
|
elif self.method == 'LassoLarsBIC':
|
|
@@ -2173,7 +2173,7 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2173
2173
|
# way, thus we monkeypatch the code and normalize
|
|
2174
2174
|
# ourselves to make results the same over different
|
|
2175
2175
|
# sklearn versions
|
|
2176
|
-
norms = norm(AB, axis=0)
|
|
2176
|
+
norms = spla.norm(AB, axis=0)
|
|
2177
2177
|
# get rid of annoying sklearn warnings that appear
|
|
2178
2178
|
# for sklearn<1.2 despite any settings
|
|
2179
2179
|
with warnings.catch_warnings():
|
|
@@ -2181,7 +2181,7 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2181
2181
|
# normalized A
|
|
2182
2182
|
model.fit(AB / norms, R)
|
|
2183
2183
|
# recover normalization in the coef's
|
|
2184
|
-
qi_real, qi_imag = hsplit(model.coef_[:] / norms / unit, 2)
|
|
2184
|
+
qi_real, qi_imag = np.hsplit(model.coef_[:] / norms / unit, 2)
|
|
2185
2185
|
# print(s,qi.size)
|
|
2186
2186
|
qi[s, locpoints] = qi_real + qi_imag * 1j
|
|
2187
2187
|
else:
|
|
@@ -2192,8 +2192,8 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2192
2192
|
)
|
|
2193
2193
|
# Generate source maps of all selected eigenmodes, and superpose source intensity
|
|
2194
2194
|
# for each source type.
|
|
2195
|
-
temp = zeros(num_points)
|
|
2196
|
-
temp[locpoints] = sum(
|
|
2195
|
+
temp = np.zeros(num_points)
|
|
2196
|
+
temp[locpoints] = np.sum(np.abs(qi[:, locpoints]) ** 2, axis=0)
|
|
2197
2197
|
self._ac[i] = temp
|
|
2198
2198
|
self._fr[i] = 1
|
|
2199
2199
|
|
|
@@ -2211,7 +2211,8 @@ class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
|
2211
2211
|
return self._gpos
|
|
2212
2212
|
|
|
2213
2213
|
def integrate(self, sector):
|
|
2214
|
-
"""
|
|
2214
|
+
"""
|
|
2215
|
+
Integrates result map over a given sector.
|
|
2215
2216
|
|
|
2216
2217
|
Parameters
|
|
2217
2218
|
----------
|
|
@@ -2222,7 +2223,6 @@ class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
|
2222
2223
|
-------
|
|
2223
2224
|
array of floats
|
|
2224
2225
|
The spectrum (all calculated frequency bands) for the integrated sector.
|
|
2225
|
-
|
|
2226
2226
|
"""
|
|
2227
2227
|
if not isinstance(sector, Sector):
|
|
2228
2228
|
msg = (
|
|
@@ -2235,21 +2235,22 @@ class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
|
2235
2235
|
|
|
2236
2236
|
ind = self.subdomain(sector)
|
|
2237
2237
|
r = self.result
|
|
2238
|
-
h = zeros(r.shape[0])
|
|
2238
|
+
h = np.zeros(r.shape[0])
|
|
2239
2239
|
for i in range(r.shape[0]):
|
|
2240
2240
|
h[i] = r[i][ind].sum()
|
|
2241
2241
|
return h
|
|
2242
2242
|
|
|
2243
2243
|
|
|
2244
2244
|
class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
2245
|
-
"""
|
|
2245
|
+
"""
|
|
2246
|
+
Orthogonal beamforming without predefined grid.
|
|
2246
2247
|
|
|
2247
2248
|
See :cite:`Sarradj2022` for details.
|
|
2248
2249
|
"""
|
|
2249
2250
|
|
|
2250
2251
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
2251
2252
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
2252
|
-
eva_list = CArray(dtype=int, value=array([-1])
|
|
2253
|
+
eva_list = CArray(dtype=int, value=np.array([-1]))
|
|
2253
2254
|
|
|
2254
2255
|
#: Number of components to consider, defaults to 1. If set,
|
|
2255
2256
|
#: :attr:`eva_list` will contain
|
|
@@ -2269,15 +2270,12 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2269
2270
|
#: and 1 iteration
|
|
2270
2271
|
shgo = Dict
|
|
2271
2272
|
|
|
2272
|
-
#:
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
'This is handled via this normalization factor.'
|
|
2277
|
-
'For this class, normalization is not implemented. Defaults to 1.0.',
|
|
2278
|
-
)
|
|
2273
|
+
#: If diagonal of the csm is removed, some signal energy is lost.
|
|
2274
|
+
#: This is handled via this normalization factor.
|
|
2275
|
+
#: For this class, normalization is not implemented. Defaults to 1.0.
|
|
2276
|
+
r_diag_norm = Enum(1.0)
|
|
2279
2277
|
|
|
2280
|
-
|
|
2278
|
+
#: A unique identifier for the beamformer, based on its properties. (read-only)
|
|
2281
2279
|
digest = Property(
|
|
2282
2280
|
depends_on=['freq_data.digest', 'steer.digest', 'precision', 'r_diag', 'eva_list', 'bounds', 'shgo'],
|
|
2283
2281
|
)
|
|
@@ -2286,17 +2284,18 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2286
2284
|
def _get_digest(self):
|
|
2287
2285
|
return digest(self)
|
|
2288
2286
|
|
|
2289
|
-
@
|
|
2290
|
-
def
|
|
2287
|
+
@observe('n')
|
|
2288
|
+
def _update_eva_list(self, event): # noqa ARG002
|
|
2291
2289
|
"""Sets the list of eigenvalues to consider."""
|
|
2292
|
-
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
2290
|
+
self.eva_list = np.arange(-1, -1 - self.n, -1)
|
|
2293
2291
|
|
|
2294
2292
|
@property_depends_on('n')
|
|
2295
2293
|
def _get_size(self):
|
|
2296
2294
|
return self.n * self.freq_data.fftfreq().shape[0]
|
|
2297
2295
|
|
|
2298
2296
|
def _calc(self, ind):
|
|
2299
|
-
"""
|
|
2297
|
+
"""
|
|
2298
|
+
Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2300
2299
|
|
|
2301
2300
|
This is an internal helper function that is automatically called when
|
|
2302
2301
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -2311,13 +2310,12 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2311
2310
|
Returns
|
|
2312
2311
|
-------
|
|
2313
2312
|
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
2314
|
-
|
|
2315
2313
|
"""
|
|
2316
2314
|
f = self._f
|
|
2317
2315
|
normfactor = self.sig_loss_norm()
|
|
2318
2316
|
num_channels = self.freq_data.num_channels
|
|
2319
2317
|
# eigenvalue number list in standard form from largest to smallest
|
|
2320
|
-
eva_list = unique(self.eva_list % self.steer.mics.num_mics)[::-1]
|
|
2318
|
+
eva_list = np.unique(self.eva_list % self.steer.mics.num_mics)[::-1]
|
|
2321
2319
|
steer_type = self.steer.steer_type
|
|
2322
2320
|
if steer_type == 'custom':
|
|
2323
2321
|
msg = 'custom steer_type is not implemented'
|
|
@@ -2336,27 +2334,27 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2336
2334
|
for y in self.bounds[1]:
|
|
2337
2335
|
for z in self.bounds[2]:
|
|
2338
2336
|
roi.append((x, y, z))
|
|
2339
|
-
self.steer.env.roi = array(roi).T
|
|
2340
|
-
bmin = array(tuple(map(min, self.bounds)))
|
|
2341
|
-
bmax = array(tuple(map(max, self.bounds)))
|
|
2337
|
+
self.steer.env.roi = np.array(roi).T
|
|
2338
|
+
bmin = np.array(tuple(map(min, self.bounds)))
|
|
2339
|
+
bmax = np.array(tuple(map(max, self.bounds)))
|
|
2342
2340
|
for i in ind:
|
|
2343
|
-
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
2344
|
-
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
2345
|
-
k = 2 * pi * f[i] / env.c
|
|
2341
|
+
eva = np.array(self.freq_data.eva[i], dtype='float64')
|
|
2342
|
+
eve = np.array(self.freq_data.eve[i], dtype='complex128')
|
|
2343
|
+
k = 2 * np.pi * f[i] / env.c
|
|
2346
2344
|
for j, n in enumerate(eva_list):
|
|
2347
2345
|
# print(f[i],n)
|
|
2348
2346
|
|
|
2349
2347
|
def func(xy):
|
|
2350
2348
|
# function to minimize globally
|
|
2351
|
-
xy = clip(xy, bmin, bmax)
|
|
2352
|
-
r0 = env._r(xy[:, newaxis])
|
|
2353
|
-
rm = env._r(xy[:, newaxis], mpos)
|
|
2349
|
+
xy = np.clip(xy, bmin, bmax)
|
|
2350
|
+
r0 = env._r(xy[:, np.newaxis])
|
|
2351
|
+
rm = env._r(xy[:, np.newaxis], mpos)
|
|
2354
2352
|
return -beamformerFreq(
|
|
2355
2353
|
steer_type,
|
|
2356
2354
|
self.r_diag,
|
|
2357
2355
|
normfactor,
|
|
2358
2356
|
(r0, rm, k),
|
|
2359
|
-
(ones(1), eve[:, n : n + 1]),
|
|
2357
|
+
(np.ones(1), eve[:, n : n + 1]),
|
|
2360
2358
|
)[0][0] # noqa: B023
|
|
2361
2359
|
|
|
2362
2360
|
# simplical global homotopy optimizer
|
|
@@ -2372,7 +2370,8 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2372
2370
|
|
|
2373
2371
|
|
|
2374
2372
|
def L_p(x): # noqa: N802
|
|
2375
|
-
r"""
|
|
2373
|
+
r"""
|
|
2374
|
+
Calculates the sound pressure level from the squared sound pressure.
|
|
2376
2375
|
|
|
2377
2376
|
:math:`L_p = 10 \lg ( x / 4\cdot 10^{-10})`
|
|
2378
2377
|
|
|
@@ -2386,17 +2385,17 @@ def L_p(x): # noqa: N802
|
|
|
2386
2385
|
array of floats
|
|
2387
2386
|
The corresponding sound pressure levels in dB.
|
|
2388
2387
|
If `x<0`, -350.0 dB is returned.
|
|
2389
|
-
|
|
2390
2388
|
"""
|
|
2391
2389
|
# new version to prevent division by zero warning for float32 arguments
|
|
2392
|
-
return 10 * log10(clip(x / 4e-10, 1e-35, None))
|
|
2390
|
+
return 10 * np.log10(np.clip(x / 4e-10, 1e-35, None))
|
|
2393
2391
|
|
|
2394
2392
|
|
|
2395
|
-
# return where(x>0, 10*log10(x/4e-10), -1000.)
|
|
2393
|
+
# return where(x>0, 10*np.log10(x/4e-10), -1000.)
|
|
2396
2394
|
|
|
2397
2395
|
|
|
2398
2396
|
def integrate(data, grid, sector):
|
|
2399
|
-
"""
|
|
2397
|
+
"""
|
|
2398
|
+
Integrates a sound pressure map over a given sector.
|
|
2400
2399
|
|
|
2401
2400
|
This function can be applied on beamforming results to
|
|
2402
2401
|
quantitatively analyze the sound pressure in a given sector.
|
|
@@ -2424,8 +2423,8 @@ def integrate(data, grid, sector):
|
|
|
2424
2423
|
of a :class:`~acoular.grids.Grid`-derived class
|
|
2425
2424
|
(e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
|
|
2426
2425
|
or :meth:`RectGrid3D.indices<acoular.grids.RectGrid3D.indices>`).
|
|
2427
|
-
Possible sectors would be `array([xmin, ymin, xmax, ymax])`
|
|
2428
|
-
or `array([x, y, radius])`.
|
|
2426
|
+
Possible sectors would be `np.array([xmin, ymin, xmax, ymax])`
|
|
2427
|
+
or `np.array([x, y, radius])`.
|
|
2429
2428
|
Alternatively, a :class:`~acoular.grids.Sector`-derived object
|
|
2430
2429
|
can be used.
|
|
2431
2430
|
|
|
@@ -2433,7 +2432,6 @@ def integrate(data, grid, sector):
|
|
|
2433
2432
|
-------
|
|
2434
2433
|
array of floats
|
|
2435
2434
|
The spectrum (all calculated frequency bands) for the integrated sector.
|
|
2436
|
-
|
|
2437
2435
|
"""
|
|
2438
2436
|
if isinstance(sector, Sector):
|
|
2439
2437
|
ind = grid.subdomain(sector)
|
|
@@ -2451,10 +2449,10 @@ def integrate(data, grid, sector):
|
|
|
2451
2449
|
|
|
2452
2450
|
gshape = grid.shape
|
|
2453
2451
|
gsize = grid.size
|
|
2454
|
-
if size(data) == gsize: # one value per grid point
|
|
2452
|
+
if np.size(data) == gsize: # one value per grid point
|
|
2455
2453
|
h = data.reshape(gshape)[ind].sum()
|
|
2456
2454
|
elif data.ndim == 2 and data.shape[1] == gsize:
|
|
2457
|
-
h = zeros(data.shape[0])
|
|
2455
|
+
h = np.zeros(data.shape[0])
|
|
2458
2456
|
for i in range(data.shape[0]):
|
|
2459
2457
|
h[i] = data[i].reshape(gshape)[ind].sum()
|
|
2460
2458
|
return h
|