acoular 24.7__py3-none-any.whl → 25.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
@@ -18,15 +18,12 @@
18
18
  """
19
19
 
20
20
  # imports from other packages
21
- import contextlib
22
- from warnings import warn
23
21
 
24
22
  from numpy import (
25
23
  arange,
26
24
  argmax,
27
25
  array,
28
26
  ceil,
29
- concatenate,
30
27
  dot,
31
28
  empty,
32
29
  float32,
@@ -40,22 +37,22 @@ from numpy import (
40
37
  r_,
41
38
  s_,
42
39
  sqrt,
43
- sum,
40
+ sum, # noqa: A004
44
41
  unique,
45
42
  where,
46
43
  zeros,
47
44
  )
48
- from numpy.linalg import norm
49
- from traits.api import Bool, CArray, Delegate, Enum, Float, Instance, Int, List, Property, Range, Trait, cached_property
50
- from traits.trait_errors import TraitError
45
+ from scipy.linalg import norm
46
+ from traits.api import Bool, CArray, Enum, Float, Instance, Int, List, Map, Property, Range, cached_property
51
47
 
48
+ from .base import SamplesGenerator, TimeOut
52
49
  from .fbeamform import SteeringVector
53
50
  from .grids import RectGrid
54
51
 
55
52
  # acoular imports
56
53
  from .internal import digest
57
- from .tfastfuncs import _delayandsum4, _delayandsum5, _delays, _steer_I, _steer_II, _steer_III, _steer_IV
58
- from .tprocess import TimeInOut
54
+ from .process import SamplesBuffer
55
+ from .tfastfuncs import _delayandsum4, _delayandsum5, _delays
59
56
  from .trajectory import Trajectory
60
57
 
61
58
 
@@ -75,9 +72,8 @@ def const_power_weight(bf):
75
72
  -------
76
73
  array of floats
77
74
  The weight factors.
78
-
79
75
  """
80
- r = bf.steer.env._r(zeros((3, 1)), bf.steer.mics.mpos) # distances to center
76
+ r = bf.steer.env._r(zeros((3, 1)), bf.steer.mics.pos) # distances to center
81
77
  # round the relative distances to one decimal place
82
78
  r = (r / r.max()).round(decimals=1)
83
79
  ru, ind = unique(r, return_inverse=True)
@@ -93,199 +89,94 @@ def const_power_weight(bf):
93
89
  possible_weights = {'none': None, 'power': const_power_weight}
94
90
 
95
91
 
96
- class BeamformerTime(TimeInOut):
92
+ class BeamformerTime(TimeOut):
97
93
  """Provides a basic time domain beamformer with time signal output
98
94
  for a spatially fixed grid.
99
95
  """
100
96
 
97
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
98
+ source = Instance(SamplesGenerator)
99
+
101
100
  # Instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes
102
101
  # that contains information about the steering vector. This is a private trait.
103
102
  # Do not set this directly, use `steer` trait instead.
104
- _steer_obj = Instance(SteeringVector(), SteeringVector)
105
-
106
- #: :class:`~acoular.fbeamform.SteeringVector` or derived object.
107
- #: Defaults to :class:`~acoular.fbeamform.SteeringVector` object.
108
- steer = Property(desc='steering vector object')
109
-
110
- def _get_steer(self):
111
- return self._steer_obj
112
-
113
- def _set_steer(self, steer):
114
- if type(steer) == SteeringVector:
115
- # This condition may be replaced at a later time by: isinstance(steer, SteeringVector): -- (derived classes allowed)
116
- self._steer_obj = steer
117
- elif steer in ('true level', 'true location', 'classic', 'inverse'):
118
- # Type of steering vectors, see also :ref:`Sarradj, 2012<Sarradj2012>`.
119
- warn(
120
- "Deprecated use of 'steer' trait. Please use object of class 'SteeringVector' in the future.",
121
- Warning,
122
- stacklevel=2,
123
- )
124
- self._steer_obj.steer_type = steer
125
- else:
126
- raise (TraitError(args=self, name='steer', info='SteeringVector', value=steer))
127
-
128
- # --- List of backwards compatibility traits and their setters/getters -----------
129
-
130
- # :class:`~acoular.environments.Environment` or derived object.
131
- # Deprecated! Only kept for backwards compatibility.
132
- # Now governed by :attr:`steer` trait.
133
- env = Property()
134
-
135
- def _get_env(self):
136
- return self._steer_obj.env
137
-
138
- def _set_env(self, env):
139
- warn("Deprecated use of 'env' trait. ", Warning, stacklevel=2)
140
- self._steer_obj.env = env
141
-
142
- # The speed of sound.
143
- # Deprecated! Only kept for backwards compatibility.
144
- # Now governed by :attr:`steer` trait.
145
- c = Property()
146
-
147
- def _get_c(self):
148
- return self._steer_obj.env.c
149
-
150
- def _set_c(self, c):
151
- warn("Deprecated use of 'c' trait. ", Warning, stacklevel=2)
152
- self._steer_obj.env.c = c
153
-
154
- # :class:`~acoular.grids.Grid`-derived object that provides the grid locations.
155
- # Deprecated! Only kept for backwards compatibility.
156
- # Now governed by :attr:`steer` trait.
157
- grid = Property()
158
-
159
- def _get_grid(self):
160
- return self._steer_obj.grid
161
-
162
- def _set_grid(self, grid):
163
- warn("Deprecated use of 'grid' trait. ", Warning, stacklevel=2)
164
- self._steer_obj.grid = grid
165
-
166
- # :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
167
- # Deprecated! Only kept for backwards compatibility.
168
- # Now governed by :attr:`steer` trait
169
- mpos = Property()
170
-
171
- def _get_mpos(self):
172
- return self._steer_obj.mics
173
-
174
- def _set_mpos(self, mpos):
175
- warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2)
176
- self._steer_obj.mics = mpos
177
-
178
- # Sound travel distances from microphone array center to grid points (r0)
179
- # and all array mics to grid points (rm). Readonly.
180
- # Deprecated! Only kept for backwards compatibility.
181
- # Now governed by :attr:`steer` trait
182
- r0 = Property()
183
-
184
- def _get_r0(self):
185
- return self._steer_obj.r0
186
-
187
- rm = Property()
188
-
189
- def _get_rm(self):
190
- return self._steer_obj.rm
191
-
192
- # --- End of backwards compatibility traits --------------------------------------
103
+ steer = Instance(SteeringVector, args=())
193
104
 
