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