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