acoular 25.7__py3-none-any.whl → 26.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
acoular/tbeamform.py CHANGED
@@ -1,7 +1,14 @@
1
1
  # ------------------------------------------------------------------------------
2
2
  # Copyright (c) Acoular Development Team.
3
3
  # ------------------------------------------------------------------------------
4
- """Implements beamformers in the time domain.
4
+ """
5
+ Implements beamformers in the time domain.
6
+
7
+ .. inheritance-diagram::
8
+ acoular.tbeamform
9
+ :top-classes:
10
+ acoular.base.TimeOut
11
+ :parts: 1
5
12
 
6
13
  .. autosummary::
7
14
  :toctree: generated/
@@ -17,32 +24,8 @@
17
24
  IntegratorSectorTime
18
25
  """
19
26
 
20
- # imports from other packages
21
-
22
- from numpy import (
23
- arange,
24
- argmax,
25
- array,
26
- ceil,
27
- dot,
28
- empty,
29
- float32,
30
- float64,
31
- histogram,
32
- int32,
33
- int64,
34
- interp,
35
- isscalar,
36
- newaxis,
37
- r_,
38
- s_,
39
- sqrt,
40
- sum, # noqa: A004
41
- unique,
42
- where,
43
- zeros,
44
- )
45
- from scipy.linalg import norm
27
+ import numpy as np
28
+ import scipy.linalg as spla
46
29
  from traits.api import Bool, CArray, Enum, Float, Instance, Int, List, Map, Property, Range, cached_property
47
30
 
48
31
  from .base import SamplesGenerator, TimeOut
@@ -57,7 +40,8 @@ from .trajectory import Trajectory
57
40
 
58
41
 
59
42
  def const_power_weight(bf):
60
- """Internal helper function for :class:`BeamformerTime`.
43
+ """
44
+ Internal helper function for :class:`BeamformerTime`.
61
45
 
62
46
  Provides microphone weighting
63
47
  to make the power per unit area of the
@@ -73,14 +57,14 @@ def const_power_weight(bf):
73
57
  array of floats
74
58
  The weight factors.
75
59
  """
76
- r = bf.steer.env._r(zeros((3, 1)), bf.steer.mics.pos) # distances to center
60
+ r = bf.steer.env._r(np.zeros((3, 1)), bf.steer.mics.pos) # distances to center
77
61
  # round the relative distances to one decimal place
78
62
  r = (r / r.max()).round(decimals=1)
79
- ru, ind = unique(r, return_inverse=True)
63
+ ru, ind = np.unique(r, return_inverse=True)
80
64
  ru = (ru[1:] + ru[:-1]) / 2
81
- count, bins = histogram(r, r_[0, ru, 1.5 * r.max() - 0.5 * ru[-1]])
65
+ count, bins = np.histogram(r, np.r_[0, ru, 1.5 * r.max() - 0.5 * ru[-1]])
82
66
  bins *= bins
83
- weights = sqrt((bins[1:] - bins[:-1]) / count)
67
+ weights = np.sqrt((bins[1:] - bins[:-1]) / count)
84
68
  weights /= weights.mean()
85
69
  return weights[ind]
86
70
 
@@ -90,26 +74,28 @@ possible_weights = {'none': None, 'power': const_power_weight}
90
74
 
91
75
 
92
76
  class BeamformerTime(TimeOut):
93
- """Provides a basic time domain beamformer with time signal output
77
+ """
78
+ Provides a basic time domain beamformer with time signal output.
79
+
94
80
  for a spatially fixed grid.
95
81
  """
96
82
 
97
83
  #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
98
84
  source = Instance(SamplesGenerator)
99
85
 
100
- # Instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes
101
- # that contains information about the steering vector. This is a private trait.
102
- # Do not set this directly, use `steer` trait instead.
86
+ #: Instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes
87
+ #: that contains information about the steering vector. This is a private trait.
88
+ #: Do not set this directly, use :attr:`steer` trait instead.
103
89
  steer = Instance(SteeringVector, args=())
104
90
 
105
91
  #: Number of channels in output (=number of grid points).
106
92
  num_channels = Property()
107
93
 
108
94
  #: Spatial weighting function.
109
- weights = Map(possible_weights, default_value='none', desc='spatial weighting function')
95
+ weights = Map(possible_weights, default_value='none')
110
96
  # (from timedomain.possible_weights)
111
97
 
112
- # internal identifier
98
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
113
99
  digest = Property(
114
100
  depends_on=['steer.digest', 'source.digest', 'weights'],
115
101
  )
@@ -121,41 +107,42 @@ class BeamformerTime(TimeOut):
121
107
  return digest(self)
122
108
 
123
109
  def _get_weights(self):
124
- return self.weights_(self)[newaxis] if self.weights_ else 1.0
110
+ return self.weights_(self)[np.newaxis] if self.weights_ else 1.0
125
111
 
126
112
  def result(self, num=2048):
127
- """Python generator that yields the time-domain beamformer output.
113
+ """
114
+ Python generator that yields the time-domain beamformer output.
128
115
 
129
116
  The output time signal starts for source signals that were emitted from
130
117
  the :class:`~acoular.grids.Grid` at `t=0`.
131
118
 
132
119
  Parameters
133
120
  ----------
134
- num : int
121
+ num : :class:`int`
135
122
  This parameter defines the size of the blocks to be yielded
136
123
  (i.e. the number of samples per block). Defaults to 2048.
137
124
 
138
125
  Yields
139
126
  ------
140
- numpy.ndarray
141
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
127
+ :class:`numpy.ndarray`
128
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
142
129
  :attr:`~BeamformerTime.num_channels` is usually very \
143
130
  large (number of grid points).
144
- The last block returned by the generator may be shorter than num.
131
+ The last block returned by the generator may be shorter than ``num``.
145
132
  """