194
105
  #: Number of channels in output (=number of grid points).
195
- numchannels = Delegate('grid', 'size')
106
+ num_channels = Property()
196
107
 
197
108
  #: Spatial weighting function.
198
- weights = Trait('none', possible_weights, desc='spatial weighting function')
109
+ weights = Map(possible_weights, default_value='none', desc='spatial weighting function')
199
110
  # (from timedomain.possible_weights)
200
111
 
201
- # buffer with microphone time signals used for processing. Internal use
202
- buffer = CArray(desc='buffer containing microphone signals')
203
-
204
- # index indicating position of current processing sample. Internal use.
205
- bufferIndex = Int(desc='index indicating position in buffer') # noqa: N815
206
-
207
112
  # internal identifier
208
113
  digest = Property(
209
- depends_on=['_steer_obj.digest', 'source.digest', 'weights', '__class__'],
114
+ depends_on=['steer.digest', 'source.digest', 'weights'],
210
115
  )
211
116
 
212
- @cached_property
117
+ def _get_num_channels(self):
118
+ return self.steer.grid.size
119
+
213
120
  def _get_digest(self):
214
121
  return digest(self)
215
122
 
216
123
  def _get_weights(self):
217
124
  return self.weights_(self)[newaxis] if self.weights_ else 1.0
218
125
 
219
- def _fill_buffer(self, num):
220
- """Generator that fills the signal buffer."""
221
- weights = self._get_weights()
222
- for block in self.source.result(num):
223
- block *= weights
224
- ns = block.shape[0]
225
- bufferSize = self.buffer.shape[0]
226
- self.buffer[0 : (bufferSize - ns)] = self.buffer[-(bufferSize - ns) :]
227
- self.buffer[-ns:, :] = block
228
- self.bufferIndex -= ns
229
- yield
230
-
231
126
  def result(self, num=2048):
232
- """Python generator that yields the *squared* deconvolved beamformer
233
- output with optional removal of autocorrelation block-wise.
127
+ """Python generator that yields the time-domain beamformer output.
128
+
129
+ The output time signal starts for source signals that were emitted from
130
+ the :class:`~acoular.grids.Grid` at `t=0`.
234
131
 
235
132
  Parameters
236
133
  ----------
237
- num : integer, defaults to 2048
134
+ num : int
238
135
  This parameter defines the size of the blocks to be yielded
