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