146
133
  # initialize values
147
134
  steer_func = self.steer._steer_funcs_time[self.steer.steer_type]
148
- fdtype = float64
149
- idtype = int64
135
+ fdtype = np.float64
136
+ idtype = np.int64
150
137
  num_mics = self.steer.mics.num_mics
151
- n_index = arange(0, num + 1)[:, newaxis]
138
+ n_index = np.arange(0, num + 1)[:, np.newaxis]
152
139
  c = self.steer.env.c / self.source.sample_freq
153
- amp = empty((1, self.steer.grid.size, num_mics), dtype=fdtype)
154
- # delays = empty((1,self.steer.grid.size,num_mics),dtype=fdtype)
155
- d_index = empty((1, self.steer.grid.size, num_mics), dtype=idtype)
156
- d_interp2 = empty((1, self.steer.grid.size, num_mics), dtype=fdtype)
157
- steer_func(self.steer.rm[newaxis, :, :], self.steer.r0[newaxis, :], amp)
158
- _delays(self.steer.rm[newaxis, :, :], c, d_interp2, d_index)
140
+ amp = np.empty((1, self.steer.grid.size, num_mics), dtype=fdtype)
141
+ # delays = np.empty((1,self.steer.grid.size,num_mics),dtype=fdtype)
142
+ d_index = np.empty((1, self.steer.grid.size, num_mics), dtype=idtype)
143
+ d_interp2 = np.empty((1, self.steer.grid.size, num_mics), dtype=fdtype)
144
+ steer_func(self.steer.rm[np.newaxis, :, :], self.steer.r0[np.newaxis, :], amp)
145
+ _delays(self.steer.rm[np.newaxis, :, :], c, d_interp2, d_index)
159
146
  amp.shape = amp.shape[1:]
160
147
  # delays.shape = delays.shape[1:]
161
148
  d_index.shape = d_index.shape[1:]
@@ -165,7 +152,7 @@ class BeamformerTime(TimeOut):
165
152
 
