acoular 25.7__py3-none-any.whl → 26.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acoular/aiaa/aiaa.py +8 -10
- acoular/base.py +13 -16
- acoular/calib.py +25 -24
- acoular/configuration.py +2 -2
- acoular/demo/__init__.py +97 -9
- acoular/demo/__main__.py +37 -0
- acoular/environments.py +119 -130
- acoular/fbeamform.py +438 -440
- acoular/fprocess.py +18 -13
- acoular/grids.py +122 -301
- acoular/h5cache.py +5 -1
- acoular/h5files.py +96 -9
- acoular/microphones.py +30 -35
- acoular/process.py +14 -25
- acoular/sdinput.py +9 -14
- acoular/signals.py +36 -34
- acoular/sources.py +263 -380
- acoular/spectra.py +60 -80
- acoular/tbeamform.py +242 -224
- acoular/tools/helpers.py +25 -33
- acoular/tools/metrics.py +5 -10
- acoular/tools/utils.py +168 -0
- acoular/tprocess.py +248 -271
- acoular/trajectory.py +5 -6
- acoular/version.py +2 -2
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/METADATA +54 -105
- acoular-26.1.dist-info/RECORD +56 -0
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/WHEEL +1 -1
- acoular/demo/acoular_demo.py +0 -135
- acoular-25.7.dist-info/RECORD +0 -56
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.7.dist-info → acoular-26.1.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,33 +37,10 @@ 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.
|
|
60
|
-
from scipy.special import sph_harm, spherical_jn, spherical_yn
|
|
43
|
+
from scipy.special import sph_harm_y, spherical_jn, spherical_yn
|
|
61
44
|
from traits.api import (
|
|
62
45
|
Any,
|
|
63
46
|
Bool,
|
|
@@ -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
|
|
|
@@ -279,13 +258,13 @@ def get_modes(lOrder, direction, mpos, sourceposition=None): # noqa: N803
|
|
|
279
258
|
--------
|
|
280
259
|
:func:`get_radiation_angles` :
|
|
281
260
|
Computes azimuth and elevation angles between microphones and the source.
|
|
282
|
-
:obj:`scipy.special.
|
|
261
|
+
:obj:`scipy.special.sph_harm_y` : Computes spherical harmonic values.
|
|
283
262
|
|
|
284
263
|
Notes
|
|
285
264
|
-----
|
|
286
265
|
- The azimuth (``azi``) and elevation (``ele``) angles between the microphones and the source
|
|
287
266
|
are calculated using the :func:`get_radiation_angles` function.
|
|
288
|
-
- Spherical harmonics (``
|
|
267
|
+
- Spherical harmonics (``sph_harm_y``) are computed for each mode ``(l, m)``, where ``l`` is the
|
|
289
268
|
degree (ranging from ``0`` to ``lOrder``) and ``m`` is the order
|
|
290
269
|
(ranging from ``-l`` to ``+l``).
|
|
291
270
|
- For negative orders (`m < 0`), the conjugate of the spherical harmonic is computed and scaled
|
|
@@ -305,20 +284,19 @@ 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):
|
|
314
|
-
modes[:, i] =
|
|
293
|
+
modes[:, i] = sph_harm_y(m, lidx, ele, azi)
|
|
315
294
|
if m < 0:
|
|
316
295
|
modes[:, i] = modes[:, i].conj() * 1j
|
|
317
296
|
i += 1
|
|
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
|
|
|
@@ -370,40 +347,34 @@ class TimeSamples(SamplesGenerator):
|
|
|
370
347
|
"""
|
|
371
348
|
|
|
372
349
|
#: Full path to the ``.h5`` file containing time-domain data.
|
|
373
|
-
file = Union(None, File(filter=['*.h5'], exists=True)
|
|
350
|
+
file = Union(None, File(filter=['*.h5'], exists=True))
|
|
374
351
|
|
|
375
352
|
#: Basename of the ``.h5`` file, set automatically from the :attr:`file` attribute.
|
|
376
|
-
basename = Property(depends_on=['file']
|
|
377
|
-
|
|
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')
|
|
353
|
+
basename = Property(depends_on=['file'])
|
|
381
354
|
|
|
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
|
-
num_channels = CInt(0
|
|
357
|
+
num_channels = CInt(0)
|
|
385
358
|
|
|
386
359
|
#: Total number of time-domain samples, set automatically based on the :attr:`loaded data<file>`
|
|
387
360
|
#: or :attr:`specified array<data>`.
|
|
388
|
-
num_samples = CInt(0
|
|
361
|
+
num_samples = CInt(0)
|
|
389
362
|
|
|
390
363
|
#: A 2D NumPy array containing the time-domain data, shape (:attr:`num_samples`,
|
|
391
364
|
#: :attr:`num_channels`).
|
|
392
|
-
data = Any(transient=True
|
|
365
|
+
data = Any(transient=True)
|
|
393
366
|
|
|
394
367
|
#: HDF5 file object.
|
|
395
368
|
h5f = Instance(H5FileBase, transient=True)
|
|
396
369
|
|
|
397
370
|
#: Metadata loaded from the HDF5 file, if available.
|
|
398
|
-
metadata = Dict(
|
|
371
|
+
metadata = Dict()
|
|
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
|
-----
|
|
@@ -579,38 +516,34 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
579
516
|
"""
|
|
580
517
|
|
|
581
518
|
#: Index of the first sample to be considered valid. Default is ``0``.
|
|
582
|
-
start = CInt(0
|
|
519
|
+
start = CInt(0)
|
|
583
520
|
|
|
584
521
|
#: Index of the last sample to be considered valid. If ``None``, all remaining samples from the
|
|
585
522
|
#: :attr:`start` index onward are considered valid. Default is ``None``.
|
|
586
|
-
stop = Union(None, CInt
|
|
523
|
+
stop = Union(None, CInt)
|
|
587
524
|
|
|
588
525
|
#: List of channel indices to be excluded from processing. Default is ``[]``.
|
|
589
|
-
invalid_channels = List(int
|
|
526
|
+
invalid_channels = List(int)
|
|
590
527
|
|
|
591
528
|
#: A mask or index array representing valid channels. Automatically updated based on the
|
|
592
529
|
#: :attr:`invalid_channels` and :attr:`num_channels_total` attributes.
|
|
593
|
-
channels = Property(depends_on=['invalid_channels', 'num_channels_total']
|
|
530
|
+
channels = Property(depends_on=['invalid_channels', 'num_channels_total'])
|
|
594
531
|
|
|
595
532
|
#: Total number of input channels, including invalid channels. (read-only).
|
|
596
|
-
num_channels_total = CInt(0
|
|
533
|
+
num_channels_total = CInt(0)
|
|
597
534
|
|
|
598
535
|
#: Total number of samples, including invalid samples. (read-only).
|
|
599
|
-
num_samples_total = CInt(0
|
|
536
|
+
num_samples_total = CInt(0)
|
|
600
537
|
|
|
601
538
|
#: Number of valid input channels after excluding :attr:`invalid_channels`. (read-only)
|
|
602
|
-
num_channels = Property(
|
|
603
|
-
depends_on=['invalid_channels', 'num_channels_total'], desc='number of valid input channels'
|
|
604
|
-
)
|
|
539
|
+
num_channels = Property(depends_on=['invalid_channels', 'num_channels_total'])
|
|
605
540
|
|
|
606
541
|
#: Number of valid time-domain samples, based on :attr:`start` and :attr:`stop` indices.
|
|
607
542
|
#: (read-only)
|
|
608
|
-
num_samples = Property(
|
|
609
|
-
depends_on=['start', 'stop', 'num_samples_total'], desc='number of valid samples per channel'
|
|
610
|
-
)
|
|
543
|
+
num_samples = Property(depends_on=['start', 'stop', 'num_samples_total'])
|
|
611
544
|
|
|
612
545
|
#: A unique identifier for the samples, based on its properties. (read-only)
|
|
613
|
-
digest = Property(depends_on=['basename', 'start', 'stop', '
|
|
546
|
+
digest = Property(depends_on=['basename', 'start', 'stop', 'invalid_channels', '_datachecksum'])
|
|
614
547
|
|
|
615
548
|
@cached_property
|
|
616
549
|
def _get_digest(self):
|
|
@@ -621,7 +554,7 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
621
554
|
if len(self.invalid_channels) == 0:
|
|
622
555
|
return slice(0, None, None)
|
|
623
556
|
allr = [i for i in range(self.num_channels_total) if i not in self.invalid_channels]
|
|
624
|
-
return array(allr)
|
|
557
|
+
return np.array(allr)
|
|
625
558
|
|
|
626
559
|
@cached_property
|
|
627
560
|
def _get_num_channels(self):
|
|
@@ -634,8 +567,8 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
634
567
|
sli = slice(self.start, self.stop).indices(self.num_samples_total)
|
|
635
568
|
return sli[1] - sli[0]
|
|
636
569
|
|
|
637
|
-
@
|
|
638
|
-
def _load_data(self):
|
|
570
|
+
@observe('basename')
|
|
571
|
+
def _load_data(self, event): # noqa ARG002
|
|
639
572
|
# Open the .h5 file and set attributes.
|
|
640
573
|
if not path.isfile(self.file):
|
|
641
574
|
# no file there
|
|
@@ -650,8 +583,8 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
650
583
|
self._load_timedata()
|
|
651
584
|
self._load_metadata()
|
|
652
585
|
|
|
653
|
-
@
|
|
654
|
-
def _load_shapes(self):
|
|
586
|
+
@observe('data')
|
|
587
|
+
def _load_shapes(self, event): # noqa ARG002
|
|
655
588
|
# Set :attr:`num_channels` and num_samples from :attr:`~acoular.sources.TimeSamples.data`.
|
|
656
589
|
if self.data is not None:
|
|
657
590
|
self.num_samples_total, self.num_channels_total = self.data.shape
|
|
@@ -667,8 +600,7 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
667
600
|
Generate blocks of valid time-domain data iteratively.
|
|
668
601
|
|
|
669
602
|
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.
|
|
603
|
+
based on the specified :attr:`start` and :attr:`stop` indices and the valid channels.
|
|
672
604
|
|
|
673
605
|
Parameters
|
|
674
606
|
----------
|
|
@@ -688,15 +620,6 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
688
620
|
:obj:`OSError`
|
|
689
621
|
If no valid samples are available (i.e., :attr:`start` and :attr:`stop` indices result
|
|
690
622
|
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
623
|
|
|
701
624
|
Examples
|
|
702
625
|
--------
|
|
@@ -721,33 +644,15 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
721
644
|
sli = slice(self.start, self.stop).indices(self.num_samples_total)
|
|
722
645
|
i = sli[0]
|
|
723
646
|
stop = sli[1]
|
|
724
|
-
cal_factor = 1.0
|
|
725
647
|
if i >= stop:
|
|
726
648
|
msg = 'no samples available'
|
|
727
649
|
raise OSError(msg)
|
|
728
650
|
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
651
|
while i < stop:
|
|
746
|
-
yield self.data[i : min(i + num, stop)][:, self.channels]
|
|
652
|
+
yield self.data[i : min(i + num, stop)][:, self.channels]
|
|
747
653
|
i += num
|
|
748
654
|
|
|
749
655
|
|
|
750
|
-
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True, removal_version='25.10')
|
|
751
656
|
class PointSource(SamplesGenerator):
|
|
752
657
|
"""
|
|
753
658
|
Define a fixed point source emitting a signal, intended for simulations.
|
|
@@ -759,9 +664,9 @@ class PointSource(SamplesGenerator):
|
|
|
759
664
|
|
|
760
665
|
See Also
|
|
761
666
|
--------
|
|
762
|
-
:class
|
|
763
|
-
:class
|
|
764
|
-
:class
|
|
667
|
+
:class:`~acoular.signals.SignalGenerator` : For defining custom emitted signals.
|
|
668
|
+
:class:`~acoular.microphones.MicGeom` : For specifying microphone geometries.
|
|
669
|
+
:class:`~acoular.environments.Environment` : For modeling sound propagation effects.
|
|
765
670
|
|
|
766
671
|
Notes
|
|
767
672
|
-----
|
|
@@ -815,19 +720,27 @@ class PointSource(SamplesGenerator):
|
|
|
815
720
|
#: Instance of the :class:`~acoular.signals.SignalGenerator` class defining the emitted signal.
|
|
816
721
|
signal = Instance(SignalGenerator)
|
|
817
722
|
|
|
818
|
-
#: Coordinates ``(x, y, z)`` of the source
|
|
819
|
-
|
|
820
|
-
|
|
723
|
+
#: Coordinates ``(x, y, z)`` of the source. Default is ``np.array([[0.0], [0.0], [1.0]])``.
|
|
724
|
+
loc = Property(CArray, desc='source location')
|
|
725
|
+
|
|
726
|
+
_loc = CArray(shape=(3, 1), value=np.array([[0.0], [0.0], [1.0]]), dtype=float)
|
|
727
|
+
|
|
728
|
+
def _get_loc(self):
|
|
729
|
+
return self._loc
|
|
730
|
+
|
|
731
|
+
def _set_loc(self, value):
|
|
732
|
+
value = np.asarray(value, dtype=float).reshape((3, 1))
|
|
733
|
+
self._loc = value
|
|
821
734
|
|
|
822
735
|
#: Number of output channels, automatically set based on the :attr:`microphone geometry<mics>`.
|
|
823
736
|
num_channels = Delegate('mics', 'num_mics')
|
|
824
737
|
|
|
825
738
|
#: :class:`~acoular.microphones.MicGeom` object defining the positions of the microphones.
|
|
826
|
-
mics = Instance(MicGeom
|
|
739
|
+
mics = Instance(MicGeom)
|
|
827
740
|
|
|
828
741
|
def _validate_locations(self):
|
|
829
|
-
dist = self.env._r(
|
|
830
|
-
if
|
|
742
|
+
dist = self.env._r(self.loc, self.mics.pos)
|
|
743
|
+
if np.any(dist < 1e-7):
|
|
831
744
|
warn('Source and microphone locations are identical.', Warning, stacklevel=2)
|
|
832
745
|
|
|
833
746
|
#: An :class:`~acoular.environments.Environment` or derived object providing sound propagation
|
|
@@ -836,10 +749,10 @@ class PointSource(SamplesGenerator):
|
|
|
836
749
|
env = Instance(Environment, args=())
|
|
837
750
|
|
|
838
751
|
#: Start time of the signal in seconds. Default is ``0.0``.
|
|
839
|
-
start_t = Float(0.0
|
|
752
|
+
start_t = Float(0.0)
|
|
840
753
|
|
|
841
754
|
#: Start time of data acquisition at the microphones in seconds. Default is ``0.0``.
|
|
842
|
-
start = Float(0.0
|
|
755
|
+
start = Float(0.0)
|
|
843
756
|
|
|
844
757
|
#: Behavior of the signal for negative time indices,
|
|
845
758
|
#: i.e. if (:attr:`start` ``<`` :attr:`start_t`):
|
|
@@ -848,10 +761,10 @@ class PointSource(SamplesGenerator):
|
|
|
848
761
|
#: - ``'zeros'``: Use zeros, recommended for deterministic signals.
|
|
849
762
|
#:
|
|
850
763
|
#: Default is ``'loop'``.
|
|
851
|
-
prepadding = Enum('loop', 'zeros'
|
|
764
|
+
prepadding = Enum('loop', 'zeros')
|
|
852
765
|
|
|
853
766
|
#: Internal upsampling factor for finer signal resolution. Default is ``16``.
|
|
854
|
-
up = Int(16
|
|
767
|
+
up = Int(16)
|
|
855
768
|
|
|
856
769
|
#: Total number of samples in the emitted signal, derived from the :attr:`signal` generator.
|
|
857
770
|
num_samples = Delegate('signal')
|
|
@@ -905,31 +818,32 @@ class PointSource(SamplesGenerator):
|
|
|
905
818
|
If signal processing or propagation cannot be performed.
|
|
906
819
|
"""
|
|
907
820
|
self._validate_locations()
|
|
908
|
-
|
|
821
|
+
num_samples_estimate = self.num_samples + (self.start_t - self.start) * self.sample_freq
|
|
822
|
+
N = int(np.ceil(num_samples_estimate / num)) # number of output blocks
|
|
909
823
|
signal = self.signal.usignal(self.up)
|
|
910
|
-
out = empty((num, self.num_channels))
|
|
824
|
+
out = np.empty((num, self.num_channels))
|
|
911
825
|
# distances
|
|
912
|
-
rm = self.env._r(
|
|
826
|
+
rm = self.env._r(self.loc, self.mics.pos).reshape(1, -1)
|
|
913
827
|
# emission time relative to start_t (in samples) for first sample
|
|
914
828
|
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq * self.up
|
|
915
829
|
|
|
916
830
|
if self.prepadding == 'zeros':
|
|
917
831
|
# number of blocks where signal behaviour is amended
|
|
918
|
-
pre = -int(
|
|
832
|
+
pre = -int(np.min(ind[0]) // (self.up * num))
|
|
919
833
|
# amend signal for first blocks
|
|
920
834
|
# if signal stops during prepadding, terminate
|
|
921
835
|
if pre >= N:
|
|
922
836
|
for _nb in range(N - 1):
|
|
923
|
-
|
|
837
|
+
_fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, True)
|
|
924
838
|
yield out
|
|
925
839
|
|
|
926
840
|
blocksize = self.num_samples % num or num
|
|
927
|
-
|
|
841
|
+
_fill_mic_signal_block(out, signal, rm, ind, blocksize, self.num_channels, self.up, True)
|
|
928
842
|
yield out[:blocksize]
|
|
929
843
|
return
|
|
930
844
|
else:
|
|
931
845
|
for _nb in range(pre):
|
|
932
|
-
|
|
846
|
+
_fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, True)
|
|
933
847
|
yield out
|
|
934
848
|
|
|
935
849
|
else:
|
|
@@ -937,12 +851,12 @@ class PointSource(SamplesGenerator):
|
|
|
937
851
|
|
|
938
852
|
# main generator
|
|
939
853
|
for _nb in range(N - pre - 1):
|
|
940
|
-
|
|
854
|
+
_fill_mic_signal_block(out, signal, rm, ind, num, self.num_channels, self.up, False)
|
|
941
855
|
yield out
|
|
942
856
|
|
|
943
857
|
# last block of variable size
|
|
944
858
|
blocksize = self.num_samples % num or num
|
|
945
|
-
|
|
859
|
+
_fill_mic_signal_block(out, signal, rm, ind, blocksize, self.num_channels, self.up, False)
|
|
946
860
|
yield out[:blocksize]
|
|
947
861
|
|
|
948
862
|
|
|
@@ -959,18 +873,18 @@ class SphericalHarmonicSource(PointSource):
|
|
|
959
873
|
"""
|
|
960
874
|
|
|
961
875
|
#: Order of the spherical harmonic representation. Default is ``0``.
|
|
962
|
-
lOrder = Int(0
|
|
876
|
+
lOrder = Int(0) # noqa: N815
|
|
963
877
|
|
|
964
878
|
#: Coefficients of the spherical harmonic modes for the given :attr:`lOrder`.
|
|
965
|
-
alpha = CArray(
|
|
879
|
+
alpha = CArray()
|
|
966
880
|
|
|
967
881
|
#: Vector defining the orientation of the spherical harmonic source. Default is
|
|
968
882
|
#: ``(1.0, 0.0, 0.0)``.
|
|
969
|
-
direction = Tuple((1.0, 0.0, 0.0)
|
|
883
|
+
direction = Tuple((1.0, 0.0, 0.0))
|
|
970
884
|
|
|
971
885
|
#: Behavior of the signal for negative time indices. Currently only supports `loop`. Default is
|
|
972
886
|
#: ``'loop'``.
|
|
973
|
-
prepadding = Enum('loop'
|
|
887
|
+
prepadding = Enum('loop')
|
|
974
888
|
|
|
975
889
|
# Unique identifier for the current state of the source, based on its properties. (read-only)
|
|
976
890
|
digest = Property(
|
|
@@ -1026,9 +940,9 @@ class SphericalHarmonicSource(PointSource):
|
|
|
1026
940
|
lOrder=self.lOrder,
|
|
1027
941
|
direction=self.direction,
|
|
1028
942
|
mpos=self.mics.pos,
|
|
1029
|
-
sourceposition=array(self.loc),
|
|
943
|
+
sourceposition=np.array(self.loc),
|
|
1030
944
|
)
|
|
1031
|
-
return real(ifft(fft(signals, axis=0) * (Y_lm @ self.alpha), axis=0))
|
|
945
|
+
return np.real(ifft(fft(signals, axis=0) * (Y_lm @ self.alpha), axis=0))
|
|
1032
946
|
|
|
1033
947
|
def result(self, num=128):
|
|
1034
948
|
"""
|
|
@@ -1060,15 +974,15 @@ class SphericalHarmonicSource(PointSource):
|
|
|
1060
974
|
|
|
1061
975
|
signal = self.signal.usignal(self.up)
|
|
1062
976
|
# emission time relative to start_t (in samples) for first sample
|
|
1063
|
-
rm = self.env._r(
|
|
1064
|
-
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq + pi / 30
|
|
977
|
+
rm = self.env._r(self.loc, self.mics.pos)
|
|
978
|
+
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq + np.pi / 30
|
|
1065
979
|
i = 0
|
|
1066
980
|
n = self.num_samples
|
|
1067
|
-
out = empty((num, self.num_channels))
|
|
981
|
+
out = np.empty((num, self.num_channels))
|
|
1068
982
|
while n:
|
|
1069
983
|
n -= 1
|
|
1070
984
|
try:
|
|
1071
|
-
out[i] = signal[array(0.5 + ind * self.up, dtype=int64)] / rm
|
|
985
|
+
out[i] = signal[np.array(0.5 + ind * self.up, dtype=np.int64)] / rm
|
|
1072
986
|
ind += 1
|
|
1073
987
|
i += 1
|
|
1074
988
|
if i == num:
|
|
@@ -1091,22 +1005,22 @@ class MovingPointSource(PointSource):
|
|
|
1091
1005
|
|
|
1092
1006
|
See Also
|
|
1093
1007
|
--------
|
|
1094
|
-
:class
|
|
1095
|
-
:class
|
|
1008
|
+
:class:`~acoular.sources.PointSource` : For modeling stationary point sources.
|
|
1009
|
+
:class:`~acoular.trajectory.Trajectory` : For specifying source motion paths.
|
|
1096
1010
|
"""
|
|
1097
1011
|
|
|
1098
1012
|
#: Determines whether convective amplification is considered. When ``True``, the amplitude of
|
|
1099
1013
|
#: the signal is adjusted based on the relative motion between the source and microphones.
|
|
1100
1014
|
#: Default is ``False``.
|
|
1101
|
-
conv_amp = Bool(False
|
|
1015
|
+
conv_amp = Bool(False)
|
|
1102
1016
|
|
|
1103
1017
|
#: Instance of the :class:`~acoular.trajectory.Trajectory` class specifying the source's motion.
|
|
1104
1018
|
#: The trajectory defines the source's position and velocity at any given time.
|
|
1105
|
-
trajectory = Instance(Trajectory
|
|
1019
|
+
trajectory = Instance(Trajectory)
|
|
1106
1020
|
|
|
1107
1021
|
#: Behavior of the signal for negative time indices. Currently only supports ``'loop'``.
|
|
1108
1022
|
#: Default is ``'loop'``.
|
|
1109
|
-
prepadding = Enum('loop'
|
|
1023
|
+
prepadding = Enum('loop')
|
|
1110
1024
|
|
|
1111
1025
|
#: A unique identifier for the current state of the source, based on its properties. (read-only)
|
|
1112
1026
|
digest = Property(
|
|
@@ -1127,6 +1041,51 @@ class MovingPointSource(PointSource):
|
|
|
1127
1041
|
def _get_digest(self):
|
|
1128
1042
|
return digest(self)
|
|
1129
1043
|
|
|
1044
|
+
def get_moving_direction(self, direction, time=0):
|
|
1045
|
+
"""
|
|
1046
|
+
Calculate the moving direction of the source along its trajectory.
|
|
1047
|
+
|
|
1048
|
+
This method computes the updated direction vector for the moving source, considering both
|
|
1049
|
+
translation along the :attr:`~MovingPointSource.trajectory` and rotation defined by the
|
|
1050
|
+
:attr:`reference vector<rvec>`. If the :attr:`reference vector<rvec>` is `(0, 0, 0)`, only
|
|
1051
|
+
translation is applied. Otherwise, the method incorporates rotation into the calculation.
|
|
1052
|
+
|
|
1053
|
+
Parameters
|
|
1054
|
+
----------
|
|
1055
|
+
direction : :class:`numpy.ndarray`
|
|
1056
|
+
The initial orientation of the source, specified as a three-dimensional array.
|
|
1057
|
+
time : :class:`float`, optional
|
|
1058
|
+
The time at which the :attr:`~MovingPointSource.trajectory` position and velocity are
|
|
1059
|
+
evaluated. Defaults to ``0``.
|
|
1060
|
+
|
|
1061
|
+
Returns
|
|
1062
|
+
-------
|
|
1063
|
+
:class:`numpy.ndarray`
|
|
1064
|
+
The updated direction vector of the moving source after translation and, if applicable,
|
|
1065
|
+
rotation. The output is a three-dimensional array.
|
|
1066
|
+
|
|
1067
|
+
Notes
|
|
1068
|
+
-----
|
|
1069
|
+
- The method computes the translation direction vector based on the
|
|
1070
|
+
:attr:`~MovingPointSource.trajectory`'s velocity at the specified time.
|
|
1071
|
+
- If the :attr:`reference vector<rvec>` is non-zero, the method constructs a rotation matrix
|
|
1072
|
+
to compute the new source direction based on the
|
|
1073
|
+
:attr:`~MovingPointSource.trajectory`'s motion and the :attr:`reference vector<rvec>`.
|
|
1074
|
+
- The rotation matrix ensures that the new orientation adheres to the right-hand rule and
|
|
1075
|
+
remains orthogonal.
|
|
1076
|
+
"""
|
|
1077
|
+
trajg1 = np.array(self.trajectory.location(time, der=1))[:, 0][:, np.newaxis]
|
|
1078
|
+
rflag = (self.rvec == 0).all() # flag translation vs. rotation
|
|
1079
|
+
if rflag:
|
|
1080
|
+
return direction
|
|
1081
|
+
dx = np.array(trajg1.T) # direction vector (new x-axis)
|
|
1082
|
+
dy = np.cross(self.rvec, dx) # new y-axis
|
|
1083
|
+
dz = np.cross(dx, dy) # new z-axis
|
|
1084
|
+
RM = np.array((dx, dy, dz)).T # rotation matrix
|
|
1085
|
+
RM /= np.sqrt((RM * RM).sum(0)) # column normalized
|
|
1086
|
+
newdir = np.dot(RM, direction)
|
|
1087
|
+
return np.cross(newdir[:, 0].T, self.rvec.T).T
|
|
1088
|
+
|
|
1130
1089
|
def result(self, num=128):
|
|
1131
1090
|
"""
|
|
1132
1091
|
Generate the output signal at microphones in blocks, accounting for source motion.
|
|
@@ -1167,25 +1126,25 @@ class MovingPointSource(PointSource):
|
|
|
1167
1126
|
signal = self.signal.usignal(self.up)
|
|
1168
1127
|
# shortcuts and initial values
|
|
1169
1128
|
num_mics = self.num_channels
|
|
1170
|
-
mpos = self.mics.pos[:, :, newaxis]
|
|
1171
|
-
t = self.start + ones(num_mics)[:, newaxis] * arange(num) / self.sample_freq
|
|
1129
|
+
mpos = self.mics.pos[:, :, np.newaxis]
|
|
1130
|
+
t = self.start + np.ones(num_mics)[:, np.newaxis] * np.arange(num) / self.sample_freq
|
|
1172
1131
|
epslim = 0.1 / self.up / self.sample_freq
|
|
1173
1132
|
c0 = self.env.c
|
|
1174
1133
|
tr = self.trajectory
|
|
1175
1134
|
n = self.num_samples
|
|
1176
1135
|
while n > 0:
|
|
1177
|
-
eps = ones_like(t) # init discrepancy in time
|
|
1136
|
+
eps = np.ones_like(t) # init discrepancy in time
|
|
1178
1137
|
te = t.copy() # init emission time = receiving time
|
|
1179
1138
|
j = 0
|
|
1180
1139
|
# Newton-Rhapson iteration
|
|
1181
1140
|
while abs(eps).max() > epslim and j < 100:
|
|
1182
|
-
loc = array(tr.location(te.flatten())).reshape((3, num_mics, -1))
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
Mr = (der *
|
|
1188
|
-
eps[:] = (te + rm / c0 - t) / (1
|
|
1141
|
+
loc = np.array(tr.location(te.flatten())).reshape((3, num_mics, -1))
|
|
1142
|
+
der = np.array(tr.location(te.flatten(), der=1)).reshape((3, num_mics, -1))
|
|
1143
|
+
dv = mpos - loc # distance vectors from source to microphones
|
|
1144
|
+
rm = np.sqrt((dv * dv).sum(0)) # absolute distance
|
|
1145
|
+
dv /= rm # just directions from source to microphones
|
|
1146
|
+
Mr = (der * dv).sum(0) / c0 # radial Mach number
|
|
1147
|
+
eps[:] = (te + rm / c0 - t) / (1 - Mr) # discrepancy in time
|
|
1189
1148
|
te -= eps
|
|
1190
1149
|
j += 1 # iteration count
|
|
1191
1150
|
t += num / self.sample_freq
|
|
@@ -1194,7 +1153,7 @@ class MovingPointSource(PointSource):
|
|
|
1194
1153
|
if self.conv_amp:
|
|
1195
1154
|
rm *= (1 - Mr) ** 2
|
|
1196
1155
|
try:
|
|
1197
|
-
ind = array(0.5 + ind * self.up, dtype=int64)
|
|
1156
|
+
ind = np.array(0.5 + ind * self.up, dtype=np.int64)
|
|
1198
1157
|
out = (signal[ind] / rm).T
|
|
1199
1158
|
yield out[:n]
|
|
1200
1159
|
except IndexError: # last incomplete frame
|
|
@@ -1221,7 +1180,7 @@ class PointSourceDipole(PointSource):
|
|
|
1221
1180
|
|
|
1222
1181
|
See Also
|
|
1223
1182
|
--------
|
|
1224
|
-
:class
|
|
1183
|
+
:class:`~acoular.sources.PointSource` : For modeling stationary point sources.
|
|
1225
1184
|
|
|
1226
1185
|
Notes
|
|
1227
1186
|
-----
|
|
@@ -1239,11 +1198,11 @@ class PointSourceDipole(PointSource):
|
|
|
1239
1198
|
#: Default is ``(0.0, 0.0, 1.0)`` (z-axis orientation).
|
|
1240
1199
|
#:
|
|
1241
1200
|
#: **Note:** Use vectors with order of magnitude around ``1.0`` or less for good results.
|
|
1242
|
-
direction = Tuple((0.0, 0.0, 1.0)
|
|
1201
|
+
direction = Tuple((0.0, 0.0, 1.0))
|
|
1243
1202
|
|
|
1244
1203
|
#: Behavior of the signal for negative time indices. Currently only supports ``'loop'``.
|
|
1245
1204
|
#: Default is ``'loop'``.
|
|
1246
|
-
prepadding = Enum('loop'
|
|
1205
|
+
prepadding = Enum('loop')
|
|
1247
1206
|
|
|
1248
1207
|
#: A unique identifier for the current state of the source, based on its properties. (read-only)
|
|
1249
1208
|
digest = Property(
|
|
@@ -1296,10 +1255,10 @@ class PointSourceDipole(PointSource):
|
|
|
1296
1255
|
|
|
1297
1256
|
mpos = self.mics.pos
|
|
1298
1257
|
# position of the dipole as (3,1) vector
|
|
1299
|
-
loc =
|
|
1258
|
+
loc = self.loc
|
|
1300
1259
|
# direction vector from tuple
|
|
1301
|
-
direc = array(self.direction, dtype=float) * 1e-5
|
|
1302
|
-
direc_mag = sqrt(dot(direc, direc))
|
|
1260
|
+
direc = np.array(self.direction, dtype=float) * 1e-5
|
|
1261
|
+
direc_mag = np.sqrt(np.dot(direc, direc))
|
|
1303
1262
|
|
|
1304
1263
|
# normed direction vector
|
|
1305
1264
|
direc_n = direc / direc_mag
|
|
@@ -1313,7 +1272,7 @@ class PointSourceDipole(PointSource):
|
|
|
1313
1272
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
1314
1273
|
|
|
1315
1274
|
signal = self.signal.usignal(self.up)
|
|
1316
|
-
out = empty((num, self.num_channels))
|
|
1275
|
+
out = np.empty((num, self.num_channels))
|
|
1317
1276
|
|
|
1318
1277
|
# distance from dipole center to microphones
|
|
1319
1278
|
rm = self.env._r(loc, mpos)
|
|
@@ -1336,8 +1295,8 @@ class PointSourceDipole(PointSource):
|
|
|
1336
1295
|
rm
|
|
1337
1296
|
/ dist
|
|
1338
1297
|
* (
|
|
1339
|
-
signal[array(0.5 + ind1 * self.up, dtype=int64)] / rm1
|
|
1340
|
-
- signal[array(0.5 + ind2 * self.up, dtype=int64)] / rm2
|
|
1298
|
+
signal[np.array(0.5 + ind1 * self.up, dtype=np.int64)] / rm1
|
|
1299
|
+
- signal[np.array(0.5 + ind2 * self.up, dtype=np.int64)] / rm2
|
|
1341
1300
|
)
|
|
1342
1301
|
)
|
|
1343
1302
|
ind1 += 1.0
|
|
@@ -1350,7 +1309,8 @@ class PointSourceDipole(PointSource):
|
|
|
1350
1309
|
except IndexError:
|
|
1351
1310
|
break
|
|
1352
1311
|
|
|
1353
|
-
|
|
1312
|
+
if i > 0:
|
|
1313
|
+
yield out[:i]
|
|
1354
1314
|
|
|
1355
1315
|
|
|
1356
1316
|
class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
@@ -1370,8 +1330,8 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1370
1330
|
|
|
1371
1331
|
See Also
|
|
1372
1332
|
--------
|
|
1373
|
-
:class
|
|
1374
|
-
:class
|
|
1333
|
+
:class:`~acoular.sources.PointSourceDipole` : For stationary dipole sources.
|
|
1334
|
+
:class:`~acoular.sources.MovingPointSource` :
|
|
1375
1335
|
For moving point sources without dipole characteristics.
|
|
1376
1336
|
"""
|
|
1377
1337
|
|
|
@@ -1392,7 +1352,7 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1392
1352
|
#: A reference vector, perpendicular to the x and y-axis of moving source, defining the axis of
|
|
1393
1353
|
#: rotation for the dipole directivity. If set to ``(0, 0, 0)``, the dipole is only translated
|
|
1394
1354
|
#: along the :attr:`~MovingPointSource.trajectory` without rotation. Default is ``(0, 0, 0)``.
|
|
1395
|
-
rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0))
|
|
1355
|
+
rvec = CArray(dtype=float, shape=(3,), value=np.array((0, 0, 0)))
|
|
1396
1356
|
|
|
1397
1357
|
@cached_property
|
|
1398
1358
|
def _get_digest(self):
|
|
@@ -1434,70 +1394,25 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1434
1394
|
terminates when the time discrepancy (``eps``) is below a threshold (``epslim``)
|
|
1435
1395
|
or after 100 iterations.
|
|
1436
1396
|
"""
|
|
1437
|
-
eps = ones(self.mics.num_mics)
|
|
1397
|
+
eps = np.ones(self.mics.num_mics)
|
|
1438
1398
|
epslim = 0.1 / self.up / self.sample_freq
|
|
1439
1399
|
te = t.copy() # init emission time = receiving time
|
|
1440
1400
|
j = 0
|
|
1441
1401
|
# Newton-Rhapson iteration
|
|
1442
1402
|
while abs(eps).max() > epslim and j < 100:
|
|
1443
|
-
xs = array(self.trajectory.location(te))
|
|
1403
|
+
xs = np.array(self.trajectory.location(te))
|
|
1444
1404
|
loc = xs.copy()
|
|
1445
1405
|
loc += direction
|
|
1446
1406
|
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))
|
|
1407
|
+
rm = np.sqrt((rm * rm).sum(0)) # absolute distance
|
|
1408
|
+
loc /= np.sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1409
|
+
der = np.array(self.trajectory.location(te, der=1))
|
|
1450
1410
|
Mr = (der * loc).sum(0) / self.env.c # radial Mach number
|
|
1451
1411
|
eps = (te + rm / self.env.c - t) / (1 + Mr) # discrepancy in time
|
|
1452
1412
|
te -= eps
|
|
1453
1413
|
j += 1 # iteration count
|
|
1454
1414
|
return te, rm, Mr, xs
|
|
1455
1415
|
|
|
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
1416
|
def result(self, num=128):
|
|
1502
1417
|
"""
|
|
1503
1418
|
Generate the output signal at microphones in blocks.
|
|
@@ -1524,8 +1439,8 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1524
1439
|
mpos = self.mics.pos
|
|
1525
1440
|
|
|
1526
1441
|
# direction vector from tuple
|
|
1527
|
-
direc = array(self.direction, dtype=float) * 1e-5
|
|
1528
|
-
direc_mag = sqrt(dot(direc, direc))
|
|
1442
|
+
direc = np.array(self.direction, dtype=float) * 1e-5
|
|
1443
|
+
direc_mag = np.sqrt(np.dot(direc, direc))
|
|
1529
1444
|
# normed direction vector
|
|
1530
1445
|
direc_n = direc / direc_mag
|
|
1531
1446
|
c = self.env.c
|
|
@@ -1536,10 +1451,10 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1536
1451
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
1537
1452
|
|
|
1538
1453
|
signal = self.signal.usignal(self.up)
|
|
1539
|
-
out = empty((num, self.num_channels))
|
|
1454
|
+
out = np.empty((num, self.num_channels))
|
|
1540
1455
|
# shortcuts and initial values
|
|
1541
1456
|
m = self.mics
|
|
1542
|
-
t = self.start * ones(m.num_mics)
|
|
1457
|
+
t = self.start * np.ones(m.num_mics)
|
|
1543
1458
|
|
|
1544
1459
|
i = 0
|
|
1545
1460
|
n = self.num_samples
|
|
@@ -1548,7 +1463,7 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1548
1463
|
te, rm, Mr, locs = self.get_emission_time(t, 0)
|
|
1549
1464
|
t += 1.0 / self.sample_freq
|
|
1550
1465
|
# location of the center
|
|
1551
|
-
loc = array(self.trajectory.location(te), dtype=float)[:, 0][:, newaxis]
|
|
1466
|
+
loc = np.array(self.trajectory.location(te), dtype=float)[:, 0][:, np.newaxis]
|
|
1552
1467
|
# distance of the dipoles from the center
|
|
1553
1468
|
diff = self.get_moving_direction(dir2, te)
|
|
1554
1469
|
|
|
@@ -1567,8 +1482,8 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1567
1482
|
rm
|
|
1568
1483
|
/ dist
|
|
1569
1484
|
* (
|
|
1570
|
-
signal[array(0.5 + ind * self.up, dtype=int64)] / rm1
|
|
1571
|
-
- signal[array(0.5 + ind * self.up, dtype=int64)] / rm2
|
|
1485
|
+
signal[np.array(0.5 + ind * self.up, dtype=np.int64)] / rm1
|
|
1486
|
+
- signal[np.array(0.5 + ind * self.up, dtype=np.int64)] / rm2
|
|
1572
1487
|
)
|
|
1573
1488
|
)
|
|
1574
1489
|
i += 1
|
|
@@ -1577,7 +1492,9 @@ class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
|
1577
1492
|
i = 0
|
|
1578
1493
|
except IndexError:
|
|
1579
1494
|
break
|
|
1580
|
-
|
|
1495
|
+
|
|
1496
|
+
if i > 0:
|
|
1497
|
+
yield out[:i]
|
|
1581
1498
|
|
|
1582
1499
|
|
|
1583
1500
|
class LineSource(PointSource):
|
|
@@ -1598,7 +1515,7 @@ class LineSource(PointSource):
|
|
|
1598
1515
|
|
|
1599
1516
|
See Also
|
|
1600
1517
|
--------
|
|
1601
|
-
:class
|
|
1518
|
+
:class:`~acoular.sources.PointSource` : For modeling stationary point sources.
|
|
1602
1519
|
|
|
1603
1520
|
Notes
|
|
1604
1521
|
-----
|
|
@@ -1606,19 +1523,19 @@ class LineSource(PointSource):
|
|
|
1606
1523
|
"""
|
|
1607
1524
|
|
|
1608
1525
|
#: Vector to define the orientation of the line source. Default is ``(0.0, 0.0, 1.0)``.
|
|
1609
|
-
direction = Tuple((0.0, 0.0, 1.0)
|
|
1526
|
+
direction = Tuple((0.0, 0.0, 1.0))
|
|
1610
1527
|
|
|
1611
1528
|
#: Vector to define the length of the line source in meters. Default is ``1.0``.
|
|
1612
|
-
length = Float(1
|
|
1529
|
+
length = Float(1)
|
|
1613
1530
|
|
|
1614
1531
|
#: Number of monopole sources in the line source. Default is ``1``.
|
|
1615
1532
|
num_sources = Int(1)
|
|
1616
1533
|
|
|
1617
1534
|
#: Strength coefficients for each monopole source.
|
|
1618
|
-
source_strength = CArray(
|
|
1535
|
+
source_strength = CArray()
|
|
1619
1536
|
|
|
1620
1537
|
#: Coherence mode for the monopoles (``'coherent'`` or ``'incoherent'``).
|
|
1621
|
-
coherence = Enum('coherent', 'incoherent'
|
|
1538
|
+
coherence = Enum('coherent', 'incoherent')
|
|
1622
1539
|
|
|
1623
1540
|
#: A unique identifier for the current state of the source, based on its properties. (read-only)
|
|
1624
1541
|
digest = Property(
|
|
@@ -1662,24 +1579,24 @@ class LineSource(PointSource):
|
|
|
1662
1579
|
mpos = self.mics.pos
|
|
1663
1580
|
|
|
1664
1581
|
# direction vector from tuple
|
|
1665
|
-
direc = array(self.direction, dtype=float)
|
|
1582
|
+
direc = np.array(self.direction, dtype=float)
|
|
1666
1583
|
# normed direction vector
|
|
1667
|
-
direc_n = direc / norm(direc)
|
|
1584
|
+
direc_n = direc / spla.norm(direc)
|
|
1668
1585
|
c = self.env.c
|
|
1669
1586
|
|
|
1670
1587
|
# distance between monopoles in the line
|
|
1671
1588
|
dist = self.length / self.num_sources
|
|
1672
1589
|
|
|
1673
1590
|
# blocwise output
|
|
1674
|
-
out = zeros((num, self.num_channels))
|
|
1591
|
+
out = np.zeros((num, self.num_channels))
|
|
1675
1592
|
|
|
1676
1593
|
# distance from line start position to microphones
|
|
1677
|
-
loc =
|
|
1594
|
+
loc = self.loc
|
|
1678
1595
|
|
|
1679
1596
|
# 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))))
|
|
1597
|
+
rms = np.empty((self.num_channels, self.num_sources))
|
|
1598
|
+
inds = np.empty((self.num_channels, self.num_sources))
|
|
1599
|
+
signals = np.empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1683
1600
|
# for every source - distances
|
|
1684
1601
|
for s in range(self.num_sources):
|
|
1685
1602
|
rms[:, s] = self.env._r((loc.T + direc_n * dist * s).T, mpos)
|
|
@@ -1696,18 +1613,19 @@ class LineSource(PointSource):
|
|
|
1696
1613
|
try:
|
|
1697
1614
|
for s in range(self.num_sources):
|
|
1698
1615
|
# sum sources
|
|
1699
|
-
out[i] += signals[s, array(0.5 + inds[:, s].T * self.up, dtype=int64)] / rms[:, s]
|
|
1616
|
+
out[i] += signals[s, np.array(0.5 + inds[:, s].T * self.up, dtype=np.int64)] / rms[:, s]
|
|
1700
1617
|
|
|
1701
1618
|
inds += 1.0
|
|
1702
1619
|
i += 1
|
|
1703
1620
|
if i == num:
|
|
1704
1621
|
yield out
|
|
1705
|
-
out = zeros((num, self.num_channels))
|
|
1622
|
+
out = np.zeros((num, self.num_channels))
|
|
1706
1623
|
i = 0
|
|
1707
1624
|
except IndexError:
|
|
1708
1625
|
break
|
|
1709
1626
|
|
|
1710
|
-
|
|
1627
|
+
if i > 0:
|
|
1628
|
+
yield out[:i]
|
|
1711
1629
|
|
|
1712
1630
|
|
|
1713
1631
|
class MovingLineSource(LineSource, MovingPointSource):
|
|
@@ -1727,10 +1645,10 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1727
1645
|
|
|
1728
1646
|
See Also
|
|
1729
1647
|
--------
|
|
1730
|
-
:class
|
|
1648
|
+
:class:`~acoular.sources.LineSource` :
|
|
1731
1649
|
For :class:`line sources<LineSource>` consisting of
|
|
1732
1650
|
:attr:`coherent or incoherent<LineSource.coherence>` monopoles.
|
|
1733
|
-
:class
|
|
1651
|
+
:class:`~acoular.sources.MovingPointSource` :
|
|
1734
1652
|
For moving point sources without dipole characteristics.
|
|
1735
1653
|
"""
|
|
1736
1654
|
|
|
@@ -1752,59 +1670,12 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1752
1670
|
#: rotation for the line source directivity. If set to ``(0, 0, 0)``, the line source is only
|
|
1753
1671
|
#: translated along the :attr:`~MovingPointSource.trajectory` without rotation. Default is
|
|
1754
1672
|
#: ``(0, 0, 0)``.
|
|
1755
|
-
rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0))
|
|
1673
|
+
rvec = CArray(dtype=float, shape=(3,), value=np.array((0, 0, 0)))
|
|
1756
1674
|
|
|
1757
1675
|
@cached_property
|
|
1758
1676
|
def _get_digest(self):
|
|
1759
1677
|
return digest(self)
|
|
1760
1678
|
|
|
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
1679
|
def get_emission_time(self, t, direction):
|
|
1809
1680
|
"""
|
|
1810
1681
|
Calculate the emission time for a moving line source based on its trajectory.
|
|
@@ -1847,19 +1718,19 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1847
1718
|
- The method iterates until the difference between the computed emission time and
|
|
1848
1719
|
the current time is sufficiently small (within a defined threshold).
|
|
1849
1720
|
"""
|
|
1850
|
-
eps = ones(self.mics.num_mics)
|
|
1721
|
+
eps = np.ones(self.mics.num_mics)
|
|
1851
1722
|
epslim = 0.1 / self.up / self.sample_freq
|
|
1852
1723
|
te = t.copy() # init emission time = receiving time
|
|
1853
1724
|
j = 0
|
|
1854
1725
|
# Newton-Rhapson iteration
|
|
1855
1726
|
while abs(eps).max() > epslim and j < 100:
|
|
1856
|
-
xs = array(self.trajectory.location(te))
|
|
1727
|
+
xs = np.array(self.trajectory.location(te))
|
|
1857
1728
|
loc = xs.copy()
|
|
1858
1729
|
loc += direction
|
|
1859
1730
|
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))
|
|
1731
|
+
rm = np.sqrt((rm * rm).sum(0)) # absolute distance
|
|
1732
|
+
loc /= np.sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1733
|
+
der = np.array(self.trajectory.location(te, der=1))
|
|
1863
1734
|
Mr = (der * loc).sum(0) / self.env.c # radial Mach number
|
|
1864
1735
|
eps = (te + rm / self.env.c - t) / (1 + Mr) # discrepancy in time
|
|
1865
1736
|
te -= eps
|
|
@@ -1887,21 +1758,21 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1887
1758
|
mpos = self.mics.pos
|
|
1888
1759
|
|
|
1889
1760
|
# direction vector from tuple
|
|
1890
|
-
direc = array(self.direction, dtype=float)
|
|
1761
|
+
direc = np.array(self.direction, dtype=float)
|
|
1891
1762
|
# normed direction vector
|
|
1892
|
-
direc_n = direc / norm(direc)
|
|
1763
|
+
direc_n = direc / spla.norm(direc)
|
|
1893
1764
|
|
|
1894
1765
|
# distance between monopoles in the line
|
|
1895
1766
|
dist = self.length / self.num_sources
|
|
1896
1767
|
dir2 = (direc_n * dist).reshape((3, 1))
|
|
1897
1768
|
|
|
1898
1769
|
# blocwise output
|
|
1899
|
-
out = zeros((num, self.num_channels))
|
|
1770
|
+
out = np.zeros((num, self.num_channels))
|
|
1900
1771
|
|
|
1901
1772
|
# 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))))
|
|
1773
|
+
rms = np.empty((self.num_channels, self.num_sources))
|
|
1774
|
+
inds = np.empty((self.num_channels, self.num_sources))
|
|
1775
|
+
signals = np.empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1905
1776
|
# coherence
|
|
1906
1777
|
for s in range(self.num_sources):
|
|
1907
1778
|
# new seed for every source
|
|
@@ -1913,20 +1784,20 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1913
1784
|
|
|
1914
1785
|
# shortcuts and initial values
|
|
1915
1786
|
m = self.mics
|
|
1916
|
-
t = self.start * ones(m.num_mics)
|
|
1787
|
+
t = self.start * np.ones(m.num_mics)
|
|
1917
1788
|
i = 0
|
|
1918
1789
|
n = self.num_samples
|
|
1919
1790
|
while n:
|
|
1920
1791
|
n -= 1
|
|
1921
1792
|
t += 1.0 / self.sample_freq
|
|
1922
1793
|
te1, rm1, Mr1, locs1 = self.get_emission_time(t, 0)
|
|
1923
|
-
# trajg1 = array(self.trajectory.location( te1, der=1))[:,0][:,newaxis]
|
|
1794
|
+
# trajg1 = np.array(self.trajectory.location( te1, der=1))[:,0][:,np.newaxis]
|
|
1924
1795
|
|
|
1925
1796
|
# get distance and ind for every source in the line
|
|
1926
1797
|
for s in range(self.num_sources):
|
|
1927
1798
|
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]
|
|
1799
|
+
te, rm, Mr, locs = self.get_emission_time(t, np.tile((diff * s).T, (self.num_channels, 1)).T)
|
|
1800
|
+
loc = np.array(self.trajectory.location(te), dtype=float)[:, 0][:, np.newaxis]
|
|
1930
1801
|
diff = self.get_moving_direction(dir2, te)
|
|
1931
1802
|
rms[:, s] = self.env._r((loc + diff * s), mpos)
|
|
1932
1803
|
inds[:, s] = (te - self.start_t + self.start) * self.sample_freq
|
|
@@ -1938,19 +1809,20 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1938
1809
|
# subtract the second signal b/c of phase inversion
|
|
1939
1810
|
for s in range(self.num_sources):
|
|
1940
1811
|
# sum sources
|
|
1941
|
-
out[i] += signals[s, array(0.5 + inds[:, s].T * self.up, dtype=int64)] / rms[:, s]
|
|
1812
|
+
out[i] += signals[s, np.array(0.5 + inds[:, s].T * self.up, dtype=np.int64)] / rms[:, s]
|
|
1942
1813
|
|
|
1943
1814
|
i += 1
|
|
1944
1815
|
if i == num:
|
|
1945
1816
|
yield out
|
|
1946
|
-
out = zeros((num, self.num_channels))
|
|
1817
|
+
out = np.zeros((num, self.num_channels))
|
|
1947
1818
|
i = 0
|
|
1948
1819
|
except IndexError:
|
|
1949
1820
|
break
|
|
1950
|
-
|
|
1821
|
+
|
|
1822
|
+
if i > 0:
|
|
1823
|
+
yield out[:i]
|
|
1951
1824
|
|
|
1952
1825
|
|
|
1953
|
-
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True, removal_version='25.10')
|
|
1954
1826
|
class UncorrelatedNoiseSource(SamplesGenerator):
|
|
1955
1827
|
"""
|
|
1956
1828
|
Simulate uncorrelated white or pink noise signals at multiple channels.
|
|
@@ -1962,8 +1834,8 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
1962
1834
|
|
|
1963
1835
|
See Also
|
|
1964
1836
|
--------
|
|
1965
|
-
:class
|
|
1966
|
-
:class
|
|
1837
|
+
:class:`~acoular.signals.SignalGenerator` : For defining noise types and properties.
|
|
1838
|
+
:class:`~acoular.microphones.MicGeom` : For specifying microphone geometries.
|
|
1967
1839
|
|
|
1968
1840
|
Notes
|
|
1969
1841
|
-----
|
|
@@ -2006,12 +1878,12 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2006
1878
|
#: Instance of a :class:`~acoular.signals.NoiseGenerator`-derived class. For example:
|
|
2007
1879
|
#: - :class:`~acoular.signals.WNoiseGenerator` for white noise.
|
|
2008
1880
|
#: - :class:`~acoular.signals.PNoiseGenerator` for pink noise.
|
|
2009
|
-
signal = Instance(NoiseGenerator
|
|
1881
|
+
signal = Instance(NoiseGenerator)
|
|
2010
1882
|
|
|
2011
1883
|
#: Array of random seed values for generating uncorrelated noise at each channel. If left empty,
|
|
2012
1884
|
#: seeds will be automatically generated as ``np.arange(self.num_channels) + signal.seed``. The
|
|
2013
1885
|
#: size of the array must match the :attr:`number of output channels<num_channels>`.
|
|
2014
|
-
seed = CArray(dtype=uint32
|
|
1886
|
+
seed = CArray(dtype=np.uint32)
|
|
2015
1887
|
|
|
2016
1888
|
#: Number of output channels, automatically determined by the number of microphones
|
|
2017
1889
|
#: defined in the :attr:`mics` attribute. Corresponds to the number of uncorrelated noise
|
|
@@ -2021,15 +1893,15 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2021
1893
|
#: :class:`~acoular.microphones.MicGeom` object specifying the positions of microphones.
|
|
2022
1894
|
#: This attribute is used to define the microphone geometry and the
|
|
2023
1895
|
#: :attr:`number of channels<num_channels>`.
|
|
2024
|
-
mics = Instance(MicGeom
|
|
1896
|
+
mics = Instance(MicGeom)
|
|
2025
1897
|
|
|
2026
1898
|
#: Start time of the generated noise signal in seconds. Determines the time offset for the noise
|
|
2027
1899
|
#: output relative to the start of data acquisition. Default is ``0.0``.
|
|
2028
|
-
start_t = Float(0.0
|
|
1900
|
+
start_t = Float(0.0)
|
|
2029
1901
|
|
|
2030
1902
|
#: Start time of data acquisition at the microphones in seconds. This value determines when the
|
|
2031
1903
|
#: generated noise begins relative to the acquisition process. Default is ``0.0``.
|
|
2032
|
-
start = Float(0.0
|
|
1904
|
+
start = Float(0.0)
|
|
2033
1905
|
|
|
2034
1906
|
#: Total number of samples in the noise signal, derived from the :attr:`signal` generator.
|
|
2035
1907
|
#: This value determines the length of the output signal for all channels.
|
|
@@ -2089,14 +1961,14 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2089
1961
|
Noise = self.signal.__class__
|
|
2090
1962
|
# create or get the array of random seeds
|
|
2091
1963
|
if not self.seed.size > 0:
|
|
2092
|
-
seed = arange(self.num_channels) + self.signal.seed
|
|
1964
|
+
seed = np.arange(self.num_channels) + self.signal.seed
|
|
2093
1965
|
elif self.seed.shape == (self.num_channels,):
|
|
2094
1966
|
seed = self.seed
|
|
2095
1967
|
else:
|
|
2096
1968
|
msg = f'Seed array expected to be of shape ({self.num_channels:d},), but has shape {self.seed.shape}.'
|
|
2097
1969
|
raise ValueError(msg)
|
|
2098
1970
|
# create array with [num_channels] noise signal tracks
|
|
2099
|
-
signal = array(
|
|
1971
|
+
signal = np.array(
|
|
2100
1972
|
[
|
|
2101
1973
|
Noise(seed=s, num_samples=self.num_samples, sample_freq=self.sample_freq, rms=self.signal.rms).signal()
|
|
2102
1974
|
for s in seed
|
|
@@ -2114,7 +1986,6 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2114
1986
|
return
|
|
2115
1987
|
|
|
2116
1988
|
|
|
2117
|
-
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True, removal_version='25.10')
|
|
2118
1989
|
class SourceMixer(SamplesGenerator):
|
|
2119
1990
|
"""
|
|
2120
1991
|
Combine signals from multiple sources by mixing their outputs.
|
|
@@ -2126,7 +1997,7 @@ class SourceMixer(SamplesGenerator):
|
|
|
2126
1997
|
|
|
2127
1998
|
See Also
|
|
2128
1999
|
--------
|
|
2129
|
-
:class
|
|
2000
|
+
:class:`~acoular.base.SamplesGenerator` : Base class for signal generators.
|
|
2130
2001
|
|
|
2131
2002
|
Notes
|
|
2132
2003
|
-----
|
|
@@ -2228,7 +2099,7 @@ class SourceMixer(SamplesGenerator):
|
|
|
2228
2099
|
#: The size of the weights array must match the number of sources in :attr:`sources`.
|
|
2229
2100
|
#: For example, with two sources, ``weights = [1.0, 0.5]`` would mix the first source at
|
|
2230
2101
|
#: full amplitude and the second source at half amplitude.
|
|
2231
|
-
weights = CArray(
|
|
2102
|
+
weights = CArray()
|
|
2232
2103
|
|
|
2233
2104
|
#: Internal identifier for the combined state of all sources, used to track
|
|
2234
2105
|
#: changes in the sources for reproducibility and caching.
|
|
@@ -2316,7 +2187,7 @@ class SourceMixer(SamplesGenerator):
|
|
|
2316
2187
|
gens = [i.result(num) for i in self.sources[1:]]
|
|
2317
2188
|
weights = self.weights.copy()
|
|
2318
2189
|
if weights.size == 0:
|
|
2319
|
-
weights = array([1.0 for j in range(len(self.sources))])
|
|
2190
|
+
weights = np.array([1.0 for j in range(len(self.sources))])
|
|
2320
2191
|
assert weights.shape[0] == len(self.sources)
|
|
2321
2192
|
for temp in self.sources[0].result(num):
|
|
2322
2193
|
temp *= weights[0]
|
|
@@ -2345,7 +2216,7 @@ class PointSourceConvolve(PointSource):
|
|
|
2345
2216
|
See Also
|
|
2346
2217
|
--------
|
|
2347
2218
|
:class:`PointSource` : Base class for point sources.
|
|
2348
|
-
:class
|
|
2219
|
+
:class:`~acoular.tprocess.TimeConvolve` : Class used for performing time-domain convolution.
|
|
2349
2220
|
|
|
2350
2221
|
Notes
|
|
2351
2222
|
-----
|
|
@@ -2386,8 +2257,7 @@ class PointSourceConvolve(PointSource):
|
|
|
2386
2257
|
(256, 4)
|
|
2387
2258
|
(256, 4)
|
|
2388
2259
|
(256, 4)
|
|
2389
|
-
(
|
|
2390
|
-
(75, 4)
|
|
2260
|
+
(232, 4)
|
|
2391
2261
|
|
|
2392
2262
|
The last block has fewer samples.
|
|
2393
2263
|
"""
|
|
@@ -2395,24 +2265,36 @@ class PointSourceConvolve(PointSource):
|
|
|
2395
2265
|
#: Convolution kernel in the time domain.
|
|
2396
2266
|
#: The array must either have one column (a single kernel applied to all channels)
|
|
2397
2267
|
#: or match the number of output channels in its second dimension.
|
|
2398
|
-
kernel = CArray(dtype=float
|
|
2268
|
+
kernel = CArray(dtype=float)
|
|
2399
2269
|
|
|
2400
2270
|
#: Start time of the signal in seconds. Default is ``0.0``.
|
|
2401
|
-
start_t = Enum(0.0
|
|
2271
|
+
start_t = Enum(0.0)
|
|
2402
2272
|
|
|
2403
2273
|
#: Start time of the data acquisition the the microphones in seconds. Default is ``0.0``.
|
|
2404
|
-
start = Enum(0.0
|
|
2274
|
+
start = Enum(0.0)
|
|
2405
2275
|
|
|
2406
2276
|
#: Behavior for negative time indices. Default is ``None``.
|
|
2407
|
-
prepadding = Enum(None
|
|
2277
|
+
prepadding = Enum(None)
|
|
2408
2278
|
|
|
2409
2279
|
#: Upsampling factor for internal use. Default is ``None``.
|
|
2410
|
-
up = Enum(None
|
|
2280
|
+
up = Enum(None)
|
|
2411
2281
|
|
|
2412
2282
|
#: Unique identifier for the current state of the source,
|
|
2413
2283
|
#: based on microphone geometry, input signal, source location, and kernel. (read-only)
|
|
2414
2284
|
digest = Property(depends_on=['mics.digest', 'signal.digest', 'loc', 'kernel'])
|
|
2415
2285
|
|
|
2286
|
+
#: Controls whether to extend the output to include the full convolution result.
|
|
2287
|
+
#:
|
|
2288
|
+
#: - If ``False`` (default): Output length is :math:`\\max(L, M)`, where :math:`L` is the
|
|
2289
|
+
#: kernel length and :math:`M` is the signal length. This mode keeps the output length
|
|
2290
|
+
#: equal to the longest input (different from NumPy's ``mode='same'``, since it does not
|
|
2291
|
+
#: pad the output).
|
|
2292
|
+
#: - If ``True``: Output length is :math:`L + M - 1`, returning the full convolution at
|
|
2293
|
+
#: each overlap point (similar to NumPy's ``mode='full'``).
|
|
2294
|
+
#:
|
|
2295
|
+
#: Default is ``False``.
|
|
2296
|
+
extend_signal = Bool(False)
|
|
2297
|
+
|
|
2416
2298
|
@cached_property
|
|
2417
2299
|
def _get_digest(self):
|
|
2418
2300
|
return digest(self)
|
|
@@ -2444,7 +2326,7 @@ class PointSourceConvolve(PointSource):
|
|
|
2444
2326
|
- Convolution is performed using the :class:`~acoular.tprocess.TimeConvolve` class
|
|
2445
2327
|
to ensure efficiency.
|
|
2446
2328
|
"""
|
|
2447
|
-
data = repeat(self.signal.signal()[:, newaxis], self.mics.num_mics, axis=1)
|
|
2329
|
+
data = np.repeat(self.signal.signal()[:, np.newaxis], self.mics.num_mics, axis=1)
|
|
2448
2330
|
source = TimeSamples(
|
|
2449
2331
|
data=data,
|
|
2450
2332
|
sample_freq=self.sample_freq,
|
|
@@ -2454,5 +2336,6 @@ class PointSourceConvolve(PointSource):
|
|
|
2454
2336
|
time_convolve = TimeConvolve(
|
|
2455
2337
|
source=source,
|
|
2456
2338
|
kernel=self.kernel,
|
|
2339
|
+
extend_signal=self.extend_signal,
|
|
2457
2340
|
)
|
|
2458
2341
|
yield from time_convolve.result(num)
|