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/__init__.py +21 -9
- acoular/aiaa/__init__.py +12 -0
- acoular/{tools → aiaa}/aiaa.py +26 -31
- acoular/base.py +332 -0
- acoular/calib.py +129 -34
- acoular/configuration.py +13 -11
- acoular/demo/__init__.py +1 -0
- acoular/demo/acoular_demo.py +30 -17
- acoular/deprecation.py +85 -0
- acoular/environments.py +38 -24
- acoular/fastFuncs.py +90 -84
- acoular/fbeamform.py +342 -387
- acoular/fprocess.py +376 -0
- acoular/grids.py +122 -150
- acoular/h5cache.py +29 -40
- acoular/h5files.py +2 -6
- acoular/microphones.py +50 -59
- acoular/process.py +771 -0
- acoular/sdinput.py +35 -21
- acoular/signals.py +120 -113
- acoular/sources.py +208 -234
- acoular/spectra.py +59 -254
- acoular/tbeamform.py +280 -280
- acoular/tfastfuncs.py +21 -21
- acoular/tools/__init__.py +3 -7
- acoular/tools/helpers.py +218 -4
- acoular/tools/metrics.py +5 -5
- acoular/tools/utils.py +116 -0
- acoular/tprocess.py +416 -741
- acoular/traitsviews.py +15 -13
- acoular/trajectory.py +7 -10
- acoular/version.py +2 -2
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/METADATA +63 -21
- acoular-25.1.dist-info/RECORD +56 -0
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/WHEEL +1 -1
- acoular-24.7.dist-info/RECORD +0 -50
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.7.dist-info → acoular-25.1.dist-info}/licenses/LICENSE +0 -0
acoular/sources.py
CHANGED
|
@@ -51,13 +51,13 @@ from numpy import (
|
|
|
51
51
|
)
|
|
52
52
|
from numpy import min as npmin
|
|
53
53
|
from numpy.fft import fft, ifft
|
|
54
|
-
from
|
|
54
|
+
from scipy.linalg import norm
|
|
55
55
|
from scipy.special import sph_harm, spherical_jn, spherical_yn
|
|
56
56
|
from traits.api import (
|
|
57
57
|
Any,
|
|
58
58
|
Bool,
|
|
59
59
|
CArray,
|
|
60
|
-
|
|
60
|
+
CInt,
|
|
61
61
|
Delegate,
|
|
62
62
|
Dict,
|
|
63
63
|
Enum,
|
|
@@ -66,32 +66,35 @@ from traits.api import (
|
|
|
66
66
|
Instance,
|
|
67
67
|
Int,
|
|
68
68
|
List,
|
|
69
|
-
ListInt,
|
|
70
69
|
Property,
|
|
71
70
|
Str,
|
|
72
|
-
Trait,
|
|
73
71
|
Tuple,
|
|
72
|
+
Union,
|
|
74
73
|
cached_property,
|
|
75
74
|
observe,
|
|
76
75
|
on_trait_change,
|
|
77
76
|
)
|
|
78
77
|
|
|
78
|
+
from .base import SamplesGenerator
|
|
79
|
+
|
|
79
80
|
# acoular imports
|
|
80
81
|
from .calib import Calib
|
|
82
|
+
from .deprecation import deprecated_alias
|
|
81
83
|
from .environments import Environment
|
|
82
84
|
from .h5files import H5FileBase, _get_h5file_class
|
|
83
85
|
from .internal import digest, ldigest
|
|
84
86
|
from .microphones import MicGeom
|
|
85
|
-
from .signals import SignalGenerator
|
|
86
|
-
from .
|
|
87
|
+
from .signals import NoiseGenerator, SignalGenerator
|
|
88
|
+
from .tools.utils import get_file_basename
|
|
89
|
+
from .tprocess import TimeConvolve
|
|
87
90
|
from .trajectory import Trajectory
|
|
88
91
|
|
|
89
92
|
|
|
90
|
-
@nb.njit(cache=True, error_model='numpy') #
|
|
91
|
-
def _fill_mic_signal_block(out, signal, rm, ind, blocksize,
|
|
93
|
+
@nb.njit(cache=True, error_model='numpy') # pragma: no cover
|
|
94
|
+
def _fill_mic_signal_block(out, signal, rm, ind, blocksize, num_channels, up, prepadding):
|
|
92
95
|
if prepadding:
|
|
93
96
|
for b in range(blocksize):
|
|
94
|
-
for m in range(
|
|
97
|
+
for m in range(num_channels):
|
|
95
98
|
if ind[0, m] < 0:
|
|
96
99
|
out[b, m] = 0
|
|
97
100
|
else:
|
|
@@ -99,7 +102,7 @@ def _fill_mic_signal_block(out, signal, rm, ind, blocksize, numchannels, up, pre
|
|
|
99
102
|
ind += up
|
|
100
103
|
else:
|
|
101
104
|
for b in range(blocksize):
|
|
102
|
-
for m in range(
|
|
105
|
+
for m in range(num_channels):
|
|
103
106
|
out[b, m] = signal[int(0.5 + ind[0, m])] / rm[0, m]
|
|
104
107
|
ind += up
|
|
105
108
|
return out
|
|
@@ -180,11 +183,12 @@ def get_modes(lOrder, direction, mpos, sourceposition=None): # noqa: N803
|
|
|
180
183
|
return modes
|
|
181
184
|
|
|
182
185
|
|
|
186
|
+
@deprecated_alias({'name': 'file'})
|
|
183
187
|
class TimeSamples(SamplesGenerator):
|
|
184
188
|
"""Container for processing time data in `*.h5` or NumPy array format.
|
|
185
189
|
|
|
186
|
-
This class loads measured data from HDF5 files and provides information about this data.
|
|
187
|
-
|
|
190
|
+
This class loads measured data from HDF5 files and provides information about this data. It also
|
|
191
|
+
serves as an interface where the data can be accessed (e.g. for use in a block chain) via the
|
|
188
192
|
:meth:`result` generator.
|
|
189
193
|
|
|
190
194
|
Examples
|
|
@@ -192,9 +196,9 @@ class TimeSamples(SamplesGenerator):
|
|
|
192
196
|
Data can be loaded from a HDF5 file as follows:
|
|
193
197
|
|
|
194
198
|
>>> from acoular import TimeSamples
|
|
195
|
-
>>>
|
|
196
|
-
>>> ts = TimeSamples(
|
|
197
|
-
>>> print(f'number of channels: {ts.
|
|
199
|
+
>>> file = <some_h5_file.h5> # doctest: +SKIP
|
|
200
|
+
>>> ts = TimeSamples(file=file) # doctest: +SKIP
|
|
201
|
+
>>> print(f'number of channels: {ts.num_channels}') # doctest: +SKIP
|
|
198
202
|
number of channels: 56 # doctest: +SKIP
|
|
199
203
|
|
|
200
204
|
Alternatively, the time data can be specified directly as a numpy array.
|
|
@@ -204,8 +208,9 @@ class TimeSamples(SamplesGenerator):
|
|
|
204
208
|
>>> data = np.random.rand(1000, 4)
|
|
205
209
|
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
206
210
|
|
|
207
|
-
Chunks of the time data can be accessed iteratively via the :meth:`result` generator.
|
|
208
|
-
|
|
211
|
+
Chunks of the time data can be accessed iteratively via the :meth:`result` generator. The last
|
|
212
|
+
block will be shorter than the block size if the number of samples is not a multiple of the
|
|
213
|
+
block size.
|
|
209
214
|
|
|
210
215
|
>>> blocksize = 512
|
|
211
216
|
>>> generator = ts.result(num=blocksize)
|
|
@@ -216,30 +221,27 @@ class TimeSamples(SamplesGenerator):
|
|
|
216
221
|
|
|
217
222
|
See Also
|
|
218
223
|
--------
|
|
219
|
-
acoular.sources.MaskedTimeSamples
|
|
220
|
-
Extends the functionality of class :class:`TimeSamples` by enabling the definition of start
|
|
221
|
-
as well as the specification of invalid channels.
|
|
224
|
+
acoular.sources.MaskedTimeSamples:
|
|
225
|
+
Extends the functionality of class :class:`TimeSamples` by enabling the definition of start
|
|
226
|
+
and stop samples as well as the specification of invalid channels.
|
|
222
227
|
"""
|
|
223
228
|
|
|
224
229
|
#: Full name of the .h5 file with data.
|
|
225
|
-
|
|
230
|
+
file = File(filter=['*.h5'], exists=True, desc='name of data file')
|
|
226
231
|
|
|
227
232
|
#: Basename of the .h5 file with data, is set automatically.
|
|
228
|
-
basename = Property(
|
|
229
|
-
depends_on='name', # filter=['*.h5'],
|
|
230
|
-
desc='basename of data file',
|
|
231
|
-
)
|
|
233
|
+
basename = Property(depends_on=['file'], desc='basename of data file')
|
|
232
234
|
|
|
233
235
|
#: Calibration data, instance of :class:`~acoular.calib.Calib` class, optional .
|
|
234
|
-
calib =
|
|
236
|
+
calib = Instance(Calib, desc='Calibration data')
|
|
235
237
|
|
|
236
238
|
#: Number of channels, is set automatically / read from file.
|
|
237
|
-
|
|
239
|
+
num_channels = CInt(0, desc='number of input channels')
|
|
238
240
|
|
|
239
241
|
#: Number of time data samples, is set automatically / read from file.
|
|
240
|
-
|
|
242
|
+
num_samples = CInt(0, desc='number of samples')
|
|
241
243
|
|
|
242
|
-
#: The time data as array of floats with dimension (
|
|
244
|
+
#: The time data as array of floats with dimension (num_samples, num_channels).
|
|
243
245
|
data = Any(transient=True, desc='the actual time data array')
|
|
244
246
|
|
|
245
247
|
#: HDF5 file object
|
|
@@ -252,7 +254,9 @@ class TimeSamples(SamplesGenerator):
|
|
|
252
254
|
_datachecksum = Property()
|
|
253
255
|
|
|
254
256
|
# internal identifier
|
|
255
|
-
digest = Property(
|
|
257
|
+
digest = Property(
|
|
258
|
+
depends_on=['basename', 'calib.digest', '_datachecksum', 'sample_freq', 'num_channels', 'num_samples']
|
|
259
|
+
)
|
|
256
260
|
|
|
257
261
|
def _get__datachecksum(self):
|
|
258
262
|
return self.data[0, :].sum()
|
|
@@ -263,27 +267,24 @@ class TimeSamples(SamplesGenerator):
|
|
|
263
267
|
|
|
264
268
|
@cached_property
|
|
265
269
|
def _get_basename(self):
|
|
266
|
-
return
|
|
270
|
+
return get_file_basename(self.file)
|
|
267
271
|
|
|
268
272
|
@on_trait_change('basename')
|
|
269
273
|
def _load_data(self):
|
|
270
274
|
"""Open the .h5 file and set attributes."""
|
|
271
|
-
if not path.isfile(self.name):
|
|
272
|
-
self.sample_freq = 0
|
|
273
|
-
raise OSError('No such file: %s' % self.name)
|
|
274
275
|
if self.h5f is not None:
|
|
275
276
|
with contextlib.suppress(OSError):
|
|
276
277
|
self.h5f.close()
|
|
277
278
|
file = _get_h5file_class()
|
|
278
|
-
self.h5f = file(self.
|
|
279
|
+
self.h5f = file(self.file)
|
|
279
280
|
self._load_timedata()
|
|
280
281
|
self._load_metadata()
|
|
281
282
|
|
|
282
283
|
@on_trait_change('data')
|
|
283
284
|
def _load_shapes(self):
|
|
284
|
-
"""Set
|
|
285
|
+
"""Set num_channels and num_samples from data."""
|
|
285
286
|
if self.data is not None:
|
|
286
|
-
self.
|
|
287
|
+
self.num_samples, self.num_channels = self.data.shape
|
|
287
288
|
|
|
288
289
|
def _load_timedata(self):
|
|
289
290
|
"""Loads timedata from .h5 file. Only for internal use."""
|
|
@@ -312,46 +313,61 @@ class TimeSamples(SamplesGenerator):
|
|
|
312
313
|
Yields
|
|
313
314
|
------
|
|
314
315
|
numpy.ndarray
|
|
315
|
-
Samples in blocks of shape (num,
|
|
316
|
+
Samples in blocks of shape (num, num_channels).
|
|
316
317
|
The last block may be shorter than num.
|
|
317
318
|
|
|
318
319
|
"""
|
|
319
|
-
if self.
|
|
320
|
+
if self.num_samples == 0:
|
|
320
321
|
msg = 'no samples available'
|
|
321
322
|
raise OSError(msg)
|
|
322
323
|
self._datachecksum # trigger checksum calculation # noqa: B018
|
|
323
324
|
i = 0
|
|
324
325
|
if self.calib:
|
|
325
|
-
|
|
326
|
+
warn(
|
|
327
|
+
'The use of the calibration functionality in TimeSamples is deprecated and will be removed in \
|
|
328
|
+
Acoular 25.10. Use the Calib class as an additional processing block instead.',
|
|
329
|
+
DeprecationWarning,
|
|
330
|
+
stacklevel=2,
|
|
331
|
+
)
|
|
332
|
+
if self.calib.num_mics == self.num_channels:
|
|
326
333
|
cal_factor = self.calib.data[newaxis]
|
|
327
334
|
else:
|
|
328
|
-
|
|
329
|
-
|
|
335
|
+
msg = f'calibration data not compatible: {self.calib.num_mics:d}, {self.num_channels:d}'
|
|
336
|
+
raise ValueError(msg)
|
|
337
|
+
while i < self.num_samples:
|
|
330
338
|
yield self.data[i : i + num] * cal_factor
|
|
331
339
|
i += num
|
|
332
340
|
else:
|
|
333
|
-
while i < self.
|
|
341
|
+
while i < self.num_samples:
|
|
334
342
|
yield self.data[i : i + num]
|
|
335
343
|
i += num
|
|
336
344
|
|
|
337
345
|
|
|
346
|
+
@deprecated_alias(
|
|
347
|
+
{
|
|
348
|
+
'numchannels_total': 'num_channels_total',
|
|
349
|
+
'numsamples_total': 'num_samples_total',
|
|
350
|
+
'numchannels': 'num_channels',
|
|
351
|
+
'numsamples': 'num_samples',
|
|
352
|
+
},
|
|
353
|
+
read_only=['numchannels', 'numsamples'],
|
|
354
|
+
)
|
|
338
355
|
class MaskedTimeSamples(TimeSamples):
|
|
339
356
|
"""Container for processing time data in `*.h5` or NumPy array format.
|
|
340
357
|
|
|
341
|
-
This class loads measured data from HDF5 files and provides information about this data.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
:meth:`result` generator.
|
|
358
|
+
This class loads measured data from HDF5 files and provides information about this data. It
|
|
359
|
+
supports storing information about (in)valid samples and (in)valid channels and allows to
|
|
360
|
+
specify a start and stop index for the valid samples. It also serves as an interface where the
|
|
361
|
+
data can be accessed (e.g. for use in a block chain) via the :meth:`result` generator.
|
|
346
362
|
|
|
347
363
|
Examples
|
|
348
364
|
--------
|
|
349
365
|
Data can be loaded from a HDF5 file and invalid channels can be specified as follows:
|
|
350
366
|
|
|
351
367
|
>>> from acoular import MaskedTimeSamples
|
|
352
|
-
>>>
|
|
353
|
-
>>> ts = MaskedTimeSamples(
|
|
354
|
-
>>> print(f'number of valid channels: {ts.
|
|
368
|
+
>>> file = <some_h5_file.h5> # doctest: +SKIP
|
|
369
|
+
>>> ts = MaskedTimeSamples(file=file, invalid_channels=[0, 1]) # doctest: +SKIP
|
|
370
|
+
>>> print(f'number of valid channels: {ts.num_channels}') # doctest: +SKIP
|
|
355
371
|
number of valid channels: 54 # doctest: +SKIP
|
|
356
372
|
|
|
357
373
|
Alternatively, the time data can be specified directly as a numpy array.
|
|
@@ -364,8 +380,8 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
364
380
|
|
|
365
381
|
Chunks of the time data can be accessed iteratively via the :meth:`result` generator:
|
|
366
382
|
|
|
367
|
-
>>>
|
|
368
|
-
>>> generator = ts.result(num=
|
|
383
|
+
>>> block_size = 512
|
|
384
|
+
>>> generator = ts.result(num=block_size)
|
|
369
385
|
>>> for block in generator:
|
|
370
386
|
... print(block.shape)
|
|
371
387
|
(512, 4)
|
|
@@ -373,28 +389,32 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
373
389
|
"""
|
|
374
390
|
|
|
375
391
|
#: Index of the first sample to be considered valid.
|
|
376
|
-
start =
|
|
392
|
+
start = CInt(0, desc='start of valid samples')
|
|
377
393
|
|
|
378
394
|
#: Index of the last sample to be considered valid.
|
|
379
|
-
stop =
|
|
395
|
+
stop = Union(None, CInt, desc='stop of valid samples')
|
|
380
396
|
|
|
381
397
|
#: Channels that are to be treated as invalid.
|
|
382
|
-
invalid_channels =
|
|
398
|
+
invalid_channels = List(int, desc='list of invalid channels')
|
|
383
399
|
|
|
384
400
|
#: Channel mask to serve as an index for all valid channels, is set automatically.
|
|
385
|
-
channels = Property(depends_on=['invalid_channels', '
|
|
401
|
+
channels = Property(depends_on=['invalid_channels', 'num_channels_total'], desc='channel mask')
|
|
386
402
|
|
|
387
403
|
#: Number of channels (including invalid channels), is set automatically.
|
|
388
|
-
|
|
404
|
+
num_channels_total = CInt(0, desc='total number of input channels')
|
|
389
405
|
|
|
390
406
|
#: Number of time data samples (including invalid samples), is set automatically.
|
|
391
|
-
|
|
407
|
+
num_samples_total = CInt(0, desc='total number of samples per channel')
|
|
392
408
|
|
|
393
409
|
#: Number of valid channels, is set automatically.
|
|
394
|
-
|
|
410
|
+
num_channels = Property(
|
|
411
|
+
depends_on=['invalid_channels', 'num_channels_total'], desc='number of valid input channels'
|
|
412
|
+
)
|
|
395
413
|
|
|
396
414
|
#: Number of valid time data samples, is set automatically.
|
|
397
|
-
|
|
415
|
+
num_samples = Property(
|
|
416
|
+
depends_on=['start', 'stop', 'num_samples_total'], desc='number of valid samples per channel'
|
|
417
|
+
)
|
|
398
418
|
|
|
399
419
|
# internal identifier
|
|
400
420
|
digest = Property(depends_on=['basename', 'start', 'stop', 'calib.digest', 'invalid_channels', '_datachecksum'])
|
|
@@ -403,55 +423,52 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
403
423
|
def _get_digest(self):
|
|
404
424
|
return digest(self)
|
|
405
425
|
|
|
406
|
-
@cached_property
|
|
407
|
-
def _get_basename(self):
|
|
408
|
-
return path.splitext(path.basename(self.name))[0]
|
|
409
|
-
|
|
410
426
|
@cached_property
|
|
411
427
|
def _get_channels(self):
|
|
412
428
|
if len(self.invalid_channels) == 0:
|
|
413
429
|
return slice(0, None, None)
|
|
414
|
-
allr = [i for i in range(self.
|
|
430
|
+
allr = [i for i in range(self.num_channels_total) if i not in self.invalid_channels]
|
|
415
431
|
return array(allr)
|
|
416
432
|
|
|
417
433
|
@cached_property
|
|
418
|
-
def
|
|
434
|
+
def _get_num_channels(self):
|
|
419
435
|
if len(self.invalid_channels) == 0:
|
|
420
|
-
return self.
|
|
436
|
+
return self.num_channels_total
|
|
421
437
|
return len(self.channels)
|
|
422
438
|
|
|
423
439
|
@cached_property
|
|
424
|
-
def
|
|
425
|
-
sli = slice(self.start, self.stop).indices(self.
|
|
440
|
+
def _get_num_samples(self):
|
|
441
|
+
sli = slice(self.start, self.stop).indices(self.num_samples_total)
|
|
426
442
|
return sli[1] - sli[0]
|
|
427
443
|
|
|
428
444
|
@on_trait_change('basename')
|
|
429
445
|
def _load_data(self):
|
|
430
446
|
# """ open the .h5 file and set attributes
|
|
431
447
|
# """
|
|
432
|
-
if not path.isfile(self.
|
|
448
|
+
if not path.isfile(self.file):
|
|
433
449
|
# no file there
|
|
434
450
|
self.sample_freq = 0
|
|
435
|
-
|
|
451
|
+
msg = f'No such file: {self.file}'
|
|
452
|
+
raise OSError(msg)
|
|
436
453
|
if self.h5f is not None:
|
|
437
454
|
with contextlib.suppress(OSError):
|
|
438
455
|
self.h5f.close()
|
|
439
456
|
file = _get_h5file_class()
|
|
440
|
-
self.h5f = file(self.
|
|
457
|
+
self.h5f = file(self.file)
|
|
441
458
|
self._load_timedata()
|
|
442
459
|
self._load_metadata()
|
|
443
460
|
|
|
444
461
|
@on_trait_change('data')
|
|
445
462
|
def _load_shapes(self):
|
|
446
|
-
"""Set
|
|
463
|
+
"""Set num_channels and num_samples from data."""
|
|
447
464
|
if self.data is not None:
|
|
448
|
-
self.
|
|
465
|
+
self.num_samples_total, self.num_channels_total = self.data.shape
|
|
449
466
|
|
|
450
467
|
def _load_timedata(self):
|
|
451
468
|
"""Loads timedata from .h5 file. Only for internal use."""
|
|
452
469
|
self.data = self.h5f.get_data_by_reference('time_data')
|
|
453
470
|
self.sample_freq = self.h5f.get_node_attribute(self.data, 'sample_freq')
|
|
454
|
-
(self.
|
|
471
|
+
(self.num_samples_total, self.num_channels_total) = self.data.shape
|
|
455
472
|
|
|
456
473
|
def result(self, num=128):
|
|
457
474
|
"""Python generator that yields the output block-wise.
|
|
@@ -469,11 +486,11 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
469
486
|
Yields
|
|
470
487
|
------
|
|
471
488
|
numpy.ndarray
|
|
472
|
-
Samples in blocks of shape (num,
|
|
489
|
+
Samples in blocks of shape (num, num_channels).
|
|
473
490
|
The last block may be shorter than num.
|
|
474
491
|
|
|
475
492
|
"""
|
|
476
|
-
sli = slice(self.start, self.stop).indices(self.
|
|
493
|
+
sli = slice(self.start, self.stop).indices(self.num_samples_total)
|
|
477
494
|
i = sli[0]
|
|
478
495
|
stop = sli[1]
|
|
479
496
|
cal_factor = 1.0
|
|
@@ -482,19 +499,27 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
482
499
|
raise OSError(msg)
|
|
483
500
|
self._datachecksum # trigger checksum calculation # noqa: B018
|
|
484
501
|
if self.calib:
|
|
485
|
-
|
|
502
|
+
warn(
|
|
503
|
+
'The use of the calibration functionality in MaskedTimeSamples is deprecated and will be removed in \
|
|
504
|
+
Acoular 25.10. Use the Calib class as an additional processing block instead.',
|
|
505
|
+
DeprecationWarning,
|
|
506
|
+
stacklevel=2,
|
|
507
|
+
)
|
|
508
|
+
if self.calib.num_mics == self.num_channels_total:
|
|
486
509
|
cal_factor = self.calib.data[self.channels][newaxis]
|
|
487
|
-
elif self.calib.num_mics == self.
|
|
510
|
+
elif self.calib.num_mics == self.num_channels:
|
|
488
511
|
cal_factor = self.calib.data[newaxis]
|
|
489
512
|
elif self.calib.num_mics == 0:
|
|
490
513
|
warn('No calibration data used.', Warning, stacklevel=2)
|
|
491
514
|
else:
|
|
492
|
-
|
|
515
|
+
msg = f'calibration data not compatible: {self.calib.num_mics:d}, {self.num_channels:d}'
|
|
516
|
+
raise ValueError(msg)
|
|
493
517
|
while i < stop:
|
|
494
518
|
yield self.data[i : min(i + num, stop)][:, self.channels] * cal_factor
|
|
495
519
|
i += num
|
|
496
520
|
|
|
497
521
|
|
|
522
|
+
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True)
|
|
498
523
|
class PointSource(SamplesGenerator):
|
|
499
524
|
"""Class to define a fixed point source with an arbitrary signal.
|
|
500
525
|
This can be used in simulations.
|
|
@@ -503,58 +528,31 @@ class PointSource(SamplesGenerator):
|
|
|
503
528
|
"""
|
|
504
529
|
|
|
505
530
|
#: Emitted signal, instance of the :class:`~acoular.signals.SignalGenerator` class.
|
|
506
|
-
signal =
|
|
531
|
+
signal = Instance(SignalGenerator)
|
|
507
532
|
|
|
508
533
|
#: Location of source in (`x`, `y`, `z`) coordinates (left-oriented system).
|
|
509
534
|
loc = Tuple((0.0, 0.0, 1.0), desc='source location')
|
|
510
535
|
|
|
511
536
|
#: Number of channels in output, is set automatically /
|
|
512
537
|
#: depends on used microphone geometry.
|
|
513
|
-
|
|
538
|
+
num_channels = Delegate('mics', 'num_mics')
|
|
514
539
|
|
|
515
540
|
#: :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
516
|
-
mics =
|
|
541
|
+
mics = Instance(MicGeom, desc='microphone geometry')
|
|
517
542
|
|
|
518
543
|
def _validate_locations(self):
|
|
519
|
-
dist = self.env._r(array(self.loc).reshape((3, 1)), self.mics.
|
|
544
|
+
dist = self.env._r(array(self.loc).reshape((3, 1)), self.mics.pos)
|
|
520
545
|
if npany(dist < 1e-7):
|
|
521
546
|
warn('Source and microphone locations are identical.', Warning, stacklevel=2)
|
|
522
547
|
|
|
523
548
|
#: :class:`~acoular.environments.Environment` or derived object,
|
|
524
549
|
#: which provides information about the sound propagation in the medium.
|
|
525
|
-
env =
|
|
526
|
-
|
|
527
|
-
# --- List of backwards compatibility traits and their setters/getters -----------
|
|
528
|
-
|
|
529
|
-
# Microphone locations.
|
|
530
|
-
# Deprecated! Use :attr:`mics` trait instead.
|
|
531
|
-
mpos = Property()
|
|
532
|
-
|
|
533
|
-
def _get_mpos(self):
|
|
534
|
-
return self.mics
|
|
535
|
-
|
|
536
|
-
def _set_mpos(self, mpos):
|
|
537
|
-
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2)
|
|
538
|
-
self.mics = mpos
|
|
539
|
-
|
|
540
|
-
# The speed of sound.
|
|
541
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
542
|
-
# Now governed by :attr:`env` trait.
|
|
543
|
-
c = Property()
|
|
544
|
-
|
|
545
|
-
def _get_c(self):
|
|
546
|
-
return self.env.c
|
|
547
|
-
|
|
548
|
-
def _set_c(self, c):
|
|
549
|
-
warn("Deprecated use of 'c' trait. ", Warning, stacklevel=2)
|
|
550
|
-
self.env.c = c
|
|
551
|
-
|
|
552
|
-
# --- End of backwards compatibility traits --------------------------------------
|
|
550
|
+
env = Instance(Environment(), Environment)
|
|
553
551
|
|
|
554
552
|
#: Start time of the signal in seconds, defaults to 0 s.
|
|
555
553
|
start_t = Float(0.0, desc='signal start time')
|
|
556
554
|
|
|
557
|
-
#: Start time of the data
|
|
555
|
+
#: Start time of the data acquisition at microphones in seconds,
|
|
558
556
|
#: defaults to 0 s.
|
|
559
557
|
start = Float(0.0, desc='sample start time')
|
|
560
558
|
|
|
@@ -562,14 +560,14 @@ class PointSource(SamplesGenerator):
|
|
|
562
560
|
#: `loop` take values from the end of :attr:`signal.signal()` array.
|
|
563
561
|
#: `zeros` set source signal to zero, advisable for deterministic signals.
|
|
564
562
|
#: defaults to `loop`.
|
|
565
|
-
prepadding =
|
|
563
|
+
prepadding = Enum('loop', 'zeros', desc='Behaviour for negative time indices.')
|
|
566
564
|
|
|
567
565
|
#: Upsampling factor, internal use, defaults to 16.
|
|
568
566
|
up = Int(16, desc='upsampling factor')
|
|
569
567
|
|
|
570
568
|
#: Number of samples, is set automatically /
|
|
571
569
|
#: depends on :attr:`signal`.
|
|
572
|
-
|
|
570
|
+
num_samples = Delegate('signal')
|
|
573
571
|
|
|
574
572
|
#: Sampling frequency of the signal, is set automatically /
|
|
575
573
|
#: depends on :attr:`signal`.
|
|
@@ -586,7 +584,6 @@ class PointSource(SamplesGenerator):
|
|
|
586
584
|
'start',
|
|
587
585
|
'up',
|
|
588
586
|
'prepadding',
|
|
589
|
-
'__class__',
|
|
590
587
|
],
|
|
591
588
|
)
|
|
592
589
|
|
|
@@ -605,16 +602,16 @@ class PointSource(SamplesGenerator):
|
|
|
605
602
|
|
|
606
603
|
Returns
|
|
607
604
|
-------
|
|
608
|
-
Samples in blocks of shape (num,
|
|
605
|
+
Samples in blocks of shape (num, num_channels).
|
|
609
606
|
The last block may be shorter than num.
|
|
610
607
|
|
|
611
608
|
"""
|
|
612
609
|
self._validate_locations()
|
|
613
|
-
N = int(ceil(self.
|
|
610
|
+
N = int(ceil(self.num_samples / num)) # number of output blocks
|
|
614
611
|
signal = self.signal.usignal(self.up)
|
|
615
|
-
out = empty((num, self.
|
|
612
|
+
out = empty((num, self.num_channels))
|
|
616
613
|
# distances
|
|
617
|
-
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.
|
|
614
|
+
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.pos).reshape(1, -1)
|
|
618
615
|
# emission time relative to start_t (in samples) for first sample
|
|
619
616
|
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq * self.up
|
|
620
617
|
|
|
@@ -625,16 +622,16 @@ class PointSource(SamplesGenerator):
|
|
|
625
622
|
# if signal stops during prepadding, terminate
|
|
626
623
|
if pre >= N:
|
|
627
624
|
for _nb in range(N - 1):
|
|
628
|
-
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.
|
|
625
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, True)
|
|
629
626
|
yield out
|
|
630
627
|
|
|
631
|
-
blocksize = self.
|
|
632
|
-
out = _fill_mic_signal_block(out, signal, rm, ind, blocksize, self.
|
|
628
|
+
blocksize = self.num_samples % num or num
|
|
629
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, blocksize, self.num_channels, self.up, True)
|
|
633
630
|
yield out[:blocksize]
|
|
634
631
|
return
|
|
635
632
|
else:
|
|
636
633
|
for _nb in range(pre):
|
|
637
|
-
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.
|
|
634
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, True)
|
|
638
635
|
yield out
|
|
639
636
|
|
|
640
637
|
else:
|
|
@@ -642,12 +639,12 @@ class PointSource(SamplesGenerator):
|
|
|
642
639
|
|
|
643
640
|
# main generator
|
|
644
641
|
for _nb in range(N - pre - 1):
|
|
645
|
-
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.
|
|
642
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, False)
|
|
646
643
|
yield out
|
|
647
644
|
|
|
648
645
|
# last block of variable size
|
|
649
|
-
blocksize = self.
|
|
650
|
-
out = _fill_mic_signal_block(out, signal, rm, ind, blocksize, self.
|
|
646
|
+
blocksize = self.num_samples % num or num
|
|
647
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, blocksize, self.num_channels, self.up, False)
|
|
651
648
|
yield out[:blocksize]
|
|
652
649
|
|
|
653
650
|
|
|
@@ -678,7 +675,6 @@ class SphericalHarmonicSource(PointSource):
|
|
|
678
675
|
'start_t',
|
|
679
676
|
'start',
|
|
680
677
|
'up',
|
|
681
|
-
'__class__',
|
|
682
678
|
'alpha',
|
|
683
679
|
'lOrder',
|
|
684
680
|
'prepadding',
|
|
@@ -693,7 +689,7 @@ class SphericalHarmonicSource(PointSource):
|
|
|
693
689
|
Y_lm = get_modes(
|
|
694
690
|
lOrder=self.lOrder,
|
|
695
691
|
direction=self.direction,
|
|
696
|
-
mpos=self.mics.
|
|
692
|
+
mpos=self.mics.pos,
|
|
697
693
|
sourceposition=array(self.loc),
|
|
698
694
|
)
|
|
699
695
|
return real(ifft(fft(signals, axis=0) * (Y_lm @ self.alpha), axis=0))
|
|
@@ -709,7 +705,7 @@ class SphericalHarmonicSource(PointSource):
|
|
|
709
705
|
|
|
710
706
|
Returns
|
|
711
707
|
-------
|
|
712
|
-
Samples in blocks of shape (num,
|
|
708
|
+
Samples in blocks of shape (num, num_channels).
|
|
713
709
|
The last block may be shorter than num.
|
|
714
710
|
|
|
715
711
|
"""
|
|
@@ -718,11 +714,11 @@ class SphericalHarmonicSource(PointSource):
|
|
|
718
714
|
|
|
719
715
|
signal = self.signal.usignal(self.up)
|
|
720
716
|
# emission time relative to start_t (in samples) for first sample
|
|
721
|
-
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.
|
|
717
|
+
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.pos)
|
|
722
718
|
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq + pi / 30
|
|
723
719
|
i = 0
|
|
724
|
-
n = self.
|
|
725
|
-
out = empty((num, self.
|
|
720
|
+
n = self.num_samples
|
|
721
|
+
out = empty((num, self.num_channels))
|
|
726
722
|
while n:
|
|
727
723
|
n -= 1
|
|
728
724
|
try:
|
|
@@ -752,7 +748,7 @@ class MovingPointSource(PointSource):
|
|
|
752
748
|
#: Trajectory of the source,
|
|
753
749
|
#: instance of the :class:`~acoular.trajectory.Trajectory` class.
|
|
754
750
|
#: The start time is assumed to be the same as for the samples.
|
|
755
|
-
trajectory =
|
|
751
|
+
trajectory = Instance(Trajectory, desc='trajectory of the source')
|
|
756
752
|
|
|
757
753
|
prepadding = Enum('loop', desc='Behaviour for negative time indices.')
|
|
758
754
|
|
|
@@ -768,7 +764,6 @@ class MovingPointSource(PointSource):
|
|
|
768
764
|
'start',
|
|
769
765
|
'trajectory.digest',
|
|
770
766
|
'prepadding',
|
|
771
|
-
'__class__',
|
|
772
767
|
],
|
|
773
768
|
)
|
|
774
769
|
|
|
@@ -787,7 +782,7 @@ class MovingPointSource(PointSource):
|
|
|
787
782
|
|
|
788
783
|
Returns
|
|
789
784
|
-------
|
|
790
|
-
Samples in blocks of shape (num,
|
|
785
|
+
Samples in blocks of shape (num, num_channels).
|
|
791
786
|
The last block may be shorter than num.
|
|
792
787
|
|
|
793
788
|
"""
|
|
@@ -795,15 +790,15 @@ class MovingPointSource(PointSource):
|
|
|
795
790
|
# from the end of the calculated signal.
|
|
796
791
|
|
|
797
792
|
signal = self.signal.usignal(self.up)
|
|
798
|
-
out = empty((num, self.
|
|
799
|
-
# shortcuts and
|
|
793
|
+
out = empty((num, self.num_channels))
|
|
794
|
+
# shortcuts and initial values
|
|
800
795
|
m = self.mics
|
|
801
796
|
t = self.start * ones(m.num_mics)
|
|
802
797
|
i = 0
|
|
803
798
|
epslim = 0.1 / self.up / self.sample_freq
|
|
804
799
|
c0 = self.env.c
|
|
805
800
|
tr = self.trajectory
|
|
806
|
-
n = self.
|
|
801
|
+
n = self.num_samples
|
|
807
802
|
while n:
|
|
808
803
|
n -= 1
|
|
809
804
|
eps = ones(m.num_mics)
|
|
@@ -812,7 +807,7 @@ class MovingPointSource(PointSource):
|
|
|
812
807
|
# Newton-Rhapson iteration
|
|
813
808
|
while abs(eps).max() > epslim and j < 100:
|
|
814
809
|
loc = array(tr.location(te))
|
|
815
|
-
rm = loc - m.
|
|
810
|
+
rm = loc - m.pos # distance vectors to microphones
|
|
816
811
|
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
817
812
|
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
818
813
|
der = array(tr.location(te, der=1))
|
|
@@ -867,7 +862,6 @@ class PointSourceDipole(PointSource):
|
|
|
867
862
|
'up',
|
|
868
863
|
'direction',
|
|
869
864
|
'prepadding',
|
|
870
|
-
'__class__',
|
|
871
865
|
],
|
|
872
866
|
)
|
|
873
867
|
|
|
@@ -886,14 +880,14 @@ class PointSourceDipole(PointSource):
|
|
|
886
880
|
|
|
887
881
|
Returns
|
|
888
882
|
-------
|
|
889
|
-
Samples in blocks of shape (num,
|
|
883
|
+
Samples in blocks of shape (num, num_channels).
|
|
890
884
|
The last block may be shorter than num.
|
|
891
885
|
|
|
892
886
|
"""
|
|
893
887
|
# If signal samples are needed for te < t_start, then samples are taken
|
|
894
888
|
# from the end of the calculated signal.
|
|
895
889
|
|
|
896
|
-
mpos = self.mics.
|
|
890
|
+
mpos = self.mics.pos
|
|
897
891
|
# position of the dipole as (3,1) vector
|
|
898
892
|
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
899
893
|
# direction vector from tuple
|
|
@@ -912,7 +906,7 @@ class PointSourceDipole(PointSource):
|
|
|
912
906
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
913
907
|
|
|
914
908
|
signal = self.signal.usignal(self.up)
|
|
915
|
-
out = empty((num, self.
|
|
909
|
+
out = empty((num, self.num_channels))
|
|
916
910
|
|
|
917
911
|
# distance from dipole center to microphones
|
|
918
912
|
rm = self.env._r(loc, mpos)
|
|
@@ -926,7 +920,7 @@ class PointSourceDipole(PointSource):
|
|
|
926
920
|
ind2 = (-rm2 / c - self.start_t + self.start) * self.sample_freq
|
|
927
921
|
|
|
928
922
|
i = 0
|
|
929
|
-
n = self.
|
|
923
|
+
n = self.num_samples
|
|
930
924
|
while n:
|
|
931
925
|
n -= 1
|
|
932
926
|
try:
|
|
@@ -964,7 +958,6 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
964
958
|
'start',
|
|
965
959
|
'up',
|
|
966
960
|
'direction',
|
|
967
|
-
'__class__',
|
|
968
961
|
],
|
|
969
962
|
)
|
|
970
963
|
|
|
@@ -986,7 +979,7 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
986
979
|
xs = array(self.trajectory.location(te))
|
|
987
980
|
loc = xs.copy()
|
|
988
981
|
loc += direction
|
|
989
|
-
rm = loc - self.mics.
|
|
982
|
+
rm = loc - self.mics.pos # distance vectors to microphones
|
|
990
983
|
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
991
984
|
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
992
985
|
der = array(self.trajectory.location(te, der=1))
|
|
@@ -1021,13 +1014,13 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1021
1014
|
|
|
1022
1015
|
Returns
|
|
1023
1016
|
-------
|
|
1024
|
-
Samples in blocks of shape (num,
|
|
1017
|
+
Samples in blocks of shape (num, num_channels).
|
|
1025
1018
|
The last block may be shorter than num.
|
|
1026
1019
|
|
|
1027
1020
|
"""
|
|
1028
1021
|
# If signal samples are needed for te < t_start, then samples are taken
|
|
1029
1022
|
# from the end of the calculated signal.
|
|
1030
|
-
mpos = self.mics.
|
|
1023
|
+
mpos = self.mics.pos
|
|
1031
1024
|
|
|
1032
1025
|
# direction vector from tuple
|
|
1033
1026
|
direc = array(self.direction, dtype=float) * 1e-5
|
|
@@ -1042,13 +1035,13 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1042
1035
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
1043
1036
|
|
|
1044
1037
|
signal = self.signal.usignal(self.up)
|
|
1045
|
-
out = empty((num, self.
|
|
1046
|
-
# shortcuts and
|
|
1038
|
+
out = empty((num, self.num_channels))
|
|
1039
|
+
# shortcuts and initial values
|
|
1047
1040
|
m = self.mics
|
|
1048
1041
|
t = self.start * ones(m.num_mics)
|
|
1049
1042
|
|
|
1050
1043
|
i = 0
|
|
1051
|
-
n = self.
|
|
1044
|
+
n = self.num_samples
|
|
1052
1045
|
while n:
|
|
1053
1046
|
n -= 1
|
|
1054
1047
|
te, rm, Mr, locs = self.get_emission_time(t, 0)
|
|
@@ -1106,7 +1099,7 @@ class LineSource(PointSource):
|
|
|
1106
1099
|
source_strength = CArray(desc='coefficients of the source strength')
|
|
1107
1100
|
|
|
1108
1101
|
#:coherence
|
|
1109
|
-
coherence =
|
|
1102
|
+
coherence = Enum('coherent', 'incoherent', desc='coherence mode')
|
|
1110
1103
|
|
|
1111
1104
|
# internal identifier
|
|
1112
1105
|
digest = Property(
|
|
@@ -1121,7 +1114,6 @@ class LineSource(PointSource):
|
|
|
1121
1114
|
'direction',
|
|
1122
1115
|
'source_strength',
|
|
1123
1116
|
'coherence',
|
|
1124
|
-
'__class__',
|
|
1125
1117
|
],
|
|
1126
1118
|
)
|
|
1127
1119
|
|
|
@@ -1140,14 +1132,14 @@ class LineSource(PointSource):
|
|
|
1140
1132
|
|
|
1141
1133
|
Returns
|
|
1142
1134
|
-------
|
|
1143
|
-
Samples in blocks of shape (num,
|
|
1135
|
+
Samples in blocks of shape (num, num_channels).
|
|
1144
1136
|
The last block may be shorter than num.
|
|
1145
1137
|
|
|
1146
1138
|
"""
|
|
1147
1139
|
# If signal samples are needed for te < t_start, then samples are taken
|
|
1148
1140
|
# from the end of the calculated signal.
|
|
1149
1141
|
|
|
1150
|
-
mpos = self.mics.
|
|
1142
|
+
mpos = self.mics.pos
|
|
1151
1143
|
|
|
1152
1144
|
# direction vector from tuple
|
|
1153
1145
|
direc = array(self.direction, dtype=float)
|
|
@@ -1159,14 +1151,14 @@ class LineSource(PointSource):
|
|
|
1159
1151
|
dist = self.length / self.num_sources
|
|
1160
1152
|
|
|
1161
1153
|
# blocwise output
|
|
1162
|
-
out = zeros((num, self.
|
|
1154
|
+
out = zeros((num, self.num_channels))
|
|
1163
1155
|
|
|
1164
1156
|
# distance from line start position to microphones
|
|
1165
1157
|
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
1166
1158
|
|
|
1167
1159
|
# distances from monopoles in the line to microphones
|
|
1168
|
-
rms = empty((self.
|
|
1169
|
-
inds = empty((self.
|
|
1160
|
+
rms = empty((self.num_channels, self.num_sources))
|
|
1161
|
+
inds = empty((self.num_channels, self.num_sources))
|
|
1170
1162
|
signals = empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1171
1163
|
# for every source - distances
|
|
1172
1164
|
for s in range(self.num_sources):
|
|
@@ -1178,7 +1170,7 @@ class LineSource(PointSource):
|
|
|
1178
1170
|
self.signal.rms = self.signal.rms * self.source_strength[s]
|
|
1179
1171
|
signals[s] = self.signal.usignal(self.up)
|
|
1180
1172
|
i = 0
|
|
1181
|
-
n = self.
|
|
1173
|
+
n = self.num_samples
|
|
1182
1174
|
while n:
|
|
1183
1175
|
n -= 1
|
|
1184
1176
|
try:
|
|
@@ -1190,7 +1182,7 @@ class LineSource(PointSource):
|
|
|
1190
1182
|
i += 1
|
|
1191
1183
|
if i == num:
|
|
1192
1184
|
yield out
|
|
1193
|
-
out = zeros((num, self.
|
|
1185
|
+
out = zeros((num, self.num_channels))
|
|
1194
1186
|
i = 0
|
|
1195
1187
|
except IndexError:
|
|
1196
1188
|
break
|
|
@@ -1210,7 +1202,6 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1210
1202
|
'start',
|
|
1211
1203
|
'up',
|
|
1212
1204
|
'direction',
|
|
1213
|
-
'__class__',
|
|
1214
1205
|
],
|
|
1215
1206
|
)
|
|
1216
1207
|
|
|
@@ -1246,7 +1237,7 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1246
1237
|
xs = array(self.trajectory.location(te))
|
|
1247
1238
|
loc = xs.copy()
|
|
1248
1239
|
loc += direction
|
|
1249
|
-
rm = loc - self.mics.
|
|
1240
|
+
rm = loc - self.mics.pos # distance vectors to microphones
|
|
1250
1241
|
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
1251
1242
|
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1252
1243
|
der = array(self.trajectory.location(te, der=1))
|
|
@@ -1267,13 +1258,13 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1267
1258
|
|
|
1268
1259
|
Returns
|
|
1269
1260
|
-------
|
|
1270
|
-
Samples in blocks of shape (num,
|
|
1261
|
+
Samples in blocks of shape (num, num_channels).
|
|
1271
1262
|
The last block may be shorter than num.
|
|
1272
1263
|
|
|
1273
1264
|
"""
|
|
1274
1265
|
# If signal samples are needed for te < t_start, then samples are taken
|
|
1275
1266
|
# from the end of the calculated signal.
|
|
1276
|
-
mpos = self.mics.
|
|
1267
|
+
mpos = self.mics.pos
|
|
1277
1268
|
|
|
1278
1269
|
# direction vector from tuple
|
|
1279
1270
|
direc = array(self.direction, dtype=float)
|
|
@@ -1285,11 +1276,11 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1285
1276
|
dir2 = (direc_n * dist).reshape((3, 1))
|
|
1286
1277
|
|
|
1287
1278
|
# blocwise output
|
|
1288
|
-
out = zeros((num, self.
|
|
1279
|
+
out = zeros((num, self.num_channels))
|
|
1289
1280
|
|
|
1290
1281
|
# distances from monopoles in the line to microphones
|
|
1291
|
-
rms = empty((self.
|
|
1292
|
-
inds = empty((self.
|
|
1282
|
+
rms = empty((self.num_channels, self.num_sources))
|
|
1283
|
+
inds = empty((self.num_channels, self.num_sources))
|
|
1293
1284
|
signals = empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1294
1285
|
# coherence
|
|
1295
1286
|
for s in range(self.num_sources):
|
|
@@ -1298,13 +1289,13 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1298
1289
|
self.signal.seed = s + abs(int(hash(self.digest) // 10e12))
|
|
1299
1290
|
self.signal.rms = self.signal.rms * self.source_strength[s]
|
|
1300
1291
|
signals[s] = self.signal.usignal(self.up)
|
|
1301
|
-
mpos = self.mics.
|
|
1292
|
+
mpos = self.mics.pos
|
|
1302
1293
|
|
|
1303
|
-
# shortcuts and
|
|
1294
|
+
# shortcuts and initial values
|
|
1304
1295
|
m = self.mics
|
|
1305
1296
|
t = self.start * ones(m.num_mics)
|
|
1306
1297
|
i = 0
|
|
1307
|
-
n = self.
|
|
1298
|
+
n = self.num_samples
|
|
1308
1299
|
while n:
|
|
1309
1300
|
n -= 1
|
|
1310
1301
|
t += 1.0 / self.sample_freq
|
|
@@ -1314,7 +1305,7 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1314
1305
|
# get distance and ind for every source in the line
|
|
1315
1306
|
for s in range(self.num_sources):
|
|
1316
1307
|
diff = self.get_moving_direction(dir2, te1)
|
|
1317
|
-
te, rm, Mr, locs = self.get_emission_time(t, tile((diff * s).T, (self.
|
|
1308
|
+
te, rm, Mr, locs = self.get_emission_time(t, tile((diff * s).T, (self.num_channels, 1)).T)
|
|
1318
1309
|
loc = array(self.trajectory.location(te), dtype=float)[:, 0][:, newaxis]
|
|
1319
1310
|
diff = self.get_moving_direction(dir2, te)
|
|
1320
1311
|
rms[:, s] = self.env._r((loc + diff * s), mpos)
|
|
@@ -1332,13 +1323,14 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1332
1323
|
i += 1
|
|
1333
1324
|
if i == num:
|
|
1334
1325
|
yield out
|
|
1335
|
-
out = zeros((num, self.
|
|
1326
|
+
out = zeros((num, self.num_channels))
|
|
1336
1327
|
i = 0
|
|
1337
1328
|
except IndexError:
|
|
1338
1329
|
break
|
|
1339
1330
|
yield out[:i]
|
|
1340
1331
|
|
|
1341
1332
|
|
|
1333
|
+
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True)
|
|
1342
1334
|
class UncorrelatedNoiseSource(SamplesGenerator):
|
|
1343
1335
|
"""Class to simulate white or pink noise as uncorrelated signal at each
|
|
1344
1336
|
channel.
|
|
@@ -1349,45 +1341,30 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
1349
1341
|
#: Type of noise to generate at the channels.
|
|
1350
1342
|
#: The `~acoular.signals.SignalGenerator`-derived class has to
|
|
1351
1343
|
# feature the parameter "seed" (i.e. white or pink noise).
|
|
1352
|
-
signal =
|
|
1344
|
+
signal = Instance(NoiseGenerator, desc='type of noise')
|
|
1353
1345
|
|
|
1354
1346
|
#: Array with seeds for random number generator.
|
|
1355
|
-
#: When left empty, arange(:attr:`
|
|
1347
|
+
#: When left empty, arange(:attr:`num_channels`) + :attr:`signal`.seed
|
|
1356
1348
|
#: will be used.
|
|
1357
1349
|
seed = CArray(dtype=uint32, desc='random seed values')
|
|
1358
1350
|
|
|
1359
1351
|
#: Number of channels in output; is set automatically /
|
|
1360
1352
|
#: depends on used microphone geometry.
|
|
1361
|
-
|
|
1353
|
+
num_channels = Delegate('mics', 'num_mics')
|
|
1362
1354
|
|
|
1363
1355
|
#: :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
1364
|
-
mics =
|
|
1365
|
-
|
|
1366
|
-
# --- List of backwards compatibility traits and their setters/getters -----------
|
|
1367
|
-
|
|
1368
|
-
# Microphone locations.
|
|
1369
|
-
# Deprecated! Use :attr:`mics` trait instead.
|
|
1370
|
-
mpos = Property()
|
|
1371
|
-
|
|
1372
|
-
def _get_mpos(self):
|
|
1373
|
-
return self.mics
|
|
1374
|
-
|
|
1375
|
-
def _set_mpos(self, mpos):
|
|
1376
|
-
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2)
|
|
1377
|
-
self.mics = mpos
|
|
1378
|
-
|
|
1379
|
-
# --- End of backwards compatibility traits --------------------------------------
|
|
1356
|
+
mics = Instance(MicGeom, desc='microphone geometry')
|
|
1380
1357
|
|
|
1381
1358
|
#: Start time of the signal in seconds, defaults to 0 s.
|
|
1382
1359
|
start_t = Float(0.0, desc='signal start time')
|
|
1383
1360
|
|
|
1384
|
-
#: Start time of the data
|
|
1361
|
+
#: Start time of the data acquisition at microphones in seconds,
|
|
1385
1362
|
#: defaults to 0 s.
|
|
1386
1363
|
start = Float(0.0, desc='sample start time')
|
|
1387
1364
|
|
|
1388
1365
|
#: Number of samples is set automatically /
|
|
1389
1366
|
#: depends on :attr:`signal`.
|
|
1390
|
-
|
|
1367
|
+
num_samples = Delegate('signal')
|
|
1391
1368
|
|
|
1392
1369
|
#: Sampling frequency of the signal; is set automatically /
|
|
1393
1370
|
#: depends on :attr:`signal`.
|
|
@@ -1397,15 +1374,11 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
1397
1374
|
digest = Property(
|
|
1398
1375
|
depends_on=[
|
|
1399
1376
|
'mics.digest',
|
|
1400
|
-
'signal.
|
|
1401
|
-
'signal.numsamples',
|
|
1402
|
-
'signal.sample_freq',
|
|
1403
|
-
'signal.__class__',
|
|
1377
|
+
'signal.digest',
|
|
1404
1378
|
'seed',
|
|
1405
1379
|
'loc',
|
|
1406
1380
|
'start_t',
|
|
1407
1381
|
'start',
|
|
1408
|
-
'__class__',
|
|
1409
1382
|
],
|
|
1410
1383
|
)
|
|
1411
1384
|
|
|
@@ -1424,45 +1397,43 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
1424
1397
|
|
|
1425
1398
|
Returns
|
|
1426
1399
|
-------
|
|
1427
|
-
Samples in blocks of shape (num,
|
|
1400
|
+
Samples in blocks of shape (num, num_channels).
|
|
1428
1401
|
The last block may be shorter than num.
|
|
1429
1402
|
|
|
1430
1403
|
"""
|
|
1431
1404
|
Noise = self.signal.__class__
|
|
1432
1405
|
# create or get the array of random seeds
|
|
1433
|
-
if not self.seed:
|
|
1434
|
-
seed = arange(self.
|
|
1435
|
-
elif self.seed.shape == (self.
|
|
1406
|
+
if not self.seed.size > 0:
|
|
1407
|
+
seed = arange(self.num_channels) + self.signal.seed
|
|
1408
|
+
elif self.seed.shape == (self.num_channels,):
|
|
1436
1409
|
seed = self.seed
|
|
1437
1410
|
else:
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
)
|
|
1442
|
-
|
|
1443
|
-
# create array with [numchannels] noise signal tracks
|
|
1411
|
+
msg = f'Seed array expected to be of shape ({self.num_channels:d},), but has shape {self.seed.shape}.'
|
|
1412
|
+
raise ValueError(msg)
|
|
1413
|
+
# create array with [num_channels] noise signal tracks
|
|
1444
1414
|
signal = array(
|
|
1445
1415
|
[
|
|
1446
|
-
Noise(seed=s,
|
|
1416
|
+
Noise(seed=s, num_samples=self.num_samples, sample_freq=self.sample_freq, rms=self.signal.rms).signal()
|
|
1447
1417
|
for s in seed
|
|
1448
1418
|
],
|
|
1449
1419
|
).T
|
|
1450
1420
|
|
|
1451
1421
|
n = num
|
|
1452
|
-
while n <= self.
|
|
1422
|
+
while n <= self.num_samples:
|
|
1453
1423
|
yield signal[n - num : n, :]
|
|
1454
1424
|
n += num
|
|
1455
1425
|
else:
|
|
1456
|
-
if (n - num) < self.
|
|
1426
|
+
if (n - num) < self.num_samples:
|
|
1457
1427
|
yield signal[n - num :, :]
|
|
1458
1428
|
else:
|
|
1459
1429
|
return
|
|
1460
1430
|
|
|
1461
1431
|
|
|
1432
|
+
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True)
|
|
1462
1433
|
class SourceMixer(SamplesGenerator):
|
|
1463
1434
|
"""Mixes the signals from several sources."""
|
|
1464
1435
|
|
|
1465
|
-
#: List of :class:`~acoular.
|
|
1436
|
+
#: List of :class:`~acoular.base.SamplesGenerator` objects
|
|
1466
1437
|
#: to be mixed.
|
|
1467
1438
|
sources = List(Instance(SamplesGenerator, ()))
|
|
1468
1439
|
|
|
@@ -1470,10 +1441,10 @@ class SourceMixer(SamplesGenerator):
|
|
|
1470
1441
|
sample_freq = Property(depends_on=['sdigest'])
|
|
1471
1442
|
|
|
1472
1443
|
#: Number of channels.
|
|
1473
|
-
|
|
1444
|
+
num_channels = Property(depends_on=['sdigest'])
|
|
1474
1445
|
|
|
1475
1446
|
#: Number of samples.
|
|
1476
|
-
|
|
1447
|
+
num_samples = Property(depends_on=['sdigest'])
|
|
1477
1448
|
|
|
1478
1449
|
#: Amplitude weight(s) for the sources as array. If not set,
|
|
1479
1450
|
#: all source signals are equally weighted.
|
|
@@ -1499,12 +1470,12 @@ class SourceMixer(SamplesGenerator):
|
|
|
1499
1470
|
return self.sources[0].sample_freq if self.sources else 0
|
|
1500
1471
|
|
|
1501
1472
|
@cached_property
|
|
1502
|
-
def
|
|
1503
|
-
return self.sources[0].
|
|
1473
|
+
def _get_num_channels(self):
|
|
1474
|
+
return self.sources[0].num_channels if self.sources else 0
|
|
1504
1475
|
|
|
1505
1476
|
@cached_property
|
|
1506
|
-
def
|
|
1507
|
-
return self.sources[0].
|
|
1477
|
+
def _get_num_samples(self):
|
|
1478
|
+
return self.sources[0].num_samples if self.sources else 0
|
|
1508
1479
|
|
|
1509
1480
|
def validate_sources(self):
|
|
1510
1481
|
"""Validates if sources fit together."""
|
|
@@ -1513,11 +1484,14 @@ class SourceMixer(SamplesGenerator):
|
|
|
1513
1484
|
raise ValueError(msg)
|
|
1514
1485
|
for s in self.sources[1:]:
|
|
1515
1486
|
if self.sample_freq != s.sample_freq:
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
raise ValueError(
|
|
1487
|
+
msg = f'Sample frequency of {s} does not fit'
|
|
1488
|
+
raise ValueError(msg)
|
|
1489
|
+
if self.num_channels != s.num_channels:
|
|
1490
|
+
msg = f'Channel count of {s} does not fit'
|
|
1491
|
+
raise ValueError(msg)
|
|
1492
|
+
if self.num_samples != s.num_samples:
|
|
1493
|
+
msg = f'Number of samples of {s} does not fit'
|
|
1494
|
+
raise ValueError(msg)
|
|
1521
1495
|
|
|
1522
1496
|
def result(self, num):
|
|
1523
1497
|
"""Python generator that yields the output block-wise.
|
|
@@ -1531,7 +1505,7 @@ class SourceMixer(SamplesGenerator):
|
|
|
1531
1505
|
|
|
1532
1506
|
Returns
|
|
1533
1507
|
-------
|
|
1534
|
-
Samples in blocks of shape (num,
|
|
1508
|
+
Samples in blocks of shape (num, num_channels).
|
|
1535
1509
|
The last block may be shorter than num.
|
|
1536
1510
|
|
|
1537
1511
|
"""
|
|
@@ -1557,10 +1531,10 @@ class SourceMixer(SamplesGenerator):
|
|
|
1557
1531
|
|
|
1558
1532
|
|
|
1559
1533
|
class PointSourceConvolve(PointSource):
|
|
1560
|
-
"""Class to blockwise convolve an arbitrary source signal with a
|
|
1534
|
+
"""Class to blockwise convolve an arbitrary source signal with a room impulse response."""
|
|
1561
1535
|
|
|
1562
|
-
#: Convolution kernel in the time domain.
|
|
1563
|
-
#:
|
|
1536
|
+
#: Convolution kernel in the time domain. The second dimension of the kernel array
|
|
1537
|
+
#: has to be either 1 or match :attr:`~SamplesGenerator.num_channels`.
|
|
1564
1538
|
#: If only a single kernel is supplied, it is applied to all channels.
|
|
1565
1539
|
kernel = CArray(dtype=float, desc='Convolution kernel.')
|
|
1566
1540
|
|
|
@@ -1569,7 +1543,7 @@ class PointSourceConvolve(PointSource):
|
|
|
1569
1543
|
#: Start time of the signal in seconds, defaults to 0 s.
|
|
1570
1544
|
start_t = Enum(0.0, desc='signal start time')
|
|
1571
1545
|
|
|
1572
|
-
#: Start time of the data
|
|
1546
|
+
#: Start time of the data acquisition at microphones in seconds,
|
|
1573
1547
|
#: defaults to 0 s.
|
|
1574
1548
|
start = Enum(0.0, desc='sample start time')
|
|
1575
1549
|
|
|
@@ -1584,7 +1558,7 @@ class PointSourceConvolve(PointSource):
|
|
|
1584
1558
|
|
|
1585
1559
|
# internal identifier
|
|
1586
1560
|
digest = Property(
|
|
1587
|
-
depends_on=['mics.digest', 'signal.digest', 'loc', 'kernel'
|
|
1561
|
+
depends_on=['mics.digest', 'signal.digest', 'loc', 'kernel'],
|
|
1588
1562
|
)
|
|
1589
1563
|
|
|
1590
1564
|
@cached_property
|
|
@@ -1602,7 +1576,7 @@ class PointSourceConvolve(PointSource):
|
|
|
1602
1576
|
|
|
1603
1577
|
Returns
|
|
1604
1578
|
-------
|
|
1605
|
-
Samples in blocks of shape (num,
|
|
1579
|
+
Samples in blocks of shape (num, num_channels).
|
|
1606
1580
|
The last block may be shorter than num.
|
|
1607
1581
|
|
|
1608
1582
|
"""
|
|
@@ -1610,8 +1584,8 @@ class PointSourceConvolve(PointSource):
|
|
|
1610
1584
|
source = TimeSamples(
|
|
1611
1585
|
data=data,
|
|
1612
1586
|
sample_freq=self.sample_freq,
|
|
1613
|
-
|
|
1614
|
-
|
|
1587
|
+
num_samples=self.num_samples,
|
|
1588
|
+
num_channels=self.mics.num_mics,
|
|
1615
1589
|
)
|
|
1616
1590
|
time_convolve = TimeConvolve(
|
|
1617
1591
|
source=source,
|