166
153
  buffer = SamplesBuffer(
167
154
  source=self.source,
168
- length=int(ceil((num + max_sample_delay) / num)) * num,
155
+ length=int(np.ceil((num + max_sample_delay) / num)) * num,
169
156
  result_num=num + max_sample_delay,
170
157
  shift_index_by='num',
171
158
  dtype=fdtype,
@@ -177,74 +164,76 @@ class BeamformerTime(TimeOut):
177
164
  # exit loop if there is not enough data left to be processed
178
165
  if num <= 0:
179
166
  break
180
- n_index = arange(0, num + 1)[:, newaxis]
167
+ n_index = np.arange(0, num + 1)[:, np.newaxis]
181
168
  # init step
182
- Phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
169
+ phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
183
170
  if 'Cleant' not in self.__class__.__name__:
184
171
  if 'Sq' not in self.__class__.__name__:
185
- yield Phi[:num]
172
+ yield phi[:num]
186
173
  elif self.r_diag:
187
- yield (Phi[:num] ** 2 - autopow[:num]).clip(min=0)
174
+ yield (phi[:num] ** 2 - autopow[:num]).clip(min=0)
188
175
  else:
189
- yield Phi[:num] ** 2
176
+ yield phi[:num] ** 2
190
177
  else:
191
178
  p_res_copy = p_res.copy()
192
- Gamma = zeros(Phi.shape)
193
- Gamma_autopow = zeros(Phi.shape)
194
- J = 0
179
+ gamma = np.zeros(phi.shape)
180
+ gamma_autopow = np.zeros(phi.shape)
181
+ j = 0
195
182
  # deconvolution
196
- while self.n_iter > J:
197
- # print(f"start clean iteration {J+1} of max {self.n_iter}")
198
- powPhi = (Phi[:num] ** 2 - autopow).sum(0).clip(min=0) if self.r_diag else (Phi[:num] ** 2).sum(0)
199
- imax = argmax(powPhi)
183
+ while self.n_iter > j:
184
+ # print(f"start clean iteration {j+1} of max {self.n_iter}")
185
+ pow_phi = (phi[:num] ** 2 - autopow).sum(0).clip(min=0) if self.r_diag else (phi[:num] ** 2).sum(0)
186
+ imax = np.argmax(pow_phi)
200
187
  t_float = d_interp2[imax] + d_index[imax] + n_index
201
- t_ind = t_float.astype(int64)
188
+ t_ind = t_float.astype(np.int64)
202
189
  for m in range(num_mics):
203
- p_res_copy[t_ind[: num + 1, m], m] -= self.damp * interp(
190
+ p_res_copy[t_ind[: num + 1, m], m] -= self.damp * np.interp(
204
191
  t_ind[: num + 1, m],
205
192
  t_float[:num, m],
206
- Phi[:num, imax] * self.steer.r0[imax] / self.steer.rm[imax, m],
193
+ phi[:num, imax] * self.steer.r0[imax] / self.steer.rm[imax, m],
207
194
  )
208
- nextPhi, nextAutopow = self._delay_and_sum(num, p_res_copy, d_interp2, d_index, amp)
195
+ next_phi, next_autopow = self._delay_and_sum(num, p_res_copy, d_interp2, d_index, amp)
209
196
  if self.r_diag:
210
- pownextPhi = (nextPhi[:num] ** 2 - nextAutopow).sum(0).clip(min=0)
197
+ pow_next_phi = (next_phi[:num] ** 2 - next_autopow).sum(0).clip(min=0)
211
198
  else:
212
- pownextPhi = (nextPhi[:num] ** 2).sum(0)
213
- # print(f"total signal power: {powPhi.sum()}")
214
- if pownextPhi.sum() < powPhi.sum(): # stopping criterion
215
- Gamma[:num, imax] += self.damp * Phi[:num, imax]
216
- Gamma_autopow[:num, imax] = autopow[:num, imax].copy()
217
- Phi = nextPhi
218
- autopow = nextAutopow
219
- # print(f"clean max: {L_p((Gamma**2).sum(0)/num).max()} dB")
220
- J += 1
199
+ pow_next_phi = (next_phi[:num] ** 2).sum(0)
200
+ # print(f"total signal power: {pow_phi.sum()}")
201
+ if pow_next_phi.sum() < pow_phi.sum(): # stopping criterion
202
+ gamma[:num, imax] += self.damp * phi[:num, imax]
203
+ gamma_autopow[:num, imax] = autopow[:num, imax].copy()
204
+ phi = next_phi
205
+ autopow = next_autopow
206
+ # print(f"clean max: {L_p((gamma**2).sum(0)/num).max()} dB")
207
+ j += 1
221
208
  else:
222
209
  break
223
210
  if 'Sq' not in self.__class__.__name__:
224
- yield Gamma[:num]
211
+ yield gamma[:num]
225
212
  elif self.r_diag:
226
- yield Gamma[:num] ** 2 - (self.damp**2) * Gamma_autopow[:num]
213
+ yield gamma[:num] ** 2 - (self.damp**2) * gamma_autopow[:num]
227
214
  else:
228
- yield Gamma[:num] ** 2
215
+ yield gamma[:num] ** 2
229
216
 
230
217
  def _delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
231
218
  """Standard delay-and-sum method."""
232
- result = empty((num, self.steer.grid.size), dtype=float) # output array
233
- autopow = empty((num, self.steer.grid.size), dtype=float) # output array
219
+ result = np.empty((num, self.steer.grid.size), dtype=float) # output array
220
+ autopow = np.empty((num, self.steer.grid.size), dtype=float) # output array
234
221
  _delayandsum4(p_res, d_index, d_interp2, amp, result, autopow)
235
222
  return result, autopow
236
223
 
237
224
 
238
225
  class BeamformerTimeSq(BeamformerTime):
239
- """Provides a time domain beamformer with time-dependend
240
- power signal output and possible autopower removal
241
- for a spatially fixed grid.
226
+ """
227
+ Time domain beamformer with squared output and optional autopower removal.
228
+
229
+ Provides a time domain beamformer with time-dependend power signal output and possible autopower
230
+ removal for a spatially fixed grid.
242
231
  """
243
232
 
244
233
  #: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
245
- r_diag = Bool(True, desc='removal of diagonal')
234
+ r_diag = Bool(True)
246
235
 
247
- # internal identifier
236
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
248
237
  digest = Property(
249
238
  depends_on=['steer.digest', 'source.digest', 'r_diag', 'weights'],
250
239
  )
@@ -254,47 +243,50 @@ class BeamformerTimeSq(BeamformerTime):
254
243
  return digest(self)
255
244
 
256
245
  def result(self, num=2048):
257
- """Python generator that yields the **squared** time-domain beamformer output.
246
+ """
247
+ Python generator that yields the **squared** time-domain beamformer output.
258
248
 
259
249
  The squared output time signal starts for source signals that were emitted from
260
250
  the :class:`~acoular.grids.Grid` at `t=0`.
261
251
 
262
252
  Parameters
263
253
  ----------
264
- num : int
254
+ num : :class:`int`
265
255
  This parameter defines the size of the blocks to be yielded
266
256
  (i.e. the number of samples per block). Defaults to 2048.
267
257
 
268
258
  Yields
269
259
  ------
270
- numpy.ndarray
271
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
260
+ :class:`numpy.ndarray`
261
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
272
262
  :attr:`~BeamformerTime.num_channels` is usually very \
273
263
  large (number of grid points).
274
- The last block returned by the generator may be shorter than num.
264
+ The last block returned by the generator may be shorter than ``num``.
275
265
  """
276
266
  return super().result(num)
277
267
 
278
268
 
279
269
  class BeamformerTimeTraj(BeamformerTime):
280
- """Provides a basic time domain beamformer with time signal output
270
+ """
271
+ Provides a basic time domain beamformer with time signal output.
272
+
281
273
  for a grid moving along a trajectory.
282
274
  """
283
275
 
284
276
  #: :class:`~acoular.trajectory.Trajectory` or derived object.
285
277
  #: Start time is assumed to be the same as for the samples.
286
- trajectory = Instance(Trajectory, desc='trajectory of the grid center')
278
+ trajectory = Instance(Trajectory)
287
279
 
288
280
  #: Reference vector, perpendicular to the y-axis of moving grid.
289
- rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0)), desc='reference vector')
281
+ rvec = CArray(dtype=float, shape=(3,), value=np.array((0, 0, 0)))
290
282
 
291
283
  #: Considering of convective amplification in beamforming formula.
292
- conv_amp = Bool(False, desc='determines if convective amplification of source is considered')
284
+ conv_amp = Bool(False)
293
285
 
294
286
  #: Floating point and integer precision
295
- precision = Enum(64, 32, desc='numeric precision')
287
+ precision = Enum(64, 32)
296
288
 
297
- # internal identifier
289
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
298
290
  digest = Property(
299
291
  depends_on=[
300
292
  'steer.digest',
@@ -315,10 +307,12 @@ class BeamformerTimeTraj(BeamformerTime):
315
307
  """Python generator that yields the moving grid coordinates samplewise."""
316
308
 
317
309
  def cross(a, b):
318
- """Cross product for fast computation
310
+ """
311
+ Cross product for fast computation.
312
+
319
313
  because numpy.cross is ultra slow in this case.
320
314
  """
321
- return array([a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]])
315
+ return np.array([a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]])
322
316
 