239
- (i.e. the number of samples per block).
240
-
241
- Returns
242
- -------
243
- Samples in blocks of shape \
244
- (num, :attr:`~BeamformerTime.numchannels`).
245
- :attr:`~BeamformerTime.numchannels` is usually very \
246
- large (number of grid points).
247
- The last block may be shorter than num. \
248
- The output starts for signals that were emitted
249
- from the grid at `t=0`.
250
-
136
+ (i.e. the number of samples per block). Defaults to 2048.
137
+
138
+ Yields
139
+ ------
140
+ numpy.ndarray
141
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
142
+ :attr:`~BeamformerTime.num_channels` is usually very \
143
+ large (number of grid points).
144
+ The last block returned by the generator may be shorter than num.
251
145
  """
252
146
  # initialize values
147
+ steer_func = self.steer._steer_funcs_time[self.steer.steer_type]
253
148
  fdtype = float64
254
149
  idtype = int64
255
- numMics = self.steer.mics.num_mics
150
+ num_mics = self.steer.mics.num_mics
256
151
  n_index = arange(0, num + 1)[:, newaxis]
257
152
  c = self.steer.env.c / self.source.sample_freq
258
- amp = empty((1, self.grid.size, numMics), dtype=fdtype)
259
- # delays = empty((1,self.grid.size,numMics),dtype=fdtype)
260
- d_index = empty((1, self.grid.size, numMics), dtype=idtype)
261
- d_interp2 = empty((1, self.grid.size, numMics), dtype=fdtype)
262
- _steer_III(self.rm[newaxis, :, :], self.r0[newaxis, :], amp)
263
- _delays(self.rm[newaxis, :, :], c, d_interp2, d_index)
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)
264
159
  amp.shape = amp.shape[1:]
265
160
  # delays.shape = delays.shape[1:]
266
161
  d_index.shape = d_index.shape[1:]
267
162
  d_interp2.shape = d_interp2.shape[1:]
268
- maxdelay = int((self.rm / c).max()) + 2 + num # +2 because of interpolation
269
- initialNumberOfBlocks = int(ceil(maxdelay / num))
270
- bufferSize = initialNumberOfBlocks * num
271
- self.buffer = zeros((bufferSize, numMics), dtype=float)
272
- self.bufferIndex = bufferSize # indexing current time sample in buffer
273
- fill_buffer_generator = self._fill_buffer(num)
274
- for _ in range(initialNumberOfBlocks):
275
- next(fill_buffer_generator)
163
+ max_sample_delay = int((self.steer.rm / c).max()) + 2
164
+ weights = self._get_weights()
276
165
 
277
- # start processing
278
- flag = True
279
- while flag:
280
- samplesleft = self.buffer.shape[0] - self.bufferIndex
281
- if samplesleft - maxdelay <= 0:
282
- num += samplesleft - maxdelay
283
- maxdelay += samplesleft - maxdelay
166
+ buffer = SamplesBuffer(
167
+ source=self.source,
168
+ length=int(ceil((num + max_sample_delay) / num)) * num,
169
+ result_num=num + max_sample_delay,
170
+ shift_index_by='num',
171
+ dtype=fdtype,
172
+ )
173
+ for p_res in buffer.result(num):
174
+ p_res *= weights
175
+ if p_res.shape[0] < buffer.result_num: # last block shorter
176
+ num = p_res.shape[0] - max_sample_delay
284
177
  n_index = arange(0, num + 1)[:, newaxis]
285
- flag = False
286
178
  # init step
287
- p_res = array(self.buffer[self.bufferIndex : self.bufferIndex + maxdelay, :])
288
- Phi, autopow = self.delay_and_sum(num, p_res, d_interp2, d_index, amp)
179
+ Phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
289
180
  if 'Cleant' not in self.__class__.__name__:
290
181
  if 'Sq' not in self.__class__.__name__:
291
182
  yield Phi[:num]
@@ -294,6 +185,7 @@ class BeamformerTime(TimeInOut):
294
185
  else:
295
186
  yield Phi[:num] ** 2
296
187
  else:
188
+ p_res_copy = p_res.copy()
297
189
  Gamma = zeros(Phi.shape)
298
190
  Gamma_autopow = zeros(Phi.shape)
299
191
  J = 0
@@ -304,13 +196,13 @@ class BeamformerTime(TimeInOut):
304
196
  imax = argmax(powPhi)
305
197
  t_float = d_interp2[imax] + d_index[imax] + n_index
306
198
  t_ind = t_float.astype(int64)
307
- for m in range(numMics):
308
- p_res[t_ind[: num + 1, m], m] -= self.damp * interp(
199
+ for m in range(num_mics):
200
+ p_res_copy[t_ind[: num + 1, m], m] -= self.damp * interp(
309
201
  t_ind[: num + 1, m],
310
202
  t_float[:num, m],
311
- Phi[:num, imax] * self.r0[imax] / self.rm[imax, m],
203
+ Phi[:num, imax] * self.steer.r0[imax] / self.steer.rm[imax, m],
312
204
  )
313
- nextPhi, nextAutopow = self.delay_and_sum(num, p_res, d_interp2, d_index, amp)
205
+ nextPhi, nextAutopow = self._delay_and_sum(num, p_res_copy, d_interp2, d_index, amp)
314
206
  if self.r_diag:
315
207
  pownextPhi = (nextPhi[:num] ** 2 - nextAutopow).sum(0).clip(min=0)
316
208
  else:
@@ -331,14 +223,11 @@ class BeamformerTime(TimeInOut):
331
223
  yield Gamma[:num] ** 2 - (self.damp**2) * Gamma_autopow[:num]
332
224
  else:
333
225
  yield Gamma[:num] ** 2
334
- self.bufferIndex += num
335
- with contextlib.suppress(StopIteration):
336
- next(fill_buffer_generator)
337
226
 
338
- def delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
227
+ def _delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
339
228
  """Standard delay-and-sum method."""
340
- result = empty((num, self.grid.size), dtype=float) # output array
341
- autopow = empty((num, self.grid.size), dtype=float) # output array
229
+ result = empty((num, self.steer.grid.size), dtype=float) # output array
230
+ autopow = empty((num, self.steer.grid.size), dtype=float) # output array
342
231
  _delayandsum4(p_res, d_index, d_interp2, amp, result, autopow)
343
232
  return result, autopow
344
233
 
@@ -354,13 +243,35 @@ class BeamformerTimeSq(BeamformerTime):
354
243
 
355
244
  # internal identifier
356
245
  digest = Property(
357
- depends_on=['_steer_obj.digest', 'source.digest', 'r_diag', 'weights', '__class__'],
246
+ depends_on=['steer.digest', 'source.digest', 'r_diag', 'weights'],
358
247
  )
359
248
 
360
249
  @cached_property
361
250
  def _get_digest(self):
362
251
  return digest(self)
363
252
 
253
+ def result(self, num=2048):
254
+ """Python generator that yields the **squared** time-domain beamformer output.
255
+
256
+ The squared output time signal starts for source signals that were emitted from
257
+ the :class:`~acoular.grids.Grid` at `t=0`.
258
+
259
+ Parameters
260
+ ----------
261
+ num : int
262
+ This parameter defines the size of the blocks to be yielded
263
+ (i.e. the number of samples per block). Defaults to 2048.
264
+
265
+ Yields
266
+ ------
267
+ numpy.ndarray
268
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
269
+ :attr:`~BeamformerTime.num_channels` is usually very \
270
+ large (number of grid points).
271
+ The last block returned by the generator may be shorter than num.
272
+ """
273
+ return super().result(num)
274
+
364
275
 
365
276
  class BeamformerTimeTraj(BeamformerTime):
366
277
  """Provides a basic time domain beamformer with time signal output
