acoular 25.4__py3-none-any.whl → 25.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acoular/__init__.py +2 -4
- acoular/aiaa/aiaa.py +10 -10
- acoular/base.py +12 -34
- acoular/calib.py +20 -19
- acoular/configuration.py +3 -3
- acoular/demo/__init__.py +6 -1
- acoular/demo/acoular_demo.py +34 -10
- acoular/deprecation.py +10 -1
- acoular/environments.py +107 -117
- acoular/fastFuncs.py +16 -10
- acoular/fbeamform.py +300 -402
- acoular/fprocess.py +7 -33
- acoular/grids.py +228 -134
- acoular/h5cache.py +10 -4
- acoular/h5files.py +106 -9
- acoular/internal.py +4 -0
- acoular/microphones.py +22 -10
- acoular/process.py +7 -53
- acoular/sdinput.py +8 -5
- acoular/signals.py +29 -27
- acoular/sources.py +205 -335
- acoular/spectra.py +33 -43
- acoular/tbeamform.py +220 -199
- acoular/tools/helpers.py +52 -33
- acoular/tools/metrics.py +5 -10
- acoular/tprocess.py +1392 -647
- acoular/traitsviews.py +1 -3
- acoular/trajectory.py +5 -5
- acoular/version.py +4 -3
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/METADATA +8 -4
- acoular-25.10.dist-info/RECORD +56 -0
- acoular-25.4.dist-info/RECORD +0 -56
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/WHEEL +0 -0
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/licenses/LICENSE +0 -0
acoular/tbeamform.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# ------------------------------------------------------------------------------
|
|
2
2
|
# Copyright (c) Acoular Development Team.
|
|
3
3
|
# ------------------------------------------------------------------------------
|
|
4
|
-
"""
|
|
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
|
-
|
|
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
|
-
"""
|
|
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,7 +74,9 @@ possible_weights = {'none': None, 'power': const_power_weight}
|
|
|
90
74
|
|
|
91
75
|
|
|
92
76
|
class BeamformerTime(TimeOut):
|
|
93
|
-
"""
|
|
77
|
+
"""
|
|
78
|
+
Provides a basic time domain beamformer with time signal output.
|
|
79
|
+
|
|
94
80
|
for a spatially fixed grid.
|
|
95
81
|
"""
|
|
96
82
|
|
|
@@ -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
|
-
"""
|
|
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
|
|
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,
|
|
@@ -174,68 +161,73 @@ class BeamformerTime(TimeOut):
|
|
|
174
161
|
p_res *= weights
|
|
175
162
|
if p_res.shape[0] < buffer.result_num: # last block shorter
|
|
176
163
|
num = p_res.shape[0] - max_sample_delay
|
|
177
|
-
|
|
164
|
+
# exit loop if there is not enough data left to be processed
|
|
165
|
+
if num <= 0:
|
|
166
|
+
break
|
|
167
|
+
n_index = np.arange(0, num + 1)[:, np.newaxis]
|
|
178
168
|
# init step
|
|
179
|
-
|
|
169
|
+
phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
|
|
180
170
|
if 'Cleant' not in self.__class__.__name__:
|
|
181
171
|
if 'Sq' not in self.__class__.__name__:
|
|
182
|
-
yield
|
|
172
|
+
yield phi[:num]
|
|
183
173
|
elif self.r_diag:
|
|
184
|
-
yield (
|
|
174
|
+
yield (phi[:num] ** 2 - autopow[:num]).clip(min=0)
|
|
185
175
|
else:
|
|
186
|
-
yield
|
|
176
|
+
yield phi[:num] ** 2
|
|
187
177
|
else:
|
|
188
178
|
p_res_copy = p_res.copy()
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
179
|
+
gamma = np.zeros(phi.shape)
|
|
180
|
+
gamma_autopow = np.zeros(phi.shape)
|
|
181
|
+
j = 0
|
|
192
182
|
# deconvolution
|
|
193
|
-
while self.n_iter >
|
|
194
|
-
# print(f"start clean iteration {
|
|
195
|
-
|
|
196
|
-
imax = argmax(
|
|
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)
|
|
197
187
|
t_float = d_interp2[imax] + d_index[imax] + n_index
|
|
198
|
-
t_ind = t_float.astype(int64)
|
|
188
|
+
t_ind = t_float.astype(np.int64)
|
|
199
189
|
for m in range(num_mics):
|
|
200
|
-
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(
|
|
201
191
|
t_ind[: num + 1, m],
|
|
202
192
|
t_float[:num, m],
|
|
203
|
-
|
|
193
|
+
phi[:num, imax] * self.steer.r0[imax] / self.steer.rm[imax, m],
|
|
204
194
|
)
|
|
205
|
-
|
|
195
|
+
next_phi, next_autopow = self._delay_and_sum(num, p_res_copy, d_interp2, d_index, amp)
|
|
206
196
|
if self.r_diag:
|
|
207
|
-
|
|
197
|
+
pow_next_phi = (next_phi[:num] ** 2 - next_autopow).sum(0).clip(min=0)
|
|
208
198
|
else:
|
|
209
|
-
|
|
210
|
-
# print(f"total signal power: {
|
|
211
|
-
if
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
autopow =
|
|
216
|
-
# print(f"clean max: {L_p((
|
|
217
|
-
|
|
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
|
|
218
208
|
else:
|
|
219
209
|
break
|
|
220
210
|
if 'Sq' not in self.__class__.__name__:
|
|
221
|
-
yield
|
|
211
|
+
yield gamma[:num]
|
|
222
212
|
elif self.r_diag:
|
|
223
|
-
yield
|
|
213
|
+
yield gamma[:num] ** 2 - (self.damp**2) * gamma_autopow[:num]
|
|
224
214
|
else:
|
|
225
|
-
yield
|
|
215
|
+
yield gamma[:num] ** 2
|
|
226
216
|
|
|
227
217
|
def _delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
|
|
228
218
|
"""Standard delay-and-sum method."""
|
|
229
|
-
result = empty((num, self.steer.grid.size), dtype=float) # output array
|
|
230
|
-
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
|
|
231
221
|
_delayandsum4(p_res, d_index, d_interp2, amp, result, autopow)
|
|
232
222
|
return result, autopow
|
|
233
223
|
|
|
234
224
|
|
|
235
225
|
class BeamformerTimeSq(BeamformerTime):
|
|
236
|
-
"""
|
|
237
|
-
|
|
238
|
-
|
|
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.
|
|
239
231
|
"""
|
|
240
232
|
|
|
241
233
|
#: Boolean flag, if 'True' (default), the main diagonal is removed before beamforming.
|
|
@@ -251,30 +243,33 @@ class BeamformerTimeSq(BeamformerTime):
|
|
|
251
243
|
return digest(self)
|
|
252
244
|
|
|
253
245
|
def result(self, num=2048):
|
|
254
|
-
"""
|
|
246
|
+
"""
|
|
247
|
+
Python generator that yields the **squared** time-domain beamformer output.
|
|
255
248
|
|
|
256
249
|
The squared output time signal starts for source signals that were emitted from
|
|
257
250
|
the :class:`~acoular.grids.Grid` at `t=0`.
|
|
258
251
|
|
|
259
252
|
Parameters
|
|
260
253
|
----------
|
|
261
|
-
num : int
|
|
254
|
+
num : :class:`int`
|
|
262
255
|
This parameter defines the size of the blocks to be yielded
|
|
263
256
|
(i.e. the number of samples per block). Defaults to 2048.
|
|
264
257
|
|
|
265
258
|
Yields
|
|
266
259
|
------
|
|
267
|
-
numpy.ndarray
|
|
268
|
-
Samples in blocks of shape (num
|
|
260
|
+
:class:`numpy.ndarray`
|
|
261
|
+
Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
|
|
269
262
|
:attr:`~BeamformerTime.num_channels` is usually very \
|
|
270
263
|
large (number of grid points).
|
|
271
|
-
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``.
|
|
272
265
|
"""
|
|
273
266
|
return super().result(num)
|
|
274
267
|
|
|
275
268
|
|
|
276
269
|
class BeamformerTimeTraj(BeamformerTime):
|
|
277
|
-
"""
|
|
270
|
+
"""
|
|
271
|
+
Provides a basic time domain beamformer with time signal output.
|
|
272
|
+
|
|
278
273
|
for a grid moving along a trajectory.
|
|
279
274
|
"""
|
|
280
275
|
|
|
@@ -283,7 +278,7 @@ class BeamformerTimeTraj(BeamformerTime):
|
|
|
283
278
|
trajectory = Instance(Trajectory, desc='trajectory of the grid center')
|
|
284
279
|
|
|
285
280
|
#: Reference vector, perpendicular to the y-axis of moving grid.
|
|
286
|
-
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)), desc='reference vector')
|
|
287
282
|
|
|
288
283
|
#: Considering of convective amplification in beamforming formula.
|
|
289
284
|
conv_amp = Bool(False, desc='determines if convective amplification of source is considered')
|
|
@@ -312,10 +307,12 @@ class BeamformerTimeTraj(BeamformerTime):
|
|
|
312
307
|
"""Python generator that yields the moving grid coordinates samplewise."""
|
|
313
308
|
|
|
314
309
|
def cross(a, b):
|
|
315
|
-
"""
|
|
310
|
+
"""
|
|
311
|
+
Cross product for fast computation.
|
|
312
|
+
|
|
316
313
|
because numpy.cross is ultra slow in this case.
|
|
317
314
|
"""
|
|
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]])
|
|
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]])
|
|
319
316
|
|
|
320
317
|
start_t = 0.0
|
|
321
318
|
gpos = self.steer.grid.pos
|
|
@@ -325,74 +322,87 @@ class BeamformerTimeTraj(BeamformerTime):
|
|
|
325
322
|
if rflag:
|
|
326
323
|
for g in trajg:
|
|
327
324
|
# grid is only translated, not rotated
|
|
328
|
-
tpos = gpos + array(g)[:, newaxis]
|
|
325
|
+
tpos = gpos + np.array(g)[:, np.newaxis]
|
|
329
326
|
yield tpos
|
|
330
327
|
else:
|
|
331
328
|
for g, g1 in zip(trajg, trajg1):
|
|
332
329
|
# grid is both translated and rotated
|
|
333
|
-
loc = array(g) # translation array([0., 0.4, 1.])
|
|
334
|
-
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)
|
|
335
332
|
dy = cross(self.rvec, dx) # new y-axis
|
|
336
333
|
dz = cross(dx, dy) # new z-axis
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
tpos = dot(
|
|
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
|
|
340
337
|
# print(loc[:])
|
|
341
338
|
yield tpos
|
|
342
339
|
|
|
343
340
|
def _get_macostheta(self, g1, tpos, rm):
|
|
344
|
-
vvec = array(g1) # velocity vector
|
|
345
|
-
ma = norm(vvec) / self.steer.env.c # machnumber
|
|
346
|
-
fdv = (vvec / sqrt((vvec * vvec).sum()))[:, newaxis] # unit vecor velocity
|
|
347
|
-
mpos = self.steer.mics.pos[:, newaxis, :]
|
|
348
|
-
rmv = tpos[:, :, newaxis] - mpos
|
|
349
|
-
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)
|
|
350
347
|
|
|
351
348
|
def get_r0(self, tpos):
|
|
352
|
-
|
|
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:
|
|
353
363
|
return self.steer.ref # full((self.steer.grid.size,), self.steer.ref)
|
|
354
364
|
return self.steer.env._r(tpos)
|
|
355
365
|
|
|
356
366
|
def result(self, num=2048):
|
|
357
|
-
"""
|
|
367
|
+
"""
|
|
368
|
+
Python generator that yields the time-domain beamformer output.
|
|
358
369
|
|
|
359
370
|
The output time signal starts for source signals that were emitted from
|
|
360
371
|
the :class:`~acoular.grids.Grid` at `t=0`.
|
|
361
372
|
|
|
362
373
|
Parameters
|
|
363
374
|
----------
|
|
364
|
-
num : int
|
|
375
|
+
num : :class:`int`
|
|
365
376
|
This parameter defines the size of the blocks to be yielded
|
|
366
377
|
(i.e. the number of samples per block). Defaults to 2048.
|
|
367
378
|
|
|
368
379
|
Yields
|
|
369
380
|
------
|
|
370
|
-
numpy.ndarray
|
|
371
|
-
Samples in blocks of shape (num
|
|
381
|
+
:class:`numpy.ndarray`
|
|
382
|
+
Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
|
|
372
383
|
:attr:`~BeamformerTime.num_channels` is usually very \
|
|
373
384
|
large (number of grid points).
|
|
374
|
-
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``.
|
|
375
386
|
"""
|
|
376
387
|
# initialize values
|
|
377
388
|
if self.precision == 64:
|
|
378
|
-
fdtype = float64
|
|
379
|
-
idtype = int64
|
|
389
|
+
fdtype = np.float64
|
|
390
|
+
idtype = np.int64
|
|
380
391
|
else:
|
|
381
|
-
fdtype = float32
|
|
382
|
-
idtype = int32
|
|
383
|
-
w = self._get_weights()
|
|
392
|
+
fdtype = np.float32
|
|
393
|
+
idtype = np.int32
|
|
384
394
|
c = self.steer.env.c / self.source.sample_freq
|
|
385
395
|
num_mics = self.steer.mics.num_mics
|
|
386
396
|
mpos = self.steer.mics.pos.astype(fdtype)
|
|
387
|
-
m_index = arange(num_mics, dtype=idtype)
|
|
388
|
-
n_index = arange(num, dtype=idtype)[:, newaxis]
|
|
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)
|
|
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)
|
|
396
406
|
movgpos = self._get_moving_gpos() # create moving grid pos generator
|
|
397
407
|
movgspeed = self.trajectory.traj(0.0, delta_t=1 / self.source.sample_freq, der=1)
|
|
398
408
|
weights = self._get_weights()
|
|
@@ -427,35 +437,35 @@ class BeamformerTimeTraj(BeamformerTime):
|
|
|
427
437
|
except StopIteration:
|
|
428
438
|
break
|
|
429
439
|
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])
|
|
431
|
-
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]
|
|
432
442
|
flag = False
|
|
433
443
|
# init step
|
|
434
444
|
p_res = time_block.copy()
|
|
435
|
-
|
|
445
|
+
phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
|
|
436
446
|
if 'Cleant' not in self.__class__.__name__:
|
|
437
447
|
if 'Sq' not in self.__class__.__name__:
|
|
438
|
-
yield
|
|
448
|
+
yield phi[:num]
|
|
439
449
|
elif self.r_diag:
|
|
440
|
-
yield (
|
|
450
|
+
yield (phi[:num] ** 2 - autopow[:num]).clip(min=0)
|
|
441
451
|
else:
|
|
442
|
-
yield
|
|
452
|
+
yield phi[:num] ** 2
|
|
443
453
|
else:
|
|
444
454
|
# choose correct distance
|
|
445
455
|
blockrm1 = blockrmconv if self.conv_amp else blockrm
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
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)
|
|
450
460
|
# deconvolution
|
|
451
|
-
while self.n_iter >
|
|
452
|
-
# print(f"start clean iteration {
|
|
461
|
+
while self.n_iter > j:
|
|
462
|
+
# print(f"start clean iteration {j+1} of max {self.n_iter}")
|
|
453
463
|
if self.r_diag:
|
|
454
|
-
|
|
464
|
+
pow_phi = (phi[:num] * phi[:num] - autopow).sum(0).clip(min=0)
|
|
455
465
|
else:
|
|
456
|
-
|
|
466
|
+
pow_phi = (phi[:num] * phi[:num]).sum(0)
|
|
457
467
|
# find index of max power focus point
|
|
458
|
-
imax = argmax(
|
|
468
|
+
imax = np.argmax(pow_phi)
|
|
459
469
|
# find backward delays
|
|
460
470
|
t_float = (d_interp2[:num, imax, m_index] + d_index[:num, imax, m_index] + n_index).astype(fdtype)
|
|
461
471
|
# determine max/min delays in sample units
|
|
@@ -463,49 +473,51 @@ class BeamformerTimeTraj(BeamformerTime):
|
|
|
463
473
|
ind_max = t_float.max(0).astype(idtype) + 2
|
|
464
474
|
ind_min = t_float.min(0).astype(idtype)
|
|
465
475
|
# store time history at max power focus point
|
|
466
|
-
h =
|
|
476
|
+
h = phi[:num, imax] * blockr0[:num, imax]
|
|
467
477
|
for m in range(num_mics):
|
|
468
478
|
# subtract interpolated time history from microphone signals
|
|
469
|
-
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(
|
|
470
480
|
t_ind[ind_min[m] : ind_max[m]],
|
|
471
481
|
t_float[:num, m],
|
|
472
482
|
h / blockrm1[:num, imax, m],
|
|
473
483
|
)
|
|
474
|
-
|
|
484
|
+
next_phi, next_autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
|
|
475
485
|
if self.r_diag:
|
|
476
|
-
|
|
486
|
+
pow_next_phi = (next_phi[:num] * next_phi[:num] - next_autopow).sum(0).clip(min=0)
|
|
477
487
|
else:
|
|
478
|
-
|
|
479
|
-
# print(f"total signal power: {
|
|
480
|
-
if
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
autopow =
|
|
485
|
-
# print(f"clean max: {L_p((
|
|
486
|
-
|
|
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
|
|
487
497
|
else:
|
|
488
498
|
break
|
|
489
499
|
if 'Sq' not in self.__class__.__name__:
|
|
490
|
-
yield
|
|
500
|
+
yield gamma[:num]
|
|
491
501
|
elif self.r_diag:
|
|
492
|
-
yield
|
|
502
|
+
yield gamma[:num] ** 2 - (self.damp**2) * gamma_autopow[:num]
|
|
493
503
|
else:
|
|
494
|
-
yield
|
|
504
|
+
yield gamma[:num] ** 2
|
|
495
505
|
|
|
496
506
|
def _delay_and_sum(self, num, p_res, d_interp2, d_index, amp):
|
|
497
507
|
"""Standard delay-and-sum method."""
|
|
498
|
-
fdtype = float64 if self.precision == 64 else float32
|
|
499
|
-
result = empty((num, self.steer.grid.size), dtype=fdtype) # output array
|
|
500
|
-
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
|
|
501
511
|
_delayandsum5(p_res, d_index, d_interp2, amp, result, autopow)
|
|
502
512
|
return result, autopow
|
|
503
513
|
|
|
504
514
|
|
|
505
515
|
class BeamformerTimeSqTraj(BeamformerTimeSq, BeamformerTimeTraj):
|
|
506
|
-
"""
|
|
507
|
-
|
|
508
|
-
|
|
516
|
+
"""
|
|
517
|
+
Time domain beamformer with squared output for a grid moving along a trajectory.
|
|
518
|
+
|
|
519
|
+
Provides a time domain beamformer with time-dependent power signal output and possible autopower
|
|
520
|
+
removal for a grid moving along a trajectory.
|
|
509
521
|
"""
|
|
510
522
|
|
|
511
523
|
# internal identifier
|
|
@@ -527,30 +539,32 @@ class BeamformerTimeSqTraj(BeamformerTimeSq, BeamformerTimeTraj):
|
|
|
527
539
|
return digest(self)
|
|
528
540
|
|
|
529
541
|
def result(self, num=2048):
|
|
530
|
-
"""
|
|
542
|
+
"""
|
|
543
|
+
Python generator that yields the **squared** time-domain beamformer output.
|
|
531
544
|
|
|
532
545
|
The squared output time signal starts for source signals that were emitted from
|
|
533
546
|
the :class:`~acoular.grids.Grid` at `t=0`.
|
|
534
547
|
|
|
535
548
|
Parameters
|
|
536
549
|
----------
|
|
537
|
-
num : int
|
|
550
|
+
num : :class:`int`
|
|
538
551
|
This parameter defines the size of the blocks to be yielded
|
|
539
552
|
(i.e. the number of samples per block). Defaults to 2048.
|
|
540
553
|
|
|
541
554
|
Yields
|
|
542
555
|
------
|
|
543
|
-
numpy.ndarray
|
|
544
|
-
Samples in blocks of shape (num
|
|
556
|
+
:class:`numpy.ndarray`
|
|
557
|
+
Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
|
|
545
558
|
:attr:`~BeamformerTime.num_channels` is usually very \
|
|
546
559
|
large (number of grid points).
|
|
547
|
-
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``.
|
|
548
561
|
"""
|
|
549
562
|
return super().result(num)
|
|
550
563
|
|
|
551
564
|
|
|
552
565
|
class BeamformerCleant(BeamformerTime):
|
|
553
|
-
"""
|
|
566
|
+
"""
|
|
567
|
+
CLEANT deconvolution method.
|
|
554
568
|
|
|
555
569
|
An implementation of the CLEAN method in time domain. This class can only
|
|
556
570
|
be used for static sources. See :cite:`Cousson2019` for details.
|
|
@@ -576,30 +590,32 @@ class BeamformerCleant(BeamformerTime):
|
|
|
576
590
|
return digest(self)
|
|
577
591
|
|
|
578
592
|
def result(self, num=2048):
|
|
579
|
-
"""
|
|
593
|
+
"""
|
|
594
|
+
Python generator that yields the deconvolved time-domain beamformer output.
|
|
580
595
|
|
|
581
596
|
The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
|
|
582
597
|
`t=0`.
|
|
583
598
|
|
|
584
599
|
Parameters
|
|
585
600
|
----------
|
|
586
|
-
num : int
|
|
601
|
+
num : :class:`int`
|
|
587
602
|
This parameter defines the size of the blocks to be yielded
|
|
588
603
|
(i.e. the number of samples per block). Defaults to 2048.
|
|
589
604
|
|
|
590
605
|
Yields
|
|
591
606
|
------
|
|
592
|
-
numpy.ndarray
|
|
593
|
-
Samples in blocks of shape (num
|
|
607
|
+
:class:`numpy.ndarray`
|
|
608
|
+
Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
|
|
594
609
|
:attr:`~BeamformerTime.num_channels` is usually very \
|
|
595
610
|
large (number of grid points).
|
|
596
|
-
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``.
|
|
597
612
|
"""
|
|
598
613
|
return super().result(num)
|
|
599
614
|
|
|
600
615
|
|
|
601
616
|
class BeamformerCleantSq(BeamformerCleant):
|
|
602
|
-
"""
|
|
617
|
+
"""
|
|
618
|
+
CLEANT deconvolution method with optional removal of autocorrelation.
|
|
603
619
|
|
|
604
620
|
An implementation of the CLEAN method in time domain. This class can only
|
|
605
621
|
be used for static sources. See :cite:`Cousson2019` for details on the method
|
|
@@ -619,7 +635,8 @@ class BeamformerCleantSq(BeamformerCleant):
|
|
|
619
635
|
return digest(self)
|
|
620
636
|
|
|
621
637
|
def result(self, num=2048):
|
|
622
|
-
"""
|
|
638
|
+
"""
|
|
639
|
+
Python generator that yields the *squared* deconvolved time-domain beamformer output.
|
|
623
640
|
|
|
624
641
|
The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
|
|
625
642
|
`t=0`. Per default, block-wise removal of autocorrelation is performed, which can be turned
|
|
@@ -627,23 +644,24 @@ class BeamformerCleantSq(BeamformerCleant):
|
|
|
627
644
|
|
|
628
645
|
Parameters
|
|
629
646
|
----------
|
|
630
|
-
num : int
|
|
647
|
+
num : :class:`int`
|
|
631
648
|
This parameter defines the size of the blocks to be yielded
|
|
632
649
|
(i.e. the number of samples per block). Defaults to 2048.
|
|
633
650
|
|
|
634
651
|
Yields
|
|
635
652
|
------
|
|
636
|
-
numpy.ndarray
|
|
637
|
-
Samples in blocks of shape (num
|
|
653
|
+
:class:`numpy.ndarray`
|
|
654
|
+
Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
|
|
638
655
|
:attr:`~BeamformerTime.num_channels` is usually very \
|
|
639
656
|
large (number of grid points).
|
|
640
|
-
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``.
|
|
641
658
|
"""
|
|
642
659
|
return super().result(num)
|
|
643
660
|
|
|
644
661
|
|
|
645
662
|
class BeamformerCleantTraj(BeamformerCleant, BeamformerTimeTraj):
|
|
646
|
-
"""
|
|
663
|
+
"""
|
|
664
|
+
CLEANT deconvolution method.
|
|
647
665
|
|
|
648
666
|
An implementation of the CLEAN method in time domain for moving sources
|
|
649
667
|
with known trajectory. See :cite:`Cousson2019` for details.
|
|
@@ -672,30 +690,32 @@ class BeamformerCleantTraj(BeamformerCleant, BeamformerTimeTraj):
|
|
|
672
690
|
return digest(self)
|
|
673
691
|
|
|
674
692
|
def result(self, num=2048):
|
|
675
|
-
"""
|
|
693
|
+
"""
|
|
694
|
+
Python generator that yields the deconvolved time-domain beamformer output.
|
|
676
695
|
|
|
677
696
|
The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
|
|
678
697
|
`t=0`.
|
|
679
698
|
|
|
680
699
|
Parameters
|
|
681
700
|
----------
|
|
682
|
-
num : int
|
|
701
|
+
num : :class:`int`
|
|
683
702
|
This parameter defines the size of the blocks to be yielded
|
|
684
703
|
(i.e. the number of samples per block). Defaults to 2048.
|
|
685
704
|
|
|
686
705
|
Yields
|
|
687
706
|
------
|
|
688
|
-
numpy.ndarray
|
|
689
|
-
Samples in blocks of shape (num
|
|
707
|
+
:class:`numpy.ndarray`
|
|
708
|
+
Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
|
|
690
709
|
:attr:`~BeamformerTime.num_channels` is usually very \
|
|
691
710
|
large (number of grid points).
|
|
692
|
-
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``.
|
|
693
712
|
"""
|
|
694
713
|
return super().result(num)
|
|
695
714
|
|
|
696
715
|
|
|
697
716
|
class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
|
|
698
|
-
"""
|
|
717
|
+
"""
|
|
718
|
+
CLEANT deconvolution method with optional removal of autocorrelation.
|
|
699
719
|
|
|
700
720
|
An implementation of the CLEAN method in time domain for moving sources
|
|
701
721
|
with known trajectory. See :cite:`Cousson2019` for details on the method and
|
|
@@ -726,7 +746,8 @@ class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
|
|
|
726
746
|
return digest(self)
|
|
727
747
|
|
|
728
748
|
def result(self, num=2048):
|
|
729
|
-
"""
|
|
749
|
+
"""
|
|
750
|
+
Python generator that yields the *squared* deconvolved time-domain beamformer output.
|
|
730
751
|
|
|
731
752
|
The output starts for signals that were emitted from the :class:`~acoular.grids.Grid` at
|
|
732
753
|
`t=0`. Per default, block-wise removal of autocorrelation is performed, which can be turned
|
|
@@ -734,17 +755,17 @@ class BeamformerCleantSqTraj(BeamformerCleantTraj, BeamformerTimeSq):
|
|
|
734
755
|
|
|
735
756
|
Parameters
|
|
736
757
|
----------
|
|
737
|
-
num : int
|
|
758
|
+
num : :class:`int`
|
|
738
759
|
This parameter defines the size of the blocks to be yielded
|
|
739
760
|
(i.e. the number of samples per block). Defaults to 2048.
|
|
740
761
|
|
|
741
762
|
Yields
|
|
742
763
|
------
|
|
743
|
-
numpy.ndarray
|
|
744
|
-
Samples in blocks of shape (num
|
|
764
|
+
:class:`numpy.ndarray`
|
|
765
|
+
Samples in blocks of shape (``num``, :attr:`~BeamformerTime.num_channels`).
|
|
745
766
|
:attr:`~BeamformerTime.num_channels` is usually very \
|
|
746
767
|
large (number of grid points).
|
|
747
|
-
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``.
|
|
748
769
|
"""
|
|
749
770
|
return super().result(num)
|
|
750
771
|
|
|
@@ -781,32 +802,32 @@ class IntegratorSectorTime(TimeOut):
|
|
|
781
802
|
return len(self.sectors)
|
|
782
803
|
|
|
783
804
|
def result(self, num=1):
|
|
784
|
-
"""
|
|
785
|
-
sectors
|
|
805
|
+
"""
|
|
806
|
+
Python generator that yields the source output integrated over specified grid sectors.
|
|
786
807
|
|
|
787
808
|
Parameters
|
|
788
809
|
----------
|
|
789
|
-
num :
|
|
790
|
-
|
|
791
|
-
(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``.
|
|
792
812
|
|
|
793
813
|
Returns
|
|
794
814
|
-------
|
|
795
|
-
|
|
815
|
+
:class:`numpy.ndarray`
|
|
816
|
+
Samples in blocks of shape (``num``, :attr:`num_channels`).
|
|
796
817
|
:attr:`num_channels` is the number of sectors.
|
|
797
818
|
The last block may be shorter than num.
|
|
798
819
|
"""
|
|
799
820
|
inds = [self.grid.indices(*sector) for sector in self.sectors]
|
|
800
821
|
gshape = self.grid.shape
|
|
801
|
-
o = empty((num, self.num_channels), dtype=float) # output array
|
|
822
|
+
o = np.empty((num, self.num_channels), dtype=float) # output array
|
|
802
823
|
for r in self.source.result(num):
|
|
803
824
|
ns = r.shape[0]
|
|
804
825
|
mapshape = (ns,) + gshape
|
|
805
826
|
rmax = r.max()
|
|
806
827
|
rmin = rmax * 10 ** (self.clip / 10.0)
|
|
807
|
-
r = where(r > rmin, r, 0.0)
|
|
828
|
+
r = np.where(r > rmin, r, 0.0)
|
|
808
829
|
for i, ind in enumerate(inds):
|
|
809
|
-
h = r[:].reshape(mapshape)[(s_[:],) + ind]
|
|
830
|
+
h = r[:].reshape(mapshape)[(np.s_[:],) + ind]
|
|
810
831
|
o[:ns, i] = h.reshape(h.shape[0], -1).sum(axis=1)
|
|
811
832
|
i += 1
|
|
812
833
|
yield o[:ns]
|