acoular 25.4__py3-none-any.whl → 25.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acoular/__init__.py +2 -4
- acoular/aiaa/aiaa.py +10 -10
- acoular/base.py +12 -34
- acoular/calib.py +20 -19
- acoular/configuration.py +3 -3
- acoular/demo/__init__.py +6 -1
- acoular/demo/acoular_demo.py +34 -10
- acoular/deprecation.py +10 -1
- acoular/environments.py +107 -117
- acoular/fastFuncs.py +16 -10
- acoular/fbeamform.py +300 -402
- acoular/fprocess.py +7 -33
- acoular/grids.py +228 -134
- acoular/h5cache.py +10 -4
- acoular/h5files.py +106 -9
- acoular/internal.py +4 -0
- acoular/microphones.py +22 -10
- acoular/process.py +7 -53
- acoular/sdinput.py +8 -5
- acoular/signals.py +29 -27
- acoular/sources.py +205 -335
- acoular/spectra.py +33 -43
- acoular/tbeamform.py +220 -199
- acoular/tools/helpers.py +52 -33
- acoular/tools/metrics.py +5 -10
- acoular/tprocess.py +1392 -647
- acoular/traitsviews.py +1 -3
- acoular/trajectory.py +5 -5
- acoular/version.py +4 -3
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/METADATA +8 -4
- acoular-25.10.dist-info/RECORD +56 -0
- acoular-25.4.dist-info/RECORD +0 -56
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/WHEEL +0 -0
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.4.dist-info → acoular-25.10.dist-info}/licenses/LICENSE +0 -0
acoular/sources.py
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
"""
|
|
5
5
|
Measured multichannel data management and simulation of acoustic sources.
|
|
6
6
|
|
|
7
|
+
.. inheritance-diagram::
|
|
8
|
+
acoular.sources
|
|
9
|
+
:top-classes:
|
|
10
|
+
acoular.base.SamplesGenerator
|
|
11
|
+
:parts: 1
|
|
12
|
+
|
|
7
13
|
.. autosummary::
|
|
8
14
|
:toctree: generated/
|
|
9
15
|
|
|
@@ -31,31 +37,9 @@ from os import path
|
|
|
31
37
|
from warnings import warn
|
|
32
38
|
|
|
33
39
|
import numba as nb
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
arange,
|
|
37
|
-
arctan2,
|
|
38
|
-
array,
|
|
39
|
-
ceil,
|
|
40
|
-
complex128,
|
|
41
|
-
cross,
|
|
42
|
-
dot,
|
|
43
|
-
empty,
|
|
44
|
-
int64,
|
|
45
|
-
mod,
|
|
46
|
-
newaxis,
|
|
47
|
-
ones,
|
|
48
|
-
pi,
|
|
49
|
-
real,
|
|
50
|
-
repeat,
|
|
51
|
-
sqrt,
|
|
52
|
-
tile,
|
|
53
|
-
uint32,
|
|
54
|
-
zeros,
|
|
55
|
-
)
|
|
56
|
-
from numpy import min as npmin
|
|
40
|
+
import numpy as np
|
|
41
|
+
import scipy.linalg as spla
|
|
57
42
|
from numpy.fft import fft, ifft
|
|
58
|
-
from scipy.linalg import norm
|
|
59
43
|
from scipy.special import sph_harm, spherical_jn, spherical_yn
|
|
60
44
|
from traits.api import (
|
|
61
45
|
Any,
|
|
@@ -76,14 +60,11 @@ from traits.api import (
|
|
|
76
60
|
Union,
|
|
77
61
|
cached_property,
|
|
78
62
|
observe,
|
|
79
|
-
on_trait_change,
|
|
80
63
|
)
|
|
81
64
|
|
|
82
65
|
from .base import SamplesGenerator
|
|
83
66
|
|
|
84
67
|
# acoular imports
|
|
85
|
-
from .calib import Calib
|
|
86
|
-
from .deprecation import deprecated_alias
|
|
87
68
|
from .environments import Environment
|
|
88
69
|
from .h5files import H5FileBase, _get_h5file_class
|
|
89
70
|
from .internal import digest, ldigest
|
|
@@ -109,7 +90,6 @@ def _fill_mic_signal_block(out, signal, rm, ind, blocksize, num_channels, up, pr
|
|
|
109
90
|
for m in range(num_channels):
|
|
110
91
|
out[b, m] = signal[int(0.5 + ind[0, m])] / rm[0, m]
|
|
111
92
|
ind += up
|
|
112
|
-
return out
|
|
113
93
|
|
|
114
94
|
|
|
115
95
|
def spherical_hn1(n, z):
|
|
@@ -201,7 +181,7 @@ def get_radiation_angles(direction, mpos, sourceposition):
|
|
|
201
181
|
--------
|
|
202
182
|
:func:`numpy.linalg.norm` :
|
|
203
183
|
Computes the norm of a vector.
|
|
204
|
-
:
|
|
184
|
+
:obj:`numpy.arctan2` :
|
|
205
185
|
Computes the arctangent of two variables, preserving quadrant information.
|
|
206
186
|
|
|
207
187
|
Notes
|
|
@@ -227,21 +207,21 @@ def get_radiation_angles(direction, mpos, sourceposition):
|
|
|
227
207
|
array([4.71238898, 4.71238898])
|
|
228
208
|
"""
|
|
229
209
|
# direction of the Spherical Harmonics
|
|
230
|
-
direc = array(direction, dtype=float)
|
|
231
|
-
direc = direc / norm(direc)
|
|
210
|
+
direc = np.array(direction, dtype=float)
|
|
211
|
+
direc = direc / spla.norm(direc)
|
|
232
212
|
# distances
|
|
233
|
-
source_to_mic_vecs = mpos - array(sourceposition).reshape((3, 1))
|
|
213
|
+
source_to_mic_vecs = mpos - np.array(sourceposition).reshape((3, 1))
|
|
234
214
|
source_to_mic_vecs[2] *= -1 # invert z-axis (acoular) #-1
|
|
235
215
|
# z-axis (acoular) -> y-axis (spherical)
|
|
236
216
|
# y-axis (acoular) -> z-axis (spherical)
|
|
237
217
|
# theta
|
|
238
|
-
ele = arctan2(sqrt(source_to_mic_vecs[0] ** 2 + source_to_mic_vecs[2] ** 2), source_to_mic_vecs[1])
|
|
239
|
-
ele += arctan2(sqrt(direc[0] ** 2 + direc[2] ** 2), direc[1])
|
|
240
|
-
ele += pi * 0.5 # convert from [-pi/2, pi/2] to [0,pi] range
|
|
218
|
+
ele = np.arctan2(np.sqrt(source_to_mic_vecs[0] ** 2 + source_to_mic_vecs[2] ** 2), source_to_mic_vecs[1])
|
|
219
|
+
ele += np.arctan2(np.sqrt(direc[0] ** 2 + direc[2] ** 2), direc[1])
|
|
220
|
+
ele += np.pi * 0.5 # convert from [-pi/2, pi/2] to [0,pi] range
|
|
241
221
|
# phi
|
|
242
|
-
azi = arctan2(source_to_mic_vecs[2], source_to_mic_vecs[0])
|
|
243
|
-
azi += arctan2(direc[2], direc[0])
|
|
244
|
-
azi = mod(azi, 2 * pi)
|
|
222
|
+
azi = np.arctan2(source_to_mic_vecs[2], source_to_mic_vecs[0])
|
|
223
|
+
azi += np.arctan2(direc[2], direc[0])
|
|
224
|
+
azi = np.mod(azi, 2 * np.pi)
|
|
245
225
|
return azi, ele
|
|
246
226
|
|
|
247
227
|
|
|
@@ -304,9 +284,9 @@ def get_modes(lOrder, direction, mpos, sourceposition=None): # noqa: N803
|
|
|
304
284
|
>>> modes.shape
|
|
305
285
|
(2, 9)
|
|
306
286
|
"""
|
|
307
|
-
sourceposition = sourceposition if sourceposition is not None else array([0, 0, 0])
|
|
287
|
+
sourceposition = sourceposition if sourceposition is not None else np.array([0, 0, 0])
|
|
308
288
|
azi, ele = get_radiation_angles(direction, mpos, sourceposition) # angles between source and mics
|
|
309
|
-
modes = zeros((azi.shape[0], (lOrder + 1) ** 2), dtype=complex128)
|
|
289
|
+
modes = np.zeros((azi.shape[0], (lOrder + 1) ** 2), dtype=np.complex128)
|
|
310
290
|
i = 0
|
|
311
291
|
for lidx in range(lOrder + 1):
|
|
312
292
|
for m in range(-lidx, lidx + 1):
|
|
@@ -317,7 +297,6 @@ def get_modes(lOrder, direction, mpos, sourceposition=None): # noqa: N803
|
|
|
317
297
|
return modes
|
|
318
298
|
|
|
319
299
|
|
|
320
|
-
@deprecated_alias({'name': 'file'})
|
|
321
300
|
class TimeSamples(SamplesGenerator):
|
|
322
301
|
"""
|
|
323
302
|
Container for processing time data in ``*.h5`` or NumPy array format.
|
|
@@ -329,13 +308,12 @@ class TimeSamples(SamplesGenerator):
|
|
|
329
308
|
|
|
330
309
|
See Also
|
|
331
310
|
--------
|
|
332
|
-
:class
|
|
311
|
+
:class:`~acoular.sources.MaskedTimeSamples` :
|
|
333
312
|
Extends the functionality of class :class:`TimeSamples` by enabling the definition of start
|
|
334
313
|
and stop samples as well as the specification of invalid channels.
|
|
335
314
|
|
|
336
315
|
Notes
|
|
337
316
|
-----
|
|
338
|
-
- If a calibration object is provided, calibrated time-domain data will be returned.
|
|
339
317
|
- Metadata from the :attr:`HDF5 file<file>` can be accessed through the :attr:`metadata`
|
|
340
318
|
attribute.
|
|
341
319
|
|
|
@@ -374,10 +352,6 @@ class TimeSamples(SamplesGenerator):
|
|
|
374
352
|
#: Basename of the ``.h5`` file, set automatically from the :attr:`file` attribute.
|
|
375
353
|
basename = Property(depends_on=['file'], desc='basename of data file')
|
|
376
354
|
|
|
377
|
-
#: Calibration data, an instance of the :class:`~acoular.calib.Calib` class.
|
|
378
|
-
#: (optional; if provided, the time data will be calibrated.)
|
|
379
|
-
calib = Instance(Calib, desc='Calibration data')
|
|
380
|
-
|
|
381
355
|
#: Number of input channels in the time data, set automatically based on the
|
|
382
356
|
#: :attr:`loaded data<file>` or :attr:`specified array<data>`.
|
|
383
357
|
num_channels = CInt(0, desc='number of input channels')
|
|
@@ -397,12 +371,10 @@ class TimeSamples(SamplesGenerator):
|
|
|
397
371
|
metadata = Dict(desc='metadata contained in .h5 file')
|
|
398
372
|
|
|
399
373
|
# Checksum over first data entries of all channels
|
|
400
|
-
_datachecksum = Property()
|
|
374
|
+
_datachecksum = Property(depends_on=['data'])
|
|
401
375
|
|
|
402
376
|
#: A unique identifier for the samples, based on its properties. (read-only)
|
|
403
|
-
digest = Property(
|
|
404
|
-
depends_on=['basename', 'calib.digest', '_datachecksum', 'sample_freq', 'num_channels', 'num_samples']
|
|
405
|
-
)
|
|
377
|
+
digest = Property(depends_on=['basename', '_datachecksum', 'sample_freq', 'num_channels', 'num_samples'])
|
|
406
378
|
|
|
407
379
|
def _get__datachecksum(self):
|
|
408
380
|
return self.data[0, :].sum()
|
|
@@ -415,8 +387,8 @@ class TimeSamples(SamplesGenerator):
|
|
|
415
387
|
def _get_basename(self):
|
|
416
388
|
return get_file_basename(self.file)
|
|
417
389
|
|
|
418
|
-
@
|
|
419
|
-
def _load_data(self):
|
|
390
|
+
@observe('basename')
|
|
391
|
+
def _load_data(self, event): # noqa ARG002
|
|
420
392
|
# Open the .h5 file and set attributes.
|
|
421
393
|
if self.h5f is not None:
|
|
422
394
|
with contextlib.suppress(OSError):
|
|
@@ -426,8 +398,8 @@ class TimeSamples(SamplesGenerator):
|
|
|
426
398
|
self._load_timedata()
|
|
427
399
|
self._load_metadata()
|
|
428
400
|
|
|
429
|
-
@
|
|
430
|
-
def _load_shapes(self):
|
|
401
|
+
@observe('data')
|
|
402
|
+
def _load_shapes(self, event): # noqa ARG002
|
|
431
403
|
# Set :attr:`num_channels` and :attr:`num_samples` from data.
|
|
432
404
|
if self.data is not None:
|
|
433
405
|
self.num_samples, self.num_channels = self.data.shape
|
|
@@ -449,8 +421,7 @@ class TimeSamples(SamplesGenerator):
|
|
|
449
421
|
|
|
450
422
|
The :meth:`result` method is a Python generator that yields blocks of time-domain data
|
|
451
423
|
of the specified size. Data is either read from an HDF5 file (if :attr:`file` is set)
|
|
452
|
-
or from a NumPy array (if :attr:`data` is directly provided).
|
|
453
|
-
is specified, the returned data is calibrated.
|
|
424
|
+
or from a NumPy array (if :attr:`data` is directly provided).
|
|
454
425
|
|
|
455
426
|
Parameters
|
|
456
427
|
----------
|
|
@@ -469,14 +440,6 @@ class TimeSamples(SamplesGenerator):
|
|
|
469
440
|
------
|
|
470
441
|
:obj:`OSError`
|
|
471
442
|
If no samples are available (i.e., :attr:`num_samples` is ``0``).
|
|
472
|
-
:obj:`ValueError`
|
|
473
|
-
If the calibration data does not match the number of channels.
|
|
474
|
-
|
|
475
|
-
Warnings
|
|
476
|
-
--------
|
|
477
|
-
A deprecation warning is raised if the calibration functionality is used directly in
|
|
478
|
-
:class:`TimeSamples`. Instead, the :class:`~acoular.calib.Calib` class should be used as a
|
|
479
|
-
separate processing block.
|
|
480
443
|
|
|
481
444
|
Examples
|
|
482
445
|
--------
|
|
@@ -500,36 +463,11 @@ class TimeSamples(SamplesGenerator):
|
|
|
500
463
|
raise OSError(msg)
|
|
501
464
|
self._datachecksum # trigger checksum calculation # noqa: B018
|
|
502
465
|
i = 0
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
stacklevel=2,
|
|
509
|
-
)
|
|
510
|
-
if self.calib.num_mics == self.num_channels:
|
|
511
|
-
cal_factor = self.calib.data[newaxis]
|
|
512
|
-
else:
|
|
513
|
-
msg = f'calibration data not compatible: {self.calib.num_mics:d}, {self.num_channels:d}'
|
|
514
|
-
raise ValueError(msg)
|
|
515
|
-
while i < self.num_samples:
|
|
516
|
-
yield self.data[i : i + num] * cal_factor
|
|
517
|
-
i += num
|
|
518
|
-
else:
|
|
519
|
-
while i < self.num_samples:
|
|
520
|
-
yield self.data[i : i + num]
|
|
521
|
-
i += num
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
@deprecated_alias(
|
|
525
|
-
{
|
|
526
|
-
'numchannels_total': 'num_channels_total',
|
|
527
|
-
'numsamples_total': 'num_samples_total',
|
|
528
|
-
'numchannels': 'num_channels',
|
|
529
|
-
'numsamples': 'num_samples',
|
|
530
|
-
},
|
|
531
|
-
read_only=['numchannels', 'numsamples'],
|
|
532
|
-
)
|
|
466
|
+
while i < self.num_samples:
|
|
467
|
+
yield self.data[i : num + i]
|
|
468
|
+
i += num
|
|
469
|
+
|
|
470
|
+
|
|
533
471
|
class MaskedTimeSamples(TimeSamples):
|
|
534
472
|
"""
|
|
535
473
|
Container to process and manage time-domain data with support for masking samples and channels.
|
|
@@ -542,7 +480,7 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
542
480
|
|
|
543
481
|
See Also
|
|
544
482
|
--------
|
|
545
|
-
:class
|
|
483
|
+
:class:`~acoular.sources.TimeSamples` : The parent class for managing unmasked time-domain data.
|
|
546
484
|
|
|
547
485
|
Notes
|
|
548
486
|
-----
|
|
@@ -609,7 +547,7 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
609
547
|
)
|
|
610
548
|
|
|
611
549
|
#: A unique identifier for the samples, based on its properties. (read-only)
|
|
612
|
-
digest = Property(depends_on=['basename', 'start', 'stop', '
|
|
550
|
+
digest = Property(depends_on=['basename', 'start', 'stop', 'invalid_channels', '_datachecksum'])
|
|
613
551
|
|
|
614
552
|
@cached_property
|
|
615
553
|
def _get_digest(self):
|
|
@@ -620,7 +558,7 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
620
558
|
if len(self.invalid_channels) == 0:
|
|
621
559
|
return slice(0, None, None)
|
|
622
560
|
allr = [i for i in range(self.num_channels_total) if i not in self.invalid_channels]
|
|
623
|
-
return array(allr)
|
|
561
|
+
return np.array(allr)
|
|
624
562
|
|
|
625
563
|
@cached_property
|
|
626
564
|
def _get_num_channels(self):
|
|
@@ -633,8 +571,8 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
633
571
|
sli = slice(self.start, self.stop).indices(self.num_samples_total)
|
|
634
572
|
return sli[1] - sli[0]
|
|
635
573
|
|
|
636
|
-
@
|
|
637
|
-
def _load_data(self):
|
|
574
|
+
@observe('basename')
|
|
575
|
+
def _load_data(self, event): # noqa ARG002
|
|
638
576
|
# Open the .h5 file and set attributes.
|
|
639
577
|
if not path.isfile(self.file):
|
|
640
578
|
# no file there
|
|
@@ -649,8 +587,8 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
649
587
|
self._load_timedata()
|
|
650
588
|
self._load_metadata()
|
|
651
589
|
|
|
652
|
-
@
|
|
653
|
-
def _load_shapes(self):
|
|
590
|
+
@observe('data')
|
|
591
|
+
def _load_shapes(self, event): # noqa ARG002
|
|
654
592
|
# Set :attr:`num_channels` and num_samples from :attr:`~acoular.sources.TimeSamples.data`.
|
|
655
593
|
if self.data is not None:
|
|
656
594
|
self.num_samples_total, self.num_channels_total = self.data.shape
|
|
@@ -666,8 +604,7 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
666
604
|
Generate blocks of valid time-domain data iteratively.
|
|
667
605
|
|
|
668
606
|
The :meth:`result` method is a Python generator that yields blocks of valid time-domain data
|
|
669
|
-
based on the specified :attr:`start` and :attr:`stop` indices and the valid channels.
|
|
670
|
-
can be calibrated if a calibration object, given by :attr:`calib`, is provided.
|
|
607
|
+
based on the specified :attr:`start` and :attr:`stop` indices and the valid channels.
|
|
671
608
|
|
|
672
609
|
Parameters
|
|
673
610
|
----------
|
|
@@ -687,15 +624,6 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
687
624
|
:obj:`OSError`
|
|
688
625
|
If no valid samples are available (i.e., :attr:`start` and :attr:`stop` indices result
|
|
689
626
|
in an empty range).
|
|
690
|
-
:obj:`ValueError`
|
|
691
|
-
If the :attr:`calibration data<calib>` is incompatible with the
|
|
692
|
-
:attr:`number of valid channels<num_channels>`.
|
|
693
|
-
|
|
694
|
-
Warnings
|
|
695
|
-
--------
|
|
696
|
-
A deprecation warning is raised if the calibration functionality is used directly in
|
|
697
|
-
:class:`MaskedTimeSamples`. Instead, the :class:`acoular.calib.Calib` class should be used
|
|
698
|
-
as a separate processing block.
|
|
699
627
|
|
|
700
628
|
Examples
|
|
701
629
|
--------
|
|
@@ -720,33 +648,15 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
720
648
|
sli = slice(self.start, self.stop).indices(self.num_samples_total)
|
|
721
649
|
i = sli[0]
|
|
722
650
|
stop = sli[1]
|
|
723
|
-
cal_factor = 1.0
|
|
724
651
|
if i >= stop:
|
|
725
652
|
msg = 'no samples available'
|
|
726
653
|
raise OSError(msg)
|
|
727
654
|
self._datachecksum # trigger checksum calculation # noqa: B018
|
|
728
|
-
if self.calib:
|
|
729
|
-
warn(
|
|
730
|
-
'The use of the calibration functionality in MaskedTimeSamples is deprecated and will be removed in \
|
|
731
|
-
Acoular 25.10. Use the Calib class as an additional processing block instead.',
|
|
732
|
-
DeprecationWarning,
|
|
733
|
-
stacklevel=2,
|
|
734
|
-
)
|
|
735
|
-
if self.calib.num_mics == self.num_channels_total:
|
|
736
|
-
cal_factor = self.calib.data[self.channels][newaxis]
|
|
737
|
-
elif self.calib.num_mics == self.num_channels:
|
|
738
|
-
cal_factor = self.calib.data[newaxis]
|
|
739
|
-
elif self.calib.num_mics == 0:
|
|
740
|
-
warn('No calibration data used.', Warning, stacklevel=2)
|
|
741
|
-
else:
|
|
742
|
-
msg = f'calibration data not compatible: {self.calib.num_mics:d}, {self.num_channels:d}'
|
|
743
|
-
raise ValueError(msg)
|
|
744
655
|
while i < stop:
|
|
745
|
-
yield self.data[i : min(i + num, stop)][:, self.channels]
|
|
656
|
+
yield self.data[i : min(i + num, stop)][:, self.channels]
|
|
746
657
|
i += num
|
|
747
658
|
|
|
748
659
|
|
|
749
|
-
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True)
|
|
750
660
|
class PointSource(SamplesGenerator):
|
|
751
661
|
"""
|
|
752
662
|
Define a fixed point source emitting a signal, intended for simulations.
|
|
@@ -758,9 +668,9 @@ class PointSource(SamplesGenerator):
|
|
|
758
668
|
|
|
759
669
|
See Also
|
|
760
670
|
--------
|
|
761
|
-
:class
|
|
762
|
-
:class
|
|
763
|
-
:class
|
|
671
|
+
:class:`~acoular.signals.SignalGenerator` : For defining custom emitted signals.
|
|
672
|
+
:class:`~acoular.microphones.MicGeom` : For specifying microphone geometries.
|
|
673
|
+
:class:`~acoular.environments.Environment` : For modeling sound propagation effects.
|
|
764
674
|
|
|
765
675
|
Notes
|
|
766
676
|
-----
|
|
@@ -825,8 +735,8 @@ class PointSource(SamplesGenerator):
|
|
|
825
735
|
mics = Instance(MicGeom, desc='microphone geometry')
|
|
826
736
|
|
|
827
737
|
def _validate_locations(self):
|
|
828
|
-
dist = self.env._r(array(self.loc).reshape((3, 1)), self.mics.pos)
|
|
829
|
-
if
|
|
738
|
+
dist = self.env._r(np.array(self.loc).reshape((3, 1)), self.mics.pos)
|
|
739
|
+
if np.any(dist < 1e-7):
|
|
830
740
|
warn('Source and microphone locations are identical.', Warning, stacklevel=2)
|
|
831
741
|
|
|
832
742
|
#: An :class:`~acoular.environments.Environment` or derived object providing sound propagation
|
|
@@ -904,31 +814,32 @@ class PointSource(SamplesGenerator):
|
|
|
904
814
|
If signal processing or propagation cannot be performed.
|
|
905
815
|
"""
|
|
906
816
|
self._validate_locations()
|
|
907
|
-
|
|
817
|
+
num_samples_estimate = self.num_samples + (self.start_t - self.start) * self.sample_freq
|
|
818
|
+
N = int(np.ceil(num_samples_estimate / num)) # number of output blocks
|
|
908
819
|
signal = self.signal.usignal(self.up)
|
|
909
|
-
out = empty((num, self.num_channels))
|
|
820
|
+
out = np.empty((num, self.num_channels))
|
|
910
821
|
# distances
|
|
911
|
-
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.pos).reshape(1, -1)
|
|
822
|
+
rm = self.env._r(np.array(self.loc).reshape((3, 1)), self.mics.pos).reshape(1, -1)
|
|
912
823
|
# emission time relative to start_t (in samples) for first sample
|
|
913
824
|
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq * self.up
|
|
914
825
|
|
|
915
826
|
if self.prepadding == 'zeros':
|
|
916
827
|
# number of blocks where signal behaviour is amended
|
|
917
|
-
pre = -int(
|
|
828
|
+
pre = -int(np.min(ind[0]) // (self.up * num))
|
|
918
829
|
# amend signal for first blocks
|
|
919
830
|
# if signal stops during prepadding, terminate
|
|
920
831
|
if pre >= N:
|
|
921
832
|
for _nb in range(N - 1):
|
|
922
|
-
|
|
833
|
+
_fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, True)
|
|
923
834
|
yield out
|
|
924
835
|
|
|
925
836
|
blocksize = self.num_samples % num or num
|
|
926
|
-
|
|
837
|
+
_fill_mic_signal_block(out, signal, rm, ind, blocksize, self.num_channels, self.up, True)
|
|
927
838
|
yield out[:blocksize]
|
|
928
839
|
return
|
|
929
840
|
else:
|
|
930
841
|
for _nb in range(pre):
|
|
931
|
-
|
|
842
|
+
_fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, True)
|
|
932
843
|
yield out
|
|
933
844
|
|
|
934
845
|
else:
|
|
@@ -936,12 +847,12 @@ class PointSource(SamplesGenerator):
|
|
|
936
847
|
|
|
937
848
|
# main generator
|
|
938
849
|
for _nb in range(N - pre - 1):
|
|
939
|
-
|
|
850
|
+
_fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, False)
|
|
940
851
|
yield out
|
|
941
852
|
|
|
942
853
|
# last block of variable size
|
|
943
854
|
blocksize = self.num_samples % num or num
|
|
944
|
-
|
|
855
|
+
_fill_mic_signal_block(out, signal, rm, ind, blocksize, self.num_channels, self.up, False)
|
|
945
856
|
yield out[:blocksize]
|
|
946
857
|
|
|
947
858
|
|
|
@@ -1025,9 +936,9 @@ class SphericalHarmonicSource(PointSource):
|
|
|
1025
936
|
lOrder=self.lOrder,
|
|
1026
937
|
direction=self.direction,
|
|
1027
938
|
mpos=self.mics.pos,
|
|
1028
|
-
sourceposition=array(self.loc),
|
|
939
|
+
sourceposition=np.array(self.loc),
|
|
1029
940
|
)
|
|
1030
|
-
return real(ifft(fft(signals, axis=0) * (Y_lm @ self.alpha), axis=0))
|
|
941
|
+
return np.real(ifft(fft(signals, axis=0) * (Y_lm @ self.alpha), axis=0))
|
|
1031
942
|
|
|
1032
943
|
def result(self, num=128):
|
|
1033
944
|
"""
|
|
@@ -1059,15 +970,15 @@ class SphericalHarmonicSource(PointSource):
|
|
|
1059
970
|
|
|
1060
971
|
signal = self.signal.usignal(self.up)
|
|
1061
972
|
# emission time relative to start_t (in samples) for first sample
|
|
1062
|
-
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.pos)
|
|
1063
|
-
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq + pi / 30
|
|
973
|
+
rm = self.env._r(np.array(self.loc).reshape((3, 1)), self.mics.pos)
|
|
974
|
+
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq + np.pi / 30
|
|
1064
975
|
i = 0
|
|
1065
976
|
n = self.num_samples
|
|
1066
|
-
out = empty((num, self.num_channels))
|
|
977
|
+
out = np.empty((num, self.num_channels))
|
|
1067
978
|
while n:
|
|
1068
979
|
n -= 1
|
|
1069
980
|
try:
|
|
1070
|
-
out[i] = signal[array(0.5 + ind * self.up, dtype=int64)] / rm
|
|
981
|
+
out[i] = signal[np.array(0.5 + ind * self.up, dtype=np.int64)] / rm
|
|
1071
982
|
ind += 1
|
|
1072
983
|
i += 1
|
|
1073
984
|
if i == num:
|
|
@@ -1090,8 +1001,8 @@ class MovingPointSource(PointSource):
|
|
|
1090
1001
|
|
|
1091
1002
|
See Also
|
|
1092
1003
|
--------
|
|
1093
|
-
:class
|
|
1094
|
-
:class
|
|
1004
|
+
:class:`~acoular.sources.PointSource` : For modeling stationary point sources.
|
|
1005
|
+
:class:`~acoular.trajectory.Trajectory` : For specifying source motion paths.
|
|
1095
1006
|
"""
|
|
1096
1007
|
|
|
1097
1008
|
#: Determines whether convective amplification is considered. When ``True``, the amplitude of
|
|
@@ -1126,6 +1037,51 @@ class MovingPointSource(PointSource):
|
|
|
1126
1037
|
def _get_digest(self):
|
|
1127
1038
|
return digest(self)
|
|
1128
1039
|
|
|
1040
|
+
def get_moving_direction(self, direction, time=0):
|
|
1041
|
+
"""
|
|
1042
|
+
Calculate the moving direction of the source along its trajectory.
|
|
1043
|
+
|
|
1044
|
+
This method computes the updated direction vector for the moving source, considering both
|
|
1045
|
+
translation along the :attr:`~MovingPointSource.trajectory` and rotation defined by the
|
|
1046
|
+
:attr:`reference vector<rvec>`. If the :attr:`reference vector<rvec>` is `(0, 0, 0)`, only
|
|
1047
|
+
translation is applied. Otherwise, the method incorporates rotation into the calculation.
|
|
1048
|
+
|
|
1049
|
+
Parameters
|
|
1050
|
+
----------
|
|
1051
|
+
direction : :class:`numpy.ndarray`
|
|
1052
|
+
The initial orientation of the source, specified as a three-dimensional array.
|
|
1053
|
+
time : :class:`float`, optional
|
|
1054
|
+
The time at which the :attr:`~MovingPointSource.trajectory` position and velocity are
|
|
1055
|
+
evaluated. Defaults to ``0``.
|
|
1056
|
+
|
|
1057
|
+
Returns
|
|
1058
|
+
-------
|
|
1059
|
+
:class:`numpy.ndarray`
|
|
1060
|
+
The updated direction vector of the moving source after translation and, if applicable,
|
|
1061
|
+
rotation. The output is a three-dimensional array.
|
|
1062
|
+
|
|
1063
|
+
Notes
|
|
1064
|
+
-----
|
|
1065
|
+
- The method computes the translation direction vector based on the
|
|
1066
|
+
:attr:`~MovingPointSource.trajectory`'s velocity at the specified time.
|
|
1067
|
+
- If the :attr:`reference vector<rvec>` is non-zero, the method constructs a rotation matrix
|
|
1068
|
+
to compute the new source direction based on the
|
|
1069
|
+
:attr:`~MovingPointSource.trajectory`'s motion and the :attr:`reference vector<rvec>`.
|
|
1070
|
+
- The rotation matrix ensures that the new orientation adheres to the right-hand rule and
|
|
1071
|
+
remains orthogonal.
|
|
1072
|
+
"""
|
|
1073
|
+
trajg1 = np.array(self.trajectory.location(time, der=1))[:, 0][:, np.newaxis]
|
|
1074
|
+
rflag = (self.rvec == 0).all() # flag translation vs. rotation
|
|
1075
|
+
if rflag:
|
|
1076
|
+
return direction
|
|
1077
|
+
dx = np.array(trajg1.T) # direction vector (new x-axis)
|
|
1078
|
+
dy = np.cross(self.rvec, dx) # new y-axis
|
|
1079
|
+
dz = np.cross(dx, dy) # new z-axis
|
|
1080
|
+
RM = np.array((dx, dy, dz)).T # rotation matrix
|
|
1081
|
+
RM /= np.sqrt((RM * RM).sum(0)) # column normalized
|
|
1082
|
+
newdir = np.dot(RM, direction)
|
|
1083
|
+
return np.cross(newdir[:, 0].T, self.rvec.T).T
|
|
1084
|
+
|
|
1129
1085
|
def result(self, num=128):
|
|
1130
1086
|
"""
|
|
1131
1087
|
Generate the output signal at microphones in blocks, accounting for source motion.
|
|
@@ -1164,46 +1120,48 @@ class MovingPointSource(PointSource):
|
|
|
1164
1120
|
# from the end of the calculated signal.
|
|
1165
1121
|
|
|
1166
1122
|
signal = self.signal.usignal(self.up)
|
|
1167
|
-
out = empty((num, self.num_channels))
|
|
1168
1123
|
# shortcuts and initial values
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1124
|
+
num_mics = self.num_channels
|
|
1125
|
+
mpos = self.mics.pos[:, :, np.newaxis]
|
|
1126
|
+
t = self.start + np.ones(num_mics)[:, np.newaxis] * np.arange(num) / self.sample_freq
|
|
1172
1127
|
epslim = 0.1 / self.up / self.sample_freq
|
|
1173
1128
|
c0 = self.env.c
|
|
1174
1129
|
tr = self.trajectory
|
|
1175
1130
|
n = self.num_samples
|
|
1176
|
-
while n:
|
|
1177
|
-
|
|
1178
|
-
eps = ones(m.num_mics)
|
|
1131
|
+
while n > 0:
|
|
1132
|
+
eps = np.ones_like(t) # init discrepancy in time
|
|
1179
1133
|
te = t.copy() # init emission time = receiving time
|
|
1180
1134
|
j = 0
|
|
1181
1135
|
# Newton-Rhapson iteration
|
|
1182
1136
|
while abs(eps).max() > epslim and j < 100:
|
|
1183
|
-
loc = array(tr.location(te))
|
|
1184
|
-
rm = loc -
|
|
1185
|
-
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
1186
|
-
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1187
|
-
der = array(tr.location(te, der=1))
|
|
1137
|
+
loc = np.array(tr.location(te.flatten())).reshape((3, num_mics, -1))
|
|
1138
|
+
rm = loc - mpos # distance vectors to microphones
|
|
1139
|
+
rm = np.sqrt((rm * rm).sum(0)) # absolute distance
|
|
1140
|
+
loc /= np.sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1141
|
+
der = np.array(tr.location(te.flatten(), der=1)).reshape((3, num_mics, -1))
|
|
1188
1142
|
Mr = (der * loc).sum(0) / c0 # radial Mach number
|
|
1189
|
-
eps = (te + rm / c0 - t) / (1 + Mr) # discrepancy in time
|
|
1143
|
+
eps[:] = (te + rm / c0 - t) / (1 + Mr) # discrepancy in time
|
|
1190
1144
|
te -= eps
|
|
1191
1145
|
j += 1 # iteration count
|
|
1192
|
-
t +=
|
|
1146
|
+
t += num / self.sample_freq
|
|
1193
1147
|
# emission time relative to start time
|
|
1194
1148
|
ind = (te - self.start_t + self.start) * self.sample_freq
|
|
1195
1149
|
if self.conv_amp:
|
|
1196
1150
|
rm *= (1 - Mr) ** 2
|
|
1197
1151
|
try:
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1152
|
+
ind = np.array(0.5 + ind * self.up, dtype=np.int64)
|
|
1153
|
+
out = (signal[ind] / rm).T
|
|
1154
|
+
yield out[:n]
|
|
1155
|
+
except IndexError: # last incomplete frame
|
|
1156
|
+
signal_length = signal.shape[0]
|
|
1157
|
+
# Filter ind to exclude columns containing values greater than signal_length
|
|
1158
|
+
mask = (ind < signal_length).all(axis=0)
|
|
1159
|
+
out = (signal[ind[:, mask]] / rm[:, mask]).T
|
|
1160
|
+
# If out is not empty, yield it
|
|
1161
|
+
if out.size > 0:
|
|
1162
|
+
yield out[:n]
|
|
1204
1163
|
break
|
|
1205
|
-
|
|
1206
|
-
yield out[:i]
|
|
1164
|
+
n -= num
|
|
1207
1165
|
|
|
1208
1166
|
|
|
1209
1167
|
class PointSourceDipole(PointSource):
|
|
@@ -1218,7 +1176,7 @@ class PointSourceDipole(PointSource):
|
|
|
1218
1176
|
|
|
1219
1177
|
See Also
|
|
1220
1178
|
--------
|
|
1221
|
-
:class
|
|
1179
|
+
:class:`~acoular.sources.PointSource` : For modeling stationary point sources.
|
|
1222
1180
|
|
|
1223
1181
|
Notes
|
|
1224
1182
|
-----
|
|
@@ -1293,10 +1251,10 @@ class PointSourceDipole(PointSource):
|
|
|
1293
1251
|
|
|
1294
1252
|
mpos = self.mics.pos
|
|
1295
1253
|
# position of the dipole as (3,1) vector
|
|
1296
|
-
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
1254
|
+
loc = np.array(self.loc, dtype=float).reshape((3, 1))
|
|
1297
1255
|
# direction vector from tuple
|
|
1298
|
-
direc = array(self.direction, dtype=float) * 1e-5
|
|
1299
|
-
direc_mag = sqrt(dot(direc, direc))
|
|
1256
|
+
direc = np.array(self.direction, dtype=float) * 1e-5
|
|
1257
|
+
direc_mag = np.sqrt(np.dot(direc, direc))
|
|
1300
1258
|
|
|
1301
1259
|
# normed direction vector
|
|
1302
1260
|
direc_n = direc / direc_mag
|
|
@@ -1310,7 +1268,7 @@ class PointSourceDipole(PointSource):
|
|
|
1310
1268
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
1311
1269
|
|
|
1312
1270
|
signal = self.signal.usignal(self.up)
|
|
1313
|
-
out = empty((num, self.num_channels))
|
|
1271
|
+
out = np.empty((num, self.num_channels))
|
|
1314
1272
|
|
|
1315
1273
|
# distance from dipole center to microphones
|
|
1316
1274
|
rm = self.env._r(loc, mpos)
|
|
@@ -1333,8 +1291,8 @@ class PointSourceDipole(PointSource):
|
|
|
1333
1291
|
rm
|
|
1334
1292
|
/ dist
|
|
1335
1293
|
* (
|
|
1336
|
-
signal[array(0.5 + ind1 * self.up, dtype=int64)] / rm1
|
|
1337
|
-
- signal[array(0.5 + ind2 * self.up, dtype=int64)] / rm2
|
|
1294
|
+
signal[np.array(0.5 + ind1 * self.up, dtype=np.int64)] / rm1
|
|
1295
|
+
- signal[np.array(0.5 + ind2 * self.up, dtype=np.int64)] / rm2
|
|
1338
1296
|
)
|
|
1339
1297
|
)
|
|
1340
1298
|
ind1 += 1.0
|
|
@@ -1347,7 +1305,8 @@ class PointSourceDipole(PointSource):
|
|
|
1347
1305
|
except IndexError:
|
|
1348
1306
|
break
|
|
1349
1307
|
|
|
1350
|
-
|
|
1308
|
+
if i > 0:
|
|
1309
|
+
yield out[:i]
|
|
1351
1310
|
|
|
1352
1311
|
|
|
1353
1312
|
class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
@@ -1367,8 +1326,8 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1367
1326
|
|
|
1368
1327
|
See Also
|
|
1369
1328
|
--------
|
|
1370
|
-
:class
|
|
1371
|
-
:class
|
|
1329
|
+
:class:`~acoular.sources.PointSourceDipole` : For stationary dipole sources.
|
|
1330
|
+
:class:`~acoular.sources.MovingPointSource` :
|
|
1372
1331
|
For moving point sources without dipole characteristics.
|
|
1373
1332
|
"""
|
|
1374
1333
|
|
|
@@ -1389,7 +1348,7 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1389
1348
|
#: A reference vector, perpendicular to the x and y-axis of moving source, defining the axis of
|
|
1390
1349
|
#: rotation for the dipole directivity. If set to ``(0, 0, 0)``, the dipole is only translated
|
|
1391
1350
|
#: along the :attr:`~MovingPointSource.trajectory` without rotation. Default is ``(0, 0, 0)``.
|
|
1392
|
-
rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0)), desc='reference vector')
|
|
1351
|
+
rvec = CArray(dtype=float, shape=(3,), value=np.array((0, 0, 0)), desc='reference vector')
|
|
1393
1352
|
|
|
1394
1353
|
@cached_property
|
|
1395
1354
|
def _get_digest(self):
|
|
@@ -1431,70 +1390,25 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1431
1390
|
terminates when the time discrepancy (``eps``) is below a threshold (``epslim``)
|
|
1432
1391
|
or after 100 iterations.
|
|
1433
1392
|
"""
|
|
1434
|
-
eps = ones(self.mics.num_mics)
|
|
1393
|
+
eps = np.ones(self.mics.num_mics)
|
|
1435
1394
|
epslim = 0.1 / self.up / self.sample_freq
|
|
1436
1395
|
te = t.copy() # init emission time = receiving time
|
|
1437
1396
|
j = 0
|
|
1438
1397
|
# Newton-Rhapson iteration
|
|
1439
1398
|
while abs(eps).max() > epslim and j < 100:
|
|
1440
|
-
xs = array(self.trajectory.location(te))
|
|
1399
|
+
xs = np.array(self.trajectory.location(te))
|
|
1441
1400
|
loc = xs.copy()
|
|
1442
1401
|
loc += direction
|
|
1443
1402
|
rm = loc - self.mics.pos # distance vectors to microphones
|
|
1444
|
-
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
1445
|
-
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1446
|
-
der = array(self.trajectory.location(te, der=1))
|
|
1403
|
+
rm = np.sqrt((rm * rm).sum(0)) # absolute distance
|
|
1404
|
+
loc /= np.sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1405
|
+
der = np.array(self.trajectory.location(te, der=1))
|
|
1447
1406
|
Mr = (der * loc).sum(0) / self.env.c # radial Mach number
|
|
1448
1407
|
eps = (te + rm / self.env.c - t) / (1 + Mr) # discrepancy in time
|
|
1449
1408
|
te -= eps
|
|
1450
1409
|
j += 1 # iteration count
|
|
1451
1410
|
return te, rm, Mr, xs
|
|
1452
1411
|
|
|
1453
|
-
def get_moving_direction(self, direction, time=0):
|
|
1454
|
-
"""
|
|
1455
|
-
Calculate the moving direction of the dipole source along its trajectory.
|
|
1456
|
-
|
|
1457
|
-
This method computes the updated direction vector for the dipole source, considering both
|
|
1458
|
-
translation along the trajectory and rotation defined by the :attr:`reference vector<rvec>`.
|
|
1459
|
-
If the reference vector is ``(0, 0, 0)``, only translation is applied. Otherwise, the method
|
|
1460
|
-
incorporates rotation into the calculation.
|
|
1461
|
-
|
|
1462
|
-
Parameters
|
|
1463
|
-
----------
|
|
1464
|
-
direction : :class:`numpy.ndarray`
|
|
1465
|
-
The initial direction vector of the dipole, specified as a 3-element
|
|
1466
|
-
array representing the orientation of the dipole lobes.
|
|
1467
|
-
time : :class:`float`, optional
|
|
1468
|
-
The time at which the trajectory position and velocity are evaluated. Defaults to ``0``.
|
|
1469
|
-
|
|
1470
|
-
Returns
|
|
1471
|
-
-------
|
|
1472
|
-
:class:`numpy.ndarray`
|
|
1473
|
-
The updated direction vector of the dipole source after translation
|
|
1474
|
-
and, if applicable, rotation. The output is a 3-element array.
|
|
1475
|
-
|
|
1476
|
-
Notes
|
|
1477
|
-
-----
|
|
1478
|
-
- The method computes the translation direction vector based on the trajectory's velocity at
|
|
1479
|
-
the specified time.
|
|
1480
|
-
- If the :attr:`reference vector<rvec>` is non-zero, the method constructs a rotation matrix
|
|
1481
|
-
to compute the new dipole direction based on the trajectory's motion and the
|
|
1482
|
-
reference vector.
|
|
1483
|
-
- The rotation matrix ensures that the new dipole orientation adheres
|
|
1484
|
-
to the right-hand rule and remains orthogonal.
|
|
1485
|
-
"""
|
|
1486
|
-
trajg1 = array(self.trajectory.location(time, der=1))[:, 0][:, newaxis]
|
|
1487
|
-
rflag = (self.rvec == 0).all() # flag translation vs. rotation
|
|
1488
|
-
if rflag:
|
|
1489
|
-
return direction
|
|
1490
|
-
dx = array(trajg1.T) # direction vector (new x-axis)
|
|
1491
|
-
dy = cross(self.rvec, dx) # new y-axis
|
|
1492
|
-
dz = cross(dx, dy) # new z-axis
|
|
1493
|
-
RM = array((dx, dy, dz)).T # rotation matrix
|
|
1494
|
-
RM /= sqrt((RM * RM).sum(0)) # column normalized
|
|
1495
|
-
newdir = dot(RM, direction)
|
|
1496
|
-
return cross(newdir[:, 0].T, self.rvec.T).T
|
|
1497
|
-
|
|
1498
1412
|
def result(self, num=128):
|
|
1499
1413
|
"""
|
|
1500
1414
|
Generate the output signal at microphones in blocks.
|
|
@@ -1521,8 +1435,8 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1521
1435
|
mpos = self.mics.pos
|
|
1522
1436
|
|
|
1523
1437
|
# direction vector from tuple
|
|
1524
|
-
direc = array(self.direction, dtype=float) * 1e-5
|
|
1525
|
-
direc_mag = sqrt(dot(direc, direc))
|
|
1438
|
+
direc = np.array(self.direction, dtype=float) * 1e-5
|
|
1439
|
+
direc_mag = np.sqrt(np.dot(direc, direc))
|
|
1526
1440
|
# normed direction vector
|
|
1527
1441
|
direc_n = direc / direc_mag
|
|
1528
1442
|
c = self.env.c
|
|
@@ -1533,10 +1447,10 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1533
1447
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
1534
1448
|
|
|
1535
1449
|
signal = self.signal.usignal(self.up)
|
|
1536
|
-
out = empty((num, self.num_channels))
|
|
1450
|
+
out = np.empty((num, self.num_channels))
|
|
1537
1451
|
# shortcuts and initial values
|
|
1538
1452
|
m = self.mics
|
|
1539
|
-
t = self.start * ones(m.num_mics)
|
|
1453
|
+
t = self.start * np.ones(m.num_mics)
|
|
1540
1454
|
|
|
1541
1455
|
i = 0
|
|
1542
1456
|
n = self.num_samples
|
|
@@ -1545,7 +1459,7 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1545
1459
|
te, rm, Mr, locs = self.get_emission_time(t, 0)
|
|
1546
1460
|
t += 1.0 / self.sample_freq
|
|
1547
1461
|
# location of the center
|
|
1548
|
-
loc = array(self.trajectory.location(te), dtype=float)[:, 0][:, newaxis]
|
|
1462
|
+
loc = np.array(self.trajectory.location(te), dtype=float)[:, 0][:, np.newaxis]
|
|
1549
1463
|
# distance of the dipoles from the center
|
|
1550
1464
|
diff = self.get_moving_direction(dir2, te)
|
|
1551
1465
|
|
|
@@ -1564,8 +1478,8 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1564
1478
|
rm
|
|
1565
1479
|
/ dist
|
|
1566
1480
|
* (
|
|
1567
|
-
signal[array(0.5 + ind * self.up, dtype=int64)] / rm1
|
|
1568
|
-
- signal[array(0.5 + ind * self.up, dtype=int64)] / rm2
|
|
1481
|
+
signal[np.array(0.5 + ind * self.up, dtype=np.int64)] / rm1
|
|
1482
|
+
- signal[np.array(0.5 + ind * self.up, dtype=np.int64)] / rm2
|
|
1569
1483
|
)
|
|
1570
1484
|
)
|
|
1571
1485
|
i += 1
|
|
@@ -1574,7 +1488,9 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1574
1488
|
i = 0
|
|
1575
1489
|
except IndexError:
|
|
1576
1490
|
break
|
|
1577
|
-
|
|
1491
|
+
|
|
1492
|
+
if i > 0:
|
|
1493
|
+
yield out[:i]
|
|
1578
1494
|
|
|
1579
1495
|
|
|
1580
1496
|
class LineSource(PointSource):
|
|
@@ -1595,7 +1511,7 @@ class LineSource(PointSource):
|
|
|
1595
1511
|
|
|
1596
1512
|
See Also
|
|
1597
1513
|
--------
|
|
1598
|
-
:class
|
|
1514
|
+
:class:`~acoular.sources.PointSource` : For modeling stationary point sources.
|
|
1599
1515
|
|
|
1600
1516
|
Notes
|
|
1601
1517
|
-----
|
|
@@ -1659,24 +1575,24 @@ class LineSource(PointSource):
|
|
|
1659
1575
|
mpos = self.mics.pos
|
|
1660
1576
|
|
|
1661
1577
|
# direction vector from tuple
|
|
1662
|
-
direc = array(self.direction, dtype=float)
|
|
1578
|
+
direc = np.array(self.direction, dtype=float)
|
|
1663
1579
|
# normed direction vector
|
|
1664
|
-
direc_n = direc / norm(direc)
|
|
1580
|
+
direc_n = direc / spla.norm(direc)
|
|
1665
1581
|
c = self.env.c
|
|
1666
1582
|
|
|
1667
1583
|
# distance between monopoles in the line
|
|
1668
1584
|
dist = self.length / self.num_sources
|
|
1669
1585
|
|
|
1670
1586
|
# blocwise output
|
|
1671
|
-
out = zeros((num, self.num_channels))
|
|
1587
|
+
out = np.zeros((num, self.num_channels))
|
|
1672
1588
|
|
|
1673
1589
|
# distance from line start position to microphones
|
|
1674
|
-
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
1590
|
+
loc = np.array(self.loc, dtype=float).reshape((3, 1))
|
|
1675
1591
|
|
|
1676
1592
|
# distances from monopoles in the line to microphones
|
|
1677
|
-
rms = empty((self.num_channels, self.num_sources))
|
|
1678
|
-
inds = empty((self.num_channels, self.num_sources))
|
|
1679
|
-
signals = empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1593
|
+
rms = np.empty((self.num_channels, self.num_sources))
|
|
1594
|
+
inds = np.empty((self.num_channels, self.num_sources))
|
|
1595
|
+
signals = np.empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1680
1596
|
# for every source - distances
|
|
1681
1597
|
for s in range(self.num_sources):
|
|
1682
1598
|
rms[:, s] = self.env._r((loc.T + direc_n * dist * s).T, mpos)
|
|
@@ -1693,18 +1609,19 @@ class LineSource(PointSource):
|
|
|
1693
1609
|
try:
|
|
1694
1610
|
for s in range(self.num_sources):
|
|
1695
1611
|
# sum sources
|
|
1696
|
-
out[i] += signals[s, array(0.5 + inds[:, s].T * self.up, dtype=int64)] / rms[:, s]
|
|
1612
|
+
out[i] += signals[s, np.array(0.5 + inds[:, s].T * self.up, dtype=np.int64)] / rms[:, s]
|
|
1697
1613
|
|
|
1698
1614
|
inds += 1.0
|
|
1699
1615
|
i += 1
|
|
1700
1616
|
if i == num:
|
|
1701
1617
|
yield out
|
|
1702
|
-
out = zeros((num, self.num_channels))
|
|
1618
|
+
out = np.zeros((num, self.num_channels))
|
|
1703
1619
|
i = 0
|
|
1704
1620
|
except IndexError:
|
|
1705
1621
|
break
|
|
1706
1622
|
|
|
1707
|
-
|
|
1623
|
+
if i > 0:
|
|
1624
|
+
yield out[:i]
|
|
1708
1625
|
|
|
1709
1626
|
|
|
1710
1627
|
class MovingLineSource(LineSource, MovingPointSource):
|
|
@@ -1724,10 +1641,10 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1724
1641
|
|
|
1725
1642
|
See Also
|
|
1726
1643
|
--------
|
|
1727
|
-
:class
|
|
1644
|
+
:class:`~acoular.sources.LineSource` :
|
|
1728
1645
|
For :class:`line sources<LineSource>` consisting of
|
|
1729
1646
|
:attr:`coherent or incoherent<LineSource.coherence>` monopoles.
|
|
1730
|
-
:class
|
|
1647
|
+
:class:`~acoular.sources.MovingPointSource` :
|
|
1731
1648
|
For moving point sources without dipole characteristics.
|
|
1732
1649
|
"""
|
|
1733
1650
|
|
|
@@ -1749,59 +1666,12 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1749
1666
|
#: rotation for the line source directivity. If set to ``(0, 0, 0)``, the line source is only
|
|
1750
1667
|
#: translated along the :attr:`~MovingPointSource.trajectory` without rotation. Default is
|
|
1751
1668
|
#: ``(0, 0, 0)``.
|
|
1752
|
-
rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0)), desc='reference vector')
|
|
1669
|
+
rvec = CArray(dtype=float, shape=(3,), value=np.array((0, 0, 0)), desc='reference vector')
|
|
1753
1670
|
|
|
1754
1671
|
@cached_property
|
|
1755
1672
|
def _get_digest(self):
|
|
1756
1673
|
return digest(self)
|
|
1757
1674
|
|
|
1758
|
-
def get_moving_direction(self, direction, time=0):
|
|
1759
|
-
"""
|
|
1760
|
-
Calculate the moving direction of the line source along its trajectory.
|
|
1761
|
-
|
|
1762
|
-
This method computes the updated direction vector for the line source,
|
|
1763
|
-
considering both translation along the :attr:`~MovingPointSource.trajectory` and rotation
|
|
1764
|
-
defined by the :attr:`reference vector<rvec>`. If the :attr:`reference vector<rvec>` is
|
|
1765
|
-
`(0, 0, 0)`, only translation is applied. Otherwise, the method incorporates rotation
|
|
1766
|
-
into the calculation.
|
|
1767
|
-
|
|
1768
|
-
Parameters
|
|
1769
|
-
----------
|
|
1770
|
-
direction : :class:`numpy.ndarray`
|
|
1771
|
-
The initial direction vector of the line source, specified as a
|
|
1772
|
-
3-element array representing the orientation of the line.
|
|
1773
|
-
time : :class:`float`, optional
|
|
1774
|
-
The time at which the :attr:`~MovingPointSource.trajectory` position and velocity
|
|
1775
|
-
are evaluated. Defaults to ``0``.
|
|
1776
|
-
|
|
1777
|
-
Returns
|
|
1778
|
-
-------
|
|
1779
|
-
:class:`numpy.ndarray`
|
|
1780
|
-
The updated direction vector of the line source after translation and,
|
|
1781
|
-
if applicable, rotation. The output is a 3-element array.
|
|
1782
|
-
|
|
1783
|
-
Notes
|
|
1784
|
-
-----
|
|
1785
|
-
- The method computes the translation direction vector based on the
|
|
1786
|
-
:attr:`~MovingPointSource.trajectory`'s velocity at the specified time.
|
|
1787
|
-
- If the :attr:`reference vector<rvec>` is non-zero, the method constructs a
|
|
1788
|
-
rotation matrix to compute the new line source direction based on the
|
|
1789
|
-
:attr:`~MovingPointSource.trajectory`'s motion and the :attr:`reference vector<rvec>`.
|
|
1790
|
-
- The rotation matrix ensures that the new orientation adheres to the
|
|
1791
|
-
right-hand rule and remains orthogonal.
|
|
1792
|
-
"""
|
|
1793
|
-
trajg1 = array(self.trajectory.location(time, der=1))[:, 0][:, newaxis]
|
|
1794
|
-
rflag = (self.rvec == 0).all() # flag translation vs. rotation
|
|
1795
|
-
if rflag:
|
|
1796
|
-
return direction
|
|
1797
|
-
dx = array(trajg1.T) # direction vector (new x-axis)
|
|
1798
|
-
dy = cross(self.rvec, dx) # new y-axis
|
|
1799
|
-
dz = cross(dx, dy) # new z-axis
|
|
1800
|
-
RM = array((dx, dy, dz)).T # rotation matrix
|
|
1801
|
-
RM /= sqrt((RM * RM).sum(0)) # column normalized
|
|
1802
|
-
newdir = dot(RM, direction)
|
|
1803
|
-
return cross(newdir[:, 0].T, self.rvec.T).T
|
|
1804
|
-
|
|
1805
1675
|
def get_emission_time(self, t, direction):
|
|
1806
1676
|
"""
|
|
1807
1677
|
Calculate the emission time for a moving line source based on its trajectory.
|
|
@@ -1844,19 +1714,19 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1844
1714
|
- The method iterates until the difference between the computed emission time and
|
|
1845
1715
|
the current time is sufficiently small (within a defined threshold).
|
|
1846
1716
|
"""
|
|
1847
|
-
eps = ones(self.mics.num_mics)
|
|
1717
|
+
eps = np.ones(self.mics.num_mics)
|
|
1848
1718
|
epslim = 0.1 / self.up / self.sample_freq
|
|
1849
1719
|
te = t.copy() # init emission time = receiving time
|
|
1850
1720
|
j = 0
|
|
1851
1721
|
# Newton-Rhapson iteration
|
|
1852
1722
|
while abs(eps).max() > epslim and j < 100:
|
|
1853
|
-
xs = array(self.trajectory.location(te))
|
|
1723
|
+
xs = np.array(self.trajectory.location(te))
|
|
1854
1724
|
loc = xs.copy()
|
|
1855
1725
|
loc += direction
|
|
1856
1726
|
rm = loc - self.mics.pos # distance vectors to microphones
|
|
1857
|
-
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
1858
|
-
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1859
|
-
der = array(self.trajectory.location(te, der=1))
|
|
1727
|
+
rm = np.sqrt((rm * rm).sum(0)) # absolute distance
|
|
1728
|
+
loc /= np.sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1729
|
+
der = np.array(self.trajectory.location(te, der=1))
|
|
1860
1730
|
Mr = (der * loc).sum(0) / self.env.c # radial Mach number
|
|
1861
1731
|
eps = (te + rm / self.env.c - t) / (1 + Mr) # discrepancy in time
|
|
1862
1732
|
te -= eps
|
|
@@ -1884,21 +1754,21 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1884
1754
|
mpos = self.mics.pos
|
|
1885
1755
|
|
|
1886
1756
|
# direction vector from tuple
|
|
1887
|
-
direc = array(self.direction, dtype=float)
|
|
1757
|
+
direc = np.array(self.direction, dtype=float)
|
|
1888
1758
|
# normed direction vector
|
|
1889
|
-
direc_n = direc / norm(direc)
|
|
1759
|
+
direc_n = direc / spla.norm(direc)
|
|
1890
1760
|
|
|
1891
1761
|
# distance between monopoles in the line
|
|
1892
1762
|
dist = self.length / self.num_sources
|
|
1893
1763
|
dir2 = (direc_n * dist).reshape((3, 1))
|
|
1894
1764
|
|
|
1895
1765
|
# blocwise output
|
|
1896
|
-
out = zeros((num, self.num_channels))
|
|
1766
|
+
out = np.zeros((num, self.num_channels))
|
|
1897
1767
|
|
|
1898
1768
|
# distances from monopoles in the line to microphones
|
|
1899
|
-
rms = empty((self.num_channels, self.num_sources))
|
|
1900
|
-
inds = empty((self.num_channels, self.num_sources))
|
|
1901
|
-
signals = empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1769
|
+
rms = np.empty((self.num_channels, self.num_sources))
|
|
1770
|
+
inds = np.empty((self.num_channels, self.num_sources))
|
|
1771
|
+
signals = np.empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1902
1772
|
# coherence
|
|
1903
1773
|
for s in range(self.num_sources):
|
|
1904
1774
|
# new seed for every source
|
|
@@ -1910,20 +1780,20 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1910
1780
|
|
|
1911
1781
|
# shortcuts and initial values
|
|
1912
1782
|
m = self.mics
|
|
1913
|
-
t = self.start * ones(m.num_mics)
|
|
1783
|
+
t = self.start * np.ones(m.num_mics)
|
|
1914
1784
|
i = 0
|
|
1915
1785
|
n = self.num_samples
|
|
1916
1786
|
while n:
|
|
1917
1787
|
n -= 1
|
|
1918
1788
|
t += 1.0 / self.sample_freq
|
|
1919
1789
|
te1, rm1, Mr1, locs1 = self.get_emission_time(t, 0)
|
|
1920
|
-
# trajg1 = array(self.trajectory.location( te1, der=1))[:,0][:,newaxis]
|
|
1790
|
+
# trajg1 = np.array(self.trajectory.location( te1, der=1))[:,0][:,np.newaxis]
|
|
1921
1791
|
|
|
1922
1792
|
# get distance and ind for every source in the line
|
|
1923
1793
|
for s in range(self.num_sources):
|
|
1924
1794
|
diff = self.get_moving_direction(dir2, te1)
|
|
1925
|
-
te, rm, Mr, locs = self.get_emission_time(t, tile((diff * s).T, (self.num_channels, 1)).T)
|
|
1926
|
-
loc = array(self.trajectory.location(te), dtype=float)[:, 0][:, newaxis]
|
|
1795
|
+
te, rm, Mr, locs = self.get_emission_time(t, np.tile((diff * s).T, (self.num_channels, 1)).T)
|
|
1796
|
+
loc = np.array(self.trajectory.location(te), dtype=float)[:, 0][:, np.newaxis]
|
|
1927
1797
|
diff = self.get_moving_direction(dir2, te)
|
|
1928
1798
|
rms[:, s] = self.env._r((loc + diff * s), mpos)
|
|
1929
1799
|
inds[:, s] = (te - self.start_t + self.start) * self.sample_freq
|
|
@@ -1935,19 +1805,20 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1935
1805
|
# subtract the second signal b/c of phase inversion
|
|
1936
1806
|
for s in range(self.num_sources):
|
|
1937
1807
|
# sum sources
|
|
1938
|
-
out[i] += signals[s, array(0.5 + inds[:, s].T * self.up, dtype=int64)] / rms[:, s]
|
|
1808
|
+
out[i] += signals[s, np.array(0.5 + inds[:, s].T * self.up, dtype=np.int64)] / rms[:, s]
|
|
1939
1809
|
|
|
1940
1810
|
i += 1
|
|
1941
1811
|
if i == num:
|
|
1942
1812
|
yield out
|
|
1943
|
-
out = zeros((num, self.num_channels))
|
|
1813
|
+
out = np.zeros((num, self.num_channels))
|
|
1944
1814
|
i = 0
|
|
1945
1815
|
except IndexError:
|
|
1946
1816
|
break
|
|
1947
|
-
|
|
1817
|
+
|
|
1818
|
+
if i > 0:
|
|
1819
|
+
yield out[:i]
|
|
1948
1820
|
|
|
1949
1821
|
|
|
1950
|
-
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True)
|
|
1951
1822
|
class UncorrelatedNoiseSource(SamplesGenerator):
|
|
1952
1823
|
"""
|
|
1953
1824
|
Simulate uncorrelated white or pink noise signals at multiple channels.
|
|
@@ -1959,8 +1830,8 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
1959
1830
|
|
|
1960
1831
|
See Also
|
|
1961
1832
|
--------
|
|
1962
|
-
:class
|
|
1963
|
-
:class
|
|
1833
|
+
:class:`~acoular.signals.SignalGenerator` : For defining noise types and properties.
|
|
1834
|
+
:class:`~acoular.microphones.MicGeom` : For specifying microphone geometries.
|
|
1964
1835
|
|
|
1965
1836
|
Notes
|
|
1966
1837
|
-----
|
|
@@ -2008,7 +1879,7 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2008
1879
|
#: Array of random seed values for generating uncorrelated noise at each channel. If left empty,
|
|
2009
1880
|
#: seeds will be automatically generated as ``np.arange(self.num_channels) + signal.seed``. The
|
|
2010
1881
|
#: size of the array must match the :attr:`number of output channels<num_channels>`.
|
|
2011
|
-
seed = CArray(dtype=uint32, desc='random seed values')
|
|
1882
|
+
seed = CArray(dtype=np.uint32, desc='random seed values')
|
|
2012
1883
|
|
|
2013
1884
|
#: Number of output channels, automatically determined by the number of microphones
|
|
2014
1885
|
#: defined in the :attr:`mics` attribute. Corresponds to the number of uncorrelated noise
|
|
@@ -2086,14 +1957,14 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2086
1957
|
Noise = self.signal.__class__
|
|
2087
1958
|
# create or get the array of random seeds
|
|
2088
1959
|
if not self.seed.size > 0:
|
|
2089
|
-
seed = arange(self.num_channels) + self.signal.seed
|
|
1960
|
+
seed = np.arange(self.num_channels) + self.signal.seed
|
|
2090
1961
|
elif self.seed.shape == (self.num_channels,):
|
|
2091
1962
|
seed = self.seed
|
|
2092
1963
|
else:
|
|
2093
1964
|
msg = f'Seed array expected to be of shape ({self.num_channels:d},), but has shape {self.seed.shape}.'
|
|
2094
1965
|
raise ValueError(msg)
|
|
2095
1966
|
# create array with [num_channels] noise signal tracks
|
|
2096
|
-
signal = array(
|
|
1967
|
+
signal = np.array(
|
|
2097
1968
|
[
|
|
2098
1969
|
Noise(seed=s, num_samples=self.num_samples, sample_freq=self.sample_freq, rms=self.signal.rms).signal()
|
|
2099
1970
|
for s in seed
|
|
@@ -2111,7 +1982,6 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2111
1982
|
return
|
|
2112
1983
|
|
|
2113
1984
|
|
|
2114
|
-
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True)
|
|
2115
1985
|
class SourceMixer(SamplesGenerator):
|
|
2116
1986
|
"""
|
|
2117
1987
|
Combine signals from multiple sources by mixing their outputs.
|
|
@@ -2123,7 +1993,7 @@ class SourceMixer(SamplesGenerator):
|
|
|
2123
1993
|
|
|
2124
1994
|
See Also
|
|
2125
1995
|
--------
|
|
2126
|
-
:class
|
|
1996
|
+
:class:`~acoular.base.SamplesGenerator` : Base class for signal generators.
|
|
2127
1997
|
|
|
2128
1998
|
Notes
|
|
2129
1999
|
-----
|
|
@@ -2313,7 +2183,7 @@ class SourceMixer(SamplesGenerator):
|
|
|
2313
2183
|
gens = [i.result(num) for i in self.sources[1:]]
|
|
2314
2184
|
weights = self.weights.copy()
|
|
2315
2185
|
if weights.size == 0:
|
|
2316
|
-
weights = array([1.0 for j in range(len(self.sources))])
|
|
2186
|
+
weights = np.array([1.0 for j in range(len(self.sources))])
|
|
2317
2187
|
assert weights.shape[0] == len(self.sources)
|
|
2318
2188
|
for temp in self.sources[0].result(num):
|
|
2319
2189
|
temp *= weights[0]
|
|
@@ -2342,7 +2212,7 @@ class PointSourceConvolve(PointSource):
|
|
|
2342
2212
|
See Also
|
|
2343
2213
|
--------
|
|
2344
2214
|
:class:`PointSource` : Base class for point sources.
|
|
2345
|
-
:class
|
|
2215
|
+
:class:`~acoular.tprocess.TimeConvolve` : Class used for performing time-domain convolution.
|
|
2346
2216
|
|
|
2347
2217
|
Notes
|
|
2348
2218
|
-----
|
|
@@ -2441,7 +2311,7 @@ class PointSourceConvolve(PointSource):
|
|
|
2441
2311
|
- Convolution is performed using the :class:`~acoular.tprocess.TimeConvolve` class
|
|
2442
2312
|
to ensure efficiency.
|
|
2443
2313
|
"""
|
|
2444
|
-
data = repeat(self.signal.signal()[:, newaxis], self.mics.num_mics, axis=1)
|
|
2314
|
+
data = np.repeat(self.signal.signal()[:, np.newaxis], self.mics.num_mics, axis=1)
|
|
2445
2315
|
source = TimeSamples(
|
|
2446
2316
|
data=data,
|
|
2447
2317
|
sample_freq=self.sample_freq,
|