323
317
  start_t = 0.0
324
318
  gpos = self.steer.grid.pos
@@ -328,74 +322,87 @@ class BeamformerTimeTraj(BeamformerTime):
328
322
  if rflag:
329
323
  for g in trajg:
330
324
  # grid is only translated, not rotated
331
- tpos = gpos + array(g)[:, newaxis]
325
+ tpos = gpos + np.array(g)[:, np.newaxis]
332
326
  yield tpos
333
327
  else:
334
- for g, g1 in zip(trajg, trajg1):
328
+ for g, g1 in zip(trajg, trajg1, strict=True):
335
329
  # grid is both translated and rotated
336
- loc = array(g) # translation array([0., 0.4, 1.])
337
- dx = array(g1) # direction vector (new x-axis)
330
+ loc = np.array(g) # translation array([0., 0.4, 1.])
331
+ dx = np.array(g1) # direction vector (new x-axis)
338
332
  dy = cross(self.rvec, dx) # new y-axis
339
333
  dz = cross(dx, dy) # new z-axis
340
- RM = array((dx, dy, dz)).T # rotation matrix
341
- RM /= sqrt((RM * RM).sum(0)) # column normalized
342
- tpos = dot(RM, gpos) + loc[:, newaxis] # rotation+translation
334
+ rm = np.array((dx, dy, dz)).T # rotation matrix
335
+ rm /= np.sqrt((rm * rm).sum(0)) # column normalized
336
+ tpos = np.dot(rm, gpos) + loc[:, np.newaxis] # rotation+translation
343
337
  # print(loc[:])
344
338
  yield tpos
345
339
 
346
340
  def _get_macostheta(self, g1, tpos, rm):
347
- vvec = array(g1) # velocity vector
348
- ma = norm(vvec) / self.steer.env.c # machnumber
349
- fdv = (vvec / sqrt((vvec * vvec).sum()))[:, newaxis] # unit vecor velocity
350
- mpos = self.steer.mics.pos[:, newaxis, :]
351
- rmv = tpos[:, :, newaxis] - mpos
352
- return (ma * sum(rmv.reshape((3, -1)) * fdv, 0) / rm.reshape(-1)).reshape(rm.shape)
341
+ vvec = np.array(g1) # velocity vector
342
+ ma = spla.norm(vvec) / self.steer.env.c # machnumber
343
+ fdv = (vvec / np.sqrt((vvec * vvec).sum()))[:, np.newaxis] # unit vecor velocity
344
+ mpos = self.steer.mics.pos[:, np.newaxis, :]
345
+ rmv = tpos[:, :, np.newaxis] - mpos
346
+ return (ma * np.sum(rmv.reshape((3, -1)) * fdv, 0) / rm.reshape(-1)).reshape(rm.shape)
353
347
 
354
348
  def get_r0(self, tpos):
355
- if isscalar(self.steer.ref) and self.steer.ref > 0:
349
+ """
350
+ Get reference distance for grid positions.
351
+
352
+ Parameters
353
+ ----------
354
+ tpos : :class:`numpy.ndarray`
355
+ Grid positions.
356
+
357
+ Returns
358
+ -------
359
+ :class:`float` or :class:`numpy.ndarray`
360
+ Reference distance(s).
361
+ """
362
+ if np.isscalar(self.steer.ref) and self.steer.ref > 0:
356
363
  return self.steer.ref # full((self.steer.grid.size,), self.steer.ref)
357
364
  return self.steer.env._r(tpos)
358
365
 
359
366
  def result(self, num=2048):
360
- """Python generator that yields the time-domain beamformer output.
367
+ """
368
+ Python generator that yields the time-domain beamformer output.
361
369
 
362
370
  The output time signal starts for source signals that were emitted from
363
371
  the :class:`~acoular.grids.Grid` at `t=0`.
364
372
 
365
373
  Parameters
366
374
  ----------
367
- num : int
375
+ num : :class:`int`
368
376
  This parameter defines the size of the blocks to be yielded
369
377
  (i.e. the number of samples per block). Defaults to 2048.
370
378
 
371
379
  Yields
372
380
  ------
373
- numpy.ndarray
374
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
381
+ :class:`numpy.ndarray`
382
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
375
383
  :attr:`~BeamformerTime.num_channels` is usually very \
376
384
  large (number of grid points).