@@ -369,7 +280,7 @@ class BeamformerTimeTraj(BeamformerTime):
369
280
 
370
281
  #: :class:`~acoular.trajectory.Trajectory` or derived object.
371
282
  #: Start time is assumed to be the same as for the samples.
372
- trajectory = Trait(Trajectory, desc='trajectory of the grid center')
283
+ trajectory = Instance(Trajectory, desc='trajectory of the grid center')
373
284
 
374
285
  #: Reference vector, perpendicular to the y-axis of moving grid.
375
286
  rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0)), desc='reference vector')
@@ -378,19 +289,18 @@ class BeamformerTimeTraj(BeamformerTime):
378
289
  conv_amp = Bool(False, desc='determines if convective amplification of source is considered')
379
290
 
380
291
  #: Floating point and integer precision
381
- precision = Trait(64, [32, 64], desc='numeric precision')
292
+ precision = Enum(64, 32, desc='numeric precision')
382
293
 
383
294
  # internal identifier
384
295
  digest = Property(
385
296
  depends_on=[
386
- '_steer_obj.digest',
297
+ 'steer.digest',
387
298
  'source.digest',
388
299
  'weights',
389
300
  'precision',
390
301
  'rvec',
391
302
  'conv_amp',
392
303
  'trajectory.digest',
393
- '__class__',
394
304
  ],
395
305
  )
396
306
 
@@ -398,7 +308,7 @@ class BeamformerTimeTraj(BeamformerTime):
398
308
  def _get_digest(self):
399
309
  return digest(self)
400
310
 
401
- def get_moving_gpos(self):
311
+ def _get_moving_gpos(self):
402
312
  """Python generator that yields the moving grid coordinates samplewise."""
403
313
 
404
314
  def cross(a, b):
@@ -408,7 +318,7 @@ class BeamformerTimeTraj(BeamformerTime):
408
318
  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]])
409
319
 
410
320
  start_t = 0.0
411
- gpos = self.grid.pos()
321
+ gpos = self.steer.grid.pos
412
322
  trajg = self.trajectory.traj(start_t, delta_t=1 / self.source.sample_freq)
413
323
  trajg1 = self.trajectory.traj(start_t, delta_t=1 / self.source.sample_freq, der=1)
414
324
  rflag = (self.rvec == 0).all() # flag translation vs. rotation
@@ -430,43 +340,38 @@ class BeamformerTimeTraj(BeamformerTime):
430
340
  # print(loc[:])
431
341
  yield tpos
432
342
 
433
- def get_macostheta(self, g1, tpos, rm):
343
+ def _get_macostheta(self, g1, tpos, rm):
434
344
  vvec = array(g1) # velocity vector
435
345
  ma = norm(vvec) / self.steer.env.c # machnumber
436
346
  fdv = (vvec / sqrt((vvec * vvec).sum()))[:, newaxis] # unit vecor velocity
437
- mpos = self.steer.mics.mpos[:, newaxis, :]
347
+ mpos = self.steer.mics.pos[:, newaxis, :]
438
348
  rmv = tpos[:, :, newaxis] - mpos
439
349
  return (ma * sum(rmv.reshape((3, -1)) * fdv, 0) / rm.reshape(-1)).reshape(rm.shape)
440
350
 
441
351
  def get_r0(self, tpos):
442
352
  if isscalar(self.steer.ref) and self.steer.ref > 0:
443
353
  return self.steer.ref # full((self.steer.grid.size,), self.steer.ref)
444
- return self.env._r(tpos)
445
-
446
- def increase_buffer(self, num):
447
- ar = zeros((num, self.steer.mics.num_mics), dtype=self.buffer.dtype)
448
- self.buffer = concatenate((ar, self.buffer), axis=0)
449
- self.bufferIndex += num
354
+ return self.steer.env._r(tpos)
450
355
 
451
356
  def result(self, num=2048):
