acoular 24.5__py3-none-any.whl → 24.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acoular/__init__.py +3 -6
- acoular/configuration.py +20 -13
- acoular/demo/acoular_demo.py +28 -35
- acoular/environments.py +13 -10
- acoular/fastFuncs.py +40 -40
- acoular/fbeamform.py +895 -1088
- acoular/grids.py +5 -5
- acoular/h5cache.py +34 -34
- acoular/h5files.py +13 -13
- acoular/internal.py +3 -3
- acoular/sdinput.py +10 -1
- acoular/signals.py +15 -1
- acoular/sources.py +120 -49
- acoular/spectra.py +5 -5
- acoular/tbeamform.py +5 -6
- acoular/tfastfuncs.py +17 -18
- acoular/tools/aiaa.py +4 -5
- acoular/tprocess.py +82 -82
- acoular/version.py +2 -2
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/METADATA +24 -4
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/RECORD +24 -24
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/WHEEL +1 -1
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/licenses/LICENSE +0 -0
acoular/fbeamform.py
CHANGED
|
@@ -55,7 +55,9 @@ from numpy import (
|
|
|
55
55
|
full,
|
|
56
56
|
hsplit,
|
|
57
57
|
hstack,
|
|
58
|
+
index_exp,
|
|
58
59
|
inf,
|
|
60
|
+
integer,
|
|
59
61
|
invert,
|
|
60
62
|
isscalar,
|
|
61
63
|
linalg,
|
|
@@ -89,7 +91,6 @@ from traits.api import (
|
|
|
89
91
|
Any,
|
|
90
92
|
Bool,
|
|
91
93
|
CArray,
|
|
92
|
-
Delegate,
|
|
93
94
|
Dict,
|
|
94
95
|
Enum,
|
|
95
96
|
Float,
|
|
@@ -121,6 +122,8 @@ sklearn_ndict = {}
|
|
|
121
122
|
if parse(sklearn.__version__) < parse('1.4'):
|
|
122
123
|
sklearn_ndict['normalize'] = False
|
|
123
124
|
|
|
125
|
+
BEAMFORMER_BASE_DIGEST_DEPENDENCIES = ['freq_data.digest', 'r_diag', 'r_diag_norm', 'precision', '_steer_obj.digest']
|
|
126
|
+
|
|
124
127
|
|
|
125
128
|
class SteeringVector(HasPrivateTraits):
|
|
126
129
|
"""Basic class for implementing steering vectors with monopole source transfer models."""
|
|
@@ -139,14 +142,6 @@ class SteeringVector(HasPrivateTraits):
|
|
|
139
142
|
#: Defaults to standard :class:`~acoular.environments.Environment` object.
|
|
140
143
|
env = Instance(Environment(), Environment)
|
|
141
144
|
|
|
142
|
-
# TODO: add caching capability for transfer function
|
|
143
|
-
# Flag, if "True" (not default), the transfer function is
|
|
144
|
-
# cached in h5 files and does not have to be recomputed during subsequent
|
|
145
|
-
# program runs.
|
|
146
|
-
# Be aware that setting this to "True" may result in high memory usage.
|
|
147
|
-
# cached = Bool(False,
|
|
148
|
-
# desc="cache flag for transfer function")
|
|
149
|
-
|
|
150
145
|
# Sound travel distances from microphone array center to grid
|
|
151
146
|
# points or reference position (readonly). Feature may change.
|
|
152
147
|
r0 = Property(desc='array center to grid distances')
|
|
@@ -169,8 +164,8 @@ class SteeringVector(HasPrivateTraits):
|
|
|
169
164
|
if isscalar(ref):
|
|
170
165
|
try:
|
|
171
166
|
self._ref = absolute(float(ref))
|
|
172
|
-
except:
|
|
173
|
-
raise TraitError(args=self, name='ref', info='Float or CArray(3,)', value=ref)
|
|
167
|
+
except ValueError as ve:
|
|
168
|
+
raise TraitError(args=self, name='ref', info='Float or CArray(3,)', value=ref) from ve
|
|
174
169
|
elif len(ref) == 3:
|
|
175
170
|
self._ref = array(ref, dtype=float)
|
|
176
171
|
else:
|
|
@@ -263,6 +258,34 @@ class SteeringVector(HasPrivateTraits):
|
|
|
263
258
|
return func(self.transfer(f, ind))
|
|
264
259
|
|
|
265
260
|
|
|
261
|
+
class LazyBfResult:
|
|
262
|
+
"""Manages lazy per-frequency evaluation."""
|
|
263
|
+
|
|
264
|
+
# Internal helper class which works together with BeamformerBase to provide
|
|
265
|
+
# calculation on demand; provides an 'intelligent' [] operator. This is
|
|
266
|
+
# implemented as an extra class instead of as a method of BeamformerBase to
|
|
267
|
+
# properly control the BeamformerBase.result attribute. Might be migrated to
|
|
268
|
+
# be a method of BeamformerBase in the future.
|
|
269
|
+
|
|
270
|
+
def __init__(self, bf):
|
|
271
|
+
self.bf = bf
|
|
272
|
+
|
|
273
|
+
def __getitem__(self, key):
|
|
274
|
+
# 'intelligent' [] operator checks if results are available and triggers calculation
|
|
275
|
+
sl = index_exp[key][0]
|
|
276
|
+
if isinstance(sl, (int, integer)):
|
|
277
|
+
sl = slice(sl, sl + 1)
|
|
278
|
+
# indices which are missing
|
|
279
|
+
missingind = arange(*sl.indices(self.bf._numfreq))[self.bf._fr[sl] == 0]
|
|
280
|
+
# calc if needed
|
|
281
|
+
if missingind.size:
|
|
282
|
+
self.bf._calc(missingind)
|
|
283
|
+
if self.bf.h5f:
|
|
284
|
+
self.bf.h5f.flush()
|
|
285
|
+
|
|
286
|
+
return self.bf._ac.__getitem__(key)
|
|
287
|
+
|
|
288
|
+
|
|
266
289
|
class BeamformerBase(HasPrivateTraits):
|
|
267
290
|
"""Beamforming using the basic delay-and-sum algorithm in the frequency domain."""
|
|
268
291
|
|
|
@@ -387,25 +410,17 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
387
410
|
|
|
388
411
|
#: The beamforming result as squared sound pressure values
|
|
389
412
|
#: at all grid point locations (readonly).
|
|
390
|
-
#: Returns a (number of frequencies, number of gridpoints) array
|
|
413
|
+
#: Returns a (number of frequencies, number of gridpoints) array-like
|
|
414
|
+
#: of floats. Values can only be accessed via the index operator [].
|
|
391
415
|
result = Property(desc='beamforming result')
|
|
392
416
|
|
|
393
417
|
# internal identifier
|
|
394
|
-
digest = Property(depends_on=
|
|
395
|
-
|
|
396
|
-
# internal identifier
|
|
397
|
-
ext_digest = Property(
|
|
398
|
-
depends_on=['digest', 'freq_data.ind_low', 'freq_data.ind_high'],
|
|
399
|
-
)
|
|
418
|
+
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES)
|
|
400
419
|
|
|
401
420
|
@cached_property
|
|
402
421
|
def _get_digest(self):
|
|
403
422
|
return digest(self)
|
|
404
423
|
|
|
405
|
-
@cached_property
|
|
406
|
-
def _get_ext_digest(self):
|
|
407
|
-
return digest(self, 'ext_digest')
|
|
408
|
-
|
|
409
424
|
def _get_filecache(self):
|
|
410
425
|
"""Function collects cached results from file depending on
|
|
411
426
|
global/local caching behaviour. Returns (None, None) if no cachefile/data
|
|
@@ -438,6 +453,13 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
438
453
|
if isinstance(self, BeamformerAdaptiveGrid):
|
|
439
454
|
self.h5f.create_compressible_array('gpos', (3, self.size), 'float64', group)
|
|
440
455
|
self.h5f.create_compressible_array('result', (numfreq, self.size), self.precision, group)
|
|
456
|
+
elif isinstance(self, BeamformerSODIX):
|
|
457
|
+
self.h5f.create_compressible_array(
|
|
458
|
+
'result',
|
|
459
|
+
(numfreq, self.steer.grid.size * self.steer.mics.num_mics),
|
|
460
|
+
self.precision,
|
|
461
|
+
group,
|
|
462
|
+
)
|
|
441
463
|
else:
|
|
442
464
|
self.h5f.create_compressible_array('result', (numfreq, self.steer.grid.size), self.precision, group)
|
|
443
465
|
|
|
@@ -454,58 +476,51 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
454
476
|
if numchannels != self.steer.mics.num_mics or numchannels == 0:
|
|
455
477
|
raise ValueError('%i channels do not fit %i mics' % (numchannels, self.steer.mics.num_mics))
|
|
456
478
|
|
|
457
|
-
@property_depends_on('
|
|
479
|
+
@property_depends_on('digest')
|
|
458
480
|
def _get_result(self):
|
|
459
481
|
"""Implements the :attr:`result` getter routine.
|
|
460
482
|
The beamforming result is either loaded or calculated.
|
|
461
483
|
"""
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
self.h5f.flush()
|
|
484
|
-
# else:
|
|
485
|
-
# print("cached results are complete! return.")
|
|
484
|
+
# store locally for performance
|
|
485
|
+
self._f = self.freq_data.fftfreq()
|
|
486
|
+
self._numfreq = self._f.shape[0]
|
|
487
|
+
self._assert_equal_channels()
|
|
488
|
+
ac, fr = (None, None)
|
|
489
|
+
if not ( # if result caching is active
|
|
490
|
+
config.global_caching == 'none' or (config.global_caching == 'individual' and not self.cached)
|
|
491
|
+
):
|
|
492
|
+
(ac, fr, gpos) = self._get_filecache() # can also be (None, None, None)
|
|
493
|
+
if gpos: # we have an adaptive grid
|
|
494
|
+
self._gpos = gpos
|
|
495
|
+
if ac and fr: # cached data is available
|
|
496
|
+
if config.global_caching == 'readonly':
|
|
497
|
+
(ac, fr) = (ac[:], fr[:]) # so never write back to disk
|
|
498
|
+
else:
|
|
499
|
+
# no caching or not activated, init numpy arrays
|
|
500
|
+
if isinstance(self, BeamformerAdaptiveGrid):
|
|
501
|
+
self._gpos = zeros((3, self.size), dtype=self.precision)
|
|
502
|
+
ac = zeros((self._numfreq, self.size), dtype=self.precision)
|
|
503
|
+
elif isinstance(self, BeamformerSODIX):
|
|
504
|
+
ac = zeros((self._numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
486
505
|
else:
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
ac = zeros((numfreq, self.steer.grid.size), dtype=self.precision)
|
|
493
|
-
fr = zeros(numfreq, dtype='int8')
|
|
494
|
-
self.calc(ac, fr)
|
|
495
|
-
return ac
|
|
506
|
+
ac = zeros((self._numfreq, self.steer.grid.size), dtype=self.precision)
|
|
507
|
+
fr = zeros(self._numfreq, dtype='int8')
|
|
508
|
+
self._ac = ac
|
|
509
|
+
self._fr = fr
|
|
510
|
+
return LazyBfResult(self)
|
|
496
511
|
|
|
497
512
|
def sig_loss_norm(self):
|
|
498
513
|
"""If the diagonal of the CSM is removed one has to handle the loss
|
|
499
514
|
of signal energy --> Done via a normalization factor.
|
|
500
515
|
"""
|
|
501
516
|
if not self.r_diag: # Full CSM --> no normalization needed
|
|
502
|
-
|
|
517
|
+
normfactor = 1.0
|
|
503
518
|
elif self.r_diag_norm == 0.0: # Removed diag: standard normalization factor
|
|
504
519
|
nMics = float(self.freq_data.numchannels)
|
|
505
|
-
|
|
520
|
+
normfactor = nMics / (nMics - 1)
|
|
506
521
|
elif self.r_diag_norm != 0.0: # Removed diag: user defined normalization factor
|
|
507
|
-
|
|
508
|
-
return
|
|
522
|
+
normfactor = self.r_diag_norm
|
|
523
|
+
return normfactor
|
|
509
524
|
|
|
510
525
|
def _beamformer_params(self):
|
|
511
526
|
"""Manages the parameters for calling of the core beamformer functionality.
|
|
@@ -528,9 +543,8 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
528
543
|
param_steer_func = self.steer.steer_vector
|
|
529
544
|
return param_type, param_steer_func
|
|
530
545
|
|
|
531
|
-
def
|
|
532
|
-
"""Calculates the
|
|
533
|
-
defined by :attr:`freq_data`.
|
|
546
|
+
def _calc(self, ind):
|
|
547
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
534
548
|
|
|
535
549
|
This is an internal helper function that is automatically called when
|
|
536
550
|
accessing the beamformer's :attr:`result` or calling
|
|
@@ -538,39 +552,33 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
538
552
|
|
|
539
553
|
Parameters
|
|
540
554
|
----------
|
|
541
|
-
|
|
542
|
-
This array
|
|
543
|
-
|
|
544
|
-
value after calling this method.
|
|
545
|
-
fr : array of booleans
|
|
546
|
-
The entries of this [number of frequencies]-sized array are either
|
|
547
|
-
'True' (if the result for this frequency has already been calculated)
|
|
548
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
549
|
-
After the calculation at a certain frequency the value will be set
|
|
550
|
-
to 'True'
|
|
555
|
+
ind : array of int
|
|
556
|
+
This array contains all frequency indices for which (re)calculation is
|
|
557
|
+
to be performed
|
|
551
558
|
|
|
552
559
|
Returns
|
|
553
560
|
-------
|
|
554
|
-
This method only returns values through
|
|
561
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
555
562
|
|
|
556
563
|
"""
|
|
557
|
-
f = self.
|
|
564
|
+
f = self._f
|
|
565
|
+
normfactor = self.sig_loss_norm()
|
|
558
566
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
559
|
-
for i in
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
567
|
+
for i in ind:
|
|
568
|
+
# print(f'compute{i}')
|
|
569
|
+
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
570
|
+
beamformerOutput = beamformerFreq(
|
|
571
|
+
param_steer_type,
|
|
572
|
+
self.r_diag,
|
|
573
|
+
normfactor,
|
|
574
|
+
steer_vector(f[i]),
|
|
575
|
+
csm,
|
|
576
|
+
)[0]
|
|
577
|
+
if self.r_diag: # set (unphysical) negative output values to 0
|
|
578
|
+
indNegSign = sign(beamformerOutput) < 0
|
|
579
|
+
beamformerOutput[indNegSign] = 0.0
|
|
580
|
+
self._ac[i] = beamformerOutput
|
|
581
|
+
self._fr[i] = 1
|
|
574
582
|
|
|
575
583
|
def synthetic(self, f, num=0):
|
|
576
584
|
"""Evaluates the beamforming result for an arbitrary frequency band.
|
|
@@ -608,8 +616,6 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
608
616
|
if len(freq) == 0:
|
|
609
617
|
return None
|
|
610
618
|
|
|
611
|
-
indices = self.freq_data.indices
|
|
612
|
-
|
|
613
619
|
if num == 0:
|
|
614
620
|
# single frequency line
|
|
615
621
|
ind = searchsorted(freq, f)
|
|
@@ -629,14 +635,6 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
629
635
|
Warning,
|
|
630
636
|
stacklevel=2,
|
|
631
637
|
)
|
|
632
|
-
if ind not in indices:
|
|
633
|
-
warn(
|
|
634
|
-
'Beamforming result may not have been calculated '
|
|
635
|
-
'for queried frequency. Check '
|
|
636
|
-
'freq_data.ind_low and freq_data.ind_high!',
|
|
637
|
-
Warning,
|
|
638
|
-
stacklevel=2,
|
|
639
|
-
)
|
|
640
638
|
h = res[ind]
|
|
641
639
|
else:
|
|
642
640
|
# fractional octave band
|
|
@@ -659,46 +657,56 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
659
657
|
h = zeros_like(res[0])
|
|
660
658
|
else:
|
|
661
659
|
h = sum(res[ind1:ind2], 0)
|
|
662
|
-
if not ((ind1 in indices) and (ind2 in indices)):
|
|
663
|
-
warn(
|
|
664
|
-
'Beamforming result may not have been calculated '
|
|
665
|
-
'for all queried frequencies. Check '
|
|
666
|
-
'freq_data.ind_low and freq_data.ind_high!',
|
|
667
|
-
Warning,
|
|
668
|
-
stacklevel=2,
|
|
669
|
-
)
|
|
670
660
|
if isinstance(self, BeamformerAdaptiveGrid):
|
|
671
661
|
return h
|
|
662
|
+
if isinstance(self, BeamformerSODIX):
|
|
663
|
+
return h.reshape((self.steer.grid.size, self.steer.mics.num_mics))
|
|
672
664
|
return h.reshape(self.steer.grid.shape)
|
|
673
665
|
|
|
674
|
-
def integrate(self, sector):
|
|
666
|
+
def integrate(self, sector, frange=None, num=0):
|
|
675
667
|
"""Integrates result map over a given sector.
|
|
676
668
|
|
|
677
669
|
Parameters
|
|
678
670
|
----------
|
|
679
|
-
sector: array of floats
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
671
|
+
sector: array of floats or :class:`~acoular.grids.Sector`
|
|
672
|
+
either an array, tuple or list with arguments for the 'indices'
|
|
673
|
+
method of a :class:`~acoular.grids.Grid`-derived class
|
|
674
|
+
(e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
|
|
675
|
+
or :meth:`RectGrid3D.indices<acoular.grids.RectGrid3D.indices>`).
|
|
676
|
+
Possible sectors would be *array([xmin, ymin, xmax, ymax])*
|
|
677
|
+
or *array([x, y, radius])* or an instance of a
|
|
678
|
+
:class:`~acoular.grids.Sector`-derived class
|
|
679
|
+
|
|
680
|
+
frange: tuple or None
|
|
681
|
+
a tuple of (fmin,fmax) frequencies to include in the result if *num*==0,
|
|
682
|
+
or band center frequency/frequencies for which to return the results
|
|
683
|
+
if *num*>0; if None, then the frequency range is determined from
|
|
684
|
+
the settings of the :attr:`PowerSpectra.ind_low` and
|
|
685
|
+
:attr:`PowerSpectra.ind_high` of :attr:`freq_data`
|
|
686
|
+
|
|
687
|
+
num : integer
|
|
688
|
+
Controls the width of the frequency bands considered; defaults to
|
|
689
|
+
0 (single frequency line). Only considered if *frange* is not None.
|
|
690
|
+
|
|
691
|
+
=== =====================
|
|
692
|
+
num frequency band width
|
|
693
|
+
=== =====================
|
|
694
|
+
0 single frequency line
|
|
695
|
+
1 octave band
|
|
696
|
+
3 third-octave band
|
|
697
|
+
n 1/n-octave band
|
|
698
|
+
=== =====================
|
|
699
|
+
|
|
686
700
|
|
|
687
701
|
Returns
|
|
688
702
|
-------
|
|
689
|
-
array of floats
|
|
690
|
-
|
|
691
|
-
|
|
703
|
+
res or (f, res): array of floats or tuple(array of floats, array of floats)
|
|
704
|
+
If *frange*==None or *num*>0, the spectrum (all calculated frequency bands)
|
|
705
|
+
for the integrated sector is returned as *res*. The dimension of this array is the
|
|
706
|
+
number of frequencies given by :attr:`freq_data` and entries not computed are zero.
|
|
707
|
+
If *frange*!=None and *num*==0, then (f, res) is returned where *f* are the (band)
|
|
708
|
+
frequencies and the dimension of both arrays is determined from *frange*
|
|
692
709
|
"""
|
|
693
|
-
# resp. array([rmin, phimin, rmax, phimax]), array([r, phi, radius]).
|
|
694
|
-
|
|
695
|
-
# ind = self.grid.indices(*sector)
|
|
696
|
-
# gshape = self.grid.shape
|
|
697
|
-
# r = self.result
|
|
698
|
-
# rshape = r.shape
|
|
699
|
-
# mapshape = (rshape[0], ) + gshape
|
|
700
|
-
# h = r[:].reshape(mapshape)[ (s_[:], ) + ind ]
|
|
701
|
-
# return h.reshape(h.shape[0], prod(h.shape[1:])).sum(axis=1)
|
|
702
710
|
if isinstance(sector, Sector):
|
|
703
711
|
ind = self.steer.grid.subdomain(sector)
|
|
704
712
|
elif hasattr(self.steer.grid, 'indices'):
|
|
@@ -713,10 +721,40 @@ class BeamformerBase(HasPrivateTraits):
|
|
|
713
721
|
msg,
|
|
714
722
|
)
|
|
715
723
|
gshape = self.steer.grid.shape
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
724
|
+
if num == 0 or frange is None:
|
|
725
|
+
if frange is None:
|
|
726
|
+
ind_low = self.freq_data.ind_low
|
|
727
|
+
ind_high = self.freq_data.ind_high
|
|
728
|
+
if ind_low is None:
|
|
729
|
+
ind_low = 0
|
|
730
|
+
if ind_low < 0:
|
|
731
|
+
ind_low += self._numfreq
|
|
732
|
+
if ind_high is None:
|
|
733
|
+
ind_high = self._numfreq
|
|
734
|
+
if ind_high < 0:
|
|
735
|
+
ind_high += self._numfreq
|
|
736
|
+
irange = (ind_low, ind_high)
|
|
737
|
+
num = 0
|
|
738
|
+
elif len(frange) == 2:
|
|
739
|
+
irange = (searchsorted(self._f, frange[0]), searchsorted(self._f, frange[1]))
|
|
740
|
+
else:
|
|
741
|
+
msg = 'Only a tuple of length 2 is allowed for frange if num==0'
|
|
742
|
+
raise TypeError(
|
|
743
|
+
msg,
|
|
744
|
+
)
|
|
745
|
+
h = zeros(self._numfreq, dtype=float)
|
|
746
|
+
sl = slice(*irange)
|
|
747
|
+
r = self.result[sl]
|
|
748
|
+
for i in range(*irange):
|
|
749
|
+
# we do this per frequency because r might not have fancy indexing
|
|
750
|
+
h[i] = r[i - sl.start].reshape(gshape)[ind].sum()
|
|
751
|
+
if frange is None:
|
|
752
|
+
return h
|
|
753
|
+
return self._f[sl], h[sl]
|
|
754
|
+
|
|
755
|
+
h = zeros(len(frange), dtype=float)
|
|
756
|
+
for i, f in enumerate(frange):
|
|
757
|
+
h[i] = self.synthetic(f, num).reshape(gshape)[ind].sum()
|
|
720
758
|
return h
|
|
721
759
|
|
|
722
760
|
|
|
@@ -726,138 +764,130 @@ class BeamformerFunctional(BeamformerBase):
|
|
|
726
764
|
#: Functional exponent, defaults to 1 (= Classic Beamforming).
|
|
727
765
|
gamma = Float(1, desc='functional exponent')
|
|
728
766
|
|
|
729
|
-
# internal identifier
|
|
730
|
-
digest = Property(depends_on=['freq_data.digest', '_steer_obj.digest', 'r_diag', 'gamma'])
|
|
731
|
-
|
|
732
767
|
#: Functional Beamforming is only well defined for full CSM
|
|
733
768
|
r_diag = Enum(False, desc='False, as Functional Beamformer is only well defined for the full CSM')
|
|
734
769
|
|
|
770
|
+
#: Normalization factor in case of CSM diagonal removal. Defaults to 1.0 since Functional Beamforming is only well defined for full CSM.
|
|
771
|
+
r_diag_norm = Enum(
|
|
772
|
+
1.0,
|
|
773
|
+
desc='No normalization needed. Functional Beamforming is only well defined for full CSM.',
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# internal identifier
|
|
777
|
+
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['gamma'])
|
|
778
|
+
|
|
735
779
|
@cached_property
|
|
736
780
|
def _get_digest(self):
|
|
737
781
|
return digest(self)
|
|
738
782
|
|
|
739
|
-
def
|
|
740
|
-
"""Calculates the
|
|
783
|
+
def _calc(self, ind):
|
|
784
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
741
785
|
|
|
742
786
|
This is an internal helper function that is automatically called when
|
|
743
|
-
accessing the beamformer's :attr
|
|
744
|
-
its :meth
|
|
787
|
+
accessing the beamformer's :attr:`result` or calling
|
|
788
|
+
its :meth:`synthetic` method.
|
|
745
789
|
|
|
746
790
|
Parameters
|
|
747
791
|
----------
|
|
748
|
-
|
|
749
|
-
This array
|
|
750
|
-
|
|
751
|
-
value after calling this method.
|
|
752
|
-
fr : array of booleans
|
|
753
|
-
The entries of this [number of frequencies]-sized array are either
|
|
754
|
-
'True' (if the result for this frequency has already been calculated)
|
|
755
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
756
|
-
After the calculation at a certain frequency the value will be set
|
|
757
|
-
to 'True'
|
|
792
|
+
ind : array of int
|
|
793
|
+
This array contains all frequency indices for which (re)calculation is
|
|
794
|
+
to be performed
|
|
758
795
|
|
|
759
796
|
Returns
|
|
760
797
|
-------
|
|
761
|
-
This method only returns values through
|
|
798
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
762
799
|
|
|
763
800
|
"""
|
|
764
|
-
f = self.
|
|
765
|
-
|
|
801
|
+
f = self._f
|
|
802
|
+
normfactor = self.sig_loss_norm()
|
|
766
803
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
767
|
-
for i in
|
|
768
|
-
if
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
beamformerOutput /= steerNorm # take normalized steering vec
|
|
804
|
+
for i in ind:
|
|
805
|
+
if self.r_diag:
|
|
806
|
+
# This case is not used at the moment (see Trait r_diag)
|
|
807
|
+
# It would need some testing as structural changes were not tested...
|
|
808
|
+
# ==============================================================================
|
|
809
|
+
# One cannot use spectral decomposition when diagonal of csm is removed,
|
|
810
|
+
# as the resulting modified eigenvectors are not orthogonal to each other anymore.
|
|
811
|
+
# Therefor potentiating cannot be applied only to the eigenvalues.
|
|
812
|
+
# --> To avoid this the root of the csm (removed diag) is calculated directly.
|
|
813
|
+
# WATCH OUT: This doesn't really produce good results.
|
|
814
|
+
# ==============================================================================
|
|
815
|
+
csm = self.freq_data.csm[i]
|
|
816
|
+
fill_diagonal(csm, 0)
|
|
817
|
+
csmRoot = fractional_matrix_power(csm, 1.0 / self.gamma)
|
|
818
|
+
beamformerOutput, steerNorm = beamformerFreq(
|
|
819
|
+
param_steer_type,
|
|
820
|
+
self.r_diag,
|
|
821
|
+
normfactor,
|
|
822
|
+
steer_vector(f[i]),
|
|
823
|
+
csmRoot,
|
|
824
|
+
)
|
|
825
|
+
beamformerOutput /= steerNorm # take normalized steering vec
|
|
790
826
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
827
|
+
# set (unphysical) negative output values to 0
|
|
828
|
+
indNegSign = sign(beamformerOutput) < 0
|
|
829
|
+
beamformerOutput[indNegSign] = 0.0
|
|
830
|
+
else:
|
|
831
|
+
eva = array(self.freq_data.eva[i], dtype='float64') ** (1.0 / self.gamma)
|
|
832
|
+
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
833
|
+
beamformerOutput, steerNorm = beamformerFreq(
|
|
834
|
+
param_steer_type,
|
|
835
|
+
self.r_diag,
|
|
836
|
+
1.0,
|
|
837
|
+
steer_vector(f[i]),
|
|
838
|
+
(eva, eve),
|
|
839
|
+
)
|
|
840
|
+
beamformerOutput /= steerNorm # take normalized steering vec
|
|
841
|
+
self._ac[i] = (
|
|
842
|
+
(beamformerOutput**self.gamma) * steerNorm * normfactor
|
|
843
|
+
) # the normalization must be done outside the beamformer
|
|
844
|
+
self._fr[i] = 1
|
|
809
845
|
|
|
810
846
|
|
|
811
847
|
class BeamformerCapon(BeamformerBase):
|
|
812
|
-
"""Beamforming using the Capon (Mininimum Variance) algorithm,
|
|
813
|
-
see :ref:`Capon, 1969<Capon1969>`.
|
|
814
|
-
"""
|
|
848
|
+
"""Beamforming using the Capon (Mininimum Variance) algorithm, see :ref:`Capon, 1969<Capon1969>`."""
|
|
815
849
|
|
|
816
850
|
# Boolean flag, if 'True', the main diagonal is removed before beamforming;
|
|
817
851
|
# for Capon beamforming r_diag is set to 'False'.
|
|
818
852
|
r_diag = Enum(False, desc='removal of diagonal')
|
|
819
853
|
|
|
820
|
-
|
|
821
|
-
|
|
854
|
+
#: Normalization factor in case of CSM diagonal removal. Defaults to 1.0 since Beamformer Capon is only well defined for full CSM.
|
|
855
|
+
r_diag_norm = Enum(
|
|
856
|
+
1.0,
|
|
857
|
+
desc='No normalization. BeamformerCapon is only well defined for full CSM.',
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
def _calc(self, ind):
|
|
861
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
822
862
|
|
|
823
863
|
This is an internal helper function that is automatically called when
|
|
824
|
-
accessing the beamformer's :attr
|
|
825
|
-
its :meth
|
|
864
|
+
accessing the beamformer's :attr:`result` or calling
|
|
865
|
+
its :meth:`synthetic` method.
|
|
826
866
|
|
|
827
867
|
Parameters
|
|
828
868
|
----------
|
|
829
|
-
|
|
830
|
-
This array
|
|
831
|
-
|
|
832
|
-
value after calling this method.
|
|
833
|
-
fr : array of booleans
|
|
834
|
-
The entries of this [number of frequencies]-sized array are either
|
|
835
|
-
'True' (if the result for this frequency has already been calculated)
|
|
836
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
837
|
-
After the calculation at a certain frequency the value will be set
|
|
838
|
-
to 'True'
|
|
869
|
+
ind : array of int
|
|
870
|
+
This array contains all frequency indices for which (re)calculation is
|
|
871
|
+
to be performed
|
|
839
872
|
|
|
840
873
|
Returns
|
|
841
874
|
-------
|
|
842
|
-
This method only returns values through
|
|
875
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
843
876
|
|
|
844
877
|
"""
|
|
845
|
-
f = self.
|
|
878
|
+
f = self._f
|
|
846
879
|
nMics = self.freq_data.numchannels
|
|
847
|
-
|
|
880
|
+
normfactor = self.sig_loss_norm() * nMics**2
|
|
848
881
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
849
|
-
for i in
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
fr[i] = 1
|
|
882
|
+
for i in ind:
|
|
883
|
+
csm = array(linalg.inv(array(self.freq_data.csm[i], dtype='complex128')), order='C')
|
|
884
|
+
beamformerOutput = beamformerFreq(param_steer_type, self.r_diag, normfactor, steer_vector(f[i]), csm)[0]
|
|
885
|
+
self._ac[i] = 1.0 / beamformerOutput
|
|
886
|
+
self._fr[i] = 1
|
|
855
887
|
|
|
856
888
|
|
|
857
889
|
class BeamformerEig(BeamformerBase):
|
|
858
|
-
"""Beamforming using eigenvalue and eigenvector techniques,
|
|
859
|
-
see :ref:`Sarradj et al., 2005<Sarradj2005>`.
|
|
860
|
-
"""
|
|
890
|
+
"""Beamforming using eigenvalue and eigenvector techniques, see :ref:`Sarradj et al., 2005<Sarradj2005>`."""
|
|
861
891
|
|
|
862
892
|
#: Number of component to calculate:
|
|
863
893
|
#: 0 (smallest) ... :attr:`~acoular.tprocess.SamplesGenerator.numchannels`-1;
|
|
@@ -868,7 +898,7 @@ class BeamformerEig(BeamformerBase):
|
|
|
868
898
|
na = Property(desc='No. of eigenvalue')
|
|
869
899
|
|
|
870
900
|
# internal identifier
|
|
871
|
-
digest = Property(depends_on=
|
|
901
|
+
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n'])
|
|
872
902
|
|
|
873
903
|
@cached_property
|
|
874
904
|
def _get_digest(self):
|
|
@@ -882,51 +912,43 @@ class BeamformerEig(BeamformerBase):
|
|
|
882
912
|
na = max(nm + na, 0)
|
|
883
913
|
return min(nm - 1, na)
|
|
884
914
|
|
|
885
|
-
def
|
|
915
|
+
def _calc(self, ind):
|
|
886
916
|
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
887
917
|
|
|
888
918
|
This is an internal helper function that is automatically called when
|
|
889
|
-
accessing the beamformer's :attr
|
|
890
|
-
its :meth
|
|
919
|
+
accessing the beamformer's :attr:`result` or calling
|
|
920
|
+
its :meth:`synthetic` method.
|
|
891
921
|
|
|
892
922
|
Parameters
|
|
893
923
|
----------
|
|
894
|
-
|
|
895
|
-
This array
|
|
896
|
-
|
|
897
|
-
value after calling this method.
|
|
898
|
-
fr : array of booleans
|
|
899
|
-
The entries of this [number of frequencies]-sized array are either
|
|
900
|
-
'True' (if the result for this frequency has already been calculated)
|
|
901
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
902
|
-
After the calculation at a certain frequency the value will be set
|
|
903
|
-
to 'True'
|
|
924
|
+
ind : array of int
|
|
925
|
+
This array contains all frequency indices for which (re)calculation is
|
|
926
|
+
to be performed
|
|
904
927
|
|
|
905
928
|
Returns
|
|
906
929
|
-------
|
|
907
|
-
This method only returns values through
|
|
930
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
908
931
|
|
|
909
932
|
"""
|
|
910
|
-
f = self.
|
|
933
|
+
f = self._f
|
|
911
934
|
na = int(self.na) # eigenvalue taken into account
|
|
912
|
-
|
|
935
|
+
normfactor = self.sig_loss_norm()
|
|
913
936
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
914
|
-
for i in
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
fr[i] = 1
|
|
937
|
+
for i in ind:
|
|
938
|
+
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
939
|
+
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
940
|
+
beamformerOutput = beamformerFreq(
|
|
941
|
+
param_steer_type,
|
|
942
|
+
self.r_diag,
|
|
943
|
+
normfactor,
|
|
944
|
+
steer_vector(f[i]),
|
|
945
|
+
(eva[na : na + 1], eve[:, na : na + 1]),
|
|
946
|
+
)[0]
|
|
947
|
+
if self.r_diag: # set (unphysical) negative output values to 0
|
|
948
|
+
indNegSign = sign(beamformerOutput) < 0
|
|
949
|
+
beamformerOutput[indNegSign] = 0
|
|
950
|
+
self._ac[i] = beamformerOutput
|
|
951
|
+
self._fr[i] = 1
|
|
930
952
|
|
|
931
953
|
|
|
932
954
|
class BeamformerMusic(BeamformerEig):
|
|
@@ -936,53 +958,51 @@ class BeamformerMusic(BeamformerEig):
|
|
|
936
958
|
# for MUSIC beamforming r_diag is set to 'False'.
|
|
937
959
|
r_diag = Enum(False, desc='removal of diagonal')
|
|
938
960
|
|
|
961
|
+
#: Normalization factor in case of CSM diagonal removal. Defaults to 1.0 since BeamformerMusic is only well defined for full CSM.
|
|
962
|
+
r_diag_norm = Enum(
|
|
963
|
+
1.0,
|
|
964
|
+
desc='No normalization. BeamformerMusic is only well defined for full CSM.',
|
|
965
|
+
)
|
|
966
|
+
|
|
939
967
|
# assumed number of sources, should be set to a value not too small
|
|
940
968
|
# defaults to 1
|
|
941
969
|
n = Int(1, desc='assumed number of sources')
|
|
942
970
|
|
|
943
|
-
def
|
|
944
|
-
"""Calculates the
|
|
971
|
+
def _calc(self, ind):
|
|
972
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
945
973
|
|
|
946
974
|
This is an internal helper function that is automatically called when
|
|
947
|
-
accessing the beamformer's :attr
|
|
948
|
-
its :meth
|
|
975
|
+
accessing the beamformer's :attr:`result` or calling
|
|
976
|
+
its :meth:`synthetic` method.
|
|
949
977
|
|
|
950
978
|
Parameters
|
|
951
979
|
----------
|
|
952
|
-
|
|
953
|
-
This array
|
|
954
|
-
|
|
955
|
-
value after calling this method.
|
|
956
|
-
fr : array of booleans
|
|
957
|
-
The entries of this [number of frequencies]-sized array are either
|
|
958
|
-
'True' (if the result for this frequency has already been calculated)
|
|
959
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
960
|
-
After the calculation at a certain frequency the value will be set
|
|
961
|
-
to 'True'
|
|
980
|
+
ind : array of int
|
|
981
|
+
This array contains all frequency indices for which (re)calculation is
|
|
982
|
+
to be performed
|
|
962
983
|
|
|
963
984
|
Returns
|
|
964
985
|
-------
|
|
965
|
-
This method only returns values through
|
|
986
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
966
987
|
|
|
967
988
|
"""
|
|
968
|
-
f = self.
|
|
989
|
+
f = self._f
|
|
969
990
|
nMics = self.freq_data.numchannels
|
|
970
991
|
n = int(self.steer.mics.num_mics - self.na)
|
|
971
|
-
|
|
992
|
+
normfactor = self.sig_loss_norm() * nMics**2
|
|
972
993
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
973
|
-
for i in
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
fr[i] = 1
|
|
994
|
+
for i in ind:
|
|
995
|
+
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
996
|
+
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
997
|
+
beamformerOutput = beamformerFreq(
|
|
998
|
+
param_steer_type,
|
|
999
|
+
self.r_diag,
|
|
1000
|
+
normfactor,
|
|
1001
|
+
steer_vector(f[i]),
|
|
1002
|
+
(eva[:n], eve[:, :n]),
|
|
1003
|
+
)[0]
|
|
1004
|
+
self._ac[i] = 4e-10 * beamformerOutput.min() / beamformerOutput
|
|
1005
|
+
self._fr[i] = 1
|
|
986
1006
|
|
|
987
1007
|
|
|
988
1008
|
class PointSpreadFunction(HasPrivateTraits):
|
|
@@ -1206,19 +1226,19 @@ class PointSpreadFunction(HasPrivateTraits):
|
|
|
1206
1226
|
|
|
1207
1227
|
if self.calcmode == 'single': # calculate selected psfs one-by-one
|
|
1208
1228
|
for ind in g_ind_calc:
|
|
1209
|
-
ac[:, ind] = self.
|
|
1229
|
+
ac[:, ind] = self._psf_call([ind])[:, 0]
|
|
1210
1230
|
gp[ind] = 1
|
|
1211
1231
|
elif self.calcmode == 'full': # calculate all psfs in one go
|
|
1212
1232
|
gp[:] = 1
|
|
1213
|
-
ac[:] = self.
|
|
1233
|
+
ac[:] = self._psf_call(arange(self.steer.grid.size))
|
|
1214
1234
|
else: # 'block' # calculate selected psfs in one go
|
|
1215
|
-
hh = self.
|
|
1235
|
+
hh = self._psf_call(g_ind_calc)
|
|
1216
1236
|
for indh, ind in enumerate(g_ind_calc):
|
|
1217
1237
|
gp[ind] = 1
|
|
1218
1238
|
ac[:, ind] = hh[:, indh]
|
|
1219
1239
|
indh += 1
|
|
1220
1240
|
|
|
1221
|
-
def
|
|
1241
|
+
def _psf_call(self, ind):
|
|
1222
1242
|
"""Manages the calling of the core psf functionality.
|
|
1223
1243
|
|
|
1224
1244
|
Parameters
|
|
@@ -1249,28 +1269,13 @@ class PointSpreadFunction(HasPrivateTraits):
|
|
|
1249
1269
|
|
|
1250
1270
|
|
|
1251
1271
|
class BeamformerDamas(BeamformerBase):
|
|
1252
|
-
"""DAMAS deconvolution, see :ref:`Brooks and Humphreys, 2006<BrooksHumphreys2006>`.
|
|
1253
|
-
Needs a-priori delay-and-sum beamforming (:class:`BeamformerBase`).
|
|
1254
|
-
"""
|
|
1272
|
+
"""DAMAS deconvolution, see :ref:`Brooks and Humphreys, 2006<BrooksHumphreys2006>`."""
|
|
1255
1273
|
|
|
1256
|
-
#: :class:`BeamformerBase` object
|
|
1274
|
+
#: (only for backward compatibility) :class:`BeamformerBase` object
|
|
1275
|
+
#: if set, provides :attr:`freq_data`, :attr:`steer`, :attr:`r_diag`
|
|
1276
|
+
#: if not set, these have to be set explicitly
|
|
1257
1277
|
beamformer = Trait(BeamformerBase)
|
|
1258
1278
|
|
|
1259
|
-
#: :class:`~acoular.spectra.PowerSpectra` object that provides the cross spectral matrix;
|
|
1260
|
-
#: is set automatically.
|
|
1261
|
-
freq_data = Delegate('beamformer')
|
|
1262
|
-
|
|
1263
|
-
#: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming;
|
|
1264
|
-
#: is set automatically.
|
|
1265
|
-
r_diag = Delegate('beamformer')
|
|
1266
|
-
|
|
1267
|
-
#: instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes,
|
|
1268
|
-
#: that contains information about the steering vector. Is set automatically.
|
|
1269
|
-
steer = Delegate('beamformer')
|
|
1270
|
-
|
|
1271
|
-
#: Floating point precision of result, is set automatically.
|
|
1272
|
-
precision = Delegate('beamformer')
|
|
1273
|
-
|
|
1274
1279
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1275
1280
|
psf_precision = Trait('float64', 'float32', desc='precision of PSF')
|
|
1276
1281
|
|
|
@@ -1286,59 +1291,59 @@ class BeamformerDamas(BeamformerBase):
|
|
|
1286
1291
|
|
|
1287
1292
|
# internal identifier
|
|
1288
1293
|
digest = Property(
|
|
1289
|
-
depends_on=['
|
|
1290
|
-
)
|
|
1291
|
-
|
|
1292
|
-
# internal identifier
|
|
1293
|
-
ext_digest = Property(
|
|
1294
|
-
depends_on=['digest', 'beamformer.ext_digest'],
|
|
1294
|
+
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n_iter', 'damp', 'psf_precision'],
|
|
1295
1295
|
)
|
|
1296
1296
|
|
|
1297
1297
|
@cached_property
|
|
1298
1298
|
def _get_digest(self):
|
|
1299
1299
|
return digest(self)
|
|
1300
1300
|
|
|
1301
|
-
@
|
|
1302
|
-
def
|
|
1303
|
-
|
|
1301
|
+
@on_trait_change('beamformer.digest')
|
|
1302
|
+
def delegate_beamformer_traits(self):
|
|
1303
|
+
self.freq_data = self.beamformer.freq_data
|
|
1304
|
+
self.r_diag = self.beamformer.r_diag
|
|
1305
|
+
self.steer = self.beamformer.steer
|
|
1304
1306
|
|
|
1305
|
-
def
|
|
1306
|
-
"""Calculates the
|
|
1307
|
+
def _calc(self, ind):
|
|
1308
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1307
1309
|
|
|
1308
1310
|
This is an internal helper function that is automatically called when
|
|
1309
|
-
accessing the beamformer's :attr
|
|
1310
|
-
its :meth
|
|
1311
|
-
A Gauss-Seidel algorithm implemented in C is used for computing the result.
|
|
1311
|
+
accessing the beamformer's :attr:`result` or calling
|
|
1312
|
+
its :meth:`synthetic` method.
|
|
1312
1313
|
|
|
1313
1314
|
Parameters
|
|
1314
1315
|
----------
|
|
1315
|
-
|
|
1316
|
-
This array
|
|
1317
|
-
|
|
1318
|
-
value after calling this method.
|
|
1319
|
-
fr : array of booleans
|
|
1320
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1321
|
-
'True' (if the result for this frequency has already been calculated)
|
|
1322
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1323
|
-
After the calculation at a certain frequency the value will be set
|
|
1324
|
-
to 'True'
|
|
1316
|
+
ind : array of int
|
|
1317
|
+
This array contains all frequency indices for which (re)calculation is
|
|
1318
|
+
to be performed
|
|
1325
1319
|
|
|
1326
1320
|
Returns
|
|
1327
1321
|
-------
|
|
1328
|
-
This method only returns values through
|
|
1322
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1329
1323
|
|
|
1330
1324
|
"""
|
|
1331
|
-
f = self.
|
|
1325
|
+
f = self._f
|
|
1326
|
+
normfactor = self.sig_loss_norm()
|
|
1332
1327
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1328
|
+
param_steer_type, steer_vector = self._beamformer_params()
|
|
1329
|
+
for i in ind:
|
|
1330
|
+
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1331
|
+
y = beamformerFreq(
|
|
1332
|
+
param_steer_type,
|
|
1333
|
+
self.r_diag,
|
|
1334
|
+
normfactor,
|
|
1335
|
+
steer_vector(f[i]),
|
|
1336
|
+
csm,
|
|
1337
|
+
)[0]
|
|
1338
|
+
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1339
|
+
indNegSign = sign(y) < 0
|
|
1340
|
+
y[indNegSign] = 0.0
|
|
1341
|
+
x = y.copy()
|
|
1342
|
+
p.freq = f[i]
|
|
1343
|
+
psf = p.psf[:]
|
|
1344
|
+
damasSolverGaussSeidel(psf, y, self.n_iter, self.damp, x)
|
|
1345
|
+
self._ac[i] = x
|
|
1346
|
+
self._fr[i] = 1
|
|
1342
1347
|
|
|
1343
1348
|
|
|
1344
1349
|
class BeamformerDamasPlus(BeamformerDamas):
|
|
@@ -1374,94 +1379,89 @@ class BeamformerDamasPlus(BeamformerDamas):
|
|
|
1374
1379
|
|
|
1375
1380
|
# internal identifier
|
|
1376
1381
|
digest = Property(
|
|
1377
|
-
depends_on=['
|
|
1378
|
-
)
|
|
1379
|
-
|
|
1380
|
-
# internal identifier
|
|
1381
|
-
ext_digest = Property(
|
|
1382
|
-
depends_on=['digest', 'beamformer.ext_digest'],
|
|
1382
|
+
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['alpha', 'method', 'max_iter', 'unit_mult'],
|
|
1383
1383
|
)
|
|
1384
1384
|
|
|
1385
1385
|
@cached_property
|
|
1386
1386
|
def _get_digest(self):
|
|
1387
1387
|
return digest(self)
|
|
1388
1388
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
return digest(self, 'ext_digest')
|
|
1392
|
-
|
|
1393
|
-
def calc(self, ac, fr):
|
|
1394
|
-
"""Calculates the DAMAS result for the frequencies defined by :attr:`freq_data`.
|
|
1389
|
+
def _calc(self, ind):
|
|
1390
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1395
1391
|
|
|
1396
1392
|
This is an internal helper function that is automatically called when
|
|
1397
|
-
accessing the beamformer's :attr
|
|
1398
|
-
its :meth
|
|
1393
|
+
accessing the beamformer's :attr:`result` or calling
|
|
1394
|
+
its :meth:`synthetic` method.
|
|
1399
1395
|
|
|
1400
1396
|
Parameters
|
|
1401
1397
|
----------
|
|
1402
|
-
|
|
1403
|
-
This array
|
|
1404
|
-
|
|
1405
|
-
value after calling this method.
|
|
1406
|
-
fr : array of booleans
|
|
1407
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1408
|
-
'True' (if the result for this frequency has already been calculated)
|
|
1409
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1410
|
-
After the calculation at a certain frequency the value will be set
|
|
1411
|
-
to 'True'
|
|
1398
|
+
ind : array of int
|
|
1399
|
+
This array contains all frequency indices for which (re)calculation is
|
|
1400
|
+
to be performed
|
|
1412
1401
|
|
|
1413
1402
|
Returns
|
|
1414
1403
|
-------
|
|
1415
|
-
This method only returns values through
|
|
1404
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1416
1405
|
|
|
1417
1406
|
"""
|
|
1418
|
-
f = self.
|
|
1407
|
+
f = self._f
|
|
1419
1408
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
1420
1409
|
unit = self.unit_mult
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1410
|
+
normfactor = self.sig_loss_norm()
|
|
1411
|
+
param_steer_type, steer_vector = self._beamformer_params()
|
|
1412
|
+
for i in ind:
|
|
1413
|
+
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1414
|
+
y = beamformerFreq(
|
|
1415
|
+
param_steer_type,
|
|
1416
|
+
self.r_diag,
|
|
1417
|
+
normfactor,
|
|
1418
|
+
steer_vector(f[i]),
|
|
1419
|
+
csm,
|
|
1420
|
+
)[0]
|
|
1421
|
+
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1422
|
+
indNegSign = sign(y) < 0
|
|
1423
|
+
y[indNegSign] = 0.0
|
|
1424
|
+
y *= unit
|
|
1425
|
+
p.freq = f[i]
|
|
1426
|
+
psf = p.psf[:]
|
|
1427
|
+
|
|
1428
|
+
if self.method == 'NNLS':
|
|
1429
|
+
self._ac[i] = nnls(psf, y)[0] / unit
|
|
1430
|
+
elif self.method == 'LP': # linear programming (Dougherty)
|
|
1431
|
+
if self.r_diag:
|
|
1432
|
+
warn(
|
|
1433
|
+
'Linear programming solver may fail when CSM main '
|
|
1434
|
+
'diagonal is removed for delay-and-sum beamforming.',
|
|
1435
|
+
Warning,
|
|
1436
|
+
stacklevel=5,
|
|
1437
|
+
)
|
|
1438
|
+
cT = -1 * psf.sum(1) # turn the minimization into a maximization
|
|
1439
|
+
self._ac[i] = linprog(c=cT, A_ub=psf, b_ub=y).x / unit # defaults to simplex method and non-negative x
|
|
1440
|
+
else:
|
|
1441
|
+
if self.method == 'LassoLars':
|
|
1442
|
+
model = LassoLars(
|
|
1443
|
+
alpha=self.alpha * unit,
|
|
1444
|
+
max_iter=self.max_iter,
|
|
1445
|
+
)
|
|
1446
|
+
elif self.method == 'OMPCV':
|
|
1447
|
+
model = OrthogonalMatchingPursuitCV()
|
|
1439
1448
|
else:
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
#
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
# for sklearn<1.2 despite any settings
|
|
1457
|
-
with warnings.catch_warnings():
|
|
1458
|
-
warnings.simplefilter('ignore', category=FutureWarning)
|
|
1459
|
-
# normalized psf
|
|
1460
|
-
model.fit(psf / norms, y)
|
|
1461
|
-
# recover normalization in the coef's
|
|
1462
|
-
ac[i] = model.coef_[:] / norms / unit
|
|
1463
|
-
|
|
1464
|
-
fr[i] = 1
|
|
1449
|
+
msg = f'Method {self.method} not implemented.'
|
|
1450
|
+
raise NotImplementedError(msg)
|
|
1451
|
+
model.normalize = False
|
|
1452
|
+
# from sklearn 1.2, normalize=True does not work the same way anymore and the pipeline approach
|
|
1453
|
+
# with StandardScaler does scale in a different way, thus we monkeypatch the code and normalize
|
|
1454
|
+
# ourselves to make results the same over different sklearn versions
|
|
1455
|
+
norms = norm(psf, axis=0)
|
|
1456
|
+
# get rid of annoying sklearn warnings that appear
|
|
1457
|
+
# for sklearn<1.2 despite any settings
|
|
1458
|
+
with warnings.catch_warnings():
|
|
1459
|
+
warnings.simplefilter('ignore', category=FutureWarning)
|
|
1460
|
+
# normalized psf
|
|
1461
|
+
model.fit(psf / norms, y)
|
|
1462
|
+
# recover normalization in the coef's
|
|
1463
|
+
self._ac[i] = model.coef_[:] / norms / unit
|
|
1464
|
+
self._fr[i] = 1
|
|
1465
1465
|
|
|
1466
1466
|
|
|
1467
1467
|
class BeamformerOrth(BeamformerBase):
|
|
@@ -1476,7 +1476,7 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1476
1476
|
|
|
1477
1477
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
1478
1478
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
1479
|
-
eva_list = CArray(dtype=int, desc='components')
|
|
1479
|
+
eva_list = CArray(dtype=int, value=array([-1]), desc='components')
|
|
1480
1480
|
|
|
1481
1481
|
#: Number of components to consider, defaults to 1. If set,
|
|
1482
1482
|
#: :attr:`eva_list` will contain
|
|
@@ -1486,17 +1486,13 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1486
1486
|
|
|
1487
1487
|
# internal identifier
|
|
1488
1488
|
digest = Property(
|
|
1489
|
-
depends_on=
|
|
1489
|
+
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['eva_list'],
|
|
1490
1490
|
)
|
|
1491
1491
|
|
|
1492
1492
|
@cached_property
|
|
1493
1493
|
def _get_digest(self):
|
|
1494
1494
|
return digest(self)
|
|
1495
1495
|
|
|
1496
|
-
@cached_property
|
|
1497
|
-
def _get_ext_digest(self):
|
|
1498
|
-
return digest(self, 'ext_digest')
|
|
1499
|
-
|
|
1500
1496
|
@on_trait_change('beamformer.digest')
|
|
1501
1497
|
def delegate_beamformer_traits(self):
|
|
1502
1498
|
self.freq_data = self.beamformer.freq_data
|
|
@@ -1508,51 +1504,41 @@ class BeamformerOrth(BeamformerBase):
|
|
|
1508
1504
|
"""Sets the list of eigenvalues to consider."""
|
|
1509
1505
|
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
1510
1506
|
|
|
1511
|
-
def
|
|
1512
|
-
"""Calculates the
|
|
1513
|
-
defined by :attr:`freq_data`.
|
|
1507
|
+
def _calc(self, ind):
|
|
1508
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1514
1509
|
|
|
1515
1510
|
This is an internal helper function that is automatically called when
|
|
1516
|
-
accessing the beamformer's :attr
|
|
1517
|
-
its :meth
|
|
1511
|
+
accessing the beamformer's :attr:`result` or calling
|
|
1512
|
+
its :meth:`synthetic` method.
|
|
1518
1513
|
|
|
1519
1514
|
Parameters
|
|
1520
1515
|
----------
|
|
1521
|
-
|
|
1522
|
-
This array
|
|
1523
|
-
|
|
1524
|
-
value after calling this method.
|
|
1525
|
-
fr : array of booleans
|
|
1526
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1527
|
-
'True' (if the result for this frequency has already been calculated)
|
|
1528
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1529
|
-
After the calculation at a certain frequency the value will be set
|
|
1530
|
-
to 'True'
|
|
1516
|
+
ind : array of int
|
|
1517
|
+
This array contains all frequency indices for which (re)calculation is
|
|
1518
|
+
to be performed
|
|
1531
1519
|
|
|
1532
1520
|
Returns
|
|
1533
1521
|
-------
|
|
1534
|
-
This method only returns values through
|
|
1522
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1535
1523
|
|
|
1536
1524
|
"""
|
|
1537
|
-
|
|
1538
|
-
f = self.freq_data.fftfreq()
|
|
1525
|
+
f = self._f
|
|
1539
1526
|
numchannels = self.freq_data.numchannels
|
|
1540
|
-
|
|
1527
|
+
normfactor = self.sig_loss_norm()
|
|
1541
1528
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1542
|
-
for i in
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
fr[i] = 1
|
|
1529
|
+
for i in ind:
|
|
1530
|
+
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
1531
|
+
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
1532
|
+
for n in self.eva_list:
|
|
1533
|
+
beamformerOutput = beamformerFreq(
|
|
1534
|
+
param_steer_type,
|
|
1535
|
+
self.r_diag,
|
|
1536
|
+
normfactor,
|
|
1537
|
+
steer_vector(f[i]),
|
|
1538
|
+
(ones(1), eve[:, n].reshape((-1, 1))),
|
|
1539
|
+
)[0]
|
|
1540
|
+
self._ac[i, beamformerOutput.argmax()] += eva[n] / numchannels
|
|
1541
|
+
self._fr[i] = 1
|
|
1556
1542
|
|
|
1557
1543
|
|
|
1558
1544
|
class BeamformerCleansc(BeamformerBase):
|
|
@@ -1574,106 +1560,83 @@ class BeamformerCleansc(BeamformerBase):
|
|
|
1574
1560
|
stopn = Int(3, desc='stop criterion index')
|
|
1575
1561
|
|
|
1576
1562
|
# internal identifier
|
|
1577
|
-
digest = Property(depends_on=
|
|
1563
|
+
digest = Property(depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n', 'damp', 'stopn'])
|
|
1578
1564
|
|
|
1579
1565
|
@cached_property
|
|
1580
1566
|
def _get_digest(self):
|
|
1581
1567
|
return digest(self)
|
|
1582
1568
|
|
|
1583
|
-
def
|
|
1584
|
-
"""Calculates the
|
|
1569
|
+
def _calc(self, ind):
|
|
1570
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1585
1571
|
|
|
1586
1572
|
This is an internal helper function that is automatically called when
|
|
1587
|
-
accessing the beamformer's :attr
|
|
1588
|
-
its :meth
|
|
1573
|
+
accessing the beamformer's :attr:`result` or calling
|
|
1574
|
+
its :meth:`synthetic` method.
|
|
1589
1575
|
|
|
1590
1576
|
Parameters
|
|
1591
1577
|
----------
|
|
1592
|
-
|
|
1593
|
-
This array
|
|
1594
|
-
|
|
1595
|
-
value after calling this method.
|
|
1596
|
-
fr : array of booleans
|
|
1597
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1598
|
-
'True' (if the result for this frequency has already been calculated)
|
|
1599
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1600
|
-
After the calculation at a certain frequency the value will be set
|
|
1601
|
-
to 'True'
|
|
1578
|
+
ind : array of int
|
|
1579
|
+
This array contains all frequency indices for which (re)calculation is
|
|
1580
|
+
to be performed
|
|
1602
1581
|
|
|
1603
1582
|
Returns
|
|
1604
1583
|
-------
|
|
1605
|
-
This method only returns values through
|
|
1584
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1606
1585
|
|
|
1607
1586
|
"""
|
|
1608
|
-
|
|
1609
|
-
|
|
1587
|
+
f = self._f
|
|
1588
|
+
normfactor = self.sig_loss_norm()
|
|
1610
1589
|
numchannels = self.freq_data.numchannels
|
|
1611
|
-
f = self.freq_data.fftfreq()
|
|
1612
1590
|
result = zeros((self.steer.grid.size), 'f')
|
|
1613
|
-
normFac = self.sig_loss_norm()
|
|
1614
1591
|
J = numchannels * 2 if not self.n else self.n
|
|
1615
1592
|
powers = zeros(J, 'd')
|
|
1616
1593
|
|
|
1617
1594
|
param_steer_type, steer_vector = self._beamformer_params()
|
|
1618
|
-
for i in
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
fr[i] = 1
|
|
1595
|
+
for i in ind:
|
|
1596
|
+
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
1597
|
+
# h = self.steer._beamformerCall(f[i], self.r_diag, normfactor, (csm,))[0]
|
|
1598
|
+
h = beamformerFreq(param_steer_type, self.r_diag, normfactor, steer_vector(f[i]), csm)[0]
|
|
1599
|
+
# CLEANSC Iteration
|
|
1600
|
+
result *= 0.0
|
|
1601
|
+
for j in range(J):
|
|
1602
|
+
xi_max = h.argmax() # index of maximum
|
|
1603
|
+
powers[j] = hmax = h[xi_max] # maximum
|
|
1604
|
+
result[xi_max] += self.damp * hmax
|
|
1605
|
+
if j > self.stopn and hmax > powers[j - self.stopn]:
|
|
1606
|
+
break
|
|
1607
|
+
wmax = self.steer.steer_vector(f[i], xi_max) * sqrt(normfactor)
|
|
1608
|
+
wmax = wmax[0].conj() # as old code worked with conjugated csm..should be updated
|
|
1609
|
+
hh = wmax.copy()
|
|
1610
|
+
D1 = dot(csm.T - diag(diag(csm)), wmax) / hmax
|
|
1611
|
+
ww = wmax.conj() * wmax
|
|
1612
|
+
for _m in range(20):
|
|
1613
|
+
H = hh.conj() * hh
|
|
1614
|
+
hh = (D1 + H * wmax) / sqrt(1 + dot(ww, H))
|
|
1615
|
+
hh = hh[:, newaxis]
|
|
1616
|
+
csm1 = hmax * (hh * hh.conj().T)
|
|
1617
|
+
|
|
1618
|
+
# h1 = self.steer._beamformerCall(f[i], self.r_diag, normfactor, (array((hmax, ))[newaxis, :], hh[newaxis, :].conjugate()))[0]
|
|
1619
|
+
h1 = beamformerFreq(
|
|
1620
|
+
param_steer_type,
|
|
1621
|
+
self.r_diag,
|
|
1622
|
+
normfactor,
|
|
1623
|
+
steer_vector(f[i]),
|
|
1624
|
+
(array((hmax,)), hh.conj()),
|
|
1625
|
+
)[0]
|
|
1626
|
+
h -= self.damp * h1
|
|
1627
|
+
csm -= self.damp * csm1.T # transpose(0,2,1)
|
|
1628
|
+
self._ac[i] = result
|
|
1629
|
+
self._fr[i] = 1
|
|
1654
1630
|
|
|
1655
1631
|
|
|
1656
1632
|
class BeamformerClean(BeamformerBase):
|
|
1657
|
-
"""CLEAN deconvolution, see :ref:`Hoegbom, 1974<Hoegbom1974>`.
|
|
1658
|
-
Needs a-priori delay-and-sum beamforming (:class:`BeamformerBase`).
|
|
1659
|
-
"""
|
|
1633
|
+
"""CLEAN deconvolution, see :ref:`Hoegbom, 1974<Hoegbom1974>`."""
|
|
1660
1634
|
|
|
1661
|
-
|
|
1635
|
+
#: (only for backward compatibility) :class:`BeamformerBase` object
|
|
1636
|
+
#: if set, provides :attr:`freq_data`, :attr:`steer`, :attr:`r_diag`
|
|
1637
|
+
#: if not set, these have to be set explicitly
|
|
1662
1638
|
beamformer = Trait(BeamformerBase)
|
|
1663
1639
|
|
|
1664
|
-
# PowerSpectra object that provides the cross spectral matrix
|
|
1665
|
-
freq_data = Delegate('beamformer')
|
|
1666
|
-
|
|
1667
|
-
# flag, if true (default), the main diagonal is removed before beamforming
|
|
1668
|
-
# r_diag = Delegate('beamformer')
|
|
1669
|
-
|
|
1670
|
-
#: instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes,
|
|
1671
|
-
#: that contains information about the steering vector. Is set automatically.
|
|
1672
|
-
steer = Delegate('beamformer')
|
|
1673
|
-
|
|
1674
|
-
#: Floating point precision of result, is set automatically.
|
|
1675
|
-
precision = Delegate('beamformer')
|
|
1676
|
-
|
|
1677
1640
|
#: The floating-number-precision of the PSFs. Default is 64 bit.
|
|
1678
1641
|
psf_precision = Trait('float64', 'float32', desc='precision of PSF.')
|
|
1679
1642
|
|
|
@@ -1689,49 +1652,40 @@ class BeamformerClean(BeamformerBase):
|
|
|
1689
1652
|
|
|
1690
1653
|
# internal identifier
|
|
1691
1654
|
digest = Property(
|
|
1692
|
-
depends_on=['
|
|
1693
|
-
)
|
|
1694
|
-
|
|
1695
|
-
# internal identifier
|
|
1696
|
-
ext_digest = Property(
|
|
1697
|
-
depends_on=['digest', 'beamformer.ext_digest'],
|
|
1655
|
+
depends_on=BEAMFORMER_BASE_DIGEST_DEPENDENCIES + ['n_iter', 'damp', 'psf_precision'],
|
|
1698
1656
|
)
|
|
1699
1657
|
|
|
1700
1658
|
@cached_property
|
|
1701
1659
|
def _get_digest(self):
|
|
1702
1660
|
return digest(self)
|
|
1703
1661
|
|
|
1704
|
-
@
|
|
1705
|
-
def
|
|
1706
|
-
|
|
1662
|
+
@on_trait_change('beamformer.digest')
|
|
1663
|
+
def delegate_beamformer_traits(self):
|
|
1664
|
+
self.freq_data = self.beamformer.freq_data
|
|
1665
|
+
self.r_diag = self.beamformer.r_diag
|
|
1666
|
+
self.steer = self.beamformer.steer
|
|
1707
1667
|
|
|
1708
|
-
def
|
|
1709
|
-
"""Calculates the
|
|
1668
|
+
def _calc(self, ind):
|
|
1669
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1710
1670
|
|
|
1711
1671
|
This is an internal helper function that is automatically called when
|
|
1712
|
-
accessing the beamformer's :attr
|
|
1713
|
-
its :meth
|
|
1672
|
+
accessing the beamformer's :attr:`result` or calling
|
|
1673
|
+
its :meth:`synthetic` method.
|
|
1714
1674
|
|
|
1715
1675
|
Parameters
|
|
1716
1676
|
----------
|
|
1717
|
-
|
|
1718
|
-
This array
|
|
1719
|
-
|
|
1720
|
-
value after calling this method.
|
|
1721
|
-
fr : array of booleans
|
|
1722
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1723
|
-
'True' (if the result for this frequency has already been calculated)
|
|
1724
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1725
|
-
After the calculation at a certain frequency the value will be set
|
|
1726
|
-
to 'True'
|
|
1677
|
+
ind : array of int
|
|
1678
|
+
This array contains all frequency indices for which (re)calculation is
|
|
1679
|
+
to be performed
|
|
1727
1680
|
|
|
1728
1681
|
Returns
|
|
1729
1682
|
-------
|
|
1730
|
-
This method only returns values through
|
|
1683
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1731
1684
|
|
|
1732
1685
|
"""
|
|
1733
|
-
f = self.
|
|
1686
|
+
f = self._f
|
|
1734
1687
|
gs = self.steer.grid.size
|
|
1688
|
+
normfactor = self.sig_loss_norm()
|
|
1735
1689
|
|
|
1736
1690
|
if self.calcmode == 'full':
|
|
1737
1691
|
warn(
|
|
@@ -1740,28 +1694,37 @@ class BeamformerClean(BeamformerBase):
|
|
|
1740
1694
|
stacklevel=2,
|
|
1741
1695
|
)
|
|
1742
1696
|
p = PointSpreadFunction(steer=self.steer, calcmode=self.calcmode, precision=self.psf_precision)
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1697
|
+
param_steer_type, steer_vector = self._beamformer_params()
|
|
1698
|
+
for i in ind:
|
|
1699
|
+
p.freq = f[i]
|
|
1700
|
+
csm = array(self.freq_data.csm[i], dtype='complex128')
|
|
1701
|
+
dirty = beamformerFreq(
|
|
1702
|
+
param_steer_type,
|
|
1703
|
+
self.r_diag,
|
|
1704
|
+
normfactor,
|
|
1705
|
+
steer_vector(f[i]),
|
|
1706
|
+
csm,
|
|
1707
|
+
)[0]
|
|
1708
|
+
if self.r_diag: # set (unphysical) negative output values to 0
|
|
1709
|
+
indNegSign = sign(dirty) < 0
|
|
1710
|
+
dirty[indNegSign] = 0.0
|
|
1711
|
+
|
|
1712
|
+
clean = zeros(gs, dtype=dirty.dtype)
|
|
1713
|
+
i_iter = 0
|
|
1714
|
+
flag = True
|
|
1715
|
+
while flag:
|
|
1716
|
+
dirty_sum = abs(dirty).sum(0)
|
|
1717
|
+
next_max = dirty.argmax(0)
|
|
1718
|
+
p.grid_indices = array([next_max])
|
|
1719
|
+
psf = p.psf.reshape(gs)
|
|
1720
|
+
new_amp = self.damp * dirty[next_max] # / psf[next_max]
|
|
1721
|
+
clean[next_max] += new_amp
|
|
1722
|
+
dirty -= psf * new_amp
|
|
1723
|
+
i_iter += 1
|
|
1724
|
+
flag = dirty_sum > abs(dirty).sum(0) and i_iter < self.n_iter and max(dirty) > 0
|
|
1725
|
+
|
|
1726
|
+
self._ac[i] = clean
|
|
1727
|
+
self._fr[i] = 1
|
|
1765
1728
|
|
|
1766
1729
|
|
|
1767
1730
|
class BeamformerCMF(BeamformerBase):
|
|
@@ -1804,9 +1767,24 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1804
1767
|
#: If True, shows the status of the PyLops solver. Only relevant in case of FISTA or Split_Bregman
|
|
1805
1768
|
show = Bool(False, desc='show output of PyLops solvers')
|
|
1806
1769
|
|
|
1770
|
+
#: Energy normalization in case of diagonal removal not implemented for inverse methods.
|
|
1771
|
+
r_diag_norm = Enum(
|
|
1772
|
+
None,
|
|
1773
|
+
desc='Energy normalization in case of diagonal removal not implemented for inverse methods',
|
|
1774
|
+
)
|
|
1775
|
+
|
|
1807
1776
|
# internal identifier
|
|
1808
1777
|
digest = Property(
|
|
1809
|
-
depends_on=[
|
|
1778
|
+
depends_on=[
|
|
1779
|
+
'freq_data.digest',
|
|
1780
|
+
'alpha',
|
|
1781
|
+
'method',
|
|
1782
|
+
'max_iter',
|
|
1783
|
+
'unit_mult',
|
|
1784
|
+
'r_diag',
|
|
1785
|
+
'precision',
|
|
1786
|
+
'steer.inv_digest',
|
|
1787
|
+
],
|
|
1810
1788
|
)
|
|
1811
1789
|
|
|
1812
1790
|
@cached_property
|
|
@@ -1822,163 +1800,154 @@ class BeamformerCMF(BeamformerBase):
|
|
|
1822
1800
|
)
|
|
1823
1801
|
raise ImportError(msg)
|
|
1824
1802
|
|
|
1825
|
-
def
|
|
1826
|
-
"""Calculates the
|
|
1803
|
+
def _calc(self, ind):
|
|
1804
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
1827
1805
|
|
|
1828
1806
|
This is an internal helper function that is automatically called when
|
|
1829
|
-
accessing the beamformer's :attr
|
|
1830
|
-
its :meth
|
|
1807
|
+
accessing the beamformer's :attr:`result` or calling
|
|
1808
|
+
its :meth:`synthetic` method.
|
|
1831
1809
|
|
|
1832
1810
|
Parameters
|
|
1833
1811
|
----------
|
|
1834
|
-
|
|
1835
|
-
This array
|
|
1836
|
-
|
|
1837
|
-
value after calling this method.
|
|
1838
|
-
fr : array of booleans
|
|
1839
|
-
The entries of this [number of frequencies]-sized array are either
|
|
1840
|
-
'True' (if the result for this frequency has already been calculated)
|
|
1841
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
1842
|
-
After the calculation at a certain frequency the value will be set
|
|
1843
|
-
to 'True'
|
|
1812
|
+
ind : array of int
|
|
1813
|
+
This array contains all frequency indices for which (re)calculation is
|
|
1814
|
+
to be performed
|
|
1844
1815
|
|
|
1845
1816
|
Returns
|
|
1846
1817
|
-------
|
|
1847
|
-
This method only returns values through
|
|
1818
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
1848
1819
|
|
|
1849
1820
|
"""
|
|
1821
|
+
f = self._f
|
|
1850
1822
|
|
|
1851
1823
|
# function to repack complex matrices to deal with them in real number space
|
|
1852
|
-
def realify(
|
|
1853
|
-
return vstack([
|
|
1824
|
+
def realify(matrix):
|
|
1825
|
+
return vstack([matrix.real, matrix.imag])
|
|
1854
1826
|
|
|
1855
1827
|
# prepare calculation
|
|
1856
|
-
i = self.freq_data.indices
|
|
1857
|
-
f = self.freq_data.fftfreq()
|
|
1858
1828
|
nc = self.freq_data.numchannels
|
|
1859
1829
|
numpoints = self.steer.grid.size
|
|
1860
1830
|
unit = self.unit_mult
|
|
1861
1831
|
|
|
1862
|
-
for i in
|
|
1863
|
-
|
|
1864
|
-
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
1832
|
+
for i in ind:
|
|
1833
|
+
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
1865
1834
|
|
|
1866
|
-
|
|
1835
|
+
h = self.steer.transfer(f[i]).T
|
|
1867
1836
|
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1837
|
+
# reduced Kronecker product (only where solution matrix != 0)
|
|
1838
|
+
Bc = (h[:, :, newaxis] * h.conjugate().T[newaxis, :, :]).transpose(2, 0, 1)
|
|
1839
|
+
Ac = Bc.reshape(nc * nc, numpoints)
|
|
1871
1840
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1841
|
+
# get indices for upper triangular matrices (use tril b/c transposed)
|
|
1842
|
+
ind = reshape(tril(ones((nc, nc))), (nc * nc,)) > 0
|
|
1874
1843
|
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1844
|
+
ind_im0 = (reshape(eye(nc), (nc * nc,)) == 0)[ind]
|
|
1845
|
+
if self.r_diag:
|
|
1846
|
+
# omit main diagonal for noise reduction
|
|
1847
|
+
ind_reim = hstack([ind_im0, ind_im0])
|
|
1848
|
+
else:
|
|
1849
|
+
# take all real parts -- also main diagonal
|
|
1850
|
+
ind_reim = hstack([ones(size(ind_im0)) > 0, ind_im0])
|
|
1851
|
+
ind_reim[0] = True # why this ?
|
|
1852
|
+
|
|
1853
|
+
A = realify(Ac[ind, :])[ind_reim, :]
|
|
1854
|
+
# use csm.T for column stacking reshape!
|
|
1855
|
+
R = realify(reshape(csm.T, (nc * nc, 1))[ind, :])[ind_reim, :] * unit
|
|
1856
|
+
# choose method
|
|
1857
|
+
if self.method == 'LassoLars':
|
|
1858
|
+
model = LassoLars(alpha=self.alpha * unit, max_iter=self.max_iter, **sklearn_ndict)
|
|
1859
|
+
elif self.method == 'LassoLarsBIC':
|
|
1860
|
+
model = LassoLarsIC(criterion='bic', max_iter=self.max_iter, **sklearn_ndict)
|
|
1861
|
+
elif self.method == 'OMPCV':
|
|
1862
|
+
model = OrthogonalMatchingPursuitCV(**sklearn_ndict)
|
|
1863
|
+
elif self.method == 'NNLS':
|
|
1864
|
+
model = LinearRegression(positive=True)
|
|
1865
|
+
|
|
1866
|
+
if self.method == 'Split_Bregman' and config.have_pylops:
|
|
1867
|
+
from pylops import Identity, MatrixMult, SplitBregman
|
|
1868
|
+
|
|
1869
|
+
Oop = MatrixMult(A) # tranfer operator
|
|
1870
|
+
Iop = self.alpha * Identity(numpoints) # regularisation
|
|
1871
|
+
self._ac[i], iterations = SplitBregman(
|
|
1872
|
+
Oop,
|
|
1873
|
+
[Iop],
|
|
1874
|
+
R[:, 0],
|
|
1875
|
+
niter_outer=self.max_iter,
|
|
1876
|
+
niter_inner=5,
|
|
1877
|
+
RegsL2=None,
|
|
1878
|
+
dataregsL2=None,
|
|
1879
|
+
mu=1.0,
|
|
1880
|
+
epsRL1s=[1],
|
|
1881
|
+
tol=1e-10,
|
|
1882
|
+
tau=1.0,
|
|
1883
|
+
show=self.show,
|
|
1884
|
+
)
|
|
1885
|
+
self._ac[i] /= unit
|
|
1886
|
+
|
|
1887
|
+
elif self.method == 'FISTA' and config.have_pylops:
|
|
1888
|
+
from pylops import FISTA, MatrixMult
|
|
1889
|
+
|
|
1890
|
+
Oop = MatrixMult(A) # tranfer operator
|
|
1891
|
+
self._ac[i], iterations = FISTA(
|
|
1892
|
+
Op=Oop,
|
|
1893
|
+
data=R[:, 0],
|
|
1894
|
+
niter=self.max_iter,
|
|
1895
|
+
eps=self.alpha,
|
|
1896
|
+
alpha=None,
|
|
1897
|
+
eigsiter=None,
|
|
1898
|
+
eigstol=0,
|
|
1899
|
+
tol=1e-10,
|
|
1900
|
+
show=self.show,
|
|
1901
|
+
)
|
|
1902
|
+
self._ac[i] /= unit
|
|
1903
|
+
elif self.method == 'fmin_l_bfgs_b':
|
|
1904
|
+
# function to minimize
|
|
1905
|
+
def function(x):
|
|
1906
|
+
# function
|
|
1907
|
+
func = x.T @ A.T @ A @ x - 2 * R.T @ A @ x + R.T @ R
|
|
1908
|
+
# derivitaive
|
|
1909
|
+
der = 2 * A.T @ A @ x.T[:, newaxis] - 2 * A.T @ R
|
|
1910
|
+
return func[0].T, der[:, 0]
|
|
1911
|
+
|
|
1912
|
+
# initial guess
|
|
1913
|
+
x0 = ones([numpoints])
|
|
1914
|
+
# boundarys - set to non negative
|
|
1915
|
+
boundarys = tile((0, +inf), (len(x0), 1))
|
|
1916
|
+
|
|
1917
|
+
# optimize
|
|
1918
|
+
self._ac[i], yval, dicts = fmin_l_bfgs_b(
|
|
1919
|
+
function,
|
|
1920
|
+
x0,
|
|
1921
|
+
fprime=None,
|
|
1922
|
+
args=(),
|
|
1923
|
+
approx_grad=0,
|
|
1924
|
+
bounds=boundarys,
|
|
1925
|
+
m=10,
|
|
1926
|
+
factr=10000000.0,
|
|
1927
|
+
pgtol=1e-05,
|
|
1928
|
+
epsilon=1e-08,
|
|
1929
|
+
iprint=-1,
|
|
1930
|
+
maxfun=15000,
|
|
1931
|
+
maxiter=self.max_iter,
|
|
1932
|
+
disp=None,
|
|
1933
|
+
callback=None,
|
|
1934
|
+
maxls=20,
|
|
1935
|
+
)
|
|
1967
1936
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1937
|
+
self._ac[i] /= unit
|
|
1938
|
+
else:
|
|
1939
|
+
# from sklearn 1.2, normalize=True does not work the same way anymore and the pipeline
|
|
1940
|
+
# approach with StandardScaler does scale in a different way, thus we monkeypatch the
|
|
1941
|
+
# code and normalize ourselves to make results the same over different sklearn versions
|
|
1942
|
+
norms = norm(A, axis=0)
|
|
1943
|
+
# get rid of annoying sklearn warnings that appear for sklearn<1.2 despite any settings
|
|
1944
|
+
with warnings.catch_warnings():
|
|
1945
|
+
warnings.simplefilter('ignore', category=FutureWarning)
|
|
1946
|
+
# normalized A
|
|
1947
|
+
model.fit(A / norms, R[:, 0])
|
|
1948
|
+
# recover normalization in the coef's
|
|
1949
|
+
self._ac[i] = model.coef_[:] / norms / unit
|
|
1950
|
+
self._fr[i] = 1
|
|
1982
1951
|
|
|
1983
1952
|
|
|
1984
1953
|
class BeamformerSODIX(BeamformerBase):
|
|
@@ -2000,10 +1969,6 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2000
1969
|
#: defaults to 200
|
|
2001
1970
|
max_iter = Int(200, desc='maximum number of iterations')
|
|
2002
1971
|
|
|
2003
|
-
#: Norm to consider for the regularization
|
|
2004
|
-
#: defaults to L-1 Norm
|
|
2005
|
-
pnorm = Float(1, desc='Norm for regularization')
|
|
2006
|
-
|
|
2007
1972
|
#: Weight factor for regularization,
|
|
2008
1973
|
#: defaults to 0.0.
|
|
2009
1974
|
alpha = Range(0.0, 1.0, 0.0, desc='regularization factor')
|
|
@@ -2014,215 +1979,60 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2014
1979
|
#: within fitting method algorithms. Defaults to 1e9.
|
|
2015
1980
|
unit_mult = Float(1e9, desc='unit multiplier')
|
|
2016
1981
|
|
|
2017
|
-
#:
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
1982
|
+
#: Energy normalization in case of diagonal removal not implemented for inverse methods.
|
|
1983
|
+
r_diag_norm = Enum(
|
|
1984
|
+
None,
|
|
1985
|
+
desc='Energy normalization in case of diagonal removal not implemented for inverse methods',
|
|
1986
|
+
)
|
|
2021
1987
|
|
|
2022
1988
|
# internal identifier
|
|
2023
1989
|
digest = Property(
|
|
2024
|
-
depends_on=[
|
|
1990
|
+
depends_on=[
|
|
1991
|
+
'freq_data.digest',
|
|
1992
|
+
'alpha',
|
|
1993
|
+
'method',
|
|
1994
|
+
'max_iter',
|
|
1995
|
+
'unit_mult',
|
|
1996
|
+
'r_diag',
|
|
1997
|
+
'precision',
|
|
1998
|
+
'steer.inv_digest',
|
|
1999
|
+
],
|
|
2025
2000
|
)
|
|
2026
2001
|
|
|
2027
2002
|
@cached_property
|
|
2028
2003
|
def _get_digest(self):
|
|
2029
2004
|
return digest(self)
|
|
2030
2005
|
|
|
2031
|
-
def
|
|
2032
|
-
"""
|
|
2033
|
-
global/local caching behaviour. Returns (None, None) if no cachefile/data
|
|
2034
|
-
exist and global caching mode is 'readonly'.
|
|
2035
|
-
"""
|
|
2036
|
-
H5cache.get_cache_file(self, self.freq_data.basename)
|
|
2037
|
-
if not self.h5f:
|
|
2038
|
-
return (None, None) # only happens in case of global caching readonly
|
|
2039
|
-
|
|
2040
|
-
nodename = self.__class__.__name__ + self.digest
|
|
2041
|
-
if config.global_caching == 'overwrite' and self.h5f.is_cached(nodename):
|
|
2042
|
-
self.h5f.remove_data(nodename) # remove old data before writing in overwrite mode
|
|
2043
|
-
|
|
2044
|
-
if not self.h5f.is_cached(nodename):
|
|
2045
|
-
if config.global_caching == 'readonly':
|
|
2046
|
-
return (None, None)
|
|
2047
|
-
# print("initialize data.")
|
|
2048
|
-
numfreq = self.freq_data.fftfreq().shape[0] # block_size/2 + 1steer_obj
|
|
2049
|
-
group = self.h5f.create_new_group(nodename)
|
|
2050
|
-
self.h5f.create_compressible_array(
|
|
2051
|
-
'result',
|
|
2052
|
-
(numfreq, self.steer.grid.size * self.steer.mics.num_mics),
|
|
2053
|
-
self.precision,
|
|
2054
|
-
group,
|
|
2055
|
-
)
|
|
2056
|
-
self.h5f.create_compressible_array(
|
|
2057
|
-
'freqs',
|
|
2058
|
-
(numfreq,),
|
|
2059
|
-
'int8', #'bool',
|
|
2060
|
-
group,
|
|
2061
|
-
)
|
|
2062
|
-
ac = self.h5f.get_data_by_reference('result', '/' + nodename)
|
|
2063
|
-
fr = self.h5f.get_data_by_reference('freqs', '/' + nodename)
|
|
2064
|
-
gpos = None
|
|
2065
|
-
return (ac, fr, gpos)
|
|
2066
|
-
|
|
2067
|
-
@property_depends_on('ext_digest')
|
|
2068
|
-
def _get_sodix_result(self):
|
|
2069
|
-
"""Implements the :attr:`result` getter routine.
|
|
2070
|
-
The sodix beamforming result is either loaded or calculated.
|
|
2071
|
-
"""
|
|
2072
|
-
f = self.freq_data
|
|
2073
|
-
numfreq = f.fftfreq().shape[0] # block_size/2 + 1steer_obj
|
|
2074
|
-
_digest = ''
|
|
2075
|
-
while self.digest != _digest:
|
|
2076
|
-
_digest = self.digest
|
|
2077
|
-
self._assert_equal_channels()
|
|
2078
|
-
if not ( # if result caching is active
|
|
2079
|
-
config.global_caching == 'none' or (config.global_caching == 'individual' and not self.cached)
|
|
2080
|
-
):
|
|
2081
|
-
(ac, fr, gpos) = self._get_filecache()
|
|
2082
|
-
if ac and fr:
|
|
2083
|
-
if not fr[f.ind_low : f.ind_high].all():
|
|
2084
|
-
if config.global_caching == 'readonly':
|
|
2085
|
-
(ac, fr) = (ac[:], fr[:])
|
|
2086
|
-
self.calc(ac, fr)
|
|
2087
|
-
self.h5f.flush()
|
|
2088
|
-
|
|
2089
|
-
else:
|
|
2090
|
-
ac = zeros((numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
2091
|
-
fr = zeros(numfreq, dtype='int8')
|
|
2092
|
-
self.calc(ac, fr)
|
|
2093
|
-
else:
|
|
2094
|
-
ac = zeros((numfreq, self.steer.grid.size * self.steer.mics.num_mics), dtype=self.precision)
|
|
2095
|
-
fr = zeros(numfreq, dtype='int8')
|
|
2096
|
-
self.calc(ac, fr)
|
|
2097
|
-
return ac
|
|
2098
|
-
|
|
2099
|
-
def synthetic(self, f, num=0):
|
|
2100
|
-
"""Evaluates the beamforming result for an arbitrary frequency band.
|
|
2101
|
-
|
|
2102
|
-
Parameters
|
|
2103
|
-
----------
|
|
2104
|
-
f: float
|
|
2105
|
-
Band center frequency.
|
|
2106
|
-
num : integer
|
|
2107
|
-
Controls the width of the frequency bands considered; defaults to
|
|
2108
|
-
0 (single frequency line).
|
|
2109
|
-
|
|
2110
|
-
=== =====================
|
|
2111
|
-
num frequency band width
|
|
2112
|
-
=== =====================
|
|
2113
|
-
0 single frequency line
|
|
2114
|
-
1 octave band
|
|
2115
|
-
3 third-octave band
|
|
2116
|
-
n 1/n-octave band
|
|
2117
|
-
=== =====================
|
|
2118
|
-
|
|
2119
|
-
Returns
|
|
2120
|
-
-------
|
|
2121
|
-
array of floats
|
|
2122
|
-
The synthesized frequency band values of the beamforming result at
|
|
2123
|
-
each grid point and each microphone .
|
|
2124
|
-
Note that the frequency resolution and therefore the bandwidth
|
|
2125
|
-
represented by a single frequency line depends on
|
|
2126
|
-
the :attr:`sampling frequency<acoular.sources.SamplesGenerator.sample_freq>` and conjugate
|
|
2127
|
-
used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
2128
|
-
|
|
2129
|
-
"""
|
|
2130
|
-
res = self.sodix_result # trigger calculation
|
|
2131
|
-
freq = self.freq_data.fftfreq()
|
|
2132
|
-
if len(freq) == 0:
|
|
2133
|
-
return None
|
|
2134
|
-
|
|
2135
|
-
indices = self.freq_data.indices
|
|
2136
|
-
|
|
2137
|
-
if num == 0:
|
|
2138
|
-
# single frequency line
|
|
2139
|
-
ind = searchsorted(freq, f)
|
|
2140
|
-
if ind >= len(freq):
|
|
2141
|
-
warn(
|
|
2142
|
-
'Queried frequency (%g Hz) not in resolved frequency range. Returning zeros.' % f,
|
|
2143
|
-
Warning,
|
|
2144
|
-
stacklevel=2,
|
|
2145
|
-
)
|
|
2146
|
-
h = zeros_like(res[0])
|
|
2147
|
-
else:
|
|
2148
|
-
if freq[ind] != f:
|
|
2149
|
-
warn(
|
|
2150
|
-
f'Queried frequency ({f:g} Hz) not in set of '
|
|
2151
|
-
'discrete FFT sample frequencies. '
|
|
2152
|
-
f'Using frequency {freq[ind]:g} Hz instead.',
|
|
2153
|
-
Warning,
|
|
2154
|
-
stacklevel=2,
|
|
2155
|
-
)
|
|
2156
|
-
if ind not in indices:
|
|
2157
|
-
warn(
|
|
2158
|
-
'Beamforming result may not have been calculated '
|
|
2159
|
-
'for queried frequency. Check '
|
|
2160
|
-
'freq_data.ind_low and freq_data.ind_high!',
|
|
2161
|
-
Warning,
|
|
2162
|
-
stacklevel=2,
|
|
2163
|
-
)
|
|
2164
|
-
h = res[ind]
|
|
2165
|
-
else:
|
|
2166
|
-
# fractional octave band
|
|
2167
|
-
f1 = f * 2.0 ** (-0.5 / num)
|
|
2168
|
-
f2 = f * 2.0 ** (+0.5 / num)
|
|
2169
|
-
ind1 = searchsorted(freq, f1)
|
|
2170
|
-
ind2 = searchsorted(freq, f2)
|
|
2171
|
-
if ind1 == ind2:
|
|
2172
|
-
warn(
|
|
2173
|
-
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
2174
|
-
'include any discrete FFT sample frequencies. '
|
|
2175
|
-
'Returning zeros.',
|
|
2176
|
-
Warning,
|
|
2177
|
-
stacklevel=2,
|
|
2178
|
-
)
|
|
2179
|
-
h = zeros_like(res[0])
|
|
2180
|
-
else:
|
|
2181
|
-
h = sum(res[ind1:ind2], 0)
|
|
2182
|
-
if not ((ind1 in indices) and (ind2 in indices)):
|
|
2183
|
-
warn(
|
|
2184
|
-
'Beamforming result may not have been calculated '
|
|
2185
|
-
'for all queried frequencies. Check '
|
|
2186
|
-
'freq_data.ind_low and freq_data.ind_high!',
|
|
2187
|
-
Warning,
|
|
2188
|
-
stacklevel=2,
|
|
2189
|
-
)
|
|
2190
|
-
return h.reshape([self.steer.grid.size, self.steer.mics.num_mics])
|
|
2191
|
-
|
|
2192
|
-
def calc(self, ac, fr):
|
|
2193
|
-
"""Calculates the SODIX result for the frequencies defined by :attr:`freq_data`.
|
|
2006
|
+
def _calc(self, ind):
|
|
2007
|
+
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2194
2008
|
|
|
2195
2009
|
This is an internal helper function that is automatically called when
|
|
2196
|
-
accessing the beamformer's :attr
|
|
2197
|
-
its :meth
|
|
2010
|
+
accessing the beamformer's :attr:`result` or calling
|
|
2011
|
+
its :meth:`synthetic` method.
|
|
2198
2012
|
|
|
2199
2013
|
Parameters
|
|
2200
2014
|
----------
|
|
2201
|
-
|
|
2202
|
-
This array
|
|
2203
|
-
|
|
2204
|
-
value after calling this method.
|
|
2205
|
-
fr : array of booleans
|
|
2206
|
-
The entries of this [number of frequencies]-sized array are either
|
|
2207
|
-
'True' (if the result for this frequency has already been calculated)
|
|
2208
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
2209
|
-
After the calculation at a certain frequency the value will be set
|
|
2210
|
-
to 'True'
|
|
2015
|
+
ind : array of int
|
|
2016
|
+
This array contains all frequency indices for which (re)calculation is
|
|
2017
|
+
to be performed
|
|
2211
2018
|
|
|
2212
2019
|
Returns
|
|
2213
2020
|
-------
|
|
2214
|
-
This method only returns values through
|
|
2021
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
2215
2022
|
|
|
2216
2023
|
"""
|
|
2217
2024
|
# prepare calculation
|
|
2218
|
-
|
|
2219
|
-
f = self.freq_data.fftfreq()
|
|
2025
|
+
f = self._f
|
|
2220
2026
|
numpoints = self.steer.grid.size
|
|
2221
2027
|
# unit = self.unit_mult
|
|
2222
2028
|
num_mics = self.steer.mics.num_mics
|
|
2223
|
-
|
|
2224
|
-
for
|
|
2225
|
-
|
|
2029
|
+
# SODIX needs special treatment as the result from one frequency is used to
|
|
2030
|
+
# determine the initial guess for the next frequency in order to speed up
|
|
2031
|
+
# computation. Instead of just solving for only the frequencies in ind, we
|
|
2032
|
+
# start with index 1 (minimum frequency) and also check if the result is
|
|
2033
|
+
# already computed
|
|
2034
|
+
for i in range(1, ind.max() + 1):
|
|
2035
|
+
if not self._fr[i]:
|
|
2226
2036
|
# measured csm
|
|
2227
2037
|
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
2228
2038
|
# transfer function
|
|
@@ -2230,10 +2040,10 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2230
2040
|
|
|
2231
2041
|
if self.method == 'fmin_l_bfgs_b':
|
|
2232
2042
|
# function to minimize
|
|
2233
|
-
def function(
|
|
2043
|
+
def function(directions):
|
|
2234
2044
|
"""Parameters
|
|
2235
2045
|
----------
|
|
2236
|
-
|
|
2046
|
+
directions
|
|
2237
2047
|
[numpoints*num_mics]
|
|
2238
2048
|
|
|
2239
2049
|
Returns
|
|
@@ -2245,7 +2055,7 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2245
2055
|
|
|
2246
2056
|
"""
|
|
2247
2057
|
#### the sodix function ####
|
|
2248
|
-
Djm =
|
|
2058
|
+
Djm = directions.reshape([numpoints, num_mics])
|
|
2249
2059
|
p = h.T * Djm
|
|
2250
2060
|
csm_mod = dot(p.T, p.conj())
|
|
2251
2061
|
Q = csm - csm_mod
|
|
@@ -2262,11 +2072,11 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2262
2072
|
return func, derdrl.ravel()
|
|
2263
2073
|
|
|
2264
2074
|
##### initial guess ####
|
|
2265
|
-
if
|
|
2075
|
+
if not self._fr[(i - 1)]:
|
|
2266
2076
|
D0 = ones([numpoints, num_mics])
|
|
2267
2077
|
else:
|
|
2268
2078
|
D0 = sqrt(
|
|
2269
|
-
|
|
2079
|
+
self._ac[(i - 1)]
|
|
2270
2080
|
* real(trace(csm) / trace(array(self.freq_data.csm[i - 1], dtype='complex128', copy=1))),
|
|
2271
2081
|
)
|
|
2272
2082
|
|
|
@@ -2295,10 +2105,10 @@ class BeamformerSODIX(BeamformerBase):
|
|
|
2295
2105
|
maxls=20,
|
|
2296
2106
|
)
|
|
2297
2107
|
# squared pressure
|
|
2298
|
-
|
|
2108
|
+
self._ac[i] = qi**2
|
|
2299
2109
|
else:
|
|
2300
2110
|
pass
|
|
2301
|
-
|
|
2111
|
+
self._fr[i] = 1
|
|
2302
2112
|
|
|
2303
2113
|
|
|
2304
2114
|
class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
@@ -2352,11 +2162,18 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2352
2162
|
# First eigenvalue to consider. Defaults to 0.
|
|
2353
2163
|
m = Int(0, desc='First eigenvalue to consider')
|
|
2354
2164
|
|
|
2355
|
-
|
|
2165
|
+
#: Energy normalization in case of diagonal removal not implemented for inverse methods.
|
|
2166
|
+
r_diag_norm = Enum(
|
|
2167
|
+
None,
|
|
2168
|
+
desc='Energy normalization in case of diagonal removal not implemented for inverse methods',
|
|
2169
|
+
)
|
|
2170
|
+
|
|
2171
|
+
# internal identifier
|
|
2356
2172
|
digest = Property(
|
|
2357
2173
|
depends_on=[
|
|
2358
2174
|
'steer.inv_digest',
|
|
2359
2175
|
'freq_data.digest',
|
|
2176
|
+
'precision',
|
|
2360
2177
|
'alpha',
|
|
2361
2178
|
'method',
|
|
2362
2179
|
'max_iter',
|
|
@@ -2381,33 +2198,25 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2381
2198
|
na = max(nm + na, 0)
|
|
2382
2199
|
return min(nm - 1, na)
|
|
2383
2200
|
|
|
2384
|
-
def
|
|
2201
|
+
def _calc(self, ind):
|
|
2385
2202
|
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2386
2203
|
|
|
2387
2204
|
This is an internal helper function that is automatically called when
|
|
2388
|
-
accessing the beamformer's :attr
|
|
2389
|
-
its :meth
|
|
2205
|
+
accessing the beamformer's :attr:`result` or calling
|
|
2206
|
+
its :meth:`synthetic` method.
|
|
2390
2207
|
|
|
2391
2208
|
Parameters
|
|
2392
2209
|
----------
|
|
2393
|
-
|
|
2394
|
-
This array
|
|
2395
|
-
|
|
2396
|
-
value after calling this method.
|
|
2397
|
-
fr : array of booleans
|
|
2398
|
-
The entries of this [number of frequencies]-sized array are either
|
|
2399
|
-
'True' (if the result for this frequency has already been calculated)
|
|
2400
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
2401
|
-
After the calculation at a certain frequency the value will be set
|
|
2402
|
-
to 'True'
|
|
2210
|
+
ind : array of int
|
|
2211
|
+
This array contains all frequency indices for which (re)calculation is
|
|
2212
|
+
to be performed
|
|
2403
2213
|
|
|
2404
2214
|
Returns
|
|
2405
2215
|
-------
|
|
2406
|
-
This method only returns values through
|
|
2216
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
2407
2217
|
|
|
2408
2218
|
"""
|
|
2409
|
-
|
|
2410
|
-
f = self.freq_data.fftfreq()
|
|
2219
|
+
f = self._f
|
|
2411
2220
|
n = int(self.na) # number of eigenvalues
|
|
2412
2221
|
m = int(self.m) # number of first eigenvalue
|
|
2413
2222
|
numchannels = self.freq_data.numchannels # number of channels
|
|
@@ -2415,122 +2224,121 @@ class BeamformerGIB(BeamformerEig): # BeamformerEig #BeamformerBase
|
|
|
2415
2224
|
hh = zeros((1, numpoints, numchannels), dtype='D')
|
|
2416
2225
|
|
|
2417
2226
|
# Generate a cross spectral matrix, and perform the eigenvalue decomposition
|
|
2418
|
-
for i in
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
dot(
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
weights = weights / sum(absolute(weights))
|
|
2490
|
-
else:
|
|
2491
|
-
locpoints = arange(numpoints)
|
|
2492
|
-
unit = self.unit_mult
|
|
2493
|
-
AB = vstack([hstack([A.real, -A.imag]), hstack([A.imag, A.real])])
|
|
2494
|
-
R = hstack([emode.real.T, emode.imag.T]) * unit
|
|
2495
|
-
if self.method == 'LassoLars':
|
|
2496
|
-
model = LassoLars(alpha=self.alpha * unit, max_iter=self.max_iter)
|
|
2497
|
-
elif self.method == 'LassoLarsBIC':
|
|
2498
|
-
model = LassoLarsIC(criterion='bic', max_iter=self.max_iter)
|
|
2499
|
-
elif self.method == 'OMPCV':
|
|
2500
|
-
model = OrthogonalMatchingPursuitCV()
|
|
2501
|
-
elif self.method == 'LassoLarsCV':
|
|
2502
|
-
model = LassoLarsCV()
|
|
2503
|
-
elif self.method == 'NNLS':
|
|
2504
|
-
model = LinearRegression(positive=True)
|
|
2505
|
-
model.normalize = False
|
|
2506
|
-
# from sklearn 1.2, normalize=True does not work
|
|
2507
|
-
# the same way anymore and the pipeline approach
|
|
2508
|
-
# with StandardScaler does scale in a different
|
|
2509
|
-
# way, thus we monkeypatch the code and normalize
|
|
2510
|
-
# ourselves to make results the same over different
|
|
2511
|
-
# sklearn versions
|
|
2512
|
-
norms = norm(AB, axis=0)
|
|
2513
|
-
# get rid of annoying sklearn warnings that appear
|
|
2514
|
-
# for sklearn<1.2 despite any settings
|
|
2515
|
-
with warnings.catch_warnings():
|
|
2516
|
-
warnings.simplefilter('ignore', category=FutureWarning)
|
|
2517
|
-
# normalized A
|
|
2518
|
-
model.fit(AB / norms, R)
|
|
2519
|
-
# recover normalization in the coef's
|
|
2520
|
-
qi_real, qi_imag = hsplit(model.coef_[:] / norms / unit, 2)
|
|
2521
|
-
# print(s,qi.size)
|
|
2522
|
-
qi[s, locpoints] = qi_real + qi_imag * 1j
|
|
2227
|
+
for i in ind:
|
|
2228
|
+
# for monopole and source strenght Q needs to define density
|
|
2229
|
+
# calculate a transfer matrix A
|
|
2230
|
+
hh = self.steer.transfer(f[i])
|
|
2231
|
+
A = hh.T
|
|
2232
|
+
# eigenvalues and vectors
|
|
2233
|
+
csm = array(self.freq_data.csm[i], dtype='complex128', copy=1)
|
|
2234
|
+
eva, eve = eigh(csm)
|
|
2235
|
+
eva = eva[::-1]
|
|
2236
|
+
eve = eve[:, ::-1]
|
|
2237
|
+
eva[eva < max(eva) / 1e12] = 0 # set small values zo 0, lowers numerical errors in simulated data
|
|
2238
|
+
# init sources
|
|
2239
|
+
qi = zeros([n + m, numpoints], dtype='complex128')
|
|
2240
|
+
# Select the number of coherent modes to be processed referring to the eigenvalue distribution.
|
|
2241
|
+
# for s in arange(n):
|
|
2242
|
+
for s in list(range(m, n + m)):
|
|
2243
|
+
if eva[s] > 0:
|
|
2244
|
+
# Generate the corresponding eigenmodes
|
|
2245
|
+
emode = array(sqrt(eva[s]) * eve[:, s], dtype='complex128')
|
|
2246
|
+
# choose method for computation
|
|
2247
|
+
if self.method == 'Suzuki':
|
|
2248
|
+
leftpoints = numpoints
|
|
2249
|
+
locpoints = arange(numpoints)
|
|
2250
|
+
weights = diag(ones(numpoints))
|
|
2251
|
+
epsilon = arange(self.max_iter)
|
|
2252
|
+
for it in arange(self.max_iter):
|
|
2253
|
+
if numchannels <= leftpoints:
|
|
2254
|
+
AWA = dot(dot(A[:, locpoints], weights), A[:, locpoints].conj().T)
|
|
2255
|
+
epsilon[it] = max(absolute(eigvals(AWA))) * self.eps_perc
|
|
2256
|
+
qi[s, locpoints] = dot(
|
|
2257
|
+
dot(
|
|
2258
|
+
dot(weights, A[:, locpoints].conj().T),
|
|
2259
|
+
inv(AWA + eye(numchannels) * epsilon[it]),
|
|
2260
|
+
),
|
|
2261
|
+
emode,
|
|
2262
|
+
)
|
|
2263
|
+
elif numchannels > leftpoints:
|
|
2264
|
+
AA = dot(A[:, locpoints].conj().T, A[:, locpoints])
|
|
2265
|
+
epsilon[it] = max(absolute(eigvals(AA))) * self.eps_perc
|
|
2266
|
+
qi[s, locpoints] = dot(
|
|
2267
|
+
dot(inv(AA + inv(weights) * epsilon[it]), A[:, locpoints].conj().T),
|
|
2268
|
+
emode,
|
|
2269
|
+
)
|
|
2270
|
+
if self.beta < 1 and it > 1:
|
|
2271
|
+
# Reorder from the greatest to smallest magnitude to define a reduced-point source distribution , and reform a reduced transfer matrix
|
|
2272
|
+
leftpoints = int(round(numpoints * self.beta ** (it + 1)))
|
|
2273
|
+
idx = argsort(abs(qi[s, locpoints]))[::-1]
|
|
2274
|
+
# print(it, leftpoints, locpoints, idx )
|
|
2275
|
+
locpoints = delete(locpoints, [idx[leftpoints::]])
|
|
2276
|
+
qix = zeros([n + m, leftpoints], dtype='complex128')
|
|
2277
|
+
qix[s, :] = qi[s, locpoints]
|
|
2278
|
+
# calc weights for next iteration
|
|
2279
|
+
weights = diag(absolute(qix[s, :]) ** (2 - self.pnorm))
|
|
2280
|
+
else:
|
|
2281
|
+
weights = diag(absolute(qi[s, :]) ** (2 - self.pnorm))
|
|
2282
|
+
|
|
2283
|
+
elif self.method == 'InverseIRLS':
|
|
2284
|
+
weights = eye(numpoints)
|
|
2285
|
+
locpoints = arange(numpoints)
|
|
2286
|
+
for _it in arange(self.max_iter):
|
|
2287
|
+
if numchannels <= numpoints:
|
|
2288
|
+
wtwi = inv(dot(weights.T, weights))
|
|
2289
|
+
aH = A.conj().T
|
|
2290
|
+
qi[s, :] = dot(dot(wtwi, aH), dot(inv(dot(A, dot(wtwi, aH))), emode))
|
|
2291
|
+
weights = diag(absolute(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2292
|
+
weights = weights / sum(absolute(weights))
|
|
2293
|
+
elif numchannels > numpoints:
|
|
2294
|
+
wtw = dot(weights.T, weights)
|
|
2295
|
+
qi[s, :] = dot(dot(inv(dot(dot(A.conj.T, wtw), A)), dot(A.conj().T, wtw)), emode)
|
|
2296
|
+
weights = diag(absolute(qi[s, :]) ** ((2 - self.pnorm) / 2))
|
|
2297
|
+
weights = weights / sum(absolute(weights))
|
|
2523
2298
|
else:
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2299
|
+
locpoints = arange(numpoints)
|
|
2300
|
+
unit = self.unit_mult
|
|
2301
|
+
AB = vstack([hstack([A.real, -A.imag]), hstack([A.imag, A.real])])
|
|
2302
|
+
R = hstack([emode.real.T, emode.imag.T]) * unit
|
|
2303
|
+
if self.method == 'LassoLars':
|
|
2304
|
+
model = LassoLars(alpha=self.alpha * unit, max_iter=self.max_iter)
|
|
2305
|
+
elif self.method == 'LassoLarsBIC':
|
|
2306
|
+
model = LassoLarsIC(criterion='bic', max_iter=self.max_iter)
|
|
2307
|
+
elif self.method == 'OMPCV':
|
|
2308
|
+
model = OrthogonalMatchingPursuitCV()
|
|
2309
|
+
elif self.method == 'LassoLarsCV':
|
|
2310
|
+
model = LassoLarsCV()
|
|
2311
|
+
elif self.method == 'NNLS':
|
|
2312
|
+
model = LinearRegression(positive=True)
|
|
2313
|
+
model.normalize = False
|
|
2314
|
+
# from sklearn 1.2, normalize=True does not work
|
|
2315
|
+
# the same way anymore and the pipeline approach
|
|
2316
|
+
# with StandardScaler does scale in a different
|
|
2317
|
+
# way, thus we monkeypatch the code and normalize
|
|
2318
|
+
# ourselves to make results the same over different
|
|
2319
|
+
# sklearn versions
|
|
2320
|
+
norms = norm(AB, axis=0)
|
|
2321
|
+
# get rid of annoying sklearn warnings that appear
|
|
2322
|
+
# for sklearn<1.2 despite any settings
|
|
2323
|
+
with warnings.catch_warnings():
|
|
2324
|
+
warnings.simplefilter('ignore', category=FutureWarning)
|
|
2325
|
+
# normalized A
|
|
2326
|
+
model.fit(AB / norms, R)
|
|
2327
|
+
# recover normalization in the coef's
|
|
2328
|
+
qi_real, qi_imag = hsplit(model.coef_[:] / norms / unit, 2)
|
|
2329
|
+
# print(s,qi.size)
|
|
2330
|
+
qi[s, locpoints] = qi_real + qi_imag * 1j
|
|
2331
|
+
else:
|
|
2332
|
+
warn(
|
|
2333
|
+
f'Eigenvalue {s:g} <= 0 for frequency index {i:g}. Will not be calculated!',
|
|
2334
|
+
Warning,
|
|
2335
|
+
stacklevel=2,
|
|
2336
|
+
)
|
|
2337
|
+
# Generate source maps of all selected eigenmodes, and superpose source intensity for each source type.
|
|
2338
|
+
temp = zeros(numpoints)
|
|
2339
|
+
temp[locpoints] = sum(absolute(qi[:, locpoints]) ** 2, axis=0)
|
|
2340
|
+
self._ac[i] = temp
|
|
2341
|
+
self._fr[i] = 1
|
|
2534
2342
|
|
|
2535
2343
|
|
|
2536
2344
|
class BeamformerAdaptiveGrid(BeamformerBase, Grid):
|
|
@@ -2581,7 +2389,7 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2581
2389
|
|
|
2582
2390
|
#: List of components to consider, use this to directly set the eigenvalues
|
|
2583
2391
|
#: used in the beamformer. Alternatively, set :attr:`n`.
|
|
2584
|
-
eva_list = CArray(dtype=int, desc='components')
|
|
2392
|
+
eva_list = CArray(dtype=int, value=array([-1]), desc='components')
|
|
2585
2393
|
|
|
2586
2394
|
#: Number of components to consider, defaults to 1. If set,
|
|
2587
2395
|
#: :attr:`eva_list` will contain
|
|
@@ -2601,9 +2409,17 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2601
2409
|
#: and 1 iteration
|
|
2602
2410
|
shgo = Dict
|
|
2603
2411
|
|
|
2412
|
+
#: No normalization implemented. Defaults to 1.0.
|
|
2413
|
+
r_diag_norm = Enum(
|
|
2414
|
+
1.0,
|
|
2415
|
+
desc='If diagonal of the csm is removed, some signal energy is lost.'
|
|
2416
|
+
'This is handled via this normalization factor.'
|
|
2417
|
+
'For this class, normalization is not implemented. Defaults to 1.0.',
|
|
2418
|
+
)
|
|
2419
|
+
|
|
2604
2420
|
# internal identifier
|
|
2605
2421
|
digest = Property(
|
|
2606
|
-
depends_on=['freq_data.digest', '_steer_obj.digest', 'r_diag', 'eva_list', 'bounds', 'shgo'],
|
|
2422
|
+
depends_on=['freq_data.digest', '_steer_obj.digest', 'precision', 'r_diag', 'eva_list', 'bounds', 'shgo'],
|
|
2607
2423
|
)
|
|
2608
2424
|
|
|
2609
2425
|
@cached_property
|
|
@@ -2615,41 +2431,30 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2615
2431
|
"""Sets the list of eigenvalues to consider."""
|
|
2616
2432
|
self.eva_list = arange(-1, -1 - self.n, -1)
|
|
2617
2433
|
|
|
2618
|
-
@on_trait_change('eva_list')
|
|
2619
|
-
def set_n(self):
|
|
2620
|
-
"""Sets the list of eigenvalues to consider."""
|
|
2621
|
-
self.n = self.eva_list.shape[0]
|
|
2622
|
-
|
|
2623
2434
|
@property_depends_on('n')
|
|
2624
2435
|
def _get_size(self):
|
|
2625
2436
|
return self.n * self.freq_data.fftfreq().shape[0]
|
|
2626
2437
|
|
|
2627
|
-
def
|
|
2438
|
+
def _calc(self, ind):
|
|
2628
2439
|
"""Calculates the result for the frequencies defined by :attr:`freq_data`.
|
|
2629
2440
|
|
|
2630
2441
|
This is an internal helper function that is automatically called when
|
|
2631
|
-
accessing the beamformer's :attr
|
|
2632
|
-
its :meth
|
|
2442
|
+
accessing the beamformer's :attr:`result` or calling
|
|
2443
|
+
its :meth:`synthetic` method.
|
|
2633
2444
|
|
|
2634
2445
|
Parameters
|
|
2635
2446
|
----------
|
|
2636
|
-
|
|
2637
|
-
This array
|
|
2638
|
-
|
|
2639
|
-
value after calling this method.
|
|
2640
|
-
fr : array of booleans
|
|
2641
|
-
The entries of this [number of frequencies]-sized array are either
|
|
2642
|
-
'True' (if the result for this frequency has already been calculated)
|
|
2643
|
-
or 'False' (for the frequencies where the result has yet to be calculated).
|
|
2644
|
-
After the calculation at a certain frequency the value will be set
|
|
2645
|
-
to 'True'
|
|
2447
|
+
ind : array of int
|
|
2448
|
+
This array contains all frequency indices for which (re)calculation is
|
|
2449
|
+
to be performed
|
|
2646
2450
|
|
|
2647
2451
|
Returns
|
|
2648
2452
|
-------
|
|
2649
|
-
This method only returns values through
|
|
2453
|
+
This method only returns values through :attr:`_ac` and :attr:`_fr`
|
|
2650
2454
|
|
|
2651
2455
|
"""
|
|
2652
|
-
f = self.
|
|
2456
|
+
f = self._f
|
|
2457
|
+
normfactor = self.sig_loss_norm()
|
|
2653
2458
|
numchannels = self.freq_data.numchannels
|
|
2654
2459
|
# eigenvalue number list in standard form from largest to smallest
|
|
2655
2460
|
eva_list = unique(self.eva_list % self.steer.mics.num_mics)[::-1]
|
|
@@ -2663,7 +2468,6 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2663
2468
|
'n': 256,
|
|
2664
2469
|
'iters': 1,
|
|
2665
2470
|
'sampling_method': 'sobol',
|
|
2666
|
-
'options': {'local_iter': 1},
|
|
2667
2471
|
'minimizer_kwargs': {'method': 'Nelder-Mead'},
|
|
2668
2472
|
}
|
|
2669
2473
|
shgo_opts.update(self.shgo)
|
|
@@ -2675,36 +2479,39 @@ class BeamformerGridlessOrth(BeamformerAdaptiveGrid):
|
|
|
2675
2479
|
self.steer.env.roi = array(roi).T
|
|
2676
2480
|
bmin = array(tuple(map(min, self.bounds)))
|
|
2677
2481
|
bmax = array(tuple(map(max, self.bounds)))
|
|
2678
|
-
for i in
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2482
|
+
for i in ind:
|
|
2483
|
+
eva = array(self.freq_data.eva[i], dtype='float64')
|
|
2484
|
+
eve = array(self.freq_data.eve[i], dtype='complex128')
|
|
2485
|
+
k = 2 * pi * f[i] / env.c
|
|
2486
|
+
for j, n in enumerate(eva_list):
|
|
2487
|
+
# print(f[i],n)
|
|
2488
|
+
|
|
2489
|
+
def func(xy):
|
|
2490
|
+
# function to minimize globally
|
|
2491
|
+
xy = clip(xy, bmin, bmax)
|
|
2492
|
+
r0 = env._r(xy[:, newaxis])
|
|
2493
|
+
rm = env._r(xy[:, newaxis], mpos)
|
|
2494
|
+
return -beamformerFreq(
|
|
2495
|
+
steer_type,
|
|
2496
|
+
self.r_diag,
|
|
2497
|
+
normfactor,
|
|
2498
|
+
(r0, rm, k),
|
|
2499
|
+
(ones(1), eve[:, n : n + 1]),
|
|
2500
|
+
)[0][0] # noqa: B023
|
|
2501
|
+
|
|
2502
|
+
# simplical global homotopy optimizer
|
|
2503
|
+
oR = shgo(func, self.bounds, **shgo_opts)
|
|
2504
|
+
# index in grid
|
|
2505
|
+
i1 = i * self.n + j
|
|
2506
|
+
# store result for position
|
|
2507
|
+
self._gpos[:, i1] = oR['x']
|
|
2508
|
+
# store result for level
|
|
2509
|
+
self._ac[i, i1] = eva[n] / numchannels
|
|
2510
|
+
# print(oR['x'],eva[n]/numchannels,oR)
|
|
2511
|
+
self._fr[i] = 1
|
|
2512
|
+
|
|
2513
|
+
|
|
2514
|
+
def L_p(x): # noqa: N802
|
|
2708
2515
|
r"""Calculates the sound pressure level from the squared sound pressure.
|
|
2709
2516
|
|
|
2710
2517
|
:math:`L_p = 10 \lg ( x / 4\cdot 10^{-10})`
|