377
- The last block returned by the generator may be shorter than num.
385
+ The last block returned by the generator may be shorter than ``num``.
378
386
  """
379
387
  # initialize values
380
388
  if self.precision == 64:
381
- fdtype = float64
382
- idtype = int64
389
+ fdtype = np.float64
390
+ idtype = np.int64
383
391
  else:
384
- fdtype = float32
385
- idtype = int32
386
- w = self._get_weights()
392
+ fdtype = np.float32
393
+ idtype = np.int32
387
394
  c = self.steer.env.c / self.source.sample_freq
388
395
  num_mics = self.steer.mics.num_mics
389
396
  mpos = self.steer.mics.pos.astype(fdtype)
390
- m_index = arange(num_mics, dtype=idtype)
391
- n_index = arange(num, dtype=idtype)[:, newaxis]
392
- blockrm = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
393
- blockrmconv = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
394
- amp = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
395
- # delays = empty((num,self.steer.grid.size,num_mics),dtype=fdtype)
396
- d_index = empty((num, self.steer.grid.size, num_mics), dtype=idtype)
397
- d_interp2 = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
398
- blockr0 = empty((num, self.steer.grid.size), dtype=fdtype)
397
+ m_index = np.arange(num_mics, dtype=idtype)
398
+ n_index = np.arange(num, dtype=idtype)[:, np.newaxis]
399
+ blockrm = np.empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
400
+ blockrmconv = np.empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
401
+ amp = np.empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
402
+ # delays = np.empty((num,self.steer.grid.size,num_mics),dtype=fdtype)
403
+ d_index = np.empty((num, self.steer.grid.size, num_mics), dtype=idtype)
404
+ d_interp2 = np.empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
405
+ blockr0 = np.empty((num, self.steer.grid.size), dtype=fdtype)
399
406
  movgpos = self._get_moving_gpos() # create moving grid pos generator
400
407
  movgspeed = self.trajectory.traj(0.0, delta_t=1 / self.source.sample_freq, der=1)
401
408
  weights = self._get_weights()
@@ -430,35 +437,35 @@ class BeamformerTimeTraj(BeamformerTime):
430
437
  except StopIteration:
431
438
  break
432
439
  if time_block.shape[0] < buffer.result_num: # last block shorter
433
- num = sum((d_index.max((1, 2)) + 1 + arange(0, num)) < time_block.shape[0])
434
- n_index = arange(num, dtype=idtype)[:, newaxis]
440
+ num = np.sum((d_index.max((1, 2)) + 1 + np.arange(0, num)) < time_block.shape[0])
441
+ n_index = np.arange(num, dtype=idtype)[:, np.newaxis]
435
442
  flag = False
436
443
  # init step
437
444
  p_res = time_block.copy()
438
- Phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
445
+ phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
439
446
  if 'Cleant' not in self.__class__.__name__:
440
447
  if 'Sq' not in self.__class__.__name__:
441
- yield Phi[:num]
448
+ yield phi[:num]
442
449
  elif self.r_diag:
443
- yield (Phi[:num] ** 2 - autopow[:num]).clip(min=0)
450
+ yield (phi[:num] ** 2 - autopow[:num]).clip(min=0)
444
451
  else:
445
- yield Phi[:num] ** 2
452
+ yield phi[:num] ** 2
446
453
  else:
447
454
  # choose correct distance
448
455
  blockrm1 = blockrmconv if self.conv_amp else blockrm
449
- Gamma = zeros(Phi.shape, dtype=fdtype)
450
- Gamma_autopow = zeros(Phi.shape, dtype=fdtype)
451
- J = 0
452
- t_ind = arange(p_res.shape[0], dtype=idtype)
456
+ gamma = np.zeros(phi.shape, dtype=fdtype)
457
+ gamma_autopow = np.zeros(phi.shape, dtype=fdtype)
458
+ j = 0
459
+ t_ind = np.arange(p_res.shape[0], dtype=idtype)
453
460
  # deconvolution
454
- while self.n_iter > J:
455
- # print(f"start clean iteration {J+1} of max {self.n_iter}")
461
+ while self.n_iter > j:
462
+ # print(f"start clean iteration {j+1} of max {self.n_iter}")
456
463
  if self.r_diag:
457
- powPhi = (Phi[:num] * Phi[:num] - autopow).sum(0).clip(min=0)
464
+ pow_phi = (phi[:num] * phi[:num] - autopow).sum(0).clip(min=0)
458
465
  else:
459
- powPhi = (Phi[:num] * Phi[:num]).sum(0)
466
+ pow_phi = (phi[:num] * phi[:num]).sum(0)
460
467
  # find index of max power focus point
461
- imax = argmax(powPhi)
468
+ imax = np.argmax(pow_phi)
462
469
  # find backward delays
463
470
  t_float = (d_interp2[:num, imax, m_index] + d_index[:num, imax, m_index] + n_index).astype(fdtype)
464
471
  # determine max/min delays in sample units
@@ -466,52 +473,54 @@ class BeamformerTimeTraj(BeamformerTime):
466
473
  ind_max = t_float.max(0).astype(idtype) + 2
467
474
  ind_min = t_float.min(0).astype(idtype)
468
475
  # store time history at max power focus point
469
- h = Phi[:num, imax] * blockr0[:num, imax]
476
+ h = phi[:num, imax] * blockr0[:num, imax]
470
477
  for m in range(num_mics):
471
478
  # subtract interpolated time history from microphone signals
472
- p_res[ind_min[m] : ind_max[m], m] -= self.damp * interp(
479
+ p_res[ind_min[m] : ind_max[m], m] -= self.damp * np.interp(
473
480
  t_ind[ind_min[m] : ind_max[m]],
474
481
  t_float[:num, m],
475
482
  h / blockrm1[:num, imax, m],
476
483
  )
477
- nextPhi, nextAutopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
484
+ next_phi, next_autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
478
485
  if self.r_diag:
479
- pownextPhi = (nextPhi[:num] * nextPhi[:num] - nextAutopow).sum(0).clip(min=0)
486
+ pow_next_phi = (next_phi[:num] * next_phi[:num] - next_autopow).sum(0).clip(min=0)
480
487
  else:
481
- pownextPhi = (nextPhi[:num] * nextPhi[:num]).sum(0)
482
- # print(f"total signal power: {powPhi.sum()}")
483
- if pownextPhi.sum() < powPhi.sum(): # stopping criterion
484
- Gamma[:num, imax] += self.damp * Phi[:num, imax]
485
- Gamma_autopow[:num, imax] = autopow[:num, imax].copy()
486
- Phi = nextPhi
487
- autopow = nextAutopow
488
- # print(f"clean max: {L_p((Gamma**2).sum(0)/num).max()} dB")
489
- J += 1
488
+ pow_next_phi = (next_phi[:num] * next_phi[:num]).sum(0)
489
+ # print(f"total signal power: {pow_phi.sum()}")
490
+ if pow_next_phi.sum() < pow_phi.sum(): # stopping criterion
491
+ gamma[:num, imax] += self.damp * phi[:num, imax]
492
+ gamma_autopow[:num, imax] = autopow[:num, imax].copy()
493
+ phi = next_phi
494
+ autopow = next_autopow
495
+ # print(f"clean max: {L_p((gamma**2).sum(0)/num).max()} dB")
496
+ j += 1
490
497
  else:
491
498
  break
492
499
  if 'Sq' not in self.__class__.__name__:
493
- yield Gamma[:num]
500
+ yield gamma[:num]
494
501
  elif self.r_diag:
495
- yield Gamma[:num] ** 2 - (self.damp**2) * Gamma_autopow[:num]
502
+ yield gamma[:num] ** 2 - (self.damp**2) * gamma_autopow[:num]
496
503
  else:
497
- yield Gamma[:num] ** 2
504
+ yield gamma[:num] ** 2
498
505
 
499
506
  def _delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
500
507
  """Standard delay-and-sum method."""
501
- fdtype = float64 if self.precision == 64 else float32
502
- result = empty((num, self.steer.grid.size), dtype=fdtype) # output array
503
- autopow = empty((num, self.steer.grid.size), dtype=fdtype) # output array
508
+ fdtype = np.float64 if self.precision == 64 else np.float32
509
+ result = np.empty((num, self.steer.grid.size), dtype=fdtype) # output array
510
+ autopow = np.empty((num, self.steer.grid.size), dtype=fdtype) # output array
504
511
  _delayandsum5(p_res, d_index, d_interp2, amp, result, autopow)
505
512
  return result, autopow
506
513
 
507
514
 
508
515
  class BeamformerTimeSqTraj(BeamformerTimeSq, BeamformerTimeTraj):
509
- """Provides a time domain beamformer with time-dependent
510
- power signal output and possible autopower removal
511
- for a grid moving along a trajectory.
512
516
  """
517
+ Time domain beamformer with squared output for a grid moving along a trajectory.
513
518
 
514
- # internal identifier
519
+ Provides a time domain beamformer with time-dependent power signal output and possible autopower
520
+ removal for a grid moving along a trajectory.
521
+ """
522
+
523
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
515
524
  digest = Property(
516
525
  depends_on=[
517
526
  'steer.digest',
@@ -530,46 +539,48 @@ class BeamformerTimeSqTraj(BeamformerTimeSq, BeamformerTimeTraj):
530
539
  return digest(self)
531
540
 
532
541
  def result(self, num=2048):
533
- """Python generator that yields the **squared** time-domain beamformer output.
542
+ """
543
+ Python generator that yields the **squared** time-domain beamformer output.
534
544
 
535
545
  The squared output time signal starts for source signals that were emitted from
536
546
  the :class:`~acoular.grids.Grid` at `t=0`.
537
547
 
538
548
  Parameters
539
549
  ----------
540
- num : int
550
+ num : :class:`int`
541
551
  This parameter defines the size of the blocks to be yielded
542
552
  (i.e. the number of samples per block). Defaults to 2048.
543
553
 
544
554
  Yields
545
555
  ------
546
- numpy.ndarray
547
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
556
+ :class:`numpy.ndarray`
557
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
548
558
  :attr:`~BeamformerTime.num_channels` is usually very \
549
559
  large (number of grid points).
550
- The last block returned by the generator may be shorter than num.
560
+ The last block returned by the generator may be shorter than ``num``.
551
561
  """
552
562
  return super().result(num)
553
563
 
554
564
 
555
565
  class BeamformerCleant(BeamformerTime):
556
- """CLEANT deconvolution method.
566
+ """
567
+ CLEANT deconvolution method.
557
568
 
558
569
  An implementation of the CLEAN method in time domain. This class can only
559
570
  be used for static sources. See :cite:`Cousson2019` for details.
560
571
  """
561
572
 
562
573
  #: Boolean flag, always False
563
- r_diag = Enum(False, desc='False, as we do not remove autopower in this beamformer')
574
+ r_diag = Enum(False)
564
575
 
565
576
  #: iteration damping factor also referred as loop gain in Cousson et al.
566
577
  #: defaults to 0.6
567
- damp = Range(0.01, 1.0, 0.6, desc='damping factor (loop gain)')
578
+ damp = Range(0.01, 1.0, 0.6)
568
579
 
569
580
  #: max number of iterations
570
- n_iter = Int(100, desc='maximum number of iterations')
581
+ n_iter = Int(100)
571
582
 
572
- # internal identifier
583
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
573
584
  digest = Property(
574
585
  depends_on=['steer.digest', 'source.digest', 'weights', 'damp', 'n_iter'],
575
586
  )
@@ -579,30 +590,32 @@ class BeamformerCleant(BeamformerTime):
579
590
  return digest(self)
580
591
 
581
592
  def result(self, num=2048):
582
- """Python generator that yields the deconvolved time-domain beamformer output.
593
+ """
594
+ Python generator that yields the deconvolved time-domain beamformer output.
583
595
 
584
596
  The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
585
597
  `t=0`.
586
598
 
587
599
  Parameters
588
600
  ----------
589
- num : int
601
+ num : :class:`int`
590
602
  This parameter defines the size of the blocks to be yielded
591
603
  (i.e. the number of samples per block). Defaults to 2048.
592
604
 
593
605
  Yields
594
606
  ------
595
- numpy.ndarray
596
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
607
+ :class:`numpy.ndarray`
608
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
597
609
  :attr:`~BeamformerTime.num_channels` is usually very \
598
610
  large (number of grid points).
599
- The last block returned by the generator may be shorter than num.
611
+ The last block returned by the generator may be shorter than ``num``.
600
612
  """
601
613
  return super().result(num)
602
614
 
603
615
 
604
616
  class BeamformerCleantSq(BeamformerCleant):
605
- """CLEANT deconvolution method with optional removal of autocorrelation.
617
+ """
618
+ CLEANT deconvolution method with optional removal of autocorrelation.
606
619
 
607
620
  An implementation of the CLEAN method in time domain. This class can only
608
621
  be used for static sources. See :cite:`Cousson2019` for details on the method
@@ -610,9 +623,9 @@ class BeamformerCleantSq(BeamformerCleant):
610
623
  """
611
624
 
612
625
  #: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
613
- r_diag = Bool(True, desc='removal of diagonal')
626
+ r_diag = Bool(True)
614
627
 
615
- # internal identifier
628
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
616
629
  digest = Property(
617
630
  depends_on=['steer.digest', 'source.digest', 'weights', 'damp', 'n_iter', 'r_diag'],
618
631
  )
@@ -622,7 +635,8 @@ class BeamformerCleantSq(BeamformerCleant):
622
635
  return digest(self)
623
636
 
624
637
  def result(self, num=2048):
625
- """Python generator that yields the *squared* deconvolved time-domain beamformer output.
638
+ """
639
+ Python generator that yields the *squared* deconvolved time-domain beamformer output.
626
640
 
627
641
  The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
628
642
  `t=0`. Per default, block-wise removal of autocorrelation is performed, which can be turned
@@ -630,32 +644,33 @@ class BeamformerCleantSq(BeamformerCleant):
630
644
 
631
645
  Parameters
632
646
  ----------
633
- num : int
647
+ num : :class:`int`
634
648
  This parameter defines the size of the blocks to be yielded
635
649
  (i.e. the number of samples per block). Defaults to 2048.
636
650
 
637
651
  Yields
638
652
  ------
639
- numpy.ndarray
640
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
653
+ :class:`numpy.ndarray`
654
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
641
655
  :attr:`~BeamformerTime.num_channels` is usually very \
642
656
  large (number of grid points).
643
- The last block returned by the generator may be shorter than num.
657
+ The last block returned by the generator may be shorter than ``num``.
644
658
  """
645
659
  return super().result(num)
646
660
 
647
661
 
648
662
  class BeamformerCleantTraj(BeamformerCleant, BeamformerTimeTraj):
649
- """CLEANT deconvolution method.
663
+ """
664
+ CLEANT deconvolution method.
650
665
 
651
666
  An implementation of the CLEAN method in time domain for moving sources
652
667
  with known trajectory. See :cite:`Cousson2019` for details.
653
668
  """
654
669
 
655
670
  #: Floating point and integer precision
656
- precision = Enum(32, 64, desc='numeric precision')
671
+ precision = Enum(32, 64)
657
672
 
658
- # internal identifier
673
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
659
674
  digest = Property(
660
675
  depends_on=[
661
676
  'steer.digest',
@@ -675,30 +690,32 @@ class BeamformerCleantTraj(BeamformerCleant, BeamformerTimeTraj):
675
690
  return digest(self)
676
691
 
677
692
  def result(self, num=2048):
678
- """Python generator that yields the deconvolved time-domain beamformer output.
693
+ """
694
+ Python generator that yields the deconvolved time-domain beamformer output.
679
695
 
680
696
  The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
681
697
  `t=0`.
682
698
 
683
699
  Parameters
684
700
  ----------
685
- num : int
701
+ num : :class:`int`
686
702
  This parameter defines the size of the blocks to be yielded
687
703
  (i.e. the number of samples per block). Defaults to 2048.
688
704
 
689
705
  Yields
690
706
  ------
691
- numpy.ndarray
692
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
707
+ :class:`numpy.ndarray`
708
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
693
709
  :attr:`~BeamformerTime.num_channels` is usually very \
694
710
  large (number of grid points).
695
- The last block returned by the generator may be shorter than num.
711
+ The last block returned by the generator may be shorter than ``num``.
696
712
  """
697
713
  return super().result(num)
698
714
 
699
715
 
700
716
  class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
701
- """CLEANT deconvolution method with optional removal of autocorrelation.
717
+ """
718
+ CLEANT deconvolution method with optional removal of autocorrelation.
702
719
 
703
720
  An implementation of the CLEAN method in time domain for moving sources
704
721
  with known trajectory. See :cite:`Cousson2019` for details on the method and
@@ -706,9 +723,9 @@ class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
706
723
  """
707
724
 
708
725
  #: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
709
- r_diag = Bool(True, desc='removal of diagonal')
726
+ r_diag = Bool(True)
710
727
 
711
- # internal identifier
728
+ #: A unique identifier for the beamformer, based on its properties. (read-only)
712
729
  digest = Property(
713
730
  depends_on=[
714
731
  'steer.digest',
@@ -729,7 +746,8 @@ class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
729
746
  return digest(self)
730
747
 
731
748
  def result(self, num=2048):
732
- """Python generator that yields the *squared* deconvolved time-domain beamformer output.
749
+ """
750
+ Python generator that yields the *squared* deconvolved time-domain beamformer output.
733
751
 
734
752
  The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
735
753
  `t=0`. Per default, block-wise removal of autocorrelation is performed, which can be turned
@@ -737,17 +755,17 @@ class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
737
755
 
738
756
  Parameters
739
757
  ----------
740
- num : int
758
+ num : :class:`int`
741
759
  This parameter defines the size of the blocks to be yielded
742
760
  (i.e. the number of samples per block). Defaults to 2048.
743
761
 
744
762
  Yields
745
763
  ------
746
- numpy.ndarray
747
- Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
764
+ :class:`numpy.ndarray`
765
+ Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
748
766
  :attr:`~BeamformerTime.num_channels` is usually very \
749
767
  large (number of grid points).
750
- The last block returned by the generator may be shorter than num.
768
+ The last block returned by the generator may be shorter than ``num``.
751
769
  """
752
770
  return super().result(num)
753
771
 
@@ -759,7 +777,7 @@ class IntegratorSectorTime(TimeOut):
759
777
  source = Instance(SamplesGenerator)
760
778
 
761
779
  #: :class:`~acoular.grids.RectGrid` object that provides the grid locations.
762
- grid = Instance(RectGrid, desc='beamforming grid')
780
+ grid = Instance(RectGrid)
763
781
 
764
782
  #: List of sectors in grid
765
783
  sectors = List()
@@ -770,7 +788,7 @@ class IntegratorSectorTime(TimeOut):
770
788
  #: Number of channels in output (= number of sectors).
771
789
  num_channels = Property(depends_on=['sectors'])
772
790
 
773
- # internal identifier
791
+ #: A unique identifier for the integrator, based on its properties. (read-only)
774
792
  digest = Property(
775
793
  depends_on=['sectors', 'clip', 'grid.digest', 'source.digest'],
776
794
  )
@@ -784,32 +802,32 @@ class IntegratorSectorTime(TimeOut):
784
802
  return len(self.sectors)
785
803
 
786
804
  def result(self, num=1):
787
- """Python generator that yields the source output integrated over the given
788
- sectors, block-wise.
805
+ """
806
+ Python generator that yields the source output integrated over specified grid sectors.
789
807
 
790
808
  Parameters
791
809
  ----------
792
- num : integer, defaults to 1
793
- This parameter defines the size of the blocks to be yielded
794
- (i.e. the number of samples per block).
810
+ num : :class:`int`
811
+ Size of the blocks to be yielded (number of samples per block). Default is ``1``.
795
812
 
796
813
  Returns
797
814
  -------
798
- Samples in blocks of shape (num, :attr:`num_channels`).
815
+ :class:`numpy.ndarray`
816
+ Samples in blocks of shape (``num``, :attr:`num_channels`).
799
817
  :attr:`num_channels` is the number of sectors.
800
818
  The last block may be shorter than num.
801
819
  """
802
820
  inds = [self.grid.indices(*sector) for sector in self.sectors]
803
821
  gshape = self.grid.shape
804
- o = empty((num, self.num_channels), dtype=float) # output array
822
+ o = np.empty((num, self.num_channels), dtype=float) # output array
805
823
  for r in self.source.result(num):
806
824
  ns = r.shape[0]
807
825
  mapshape = (ns,) + gshape
808
826
  rmax = r.max()
809
827
  rmin = rmax * 10 ** (self.clip / 10.0)
810
- r = where(r > rmin, r, 0.0)
828
+ r = np.where(r > rmin, r, 0.0)
811
829
  for i, ind in enumerate(inds):
812
- h = r[:].reshape(mapshape)[(s_[:],) + ind]
830
+ h = r[:].reshape(mapshape)[(np.s_[:],) + ind]
813
831
  o[:ns, i] = h.reshape(h.shape[0], -1).sum(axis=1)
814
832
  i += 1
815
833
  yield o[:ns]