452
- """Python generator that yields the deconvolved output block-wise.
357
+ """Python generator that yields the time-domain beamformer output.
358
+
359
+ The output time signal starts for source signals that were emitted from
360
+ the :class:`~acoular.grids.Grid` at `t=0`.
453
361
 
454
362
  Parameters
455
363
  ----------
456
- num : integer, defaults to 2048
364
+ num : int
457
365
  This parameter defines the size of the blocks to be yielded
458
- (i.e. the number of samples per block).
459
-
460
- Returns
461
- -------
462
- Samples in blocks of shape \
463
- (num, :attr:`~BeamformerTime.numchannels`).
464
- :attr:`~BeamformerTime.numchannels` is usually very \
465
- large (number of grid points).
466
- The last block may be shorter than num. \
467
- The output starts for signals that were emitted
468
- from the grid at `t=0`.
469
-
366
+ (i.e. the number of samples per block). Defaults to 2048.
367
+
368
+ Yields
369
+ ------
370
+ numpy.ndarray
371
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
372
+ :attr:`~BeamformerTime.num_channels` is usually very \
373
+ large (number of grid points).
374
+ The last block returned by the generator may be shorter than num.
470
375
  """
471
376
  # initialize values
472
377
  if self.precision == 64:
@@ -477,36 +382,28 @@ class BeamformerTimeTraj(BeamformerTime):
477
382
  idtype = int32
478
383
  w = self._get_weights()
479
384
  c = self.steer.env.c / self.source.sample_freq
480
- numMics = self.steer.mics.num_mics
481
- mpos = self.steer.mics.mpos.astype(fdtype)
482
- m_index = arange(numMics, dtype=idtype)
385
+ num_mics = self.steer.mics.num_mics
386
+ mpos = self.steer.mics.pos.astype(fdtype)
387
+ m_index = arange(num_mics, dtype=idtype)
483
388
  n_index = arange(num, dtype=idtype)[:, newaxis]
484
- blockrm = empty((num, self.grid.size, numMics), dtype=fdtype)
485
- blockrmconv = empty((num, self.grid.size, numMics), dtype=fdtype)
486
- amp = empty((num, self.grid.size, numMics), dtype=fdtype)
487
- # delays = empty((num,self.grid.size,numMics),dtype=fdtype)
488
- d_index = empty((num, self.grid.size, numMics), dtype=idtype)
489
- d_interp2 = empty((num, self.grid.size, numMics), dtype=fdtype)
490
- blockr0 = empty((num, self.grid.size), dtype=fdtype)
491
- self.buffer = zeros((2 * num, numMics), dtype=fdtype)
492
- self.bufferIndex = self.buffer.shape[0]
493
- movgpos = self.get_moving_gpos() # create moving grid pos generator
389
+ blockrm = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
390
+ blockrmconv = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
391
+ amp = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
392
+ # delays = empty((num,self.steer.grid.size,num_mics),dtype=fdtype)
393
+ d_index = empty((num, self.steer.grid.size, num_mics), dtype=idtype)
394
+ d_interp2 = empty((num, self.steer.grid.size, num_mics), dtype=fdtype)
395
+ blockr0 = empty((num, self.steer.grid.size), dtype=fdtype)
396
+ movgpos = self._get_moving_gpos() # create moving grid pos generator
494
397
  movgspeed = self.trajectory.traj(0.0, delta_t=1 / self.source.sample_freq, der=1)
495
- fill_buffer_generator = self._fill_buffer(num)
496
- for _i in range(2):
497
- next(fill_buffer_generator)
398
+ weights = self._get_weights()
498
399
 
499
400
  # preliminary implementation of different steering vectors
500
- steer_func = {
501
- 'classic': _steer_I,
502
- 'inverse': _steer_II,
503
- 'true level': _steer_III,
504
- 'true location': _steer_IV,
505
- }[self.steer.steer_type]
401
+ steer_func = self.steer._steer_funcs_time[self.steer.steer_type]
506
402
 
507
403
  # start processing
508
404
  flag = True
509
- dflag = True # data is available
405
+ buffer = SamplesBuffer(source=self.source, length=num * 2, shift_index_by='num', dtype=fdtype)
406
+ buffered_result = buffer.result(num)
510
407
  while flag:
511
408
  for i in range(num):
512
409
  tpos = next(movgpos).astype(fdtype)
@@ -515,30 +412,27 @@ class BeamformerTimeTraj(BeamformerTime):
515
412
  blockrm[i, :, :] = rm
516
413
  if self.conv_amp:
517
414
  ht = next(movgspeed)
518
- blockrmconv[i, :, :] = rm * (1 - self.get_macostheta(ht, tpos, rm)) ** 2
415
+ blockrmconv[i, :, :] = rm * (1 - self._get_macostheta(ht, tpos, rm)) ** 2
519
416
  if self.conv_amp:
520
417
  steer_func(blockrmconv, blockr0, amp)
521
418
  else:
522
419
  steer_func(blockrm, blockr0, amp)
523
420
  _delays(blockrm, c, d_interp2, d_index)
524
- # _modf(delays, d_interp2, d_index)
525
- maxdelay = (d_index.max((1, 2)) + arange(0, num)).max() + 2 # + because of interpolation
526
- # increase buffer size because of greater delays
527
- while maxdelay > self.buffer.shape[0] and dflag:
528
- self.increase_buffer(num)
529
- try:
530
- next(fill_buffer_generator)
531
- except StopIteration:
532
- dflag = False
533
- samplesleft = self.buffer.shape[0] - self.bufferIndex
534
- # last block may be shorter
535
- if samplesleft - maxdelay <= 0:
536
- num = sum((d_index.max((1, 2)) + 1 + arange(0, num)) < samplesleft)
421
+ max_sample_delay = (d_index.max((1, 2)) + 2).max() # + because of interpolation
422
+ buffer.result_num = num + max_sample_delay
423
+
424
+ try:
425
+ time_block = next(buffered_result)
426
+ time_block *= weights
427
+ except StopIteration:
428
+ break
429
+ if time_block.shape[0] < buffer.result_num: # last block shorter
430
+ num = sum((d_index.max((1, 2)) + 1 + arange(0, num)) < time_block.shape[0])
537
431
  n_index = arange(num, dtype=idtype)[:, newaxis]
538
432
  flag = False
539
433
  # init step
540
- p_res = self.buffer[self.bufferIndex : self.bufferIndex + maxdelay, :].copy()
541
- Phi, autopow = self.delay_and_sum(num, p_res, d_interp2, d_index, amp)
434
+ p_res = time_block.copy()
435
+ Phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
542
436
  if 'Cleant' not in self.__class__.__name__:
543
437
  if 'Sq' not in self.__class__.__name__:
544
438
  yield Phi[:num]
@@ -570,14 +464,14 @@ class BeamformerTimeTraj(BeamformerTime):
570
464
  ind_min = t_float.min(0).astype(idtype)
571
465
  # store time history at max power focus point
572
466
  h = Phi[:num, imax] * blockr0[:num, imax]
573
- for m in range(numMics):
467
+ for m in range(num_mics):
574
468
  # subtract interpolated time history from microphone signals
575
469
  p_res[ind_min[m] : ind_max[m], m] -= self.damp * interp(
576
470
  t_ind[ind_min[m] : ind_max[m]],
577
471
  t_float[:num, m],
578
472
  h / blockrm1[:num, imax, m],
579
473
  )
580
- nextPhi, nextAutopow = self.delay_and_sum(num, p_res, d_interp2, d_index, amp)
474
+ nextPhi, nextAutopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
581
475
  if self.r_diag:
582
476
  pownextPhi = (nextPhi[:num] * nextPhi[:num] - nextAutopow).sum(0).clip(min=0)
583
477
  else:
@@ -598,17 +492,12 @@ class BeamformerTimeTraj(BeamformerTime):
598
492
  yield Gamma[:num] ** 2 - (self.damp**2) * Gamma_autopow[:num]
599
493
  else:
600
494
  yield Gamma[:num] ** 2
601
- self.bufferIndex += num
602
- try:
603
- next(fill_buffer_generator)
604
- except StopIteration:
605
- dflag = False
606
495
 
607
- def delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
496
+ def _delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
608
497
  """Standard delay-and-sum method."""
609
498
  fdtype = float64 if self.precision == 64 else float32
610
- result = empty((num, self.grid.size), dtype=fdtype) # output array
611
- autopow = empty((num, self.grid.size), dtype=fdtype) # output array
499
+ result = empty((num, self.steer.grid.size), dtype=fdtype) # output array
500
+ autopow = empty((num, self.steer.grid.size), dtype=fdtype) # output array
612
501
  _delayandsum5(p_res, d_index, d_interp2, amp, result, autopow)
613
502
  return result, autopow
614
503
 
@@ -622,7 +511,7 @@ class BeamformerTimeSqTraj(BeamformerTimeSq, BeamformerTimeTraj):
622
511
  # internal identifier
623
512
  digest = Property(
624
513
  depends_on=[
625
- '_steer_obj.digest',
514
+ 'steer.digest',
626
515
  'source.digest',
627
516
  'r_diag',
628
517
  'weights',
@@ -630,7 +519,6 @@ class BeamformerTimeSqTraj(BeamformerTimeSq, BeamformerTimeTraj):
630
519
  'rvec',
631
520
  'conv_amp',
632
521
  'trajectory.digest',
633
- '__class__',
634
522
  ],
635
523
  )
636
524
 
@@ -638,12 +526,34 @@ class BeamformerTimeSqTraj(BeamformerTimeSq, BeamformerTimeTraj):
638
526
  def _get_digest(self):
639
527
  return digest(self)
640
528
 
529
+ def result(self, num=2048):
530
+ """Python generator that yields the **squared** time-domain beamformer output.
531
+
532
+ The squared output time signal starts for source signals that were emitted from
533
+ the :class:`~acoular.grids.Grid` at `t=0`.
534
+
535
+ Parameters
536
+ ----------
537
+ num : int
538
+ This parameter defines the size of the blocks to be yielded
539
+ (i.e. the number of samples per block). Defaults to 2048.
540
+
541
+ Yields
542
+ ------
543
+ numpy.ndarray
544
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
545
+ :attr:`~BeamformerTime.num_channels` is usually very \
546
+ large (number of grid points).
547
+ The last block returned by the generator may be shorter than num.
548
+ """
549
+ return super().result(num)
550
+
641
551
 
642
552
  class BeamformerCleant(BeamformerTime):
643
- """CLEANT deconvolution method, see :ref:`Cousson et al., 2019<Cousson2019>`.
553
+ """CLEANT deconvolution method.
644
554
 
645
555
  An implementation of the CLEAN method in time domain. This class can only
646
- be used for static sources.
556
+ be used for static sources. See :cite:`Cousson2019` for details.
647
557
  """
