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