648
558
 
649
559
  #: Boolean flag, always False
@@ -658,20 +568,42 @@ class BeamformerCleant(BeamformerTime):
658
568
 
659
569
  # internal identifier
660
570
  digest = Property(
661
- depends_on=['_steer_obj.digest', 'source.digest', 'weights', '__class__', 'damp', 'n_iter'],
571
+ depends_on=['steer.digest', 'source.digest', 'weights', 'damp', 'n_iter'],
662
572
  )
663
573
 
664
574
  @cached_property
665
575
  def _get_digest(self):
666
576
  return digest(self)
667
577
 
578
+ def result(self, num=2048):
579
+ """Python generator that yields the deconvolved time-domain beamformer output.
580
+
581
+ The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
582
+ `t=0`.
583
+
584
+ Parameters
585
+ ----------
586
+ num : int
587
+ This parameter defines the size of the blocks to be yielded
588
+ (i.e. the number of samples per block). Defaults to 2048.
589
+
590
+ Yields
591
+ ------
592
+ numpy.ndarray
593
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
594
+ :attr:`~BeamformerTime.num_channels` is usually very \
595
+ large (number of grid points).
596
+ The last block returned by the generator may be shorter than num.
597
+ """
598
+ return super().result(num)
599
+
668
600
 
669
601
  class BeamformerCleantSq(BeamformerCleant):
670
- """CLEANT deconvolution method, see :ref:`Cousson et al., 2019<Cousson2019>`
671
- with optional removal of autocorrelation.
602
+ """CLEANT deconvolution method with optional removal of autocorrelation.
672
603
 
673
604
  An implementation of the CLEAN method in time domain. This class can only
674
- be used for static sources.
605
+ be used for static sources. See :cite:`Cousson2019` for details on the method
606
+ and :cite:`Kujawski2020` for details on the autocorrelation removal.
675
607
  """
676
608
 
677
609
  #: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
@@ -679,32 +611,54 @@ class BeamformerCleantSq(BeamformerCleant):
679
611
 
680
612
  # internal identifier
681
613
  digest = Property(
682
- depends_on=['_steer_obj.digest', 'source.digest', 'weights', '__class__', 'damp', 'n_iter', 'r_diag'],
614
+ depends_on=['steer.digest', 'source.digest', 'weights', 'damp', 'n_iter', 'r_diag'],
683
615
  )
684
616
 
685
617
  @cached_property
686
618
  def _get_digest(self):
687
619
  return digest(self)
688
620
 
621
+ def result(self, num=2048):
622
+ """Python generator that yields the *squared* deconvolved time-domain beamformer output.
623
+
624
+ The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
625
+ `t=0`. Per default, block-wise removal of autocorrelation is performed, which can be turned
626
+ of by setting :attr:`r_diag` to `False`.
627
+
628
+ Parameters
629
+ ----------
630
+ num : int
631
+ This parameter defines the size of the blocks to be yielded
632
+ (i.e. the number of samples per block). Defaults to 2048.
633
+
634
+ Yields
635
+ ------
636
+ numpy.ndarray
637
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
638
+ :attr:`~BeamformerTime.num_channels` is usually very \
639
+ large (number of grid points).
640
+ The last block returned by the generator may be shorter than num.
641
+ """
642
+ return super().result(num)
643
+
689
644
 
690
645
  class BeamformerCleantTraj(BeamformerCleant, BeamformerTimeTraj):
691
- """CLEANT deconvolution method, see :ref:`Cousson et al., 2019<Cousson2019>`.
646
+ """CLEANT deconvolution method.
692
647
 
693
648
  An implementation of the CLEAN method in time domain for moving sources
694
- with known trajectory.
649
+ with known trajectory. See :cite:`Cousson2019` for details.
695
650
  """
696
651
 
697
652
  #: Floating point and integer precision
698
- precision = Trait(32, [32, 64], desc='numeric precision')
653
+ precision = Enum(32, 64, desc='numeric precision')
699
654
 
700
655
  # internal identifier
701
656
  digest = Property(
702
657
  depends_on=[
703
- '_steer_obj.digest',
658
+ 'steer.digest',
704
659
  'source.digest',
705
660
  'weights',
706
661
  'precision',
707
- '__class__',
708
662
  'damp',
709
663
  'n_iter',
710
664
  'rvec',
@@ -717,13 +671,35 @@ class BeamformerCleantTraj(BeamformerCleant, BeamformerTimeTraj):
717
671
  def _get_digest(self):
718
672
  return digest(self)
719
673
 
674
+ def result(self, num=2048):
675
+ """Python generator that yields the deconvolved time-domain beamformer output.
676
+
677
+ The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
678
+ `t=0`.
679
+
680
+ Parameters
681
+ ----------
682
+ num : int
683
+ This parameter defines the size of the blocks to be yielded
684
+ (i.e. the number of samples per block). Defaults to 2048.
685
+
686
+ Yields
687
+ ------
688
+ numpy.ndarray
689
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
690
+ :attr:`~BeamformerTime.num_channels` is usually very \
691
+ large (number of grid points).
692
+ The last block returned by the generator may be shorter than num.
693
+ """
694
+ return super().result(num)
695
+
720
696
 
721
697
  class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
722
- """CLEANT deconvolution method, see :ref:`Cousson et al., 2019<Cousson2019>`
723
- with optional removal of autocorrelation.
698
+ """CLEANT deconvolution method with optional removal of autocorrelation.
724
699
 
725
700
  An implementation of the CLEAN method in time domain for moving sources
726
- with known trajectory.
701
+ with known trajectory. See :cite:`Cousson2019` for details on the method and
702
+ :cite:`Kujawski2020` for details on the autocorrelation removal.
727
703
  """
728
704
 
729
705
  #: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
@@ -732,11 +708,10 @@ class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
732
708
  # internal identifier
733
709
  digest = Property(
734
710
  depends_on=[
735
- '_steer_obj.digest',
711
+ 'steer.digest',
736
712
  'source.digest',
737
713
  'weights',
738
714
  'precision',
739
- '__class__',
740
715
  'damp',
741
716
  'n_iter',
742
717
  'rvec',
@@ -750,25 +725,51 @@ class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
750
725
  def _get_digest(self):
751
726
  return digest(self)
752
727
 
728
+ def result(self, num=2048):
729
+ """Python generator that yields the *squared* deconvolved time-domain beamformer output.
730
+
731
+ The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
732
+ `t=0`. Per default, block-wise removal of autocorrelation is performed, which can be turned
733
+ of by setting :attr:`r_diag` to `False`.
734
+
735
+ Parameters
736
+ ----------
737
+ num : int
738
+ This parameter defines the size of the blocks to be yielded
739
+ (i.e. the number of samples per block). Defaults to 2048.
740
+
741
+ Yields
742
+ ------
743
+ numpy.ndarray
744
+ Samples in blocks of shape (num, :attr:`~BeamformerTime.num_channels`).
745
+ :attr:`~BeamformerTime.num_channels` is usually very \
746
+ large (number of grid points).
747
+ The last block returned by the generator may be shorter than num.
748
+ """
749
+ return super().result(num)
750
+
753
751
 
754
- class IntegratorSectorTime(TimeInOut):
752
+ class IntegratorSectorTime(TimeOut):
755
753
  """Provides an Integrator in the time domain."""
756
754
 
755
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
756
+ source = Instance(SamplesGenerator)
757
+
757
758
  #: :class:`~acoular.grids.RectGrid` object that provides the grid locations.
758
- grid = Trait(RectGrid, desc='beamforming grid')
759
+ grid = Instance(RectGrid, desc='beamforming grid')
759
760
 
760
761
  #: List of sectors in grid
761
762
  sectors = List()
762
763
 
763
- #: Clipping, in Dezibel relative to maximum (negative values)
764
+ #: Clipping, in Decibel relative to maximum (negative values)
764
765
  clip = Float(-350.0)
765
766
 
766
767
  #: Number of channels in output (= number of sectors).
767
- numchannels = Property(depends_on=['sectors'])
768
+ num_channels = Property(depends_on=['sectors'])
768
769
 
769
770
  # internal identifier
770
771
  digest = Property(
771
- depends_on=['sectors', 'clip', 'grid.digest', 'source.digest', '__class__'],
772
+ depends_on=['sectors', 'clip', 'grid.digest', 'source.digest'],
772
773
  )
773
774
 
774
775
  @cached_property
@@ -776,7 +777,7 @@ class IntegratorSectorTime(TimeInOut):
776
777
  return digest(self)
777
778
 
778
779
  @cached_property
779
- def _get_numchannels(self):
780
+ def _get_num_channels(self):
780
781
  return len(self.sectors)
781
782
 
782
783
  def result(self, num=1):
@@ -791,14 +792,13 @@ class IntegratorSectorTime(TimeInOut):
791
792
 
792
793
  Returns
793
794
  -------
794
- Samples in blocks of shape (num, :attr:`numchannels`).
795
- :attr:`numchannels` is the number of sectors.
795
+ Samples in blocks of shape (num, :attr:`num_channels`).
796
+ :attr:`num_channels` is the number of sectors.
796
797
  The last block may be shorter than num.
797
-
798
798
  """
799
799
  inds = [self.grid.indices(*sector) for sector in self.sectors]
800
800
  gshape = self.grid.shape
801
- o = empty((num, self.numchannels), dtype=float) # output array
801
+ o = empty((num, self.num_channels), dtype=float) # output array
802
802
  for r in self.source.result(num):
803
803
  ns = r.shape[0]
804
804
  mapshape = (ns,) + gshape