acoular 24.3__py3-none-any.whl → 24.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acoular/__init__.py +119 -54
- acoular/calib.py +29 -38
- acoular/configuration.py +132 -82
- acoular/demo/__init__.py +10 -4
- acoular/demo/acoular_demo.py +73 -55
- acoular/environments.py +270 -264
- acoular/fastFuncs.py +366 -196
- acoular/fbeamform.py +1797 -1934
- acoular/grids.py +504 -548
- acoular/h5cache.py +74 -83
- acoular/h5files.py +159 -142
- acoular/internal.py +13 -14
- acoular/microphones.py +57 -53
- acoular/sdinput.py +57 -53
- acoular/signals.py +180 -178
- acoular/sources.py +920 -724
- acoular/spectra.py +353 -363
- acoular/tbeamform.py +416 -416
- acoular/tfastfuncs.py +180 -104
- acoular/tools/__init__.py +25 -0
- acoular/tools/aiaa.py +185 -0
- acoular/tools/helpers.py +189 -0
- acoular/tools/metrics.py +165 -0
- acoular/tprocess.py +1240 -1182
- acoular/traitsviews.py +513 -501
- acoular/trajectory.py +50 -52
- acoular/version.py +5 -6
- acoular/xml/minidsp_uma-16.xml +20 -0
- acoular/xml/{minidsp_uma16.xml → minidsp_uma-16_mirrored.xml} +3 -0
- {acoular-24.3.dist-info → acoular-24.7.dist-info}/METADATA +58 -39
- acoular-24.7.dist-info/RECORD +50 -0
- {acoular-24.3.dist-info → acoular-24.7.dist-info}/WHEEL +1 -1
- acoular-24.7.dist-info/licenses/LICENSE +28 -0
- acoular/fileimport.py +0 -380
- acoular/nidaqimport.py +0 -273
- acoular/tests/reference_data/BeamformerBase.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse1.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse2.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse3.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseFalse4.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue1.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue2.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue3.npy +0 -0
- acoular/tests/reference_data/BeamformerBaseTrue4.npy +0 -0
- acoular/tests/reference_data/BeamformerCMFLassoLarsBIC.npy +0 -0
- acoular/tests/reference_data/BeamformerCMFNNLS.npy +0 -0
- acoular/tests/reference_data/BeamformerCapon.npy +0 -0
- acoular/tests/reference_data/BeamformerClean.npy +0 -0
- acoular/tests/reference_data/BeamformerCleansc.npy +0 -0
- acoular/tests/reference_data/BeamformerCleant.npy +0 -0
- acoular/tests/reference_data/BeamformerCleantSq.npy +0 -0
- acoular/tests/reference_data/BeamformerCleantSqTraj.npy +0 -0
- acoular/tests/reference_data/BeamformerCleantTraj.npy +0 -0
- acoular/tests/reference_data/BeamformerDamas.npy +0 -0
- acoular/tests/reference_data/BeamformerDamasPlus.npy +0 -0
- acoular/tests/reference_data/BeamformerEig.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse1.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse2.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse3.npy +0 -0
- acoular/tests/reference_data/BeamformerEigFalse4.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue1.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue2.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue3.npy +0 -0
- acoular/tests/reference_data/BeamformerEigTrue4.npy +0 -0
- acoular/tests/reference_data/BeamformerFunctional.npy +0 -0
- acoular/tests/reference_data/BeamformerGIB.npy +0 -0
- acoular/tests/reference_data/BeamformerGridlessOrth.npy +0 -0
- acoular/tests/reference_data/BeamformerMusic.npy +0 -0
- acoular/tests/reference_data/BeamformerOrth.npy +0 -0
- acoular/tests/reference_data/BeamformerSODIX.npy +0 -0
- acoular/tests/reference_data/BeamformerTime.npy +0 -0
- acoular/tests/reference_data/BeamformerTimeSq.npy +0 -0
- acoular/tests/reference_data/BeamformerTimeSqTraj.npy +0 -0
- acoular/tests/reference_data/BeamformerTimeTraj.npy +0 -0
- acoular/tests/reference_data/Environment.npy +0 -0
- acoular/tests/reference_data/Example1_numerical_values_testsum.h5 +0 -0
- acoular/tests/reference_data/FiltFiltOctave__.npy +0 -0
- acoular/tests/reference_data/FiltFiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
- acoular/tests/reference_data/FiltFreqWeight_weight_A_.npy +0 -0
- acoular/tests/reference_data/FiltFreqWeight_weight_C_.npy +0 -0
- acoular/tests/reference_data/FiltFreqWeight_weight_Z_.npy +0 -0
- acoular/tests/reference_data/FiltOctave__.npy +0 -0
- acoular/tests/reference_data/FiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
- acoular/tests/reference_data/Filter__.npy +0 -0
- acoular/tests/reference_data/GeneralFlowEnvironment.npy +0 -0
- acoular/tests/reference_data/OctaveFilterBank__.npy +0 -0
- acoular/tests/reference_data/OpenJet.npy +0 -0
- acoular/tests/reference_data/PointSource.npy +0 -0
- acoular/tests/reference_data/PowerSpectra_csm.npy +0 -0
- acoular/tests/reference_data/PowerSpectra_ev.npy +0 -0
- acoular/tests/reference_data/RotatingFlow.npy +0 -0
- acoular/tests/reference_data/SlotJet.npy +0 -0
- acoular/tests/reference_data/TimeAverage__.npy +0 -0
- acoular/tests/reference_data/TimeCumAverage__.npy +0 -0
- acoular/tests/reference_data/TimeExpAverage_weight_F_.npy +0 -0
- acoular/tests/reference_data/TimeExpAverage_weight_I_.npy +0 -0
- acoular/tests/reference_data/TimeExpAverage_weight_S_.npy +0 -0
- acoular/tests/reference_data/TimeInOut__.npy +0 -0
- acoular/tests/reference_data/TimePower__.npy +0 -0
- acoular/tests/reference_data/TimeReverse__.npy +0 -0
- acoular/tests/reference_data/UniformFlowEnvironment.npy +0 -0
- acoular/tests/reference_data/beamformer_traj_time_data.h5 +0 -0
- acoular/tests/run_tests.sh +0 -18
- acoular/tests/run_tests_osx.sh +0 -16
- acoular/tests/test.npy +0 -0
- acoular/tests/test_beamformer_results.py +0 -213
- acoular/tests/test_classes.py +0 -60
- acoular/tests/test_digest.py +0 -125
- acoular/tests/test_environments.py +0 -73
- acoular/tests/test_example1.py +0 -124
- acoular/tests/test_grid.py +0 -92
- acoular/tests/test_integrate.py +0 -102
- acoular/tests/test_signals.py +0 -60
- acoular/tests/test_sources.py +0 -65
- acoular/tests/test_spectra.py +0 -38
- acoular/tests/test_timecache.py +0 -35
- acoular/tests/test_tprocess.py +0 -90
- acoular/tests/test_traj_beamformer_results.py +0 -164
- acoular/tests/unsupported/SpeedComparison/OvernightTestcasesBeamformer_nMics32_nGridPoints100_nFreqs4_nTrials10.png +0 -0
- acoular/tests/unsupported/SpeedComparison/cythonBeamformer.pyx +0 -237
- acoular/tests/unsupported/SpeedComparison/mainForCython.py +0 -103
- acoular/tests/unsupported/SpeedComparison/mainForParallelJit.py +0 -143
- acoular/tests/unsupported/SpeedComparison/setupCythonOpenMP.py +0 -63
- acoular/tests/unsupported/SpeedComparison/sharedFunctions.py +0 -153
- acoular/tests/unsupported/SpeedComparison/timeOverNMics_AllImportantMethods.png +0 -0
- acoular/tests/unsupported/SpeedComparison/timeOverNMics_faverage.png +0 -0
- acoular/tests/unsupported/SpeedComparison/vglOptimierungFAverage.py +0 -204
- acoular/tests/unsupported/SpeedComparison/vglOptimierungGaussSeidel.py +0 -182
- acoular/tests/unsupported/SpeedComparison/vglOptimierungR_BEAMFULL_INVERSE.py +0 -764
- acoular/tests/unsupported/SpeedComparison/vglOptimierungR_BEAM_OS.py +0 -231
- acoular/tests/unsupported/SpeedComparison/whatsFastestWayFor_absASquared.py +0 -48
- acoular/tests/unsupported/functionalBeamformer.py +0 -123
- acoular/tests/unsupported/precisionTest.py +0 -153
- acoular/tests/unsupported/validationOfBeamformerFuncsPOSTAcoularIntegration.py +0 -254
- acoular/tests/unsupported/validationOfBeamformerFuncsPREeAcoularIntegration.py +0 -531
- acoular/tools.py +0 -422
- acoular-24.3.dist-info/RECORD +0 -148
- acoular-24.3.dist-info/licenses/LICENSE +0 -29
- {acoular-24.3.dist-info → acoular-24.7.dist-info}/licenses/AUTHORS.rst +0 -0
acoular/sources.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
#
|
|
2
|
-
#pylint: disable-msg=E0611, E1101, C0103, R0901, R0902, R0903, R0904, W0232
|
|
3
|
-
#------------------------------------------------------------------------------
|
|
1
|
+
# ------------------------------------------------------------------------------
|
|
4
2
|
# Copyright (c) Acoular Development Team.
|
|
5
|
-
|
|
6
|
-
"""Measured multichannel data
|
|
3
|
+
# ------------------------------------------------------------------------------
|
|
4
|
+
"""Measured multichannel data management and simulation of acoustic sources.
|
|
7
5
|
|
|
8
6
|
.. autosummary::
|
|
9
7
|
:toctree: generated/
|
|
@@ -24,343 +22,505 @@
|
|
|
24
22
|
|
|
25
23
|
# imports from other packages
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
cross, zeros, ceil, repeat
|
|
29
|
-
from numpy import min as npmin
|
|
30
|
-
from numpy import any as npany
|
|
31
|
-
|
|
32
|
-
from numpy.fft import ifft, fft
|
|
33
|
-
from numpy.linalg import norm
|
|
34
|
-
|
|
35
|
-
import numba as nb
|
|
36
|
-
|
|
37
|
-
from traits.api import Float, Int, Property, Trait, Delegate, \
|
|
38
|
-
cached_property, Tuple, CLong, File, Instance, Any, Str, \
|
|
39
|
-
on_trait_change, observe, List, ListInt, CArray, Bool, Dict, Enum
|
|
25
|
+
import contextlib
|
|
40
26
|
from os import path
|
|
41
27
|
from warnings import warn
|
|
42
28
|
|
|
29
|
+
import numba as nb
|
|
30
|
+
from numpy import any as npany
|
|
31
|
+
from numpy import (
|
|
32
|
+
arange,
|
|
33
|
+
arctan2,
|
|
34
|
+
array,
|
|
35
|
+
ceil,
|
|
36
|
+
complex128,
|
|
37
|
+
cross,
|
|
38
|
+
dot,
|
|
39
|
+
empty,
|
|
40
|
+
int64,
|
|
41
|
+
mod,
|
|
42
|
+
newaxis,
|
|
43
|
+
ones,
|
|
44
|
+
pi,
|
|
45
|
+
real,
|
|
46
|
+
repeat,
|
|
47
|
+
sqrt,
|
|
48
|
+
tile,
|
|
49
|
+
uint32,
|
|
50
|
+
zeros,
|
|
51
|
+
)
|
|
52
|
+
from numpy import min as npmin
|
|
53
|
+
from numpy.fft import fft, ifft
|
|
54
|
+
from numpy.linalg import norm
|
|
55
|
+
from scipy.special import sph_harm, spherical_jn, spherical_yn
|
|
56
|
+
from traits.api import (
|
|
57
|
+
Any,
|
|
58
|
+
Bool,
|
|
59
|
+
CArray,
|
|
60
|
+
CLong,
|
|
61
|
+
Delegate,
|
|
62
|
+
Dict,
|
|
63
|
+
Enum,
|
|
64
|
+
File,
|
|
65
|
+
Float,
|
|
66
|
+
Instance,
|
|
67
|
+
Int,
|
|
68
|
+
List,
|
|
69
|
+
ListInt,
|
|
70
|
+
Property,
|
|
71
|
+
Str,
|
|
72
|
+
Trait,
|
|
73
|
+
Tuple,
|
|
74
|
+
cached_property,
|
|
75
|
+
observe,
|
|
76
|
+
on_trait_change,
|
|
77
|
+
)
|
|
78
|
+
|
|
43
79
|
# acoular imports
|
|
44
80
|
from .calib import Calib
|
|
45
|
-
from .
|
|
81
|
+
from .environments import Environment
|
|
82
|
+
from .h5files import H5FileBase, _get_h5file_class
|
|
46
83
|
from .internal import digest, ldigest
|
|
47
84
|
from .microphones import MicGeom
|
|
48
|
-
from .environments import Environment
|
|
49
|
-
from .tprocess import SamplesGenerator, TimeConvolve
|
|
50
85
|
from .signals import SignalGenerator
|
|
51
|
-
from .
|
|
52
|
-
from .
|
|
86
|
+
from .tprocess import SamplesGenerator, TimeConvolve
|
|
87
|
+
from .trajectory import Trajectory
|
|
53
88
|
|
|
54
89
|
|
|
55
|
-
@nb.njit(cache=True, error_model=
|
|
56
|
-
def _fill_mic_signal_block(out,signal,rm,ind,blocksize,numchannels,up,prepadding):
|
|
90
|
+
@nb.njit(cache=True, error_model='numpy') # jit with nopython
|
|
91
|
+
def _fill_mic_signal_block(out, signal, rm, ind, blocksize, numchannels, up, prepadding):
|
|
57
92
|
if prepadding:
|
|
58
93
|
for b in range(blocksize):
|
|
59
94
|
for m in range(numchannels):
|
|
60
|
-
if ind[0,m]<0:
|
|
61
|
-
out[b,m] = 0
|
|
95
|
+
if ind[0, m] < 0:
|
|
96
|
+
out[b, m] = 0
|
|
62
97
|
else:
|
|
63
|
-
out[b,m] = signal[int(0.5+ind[0,m])]/rm[0,m]
|
|
98
|
+
out[b, m] = signal[int(0.5 + ind[0, m])] / rm[0, m]
|
|
64
99
|
ind += up
|
|
65
100
|
else:
|
|
66
101
|
for b in range(blocksize):
|
|
67
102
|
for m in range(numchannels):
|
|
68
|
-
out[b,m] = signal[int(0.5+ind[0,m])]/rm[0,m]
|
|
103
|
+
out[b, m] = signal[int(0.5 + ind[0, m])] / rm[0, m]
|
|
69
104
|
ind += up
|
|
70
105
|
return out
|
|
71
106
|
|
|
72
107
|
|
|
73
|
-
|
|
108
|
+
def spherical_hn1(n, z):
|
|
109
|
+
"""Spherical Hankel Function of the First Kind."""
|
|
110
|
+
return spherical_jn(n, z, derivative=False) + 1j * spherical_yn(n, z, derivative=False)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_radiation_angles(direction, mpos, sourceposition):
|
|
114
|
+
"""Returns azimuthal and elevation angles between the mics and the source.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
direction : array of floats
|
|
119
|
+
Spherical Harmonic orientation
|
|
120
|
+
mpos : array of floats
|
|
121
|
+
x, y, z position of microphones
|
|
122
|
+
sourceposition : array of floats
|
|
123
|
+
position of the source
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
azi, ele : array of floats
|
|
128
|
+
the angle between the mics and the source
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
# direction of the Spherical Harmonics
|
|
132
|
+
direc = array(direction, dtype=float)
|
|
133
|
+
direc = direc / norm(direc)
|
|
134
|
+
# distances
|
|
135
|
+
source_to_mic_vecs = mpos - array(sourceposition).reshape((3, 1))
|
|
136
|
+
source_to_mic_vecs[2] *= -1 # invert z-axis (acoular) #-1
|
|
137
|
+
# z-axis (acoular) -> y-axis (spherical)
|
|
138
|
+
# y-axis (acoular) -> z-axis (spherical)
|
|
139
|
+
# theta
|
|
140
|
+
ele = arctan2(sqrt(source_to_mic_vecs[0] ** 2 + source_to_mic_vecs[2] ** 2), source_to_mic_vecs[1])
|
|
141
|
+
ele += arctan2(sqrt(direc[0] ** 2 + direc[2] ** 2), direc[1])
|
|
142
|
+
ele += pi * 0.5 # convert from [-pi/2, pi/2] to [0,pi] range
|
|
143
|
+
# phi
|
|
144
|
+
azi = arctan2(source_to_mic_vecs[2], source_to_mic_vecs[0])
|
|
145
|
+
azi += arctan2(direc[2], direc[0])
|
|
146
|
+
azi = mod(azi, 2 * pi)
|
|
147
|
+
return azi, ele
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_modes(lOrder, direction, mpos, sourceposition=None): # noqa: N803
|
|
151
|
+
"""Returns Spherical Harmonic Radiation Pattern at the Microphones.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
lOrder : int
|
|
156
|
+
Maximal order of spherical harmonic
|
|
157
|
+
direction : array of floats
|
|
158
|
+
Spherical Harmonic orientation
|
|
159
|
+
mpos : array of floats
|
|
160
|
+
x, y, z position of microphones
|
|
161
|
+
sourceposition : array of floats
|
|
162
|
+
position of the source
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
modes : array of floats
|
|
167
|
+
the radiation values at each microphone for each mode
|
|
168
|
+
|
|
74
169
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
170
|
+
sourceposition = sourceposition if sourceposition is not None else array([0, 0, 0])
|
|
171
|
+
azi, ele = get_radiation_angles(direction, mpos, sourceposition) # angles between source and mics
|
|
172
|
+
modes = zeros((azi.shape[0], (lOrder + 1) ** 2), dtype=complex128)
|
|
173
|
+
i = 0
|
|
174
|
+
for lidx in range(lOrder + 1):
|
|
175
|
+
for m in range(-lidx, lidx + 1):
|
|
176
|
+
modes[:, i] = sph_harm(m, lidx, azi, ele)
|
|
177
|
+
if m < 0:
|
|
178
|
+
modes[:, i] = modes[:, i].conj() * 1j
|
|
179
|
+
i += 1
|
|
180
|
+
return modes
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class TimeSamples(SamplesGenerator):
|
|
184
|
+
"""Container for processing time data in `*.h5` or NumPy array format.
|
|
185
|
+
|
|
186
|
+
This class loads measured data from HDF5 files and provides information about this data.
|
|
187
|
+
It also serves as an interface where the data can be accessed (e.g. for use in a block chain) via the
|
|
188
|
+
:meth:`result` generator.
|
|
189
|
+
|
|
190
|
+
Examples
|
|
191
|
+
--------
|
|
192
|
+
Data can be loaded from a HDF5 file as follows:
|
|
193
|
+
|
|
194
|
+
>>> from acoular import TimeSamples
|
|
195
|
+
>>> name = <some_h5_file.h5> # doctest: +SKIP
|
|
196
|
+
>>> ts = TimeSamples(name=name) # doctest: +SKIP
|
|
197
|
+
>>> print(f'number of channels: {ts.numchannels}') # doctest: +SKIP
|
|
198
|
+
number of channels: 56 # doctest: +SKIP
|
|
199
|
+
|
|
200
|
+
Alternatively, the time data can be specified directly as a numpy array.
|
|
201
|
+
In this case, the :attr:`data` and :attr:`sample_freq` attributes must be set manually.
|
|
202
|
+
|
|
203
|
+
>>> import numpy as np
|
|
204
|
+
>>> data = np.random.rand(1000, 4)
|
|
205
|
+
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
206
|
+
|
|
207
|
+
Chunks of the time data can be accessed iteratively via the :meth:`result` generator.
|
|
208
|
+
The last block will be shorter than the block size if the number of samples is not a multiple of the block size.
|
|
209
|
+
|
|
210
|
+
>>> blocksize = 512
|
|
211
|
+
>>> generator = ts.result(num=blocksize)
|
|
212
|
+
>>> for block in generator:
|
|
213
|
+
... print(block.shape)
|
|
214
|
+
(512, 4)
|
|
215
|
+
(488, 4)
|
|
216
|
+
|
|
217
|
+
See Also
|
|
218
|
+
--------
|
|
219
|
+
acoular.sources.MaskedTimeSamples :
|
|
220
|
+
Extends the functionality of class :class:`TimeSamples` by enabling the definition of start and stop samples
|
|
221
|
+
as well as the specification of invalid channels.
|
|
81
222
|
"""
|
|
82
223
|
|
|
83
224
|
#: Full name of the .h5 file with data.
|
|
84
|
-
name = File(filter=['*.h5'],
|
|
85
|
-
desc="name of data file")
|
|
225
|
+
name = File(filter=['*.h5'], desc='name of data file')
|
|
86
226
|
|
|
87
227
|
#: Basename of the .h5 file with data, is set automatically.
|
|
88
|
-
basename = Property(
|
|
89
|
-
|
|
90
|
-
|
|
228
|
+
basename = Property(
|
|
229
|
+
depends_on='name', # filter=['*.h5'],
|
|
230
|
+
desc='basename of data file',
|
|
231
|
+
)
|
|
232
|
+
|
|
91
233
|
#: Calibration data, instance of :class:`~acoular.calib.Calib` class, optional .
|
|
92
|
-
calib = Trait(
|
|
93
|
-
|
|
94
|
-
|
|
234
|
+
calib = Trait(Calib, desc='Calibration data')
|
|
235
|
+
|
|
95
236
|
#: Number of channels, is set automatically / read from file.
|
|
96
|
-
numchannels = CLong(0,
|
|
97
|
-
desc="number of input channels")
|
|
237
|
+
numchannels = CLong(0, desc='number of input channels')
|
|
98
238
|
|
|
99
239
|
#: Number of time data samples, is set automatically / read from file.
|
|
100
|
-
numsamples = CLong(0,
|
|
101
|
-
desc="number of samples")
|
|
240
|
+
numsamples = CLong(0, desc='number of samples')
|
|
102
241
|
|
|
103
242
|
#: The time data as array of floats with dimension (numsamples, numchannels).
|
|
104
|
-
data = Any(
|
|
105
|
-
desc="the actual time data array")
|
|
243
|
+
data = Any(transient=True, desc='the actual time data array')
|
|
106
244
|
|
|
107
245
|
#: HDF5 file object
|
|
108
|
-
h5f = Instance(H5FileBase, transient
|
|
109
|
-
|
|
246
|
+
h5f = Instance(H5FileBase, transient=True)
|
|
247
|
+
|
|
110
248
|
#: Provides metadata stored in HDF5 file object
|
|
111
|
-
metadata = Dict(
|
|
112
|
-
|
|
113
|
-
|
|
249
|
+
metadata = Dict(desc='metadata contained in .h5 file')
|
|
250
|
+
|
|
114
251
|
# Checksum over first data entries of all channels
|
|
115
252
|
_datachecksum = Property()
|
|
116
|
-
|
|
253
|
+
|
|
117
254
|
# internal identifier
|
|
118
|
-
digest = Property(
|
|
255
|
+
digest = Property(depends_on=['basename', 'calib.digest', '_datachecksum'])
|
|
256
|
+
|
|
257
|
+
def _get__datachecksum(self):
|
|
258
|
+
return self.data[0, :].sum()
|
|
119
259
|
|
|
120
|
-
def _get__datachecksum( self ):
|
|
121
|
-
return self.data[0,:].sum()
|
|
122
|
-
|
|
123
260
|
@cached_property
|
|
124
|
-
def _get_digest(
|
|
261
|
+
def _get_digest(self):
|
|
125
262
|
return digest(self)
|
|
126
|
-
|
|
263
|
+
|
|
127
264
|
@cached_property
|
|
128
|
-
def _get_basename(
|
|
265
|
+
def _get_basename(self):
|
|
129
266
|
return path.splitext(path.basename(self.name))[0]
|
|
130
|
-
|
|
267
|
+
|
|
131
268
|
@on_trait_change('basename')
|
|
132
|
-
def
|
|
133
|
-
"""
|
|
134
|
-
Open the .h5 file and set attributes.
|
|
135
|
-
"""
|
|
269
|
+
def _load_data(self):
|
|
270
|
+
"""Open the .h5 file and set attributes."""
|
|
136
271
|
if not path.isfile(self.name):
|
|
137
|
-
# no file there
|
|
138
|
-
self.numsamples = 0
|
|
139
|
-
self.numchannels = 0
|
|
140
272
|
self.sample_freq = 0
|
|
141
|
-
raise
|
|
142
|
-
if self.h5f
|
|
143
|
-
|
|
273
|
+
raise OSError('No such file: %s' % self.name)
|
|
274
|
+
if self.h5f is not None:
|
|
275
|
+
with contextlib.suppress(OSError):
|
|
144
276
|
self.h5f.close()
|
|
145
|
-
except IOError:
|
|
146
|
-
pass
|
|
147
277
|
file = _get_h5file_class()
|
|
148
278
|
self.h5f = file(self.name)
|
|
149
|
-
self.
|
|
150
|
-
self.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
self.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def
|
|
159
|
-
"""
|
|
279
|
+
self._load_timedata()
|
|
280
|
+
self._load_metadata()
|
|
281
|
+
|
|
282
|
+
@on_trait_change('data')
|
|
283
|
+
def _load_shapes(self):
|
|
284
|
+
"""Set numchannels and numsamples from data."""
|
|
285
|
+
if self.data is not None:
|
|
286
|
+
self.numsamples, self.numchannels = self.data.shape
|
|
287
|
+
|
|
288
|
+
def _load_timedata(self):
|
|
289
|
+
"""Loads timedata from .h5 file. Only for internal use."""
|
|
290
|
+
self.data = self.h5f.get_data_by_reference('time_data')
|
|
291
|
+
self.sample_freq = self.h5f.get_node_attribute(self.data, 'sample_freq')
|
|
292
|
+
|
|
293
|
+
def _load_metadata(self):
|
|
294
|
+
"""Loads metadata from .h5 file. Only for internal use."""
|
|
160
295
|
self.metadata = {}
|
|
161
296
|
if '/metadata' in self.h5f:
|
|
162
|
-
|
|
163
|
-
self.metadata[nodename] = nodedata
|
|
297
|
+
self.metadata = self.h5f.node_to_dict('/metadata')
|
|
164
298
|
|
|
165
299
|
def result(self, num=128):
|
|
166
|
-
"""
|
|
167
|
-
|
|
168
|
-
|
|
300
|
+
"""Python generator that yields the output block-wise.
|
|
301
|
+
|
|
302
|
+
Reads the time data either from a HDF5 file or from a numpy array given
|
|
303
|
+
by :attr:`data` and iteratively returns a block of size `num` samples.
|
|
304
|
+
Calibrated data is returned if a calibration object is given by :attr:`calib`.
|
|
305
|
+
|
|
169
306
|
Parameters
|
|
170
307
|
----------
|
|
171
308
|
num : integer, defaults to 128
|
|
172
309
|
This parameter defines the size of the blocks to be yielded
|
|
173
|
-
(i.e. the number of samples per block)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
310
|
+
(i.e. the number of samples per block).
|
|
311
|
+
|
|
312
|
+
Yields
|
|
313
|
+
------
|
|
314
|
+
numpy.ndarray
|
|
315
|
+
Samples in blocks of shape (num, numchannels).
|
|
178
316
|
The last block may be shorter than num.
|
|
317
|
+
|
|
179
318
|
"""
|
|
180
319
|
if self.numsamples == 0:
|
|
181
|
-
|
|
182
|
-
|
|
320
|
+
msg = 'no samples available'
|
|
321
|
+
raise OSError(msg)
|
|
322
|
+
self._datachecksum # trigger checksum calculation # noqa: B018
|
|
183
323
|
i = 0
|
|
184
324
|
if self.calib:
|
|
185
325
|
if self.calib.num_mics == self.numchannels:
|
|
186
326
|
cal_factor = self.calib.data[newaxis]
|
|
187
327
|
else:
|
|
188
|
-
raise ValueError(
|
|
189
|
-
(self.calib.num_mics, self.numchannels))
|
|
328
|
+
raise ValueError('calibration data not compatible: %i, %i' % (self.calib.num_mics, self.numchannels))
|
|
190
329
|
while i < self.numsamples:
|
|
191
|
-
yield self.data[i:i+num]*cal_factor
|
|
330
|
+
yield self.data[i : i + num] * cal_factor
|
|
192
331
|
i += num
|
|
193
332
|
else:
|
|
194
333
|
while i < self.numsamples:
|
|
195
|
-
yield self.data[i:i+num]
|
|
334
|
+
yield self.data[i : i + num]
|
|
196
335
|
i += num
|
|
197
336
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
Container for time data in `*.h5` format.
|
|
201
|
-
|
|
202
|
-
This class loads measured data from
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
It also serves as an interface where the data can be accessed
|
|
206
|
-
|
|
207
|
-
|
|
337
|
+
|
|
338
|
+
class MaskedTimeSamples(TimeSamples):
|
|
339
|
+
"""Container for processing time data in `*.h5` or NumPy array format.
|
|
340
|
+
|
|
341
|
+
This class loads measured data from HDF5 files and provides information about this data.
|
|
342
|
+
It supports storing information about (in)valid samples and (in)valid channels and allows
|
|
343
|
+
to specify a start and stop index for the valid samples.
|
|
344
|
+
It also serves as an interface where the data can be accessed (e.g. for use in a block chain) via the
|
|
345
|
+
:meth:`result` generator.
|
|
346
|
+
|
|
347
|
+
Examples
|
|
348
|
+
--------
|
|
349
|
+
Data can be loaded from a HDF5 file and invalid channels can be specified as follows:
|
|
350
|
+
|
|
351
|
+
>>> from acoular import MaskedTimeSamples
|
|
352
|
+
>>> name = <some_h5_file.h5> # doctest: +SKIP
|
|
353
|
+
>>> ts = MaskedTimeSamples(name=name, invalid_channels=[0, 1]) # doctest: +SKIP
|
|
354
|
+
>>> print(f'number of valid channels: {ts.numchannels}') # doctest: +SKIP
|
|
355
|
+
number of valid channels: 54 # doctest: +SKIP
|
|
356
|
+
|
|
357
|
+
Alternatively, the time data can be specified directly as a numpy array.
|
|
358
|
+
In this case, the :attr:`data` and :attr:`sample_freq` attributes must be set manually.
|
|
359
|
+
|
|
360
|
+
>>> from acoular import MaskedTimeSamples
|
|
361
|
+
>>> import numpy as np
|
|
362
|
+
>>> data = np.random.rand(1000, 4)
|
|
363
|
+
>>> ts = MaskedTimeSamples(data=data, sample_freq=51200)
|
|
364
|
+
|
|
365
|
+
Chunks of the time data can be accessed iteratively via the :meth:`result` generator:
|
|
366
|
+
|
|
367
|
+
>>> blocksize = 512
|
|
368
|
+
>>> generator = ts.result(num=blocksize)
|
|
369
|
+
>>> for block in generator:
|
|
370
|
+
... print(block.shape)
|
|
371
|
+
(512, 4)
|
|
372
|
+
(488, 4)
|
|
208
373
|
"""
|
|
209
|
-
|
|
374
|
+
|
|
210
375
|
#: Index of the first sample to be considered valid.
|
|
211
|
-
start = CLong(0,
|
|
212
|
-
|
|
213
|
-
|
|
376
|
+
start = CLong(0, desc='start of valid samples')
|
|
377
|
+
|
|
214
378
|
#: Index of the last sample to be considered valid.
|
|
215
|
-
stop = Trait(None, None, CLong,
|
|
216
|
-
|
|
217
|
-
|
|
379
|
+
stop = Trait(None, None, CLong, desc='stop of valid samples')
|
|
380
|
+
|
|
218
381
|
#: Channels that are to be treated as invalid.
|
|
219
|
-
invalid_channels = ListInt(
|
|
220
|
-
|
|
221
|
-
|
|
382
|
+
invalid_channels = ListInt(desc='list of invalid channels')
|
|
383
|
+
|
|
222
384
|
#: Channel mask to serve as an index for all valid channels, is set automatically.
|
|
223
|
-
channels = Property(depends_on
|
|
224
|
-
|
|
225
|
-
|
|
385
|
+
channels = Property(depends_on=['invalid_channels', 'numchannels_total'], desc='channel mask')
|
|
386
|
+
|
|
226
387
|
#: Number of channels (including invalid channels), is set automatically.
|
|
227
|
-
numchannels_total = CLong(0,
|
|
228
|
-
desc="total number of input channels")
|
|
388
|
+
numchannels_total = CLong(0, desc='total number of input channels')
|
|
229
389
|
|
|
230
390
|
#: Number of time data samples (including invalid samples), is set automatically.
|
|
231
|
-
numsamples_total = CLong(0,
|
|
232
|
-
desc="total number of samples per channel")
|
|
391
|
+
numsamples_total = CLong(0, desc='total number of samples per channel')
|
|
233
392
|
|
|
234
393
|
#: Number of valid channels, is set automatically.
|
|
235
|
-
numchannels = Property(depends_on
|
|
236
|
-
'numchannels_total'], desc="number of valid input channels")
|
|
394
|
+
numchannels = Property(depends_on=['invalid_channels', 'numchannels_total'], desc='number of valid input channels')
|
|
237
395
|
|
|
238
396
|
#: Number of valid time data samples, is set automatically.
|
|
239
|
-
numsamples = Property(depends_on
|
|
240
|
-
desc="number of valid samples per channel")
|
|
397
|
+
numsamples = Property(depends_on=['start', 'stop', 'numsamples_total'], desc='number of valid samples per channel')
|
|
241
398
|
|
|
242
399
|
# internal identifier
|
|
243
|
-
digest = Property(
|
|
244
|
-
'calib.digest', 'invalid_channels','_datachecksum'])
|
|
400
|
+
digest = Property(depends_on=['basename', 'start', 'stop', 'calib.digest', 'invalid_channels', '_datachecksum'])
|
|
245
401
|
|
|
246
402
|
@cached_property
|
|
247
|
-
def _get_digest(
|
|
403
|
+
def _get_digest(self):
|
|
248
404
|
return digest(self)
|
|
249
|
-
|
|
405
|
+
|
|
250
406
|
@cached_property
|
|
251
|
-
def _get_basename(
|
|
407
|
+
def _get_basename(self):
|
|
252
408
|
return path.splitext(path.basename(self.name))[0]
|
|
253
|
-
|
|
409
|
+
|
|
254
410
|
@cached_property
|
|
255
|
-
def _get_channels(
|
|
256
|
-
if len(self.invalid_channels)==0:
|
|
411
|
+
def _get_channels(self):
|
|
412
|
+
if len(self.invalid_channels) == 0:
|
|
257
413
|
return slice(0, None, None)
|
|
258
|
-
allr=[i for i in range(self.numchannels_total) if i not in self.invalid_channels]
|
|
414
|
+
allr = [i for i in range(self.numchannels_total) if i not in self.invalid_channels]
|
|
259
415
|
return array(allr)
|
|
260
|
-
|
|
416
|
+
|
|
261
417
|
@cached_property
|
|
262
|
-
def _get_numchannels(
|
|
263
|
-
if len(self.invalid_channels)==0:
|
|
418
|
+
def _get_numchannels(self):
|
|
419
|
+
if len(self.invalid_channels) == 0:
|
|
264
420
|
return self.numchannels_total
|
|
265
421
|
return len(self.channels)
|
|
266
|
-
|
|
422
|
+
|
|
267
423
|
@cached_property
|
|
268
|
-
def _get_numsamples(
|
|
424
|
+
def _get_numsamples(self):
|
|
269
425
|
sli = slice(self.start, self.stop).indices(self.numsamples_total)
|
|
270
|
-
return sli[1]-sli[0]
|
|
426
|
+
return sli[1] - sli[0]
|
|
271
427
|
|
|
272
428
|
@on_trait_change('basename')
|
|
273
|
-
def
|
|
274
|
-
#""" open the .h5 file and set attributes
|
|
275
|
-
#"""
|
|
429
|
+
def _load_data(self):
|
|
430
|
+
# """ open the .h5 file and set attributes
|
|
431
|
+
# """
|
|
276
432
|
if not path.isfile(self.name):
|
|
277
433
|
# no file there
|
|
278
|
-
self.numsamples_total = 0
|
|
279
|
-
self.numchannels_total = 0
|
|
280
434
|
self.sample_freq = 0
|
|
281
|
-
raise
|
|
282
|
-
if self.h5f
|
|
283
|
-
|
|
435
|
+
raise OSError('No such file: %s' % self.name)
|
|
436
|
+
if self.h5f is not None:
|
|
437
|
+
with contextlib.suppress(OSError):
|
|
284
438
|
self.h5f.close()
|
|
285
|
-
except IOError:
|
|
286
|
-
pass
|
|
287
439
|
file = _get_h5file_class()
|
|
288
440
|
self.h5f = file(self.name)
|
|
289
|
-
self.
|
|
290
|
-
self.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
self.
|
|
441
|
+
self._load_timedata()
|
|
442
|
+
self._load_metadata()
|
|
443
|
+
|
|
444
|
+
@on_trait_change('data')
|
|
445
|
+
def _load_shapes(self):
|
|
446
|
+
"""Set numchannels and numsamples from data."""
|
|
447
|
+
if self.data is not None:
|
|
448
|
+
self.numsamples_total, self.numchannels_total = self.data.shape
|
|
449
|
+
|
|
450
|
+
def _load_timedata(self):
|
|
451
|
+
"""Loads timedata from .h5 file. Only for internal use."""
|
|
452
|
+
self.data = self.h5f.get_data_by_reference('time_data')
|
|
453
|
+
self.sample_freq = self.h5f.get_node_attribute(self.data, 'sample_freq')
|
|
296
454
|
(self.numsamples_total, self.numchannels_total) = self.data.shape
|
|
297
455
|
|
|
298
456
|
def result(self, num=128):
|
|
299
|
-
"""
|
|
300
|
-
|
|
301
|
-
|
|
457
|
+
"""Python generator that yields the output block-wise.
|
|
458
|
+
|
|
459
|
+
Reads the time data either from a HDF5 file or from a numpy array given
|
|
460
|
+
by :attr:`data` and iteratively returns a block of size `num` samples.
|
|
461
|
+
Calibrated data is returned if a calibration object is given by :attr:`calib`.
|
|
462
|
+
|
|
302
463
|
Parameters
|
|
303
464
|
----------
|
|
304
465
|
num : integer, defaults to 128
|
|
305
466
|
This parameter defines the size of the blocks to be yielded
|
|
306
467
|
(i.e. the number of samples per block).
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
468
|
+
|
|
469
|
+
Yields
|
|
470
|
+
------
|
|
471
|
+
numpy.ndarray
|
|
472
|
+
Samples in blocks of shape (num, numchannels).
|
|
311
473
|
The last block may be shorter than num.
|
|
474
|
+
|
|
312
475
|
"""
|
|
313
476
|
sli = slice(self.start, self.stop).indices(self.numsamples_total)
|
|
314
477
|
i = sli[0]
|
|
315
478
|
stop = sli[1]
|
|
316
479
|
cal_factor = 1.0
|
|
317
480
|
if i >= stop:
|
|
318
|
-
|
|
319
|
-
|
|
481
|
+
msg = 'no samples available'
|
|
482
|
+
raise OSError(msg)
|
|
483
|
+
self._datachecksum # trigger checksum calculation # noqa: B018
|
|
320
484
|
if self.calib:
|
|
321
485
|
if self.calib.num_mics == self.numchannels_total:
|
|
322
486
|
cal_factor = self.calib.data[self.channels][newaxis]
|
|
323
487
|
elif self.calib.num_mics == self.numchannels:
|
|
324
488
|
cal_factor = self.calib.data[newaxis]
|
|
325
489
|
elif self.calib.num_mics == 0:
|
|
326
|
-
warn(
|
|
490
|
+
warn('No calibration data used.', Warning, stacklevel=2)
|
|
327
491
|
else:
|
|
328
|
-
raise ValueError(
|
|
329
|
-
(self.calib.num_mics, self.numchannels))
|
|
492
|
+
raise ValueError('calibration data not compatible: %i, %i' % (self.calib.num_mics, self.numchannels))
|
|
330
493
|
while i < stop:
|
|
331
|
-
yield self.data[i:min(i+num, stop)][:, self.channels]*cal_factor
|
|
494
|
+
yield self.data[i : min(i + num, stop)][:, self.channels] * cal_factor
|
|
332
495
|
i += num
|
|
333
496
|
|
|
334
497
|
|
|
335
|
-
class PointSource(
|
|
336
|
-
"""
|
|
337
|
-
Class to define a fixed point source with an arbitrary signal.
|
|
498
|
+
class PointSource(SamplesGenerator):
|
|
499
|
+
"""Class to define a fixed point source with an arbitrary signal.
|
|
338
500
|
This can be used in simulations.
|
|
339
|
-
|
|
501
|
+
|
|
340
502
|
The output is being generated via the :meth:`result` generator.
|
|
341
503
|
"""
|
|
342
|
-
|
|
504
|
+
|
|
343
505
|
#: Emitted signal, instance of the :class:`~acoular.signals.SignalGenerator` class.
|
|
344
506
|
signal = Trait(SignalGenerator)
|
|
345
|
-
|
|
507
|
+
|
|
346
508
|
#: Location of source in (`x`, `y`, `z`) coordinates (left-oriented system).
|
|
347
|
-
loc = Tuple((0.0, 0.0, 1.0),
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
#: Number of channels in output, is set automatically /
|
|
509
|
+
loc = Tuple((0.0, 0.0, 1.0), desc='source location')
|
|
510
|
+
|
|
511
|
+
#: Number of channels in output, is set automatically /
|
|
351
512
|
#: depends on used microphone geometry.
|
|
352
513
|
numchannels = Delegate('mics', 'num_mics')
|
|
353
514
|
|
|
354
515
|
#: :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
355
|
-
mics = Trait(MicGeom,
|
|
356
|
-
desc="microphone geometry")
|
|
516
|
+
mics = Trait(MicGeom, desc='microphone geometry')
|
|
357
517
|
|
|
358
518
|
def _validate_locations(self):
|
|
359
519
|
dist = self.env._r(array(self.loc).reshape((3, 1)), self.mics.mpos)
|
|
360
520
|
if npany(dist < 1e-7):
|
|
361
|
-
warn(
|
|
362
|
-
|
|
363
|
-
#: :class:`~acoular.environments.Environment` or derived object,
|
|
521
|
+
warn('Source and microphone locations are identical.', Warning, stacklevel=2)
|
|
522
|
+
|
|
523
|
+
#: :class:`~acoular.environments.Environment` or derived object,
|
|
364
524
|
#: which provides information about the sound propagation in the medium.
|
|
365
525
|
env = Trait(Environment(), Environment)
|
|
366
526
|
|
|
@@ -369,68 +529,73 @@ class PointSource( SamplesGenerator ):
|
|
|
369
529
|
# Microphone locations.
|
|
370
530
|
# Deprecated! Use :attr:`mics` trait instead.
|
|
371
531
|
mpos = Property()
|
|
372
|
-
|
|
532
|
+
|
|
373
533
|
def _get_mpos(self):
|
|
374
534
|
return self.mics
|
|
375
|
-
|
|
535
|
+
|
|
376
536
|
def _set_mpos(self, mpos):
|
|
377
|
-
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel
|
|
537
|
+
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2)
|
|
378
538
|
self.mics = mpos
|
|
379
|
-
|
|
539
|
+
|
|
380
540
|
# The speed of sound.
|
|
381
|
-
# Deprecated! Only kept for backwards compatibility.
|
|
541
|
+
# Deprecated! Only kept for backwards compatibility.
|
|
382
542
|
# Now governed by :attr:`env` trait.
|
|
383
543
|
c = Property()
|
|
384
|
-
|
|
544
|
+
|
|
385
545
|
def _get_c(self):
|
|
386
546
|
return self.env.c
|
|
387
|
-
|
|
547
|
+
|
|
388
548
|
def _set_c(self, c):
|
|
389
|
-
warn("Deprecated use of 'c' trait. ", Warning, stacklevel
|
|
549
|
+
warn("Deprecated use of 'c' trait. ", Warning, stacklevel=2)
|
|
390
550
|
self.env.c = c
|
|
391
551
|
|
|
392
552
|
# --- End of backwards compatibility traits --------------------------------------
|
|
393
|
-
|
|
553
|
+
|
|
394
554
|
#: Start time of the signal in seconds, defaults to 0 s.
|
|
395
|
-
start_t = Float(0.0,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
#: Start time of the data aquisition at microphones in seconds,
|
|
555
|
+
start_t = Float(0.0, desc='signal start time')
|
|
556
|
+
|
|
557
|
+
#: Start time of the data aquisition at microphones in seconds,
|
|
399
558
|
#: defaults to 0 s.
|
|
400
|
-
start = Float(0.0,
|
|
401
|
-
desc="sample start time")
|
|
559
|
+
start = Float(0.0, desc='sample start time')
|
|
402
560
|
|
|
403
561
|
#: Signal behaviour for negative time indices, i.e. if :attr:`start` < :attr:start_t.
|
|
404
562
|
#: `loop` take values from the end of :attr:`signal.signal()` array.
|
|
405
563
|
#: `zeros` set source signal to zero, advisable for deterministic signals.
|
|
406
564
|
#: defaults to `loop`.
|
|
407
|
-
prepadding = Trait('loop','zeros', desc=
|
|
565
|
+
prepadding = Trait('loop', 'zeros', desc='Behaviour for negative time indices.')
|
|
408
566
|
|
|
409
567
|
#: Upsampling factor, internal use, defaults to 16.
|
|
410
|
-
up = Int(16,
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
#: Number of samples, is set automatically /
|
|
568
|
+
up = Int(16, desc='upsampling factor')
|
|
569
|
+
|
|
570
|
+
#: Number of samples, is set automatically /
|
|
414
571
|
#: depends on :attr:`signal`.
|
|
415
572
|
numsamples = Delegate('signal')
|
|
416
|
-
|
|
417
|
-
#: Sampling frequency of the signal, is set automatically /
|
|
573
|
+
|
|
574
|
+
#: Sampling frequency of the signal, is set automatically /
|
|
418
575
|
#: depends on :attr:`signal`.
|
|
419
|
-
sample_freq = Delegate('signal')
|
|
576
|
+
sample_freq = Delegate('signal')
|
|
420
577
|
|
|
421
578
|
# internal identifier
|
|
422
|
-
digest = Property(
|
|
423
|
-
depends_on
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
579
|
+
digest = Property(
|
|
580
|
+
depends_on=[
|
|
581
|
+
'mics.digest',
|
|
582
|
+
'signal.digest',
|
|
583
|
+
'loc',
|
|
584
|
+
'env.digest',
|
|
585
|
+
'start_t',
|
|
586
|
+
'start',
|
|
587
|
+
'up',
|
|
588
|
+
'prepadding',
|
|
589
|
+
'__class__',
|
|
590
|
+
],
|
|
591
|
+
)
|
|
592
|
+
|
|
427
593
|
@cached_property
|
|
428
|
-
def _get_digest(
|
|
594
|
+
def _get_digest(self):
|
|
429
595
|
return digest(self)
|
|
430
596
|
|
|
431
597
|
def result(self, num=128):
|
|
432
|
-
"""
|
|
433
|
-
Python generator that yields the output at microphones block-wise.
|
|
598
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
434
599
|
|
|
435
600
|
Parameters
|
|
436
601
|
----------
|
|
@@ -440,542 +605,588 @@ class PointSource( SamplesGenerator ):
|
|
|
440
605
|
|
|
441
606
|
Returns
|
|
442
607
|
-------
|
|
443
|
-
Samples in blocks of shape (num, numchannels).
|
|
608
|
+
Samples in blocks of shape (num, numchannels).
|
|
444
609
|
The last block may be shorter than num.
|
|
610
|
+
|
|
445
611
|
"""
|
|
446
|
-
|
|
447
612
|
self._validate_locations()
|
|
448
|
-
N = int(ceil(self.numsamples/num))
|
|
613
|
+
N = int(ceil(self.numsamples / num)) # number of output blocks
|
|
449
614
|
signal = self.signal.usignal(self.up)
|
|
450
615
|
out = empty((num, self.numchannels))
|
|
451
616
|
# distances
|
|
452
|
-
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.mpos).reshape(1
|
|
617
|
+
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.mpos).reshape(1, -1)
|
|
453
618
|
# emission time relative to start_t (in samples) for first sample
|
|
454
|
-
ind = (-rm/self.env.c-self.start_t+self.start)*self.sample_freq*self.up
|
|
619
|
+
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq * self.up
|
|
455
620
|
|
|
456
621
|
if self.prepadding == 'zeros':
|
|
457
622
|
# number of blocks where signal behaviour is amended
|
|
458
|
-
pre = -int(npmin(ind[0])//(self.up*num))
|
|
623
|
+
pre = -int(npmin(ind[0]) // (self.up * num))
|
|
459
624
|
# amend signal for first blocks
|
|
460
625
|
# if signal stops during prepadding, terminate
|
|
461
|
-
if
|
|
462
|
-
for
|
|
463
|
-
out = _fill_mic_signal_block(out,signal,rm,ind,num,self.numchannels,self.up,True)
|
|
626
|
+
if pre >= N:
|
|
627
|
+
for _nb in range(N - 1):
|
|
628
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.numchannels, self.up, True)
|
|
464
629
|
yield out
|
|
465
630
|
|
|
466
|
-
blocksize = self.numsamples%num or num
|
|
467
|
-
out = _fill_mic_signal_block(out,signal,rm,ind,blocksize,self.numchannels,self.up,True)
|
|
631
|
+
blocksize = self.numsamples % num or num
|
|
632
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, blocksize, self.numchannels, self.up, True)
|
|
468
633
|
yield out[:blocksize]
|
|
469
634
|
return
|
|
470
635
|
else:
|
|
471
|
-
for
|
|
472
|
-
out = _fill_mic_signal_block(out,signal,rm,ind,num,self.numchannels,self.up,True)
|
|
636
|
+
for _nb in range(pre):
|
|
637
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.numchannels, self.up, True)
|
|
473
638
|
yield out
|
|
474
639
|
|
|
475
640
|
else:
|
|
476
641
|
pre = 0
|
|
477
642
|
|
|
478
643
|
# main generator
|
|
479
|
-
for
|
|
480
|
-
out = _fill_mic_signal_block(out,signal,rm,ind,num,self.numchannels,self.up,False)
|
|
644
|
+
for _nb in range(N - pre - 1):
|
|
645
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, num, self.numchannels, self.up, False)
|
|
481
646
|
yield out
|
|
482
647
|
|
|
483
648
|
# last block of variable size
|
|
484
|
-
blocksize = self.numsamples%num or num
|
|
485
|
-
out = _fill_mic_signal_block(out,signal,rm,ind,blocksize,self.numchannels,self.up,False)
|
|
649
|
+
blocksize = self.numsamples % num or num
|
|
650
|
+
out = _fill_mic_signal_block(out, signal, rm, ind, blocksize, self.numchannels, self.up, False)
|
|
486
651
|
yield out[:blocksize]
|
|
487
652
|
|
|
488
653
|
|
|
489
|
-
class SphericalHarmonicSource(
|
|
490
|
-
"""
|
|
491
|
-
Class to define a fixed Spherical Harmonic Source with an arbitrary signal.
|
|
654
|
+
class SphericalHarmonicSource(PointSource):
|
|
655
|
+
"""Class to define a fixed Spherical Harmonic Source with an arbitrary signal.
|
|
492
656
|
This can be used in simulations.
|
|
493
|
-
|
|
657
|
+
|
|
494
658
|
The output is being generated via the :meth:`result` generator.
|
|
495
659
|
"""
|
|
496
|
-
|
|
660
|
+
|
|
497
661
|
#: Order of spherical harmonic source
|
|
498
|
-
lOrder = Int(0,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
prepadding = Enum('loop', desc="Behaviour for negative time indices.")
|
|
509
|
-
|
|
662
|
+
lOrder = Int(0, desc='Order of spherical harmonic') # noqa: N815
|
|
663
|
+
|
|
664
|
+
alpha = CArray(desc='coefficients of the (lOrder,) spherical harmonic mode')
|
|
665
|
+
|
|
666
|
+
#: Vector to define the orientation of the SphericalHarmonic.
|
|
667
|
+
direction = Tuple((1.0, 0.0, 0.0), desc='Spherical Harmonic orientation')
|
|
668
|
+
|
|
669
|
+
prepadding = Enum('loop', desc='Behaviour for negative time indices.')
|
|
670
|
+
|
|
510
671
|
# internal identifier
|
|
511
|
-
digest = Property(
|
|
512
|
-
depends_on
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
672
|
+
digest = Property(
|
|
673
|
+
depends_on=[
|
|
674
|
+
'mics.digest',
|
|
675
|
+
'signal.digest',
|
|
676
|
+
'loc',
|
|
677
|
+
'env.digest',
|
|
678
|
+
'start_t',
|
|
679
|
+
'start',
|
|
680
|
+
'up',
|
|
681
|
+
'__class__',
|
|
682
|
+
'alpha',
|
|
683
|
+
'lOrder',
|
|
684
|
+
'prepadding',
|
|
685
|
+
],
|
|
686
|
+
)
|
|
687
|
+
|
|
516
688
|
@cached_property
|
|
517
|
-
def _get_digest(
|
|
689
|
+
def _get_digest(self):
|
|
518
690
|
return digest(self)
|
|
519
691
|
|
|
520
|
-
def transform(self,signals):
|
|
521
|
-
Y_lm = get_modes(
|
|
522
|
-
|
|
692
|
+
def transform(self, signals):
|
|
693
|
+
Y_lm = get_modes(
|
|
694
|
+
lOrder=self.lOrder,
|
|
695
|
+
direction=self.direction,
|
|
696
|
+
mpos=self.mics.mpos,
|
|
697
|
+
sourceposition=array(self.loc),
|
|
698
|
+
)
|
|
699
|
+
return real(ifft(fft(signals, axis=0) * (Y_lm @ self.alpha), axis=0))
|
|
523
700
|
|
|
524
701
|
def result(self, num=128):
|
|
525
|
-
"""
|
|
526
|
-
|
|
527
|
-
|
|
702
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
703
|
+
|
|
528
704
|
Parameters
|
|
529
705
|
----------
|
|
530
706
|
num : integer, defaults to 128
|
|
531
707
|
This parameter defines the size of the blocks to be yielded
|
|
532
708
|
(i.e. the number of samples per block) .
|
|
533
|
-
|
|
709
|
+
|
|
534
710
|
Returns
|
|
535
711
|
-------
|
|
536
|
-
Samples in blocks of shape (num, numchannels).
|
|
712
|
+
Samples in blocks of shape (num, numchannels).
|
|
537
713
|
The last block may be shorter than num.
|
|
714
|
+
|
|
538
715
|
"""
|
|
539
|
-
#If signal samples are needed for te < t_start, then samples are taken
|
|
540
|
-
#from the end of the calculated signal.
|
|
541
|
-
|
|
716
|
+
# If signal samples are needed for te < t_start, then samples are taken
|
|
717
|
+
# from the end of the calculated signal.
|
|
718
|
+
|
|
542
719
|
signal = self.signal.usignal(self.up)
|
|
543
720
|
# emission time relative to start_t (in samples) for first sample
|
|
544
721
|
rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.mpos)
|
|
545
|
-
ind = (-rm/self.env.c-self.start_t+self.start)*self.sample_freq
|
|
722
|
+
ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq + pi / 30
|
|
546
723
|
i = 0
|
|
547
724
|
n = self.numsamples
|
|
548
725
|
out = empty((num, self.numchannels))
|
|
549
726
|
while n:
|
|
550
727
|
n -= 1
|
|
551
728
|
try:
|
|
552
|
-
out[i] = signal[array(0.5+ind*self.up, dtype=int64)]/rm
|
|
729
|
+
out[i] = signal[array(0.5 + ind * self.up, dtype=int64)] / rm
|
|
553
730
|
ind += 1
|
|
554
731
|
i += 1
|
|
555
732
|
if i == num:
|
|
556
733
|
yield self.transform(out)
|
|
557
734
|
i = 0
|
|
558
|
-
except IndexError:
|
|
735
|
+
except IndexError: # if no more samples available from the source
|
|
559
736
|
break
|
|
560
|
-
if i > 0:
|
|
737
|
+
if i > 0: # if there are still samples to yield
|
|
561
738
|
yield self.transform(out[:i])
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
Class to define a point source with an arbitrary
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
class MovingPointSource(PointSource):
|
|
742
|
+
"""Class to define a point source with an arbitrary
|
|
566
743
|
signal moving along a given trajectory.
|
|
567
744
|
This can be used in simulations.
|
|
568
|
-
|
|
745
|
+
|
|
569
746
|
The output is being generated via the :meth:`result` generator.
|
|
570
747
|
"""
|
|
571
748
|
|
|
572
749
|
#: Considering of convective amplification
|
|
573
|
-
conv_amp = Bool(False,
|
|
574
|
-
desc="determines if convective amplification is considered")
|
|
750
|
+
conv_amp = Bool(False, desc='determines if convective amplification is considered')
|
|
575
751
|
|
|
576
|
-
#: Trajectory of the source,
|
|
752
|
+
#: Trajectory of the source,
|
|
577
753
|
#: instance of the :class:`~acoular.trajectory.Trajectory` class.
|
|
578
754
|
#: The start time is assumed to be the same as for the samples.
|
|
579
|
-
trajectory = Trait(Trajectory,
|
|
580
|
-
desc="trajectory of the source")
|
|
755
|
+
trajectory = Trait(Trajectory, desc='trajectory of the source')
|
|
581
756
|
|
|
582
|
-
prepadding = Enum('loop', desc=
|
|
757
|
+
prepadding = Enum('loop', desc='Behaviour for negative time indices.')
|
|
583
758
|
|
|
584
759
|
# internal identifier
|
|
585
|
-
digest = Property(
|
|
586
|
-
depends_on
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
760
|
+
digest = Property(
|
|
761
|
+
depends_on=[
|
|
762
|
+
'mics.digest',
|
|
763
|
+
'signal.digest',
|
|
764
|
+
'loc',
|
|
765
|
+
'conv_amp',
|
|
766
|
+
'env.digest',
|
|
767
|
+
'start_t',
|
|
768
|
+
'start',
|
|
769
|
+
'trajectory.digest',
|
|
770
|
+
'prepadding',
|
|
771
|
+
'__class__',
|
|
772
|
+
],
|
|
773
|
+
)
|
|
774
|
+
|
|
590
775
|
@cached_property
|
|
591
|
-
def _get_digest(
|
|
776
|
+
def _get_digest(self):
|
|
592
777
|
return digest(self)
|
|
593
778
|
|
|
594
779
|
def result(self, num=128):
|
|
595
|
-
"""
|
|
596
|
-
|
|
597
|
-
|
|
780
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
781
|
+
|
|
598
782
|
Parameters
|
|
599
783
|
----------
|
|
600
784
|
num : integer, defaults to 128
|
|
601
785
|
This parameter defines the size of the blocks to be yielded
|
|
602
786
|
(i.e. the number of samples per block).
|
|
603
|
-
|
|
787
|
+
|
|
604
788
|
Returns
|
|
605
789
|
-------
|
|
606
|
-
Samples in blocks of shape (num, numchannels).
|
|
790
|
+
Samples in blocks of shape (num, numchannels).
|
|
607
791
|
The last block may be shorter than num.
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
#
|
|
611
|
-
|
|
792
|
+
|
|
793
|
+
"""
|
|
794
|
+
# If signal samples are needed for te < t_start, then samples are taken
|
|
795
|
+
# from the end of the calculated signal.
|
|
796
|
+
|
|
612
797
|
signal = self.signal.usignal(self.up)
|
|
613
798
|
out = empty((num, self.numchannels))
|
|
614
799
|
# shortcuts and intial values
|
|
615
800
|
m = self.mics
|
|
616
|
-
t = self.start*ones(m.num_mics)
|
|
801
|
+
t = self.start * ones(m.num_mics)
|
|
617
802
|
i = 0
|
|
618
|
-
epslim = 0.1/self.up/self.sample_freq
|
|
803
|
+
epslim = 0.1 / self.up / self.sample_freq
|
|
619
804
|
c0 = self.env.c
|
|
620
805
|
tr = self.trajectory
|
|
621
806
|
n = self.numsamples
|
|
622
807
|
while n:
|
|
623
808
|
n -= 1
|
|
624
809
|
eps = ones(m.num_mics)
|
|
625
|
-
te = t.copy()
|
|
810
|
+
te = t.copy() # init emission time = receiving time
|
|
626
811
|
j = 0
|
|
627
812
|
# Newton-Rhapson iteration
|
|
628
|
-
while abs(eps).max()>epslim and j<100:
|
|
813
|
+
while abs(eps).max() > epslim and j < 100:
|
|
629
814
|
loc = array(tr.location(te))
|
|
630
|
-
rm = loc-m.mpos# distance vectors to microphones
|
|
631
|
-
rm = sqrt((rm*rm).sum(0))# absolute distance
|
|
632
|
-
loc /= sqrt((loc*loc).sum(0))# distance unit vector
|
|
815
|
+
rm = loc - m.mpos # distance vectors to microphones
|
|
816
|
+
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
817
|
+
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
633
818
|
der = array(tr.location(te, der=1))
|
|
634
|
-
Mr = (der*loc).sum(0)/c0# radial Mach number
|
|
635
|
-
eps = (te + rm/c0 - t)/(1+Mr)# discrepancy in time
|
|
819
|
+
Mr = (der * loc).sum(0) / c0 # radial Mach number
|
|
820
|
+
eps = (te + rm / c0 - t) / (1 + Mr) # discrepancy in time
|
|
636
821
|
te -= eps
|
|
637
|
-
j += 1
|
|
638
|
-
t += 1
|
|
822
|
+
j += 1 # iteration count
|
|
823
|
+
t += 1.0 / self.sample_freq
|
|
639
824
|
# emission time relative to start time
|
|
640
|
-
ind = (te-self.start_t+self.start)*self.sample_freq
|
|
641
|
-
if self.conv_amp:
|
|
825
|
+
ind = (te - self.start_t + self.start) * self.sample_freq
|
|
826
|
+
if self.conv_amp:
|
|
827
|
+
rm *= (1 - Mr) ** 2
|
|
642
828
|
try:
|
|
643
|
-
out[i] = signal[array(0.5+ind*self.up, dtype=int64)]/rm
|
|
829
|
+
out[i] = signal[array(0.5 + ind * self.up, dtype=int64)] / rm
|
|
644
830
|
i += 1
|
|
645
831
|
if i == num:
|
|
646
832
|
yield out
|
|
647
833
|
i = 0
|
|
648
|
-
except IndexError:
|
|
834
|
+
except IndexError: # if no more samples available from the source
|
|
649
835
|
break
|
|
650
|
-
if i > 0:
|
|
836
|
+
if i > 0: # if there are still samples to yield
|
|
651
837
|
yield out[:i]
|
|
652
|
-
|
|
653
838
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
Class to define a fixed point source with an arbitrary signal and
|
|
839
|
+
|
|
840
|
+
class PointSourceDipole(PointSource):
|
|
841
|
+
"""Class to define a fixed point source with an arbitrary signal and
|
|
657
842
|
dipole characteristics via superposition of two nearby inversely
|
|
658
843
|
phased monopoles.
|
|
659
844
|
This can be used in simulations.
|
|
660
|
-
|
|
845
|
+
|
|
661
846
|
The output is being generated via the :meth:`result` generator.
|
|
662
847
|
"""
|
|
663
|
-
|
|
848
|
+
|
|
664
849
|
#: Vector to define the orientation of the dipole lobes. Its magnitude
|
|
665
850
|
#: governs the distance between the monopoles
|
|
666
851
|
#: (dist = [lowest wavelength in spectrum] x [magnitude] x 1e-5).
|
|
667
|
-
#: Note: Use vectors with order of magnitude around 1.0 or less
|
|
852
|
+
#: Note: Use vectors with order of magnitude around 1.0 or less
|
|
668
853
|
#: for good results.
|
|
669
|
-
direction = Tuple((0.0, 0.0, 1.0),
|
|
670
|
-
|
|
854
|
+
direction = Tuple((0.0, 0.0, 1.0), desc='dipole orientation and distance of the inversely phased monopoles')
|
|
855
|
+
|
|
856
|
+
prepadding = Enum('loop', desc='Behaviour for negative time indices.')
|
|
671
857
|
|
|
672
|
-
prepadding = Enum('loop', desc="Behaviour for negative time indices.")
|
|
673
|
-
|
|
674
858
|
# internal identifier
|
|
675
|
-
digest = Property(
|
|
676
|
-
depends_on
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
859
|
+
digest = Property(
|
|
860
|
+
depends_on=[
|
|
861
|
+
'mics.digest',
|
|
862
|
+
'signal.digest',
|
|
863
|
+
'loc',
|
|
864
|
+
'env.digest',
|
|
865
|
+
'start_t',
|
|
866
|
+
'start',
|
|
867
|
+
'up',
|
|
868
|
+
'direction',
|
|
869
|
+
'prepadding',
|
|
870
|
+
'__class__',
|
|
871
|
+
],
|
|
872
|
+
)
|
|
873
|
+
|
|
680
874
|
@cached_property
|
|
681
|
-
def _get_digest(
|
|
875
|
+
def _get_digest(self):
|
|
682
876
|
return digest(self)
|
|
683
|
-
|
|
684
|
-
|
|
877
|
+
|
|
685
878
|
def result(self, num=128):
|
|
686
|
-
"""
|
|
687
|
-
|
|
688
|
-
|
|
879
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
880
|
+
|
|
689
881
|
Parameters
|
|
690
882
|
----------
|
|
691
883
|
num : integer, defaults to 128
|
|
692
884
|
This parameter defines the size of the blocks to be yielded
|
|
693
885
|
(i.e. the number of samples per block) .
|
|
694
|
-
|
|
886
|
+
|
|
695
887
|
Returns
|
|
696
888
|
-------
|
|
697
|
-
Samples in blocks of shape (num, numchannels).
|
|
889
|
+
Samples in blocks of shape (num, numchannels).
|
|
698
890
|
The last block may be shorter than num.
|
|
891
|
+
|
|
699
892
|
"""
|
|
700
|
-
#If signal samples are needed for te < t_start, then samples are taken
|
|
701
|
-
#from the end of the calculated signal.
|
|
702
|
-
|
|
893
|
+
# If signal samples are needed for te < t_start, then samples are taken
|
|
894
|
+
# from the end of the calculated signal.
|
|
895
|
+
|
|
703
896
|
mpos = self.mics.mpos
|
|
704
897
|
# position of the dipole as (3,1) vector
|
|
705
|
-
loc = array(self.loc, dtype
|
|
898
|
+
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
706
899
|
# direction vector from tuple
|
|
707
|
-
direc = array(self.direction, dtype
|
|
708
|
-
direc_mag =
|
|
709
|
-
|
|
900
|
+
direc = array(self.direction, dtype=float) * 1e-5
|
|
901
|
+
direc_mag = sqrt(dot(direc, direc))
|
|
902
|
+
|
|
710
903
|
# normed direction vector
|
|
711
904
|
direc_n = direc / direc_mag
|
|
712
|
-
|
|
905
|
+
|
|
713
906
|
c = self.env.c
|
|
714
|
-
|
|
907
|
+
|
|
715
908
|
# distance between monopoles as function of c, sample freq, direction vector
|
|
716
909
|
dist = c / self.sample_freq * direc_mag
|
|
717
|
-
|
|
910
|
+
|
|
718
911
|
# vector from dipole center to one of the monopoles
|
|
719
912
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
720
|
-
|
|
913
|
+
|
|
721
914
|
signal = self.signal.usignal(self.up)
|
|
722
915
|
out = empty((num, self.numchannels))
|
|
723
|
-
|
|
916
|
+
|
|
724
917
|
# distance from dipole center to microphones
|
|
725
918
|
rm = self.env._r(loc, mpos)
|
|
726
|
-
|
|
919
|
+
|
|
727
920
|
# distances from monopoles to microphones
|
|
728
921
|
rm1 = self.env._r(loc + dir2, mpos)
|
|
729
922
|
rm2 = self.env._r(loc - dir2, mpos)
|
|
730
|
-
|
|
923
|
+
|
|
731
924
|
# emission time relative to start_t (in samples) for first sample
|
|
732
|
-
ind1 = (-rm1 / c - self.start_t + self.start) * self.sample_freq
|
|
925
|
+
ind1 = (-rm1 / c - self.start_t + self.start) * self.sample_freq
|
|
733
926
|
ind2 = (-rm2 / c - self.start_t + self.start) * self.sample_freq
|
|
734
|
-
|
|
927
|
+
|
|
735
928
|
i = 0
|
|
736
|
-
n = self.numsamples
|
|
929
|
+
n = self.numsamples
|
|
737
930
|
while n:
|
|
738
931
|
n -= 1
|
|
739
932
|
try:
|
|
740
933
|
# subtract the second signal b/c of phase inversion
|
|
741
|
-
out[i] =
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
934
|
+
out[i] = (
|
|
935
|
+
rm
|
|
936
|
+
/ dist
|
|
937
|
+
* (
|
|
938
|
+
signal[array(0.5 + ind1 * self.up, dtype=int64)] / rm1
|
|
939
|
+
- signal[array(0.5 + ind2 * self.up, dtype=int64)] / rm2
|
|
940
|
+
)
|
|
941
|
+
)
|
|
942
|
+
ind1 += 1.0
|
|
943
|
+
ind2 += 1.0
|
|
944
|
+
|
|
747
945
|
i += 1
|
|
748
946
|
if i == num:
|
|
749
947
|
yield out
|
|
750
948
|
i = 0
|
|
751
949
|
except IndexError:
|
|
752
950
|
break
|
|
753
|
-
|
|
951
|
+
|
|
754
952
|
yield out[:i]
|
|
755
953
|
|
|
954
|
+
|
|
756
955
|
class MovingPointSourceDipole(PointSourceDipole, MovingPointSource):
|
|
757
|
-
|
|
758
956
|
# internal identifier
|
|
759
|
-
digest = Property(
|
|
760
|
-
depends_on
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
957
|
+
digest = Property(
|
|
958
|
+
depends_on=[
|
|
959
|
+
'mics.digest',
|
|
960
|
+
'signal.digest',
|
|
961
|
+
'loc',
|
|
962
|
+
'env.digest',
|
|
963
|
+
'start_t',
|
|
964
|
+
'start',
|
|
965
|
+
'up',
|
|
966
|
+
'direction',
|
|
967
|
+
'__class__',
|
|
968
|
+
],
|
|
969
|
+
)
|
|
970
|
+
|
|
764
971
|
#: Reference vector, perpendicular to the x and y-axis of moving source.
|
|
765
972
|
#: rotation source directivity around this axis
|
|
766
|
-
rvec = CArray(
|
|
767
|
-
|
|
768
|
-
|
|
973
|
+
rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0)), desc='reference vector')
|
|
974
|
+
|
|
769
975
|
@cached_property
|
|
770
|
-
def _get_digest(
|
|
771
|
-
return digest(self)
|
|
976
|
+
def _get_digest(self):
|
|
977
|
+
return digest(self)
|
|
772
978
|
|
|
773
|
-
def get_emission_time(self,t,direction):
|
|
979
|
+
def get_emission_time(self, t, direction):
|
|
774
980
|
eps = ones(self.mics.num_mics)
|
|
775
|
-
epslim = 0.1/self.up/self.sample_freq
|
|
776
|
-
te = t.copy()
|
|
981
|
+
epslim = 0.1 / self.up / self.sample_freq
|
|
982
|
+
te = t.copy() # init emission time = receiving time
|
|
777
983
|
j = 0
|
|
778
984
|
# Newton-Rhapson iteration
|
|
779
|
-
while abs(eps).max()>epslim and j<100:
|
|
985
|
+
while abs(eps).max() > epslim and j < 100:
|
|
780
986
|
xs = array(self.trajectory.location(te))
|
|
781
987
|
loc = xs.copy()
|
|
782
988
|
loc += direction
|
|
783
|
-
rm = loc-self.mics.mpos# distance vectors to microphones
|
|
784
|
-
rm = sqrt((rm*rm).sum(0))# absolute distance
|
|
785
|
-
loc /= sqrt((loc*loc).sum(0))# distance unit vector
|
|
989
|
+
rm = loc - self.mics.mpos # distance vectors to microphones
|
|
990
|
+
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
991
|
+
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
786
992
|
der = array(self.trajectory.location(te, der=1))
|
|
787
|
-
Mr = (der*loc).sum(0)/self.env.c# radial Mach number
|
|
788
|
-
eps = (te + rm/self.env.c - t)/(1+Mr)# discrepancy in time
|
|
993
|
+
Mr = (der * loc).sum(0) / self.env.c # radial Mach number
|
|
994
|
+
eps = (te + rm / self.env.c - t) / (1 + Mr) # discrepancy in time
|
|
789
995
|
te -= eps
|
|
790
|
-
j += 1
|
|
996
|
+
j += 1 # iteration count
|
|
791
997
|
return te, rm, Mr, xs
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
def get_moving_direction(self,direction,time=0):
|
|
795
|
-
"""
|
|
796
|
-
function that yields the moving coordinates along the trajectory
|
|
797
|
-
"""
|
|
798
998
|
|
|
799
|
-
|
|
800
|
-
|
|
999
|
+
def get_moving_direction(self, direction, time=0):
|
|
1000
|
+
"""Function that yields the moving coordinates along the trajectory."""
|
|
1001
|
+
trajg1 = array(self.trajectory.location(time, der=1))[:, 0][:, newaxis]
|
|
1002
|
+
rflag = (self.rvec == 0).all() # flag translation vs. rotation
|
|
801
1003
|
if rflag:
|
|
802
|
-
return direction
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
return cross(newdir[:,0].T,self.rvec.T).T
|
|
1004
|
+
return direction
|
|
1005
|
+
dx = array(trajg1.T) # direction vector (new x-axis)
|
|
1006
|
+
dy = cross(self.rvec, dx) # new y-axis
|
|
1007
|
+
dz = cross(dx, dy) # new z-axis
|
|
1008
|
+
RM = array((dx, dy, dz)).T # rotation matrix
|
|
1009
|
+
RM /= sqrt((RM * RM).sum(0)) # column normalized
|
|
1010
|
+
newdir = dot(RM, direction)
|
|
1011
|
+
return cross(newdir[:, 0].T, self.rvec.T).T
|
|
811
1012
|
|
|
812
1013
|
def result(self, num=128):
|
|
813
|
-
"""
|
|
814
|
-
|
|
815
|
-
|
|
1014
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
1015
|
+
|
|
816
1016
|
Parameters
|
|
817
1017
|
----------
|
|
818
1018
|
num : integer, defaults to 128
|
|
819
1019
|
This parameter defines the size of the blocks to be yielded
|
|
820
1020
|
(i.e. the number of samples per block) .
|
|
821
|
-
|
|
1021
|
+
|
|
822
1022
|
Returns
|
|
823
1023
|
-------
|
|
824
|
-
Samples in blocks of shape (num, numchannels).
|
|
1024
|
+
Samples in blocks of shape (num, numchannels).
|
|
825
1025
|
The last block may be shorter than num.
|
|
1026
|
+
|
|
826
1027
|
"""
|
|
827
|
-
#If signal samples are needed for te < t_start, then samples are taken
|
|
828
|
-
#from the end of the calculated signal.
|
|
1028
|
+
# If signal samples are needed for te < t_start, then samples are taken
|
|
1029
|
+
# from the end of the calculated signal.
|
|
829
1030
|
mpos = self.mics.mpos
|
|
830
|
-
|
|
1031
|
+
|
|
831
1032
|
# direction vector from tuple
|
|
832
|
-
direc = array(self.direction, dtype
|
|
833
|
-
direc_mag =
|
|
1033
|
+
direc = array(self.direction, dtype=float) * 1e-5
|
|
1034
|
+
direc_mag = sqrt(dot(direc, direc))
|
|
834
1035
|
# normed direction vector
|
|
835
1036
|
direc_n = direc / direc_mag
|
|
836
1037
|
c = self.env.c
|
|
837
1038
|
# distance between monopoles as function of c, sample freq, direction vector
|
|
838
1039
|
dist = c / self.sample_freq * direc_mag * 2
|
|
839
|
-
|
|
1040
|
+
|
|
840
1041
|
# vector from dipole center to one of the monopoles
|
|
841
1042
|
dir2 = (direc_n * dist / 2.0).reshape((3, 1))
|
|
842
|
-
|
|
1043
|
+
|
|
843
1044
|
signal = self.signal.usignal(self.up)
|
|
844
1045
|
out = empty((num, self.numchannels))
|
|
845
1046
|
# shortcuts and intial values
|
|
846
1047
|
m = self.mics
|
|
847
|
-
t = self.start*ones(m.num_mics)
|
|
1048
|
+
t = self.start * ones(m.num_mics)
|
|
848
1049
|
|
|
849
1050
|
i = 0
|
|
850
|
-
n = self.numsamples
|
|
1051
|
+
n = self.numsamples
|
|
851
1052
|
while n:
|
|
852
1053
|
n -= 1
|
|
853
|
-
te, rm, Mr, locs = self.get_emission_time(t,0)
|
|
854
|
-
t += 1
|
|
855
|
-
#location of the center
|
|
856
|
-
loc = array(self.trajectory.location(te), dtype
|
|
857
|
-
#distance of the dipoles from the center
|
|
858
|
-
diff = self.get_moving_direction(dir2,te)
|
|
859
|
-
|
|
1054
|
+
te, rm, Mr, locs = self.get_emission_time(t, 0)
|
|
1055
|
+
t += 1.0 / self.sample_freq
|
|
1056
|
+
# location of the center
|
|
1057
|
+
loc = array(self.trajectory.location(te), dtype=float)[:, 0][:, newaxis]
|
|
1058
|
+
# distance of the dipoles from the center
|
|
1059
|
+
diff = self.get_moving_direction(dir2, te)
|
|
1060
|
+
|
|
860
1061
|
# distance of sources
|
|
861
|
-
rm1 = self.env._r(loc + diff, mpos)
|
|
1062
|
+
rm1 = self.env._r(loc + diff, mpos)
|
|
862
1063
|
rm2 = self.env._r(loc - diff, mpos)
|
|
863
|
-
|
|
864
|
-
ind = (te-self.start_t+self.start)*self.sample_freq
|
|
865
|
-
if self.conv_amp:
|
|
866
|
-
rm *= (1-Mr)**2
|
|
867
|
-
rm1 *= (1-Mr)**2
|
|
868
|
-
rm2 *= (1-Mr)**2
|
|
1064
|
+
|
|
1065
|
+
ind = (te - self.start_t + self.start) * self.sample_freq
|
|
1066
|
+
if self.conv_amp:
|
|
1067
|
+
rm *= (1 - Mr) ** 2
|
|
1068
|
+
rm1 *= (1 - Mr) ** 2 # assume that Mr is the same for both poles
|
|
1069
|
+
rm2 *= (1 - Mr) ** 2
|
|
869
1070
|
try:
|
|
870
1071
|
# subtract the second signal b/c of phase inversion
|
|
871
|
-
out[i] =
|
|
872
|
-
|
|
873
|
-
|
|
1072
|
+
out[i] = (
|
|
1073
|
+
rm
|
|
1074
|
+
/ dist
|
|
1075
|
+
* (
|
|
1076
|
+
signal[array(0.5 + ind * self.up, dtype=int64)] / rm1
|
|
1077
|
+
- signal[array(0.5 + ind * self.up, dtype=int64)] / rm2
|
|
1078
|
+
)
|
|
1079
|
+
)
|
|
874
1080
|
i += 1
|
|
875
1081
|
if i == num:
|
|
876
1082
|
yield out
|
|
877
1083
|
i = 0
|
|
878
1084
|
except IndexError:
|
|
879
1085
|
break
|
|
880
|
-
yield out[:i]
|
|
881
|
-
|
|
1086
|
+
yield out[:i]
|
|
882
1087
|
|
|
883
1088
|
|
|
884
|
-
class LineSource(
|
|
885
|
-
"""
|
|
886
|
-
Class to define a fixed Line source with an arbitrary signal.
|
|
1089
|
+
class LineSource(PointSource):
|
|
1090
|
+
"""Class to define a fixed Line source with an arbitrary signal.
|
|
887
1091
|
This can be used in simulations.
|
|
888
|
-
|
|
1092
|
+
|
|
889
1093
|
The output is being generated via the :meth:`result` generator.
|
|
890
1094
|
"""
|
|
891
|
-
|
|
1095
|
+
|
|
892
1096
|
#: Vector to define the orientation of the line source
|
|
893
|
-
direction = Tuple((0.0, 0.0, 1.0),
|
|
894
|
-
|
|
895
|
-
|
|
1097
|
+
direction = Tuple((0.0, 0.0, 1.0), desc='Line orientation ')
|
|
1098
|
+
|
|
896
1099
|
#: Vector to define the length of the line source in m
|
|
897
|
-
length = Float(1,desc=
|
|
898
|
-
|
|
1100
|
+
length = Float(1, desc='length of the line source')
|
|
1101
|
+
|
|
899
1102
|
#: number of monopol sources in the line source
|
|
900
1103
|
num_sources = Int(1)
|
|
901
|
-
|
|
1104
|
+
|
|
902
1105
|
#: source strength for every monopole
|
|
903
|
-
source_strength = CArray(desc=
|
|
904
|
-
|
|
1106
|
+
source_strength = CArray(desc='coefficients of the source strength')
|
|
1107
|
+
|
|
905
1108
|
#:coherence
|
|
906
|
-
coherence = Trait(
|
|
907
|
-
|
|
908
|
-
|
|
1109
|
+
coherence = Trait('coherent', 'incoherent', desc='coherence mode')
|
|
1110
|
+
|
|
909
1111
|
# internal identifier
|
|
910
|
-
digest = Property(
|
|
911
|
-
depends_on
|
|
912
|
-
|
|
913
|
-
|
|
1112
|
+
digest = Property(
|
|
1113
|
+
depends_on=[
|
|
1114
|
+
'mics.digest',
|
|
1115
|
+
'signal.digest',
|
|
1116
|
+
'loc',
|
|
1117
|
+
'env.digest',
|
|
1118
|
+
'start_t',
|
|
1119
|
+
'start',
|
|
1120
|
+
'up',
|
|
1121
|
+
'direction',
|
|
1122
|
+
'source_strength',
|
|
1123
|
+
'coherence',
|
|
1124
|
+
'__class__',
|
|
1125
|
+
],
|
|
1126
|
+
)
|
|
914
1127
|
|
|
915
|
-
)
|
|
916
|
-
|
|
917
1128
|
@cached_property
|
|
918
|
-
def _get_digest(
|
|
1129
|
+
def _get_digest(self):
|
|
919
1130
|
return digest(self)
|
|
920
1131
|
|
|
921
1132
|
def result(self, num=128):
|
|
922
|
-
"""
|
|
923
|
-
|
|
924
|
-
|
|
1133
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
1134
|
+
|
|
925
1135
|
Parameters
|
|
926
1136
|
----------
|
|
927
1137
|
num : integer, defaults to 128
|
|
928
1138
|
This parameter defines the size of the blocks to be yielded
|
|
929
1139
|
(i.e. the number of samples per block) .
|
|
930
|
-
|
|
1140
|
+
|
|
931
1141
|
Returns
|
|
932
1142
|
-------
|
|
933
|
-
Samples in blocks of shape (num, numchannels).
|
|
1143
|
+
Samples in blocks of shape (num, numchannels).
|
|
934
1144
|
The last block may be shorter than num.
|
|
1145
|
+
|
|
935
1146
|
"""
|
|
936
|
-
#If signal samples are needed for te < t_start, then samples are taken
|
|
937
|
-
#from the end of the calculated signal.
|
|
1147
|
+
# If signal samples are needed for te < t_start, then samples are taken
|
|
1148
|
+
# from the end of the calculated signal.
|
|
938
1149
|
|
|
939
1150
|
mpos = self.mics.mpos
|
|
940
|
-
|
|
1151
|
+
|
|
941
1152
|
# direction vector from tuple
|
|
942
|
-
direc = array(self.direction, dtype
|
|
1153
|
+
direc = array(self.direction, dtype=float)
|
|
943
1154
|
# normed direction vector
|
|
944
|
-
direc_n = direc/norm(direc)
|
|
1155
|
+
direc_n = direc / norm(direc)
|
|
945
1156
|
c = self.env.c
|
|
946
|
-
|
|
947
|
-
# distance between monopoles in the line
|
|
948
|
-
dist = self.length / self.num_sources
|
|
949
|
-
|
|
950
|
-
#blocwise output
|
|
1157
|
+
|
|
1158
|
+
# distance between monopoles in the line
|
|
1159
|
+
dist = self.length / self.num_sources
|
|
1160
|
+
|
|
1161
|
+
# blocwise output
|
|
951
1162
|
out = zeros((num, self.numchannels))
|
|
952
|
-
|
|
953
|
-
# distance from line start position to microphones
|
|
954
|
-
loc = array(self.loc, dtype
|
|
955
|
-
|
|
1163
|
+
|
|
1164
|
+
# distance from line start position to microphones
|
|
1165
|
+
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
1166
|
+
|
|
956
1167
|
# distances from monopoles in the line to microphones
|
|
957
|
-
rms = empty((
|
|
958
|
-
inds = empty((self.numchannels,self.num_sources))
|
|
1168
|
+
rms = empty((self.numchannels, self.num_sources))
|
|
1169
|
+
inds = empty((self.numchannels, self.num_sources))
|
|
959
1170
|
signals = empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
960
|
-
#for every source - distances
|
|
1171
|
+
# for every source - distances
|
|
961
1172
|
for s in range(self.num_sources):
|
|
962
|
-
rms[:,s] = self.env._r((loc.T+direc_n*dist*s).T, mpos)
|
|
963
|
-
inds[:,s] = (-rms[:,s]
|
|
964
|
-
#new seed for every source
|
|
1173
|
+
rms[:, s] = self.env._r((loc.T + direc_n * dist * s).T, mpos)
|
|
1174
|
+
inds[:, s] = (-rms[:, s] / c - self.start_t + self.start) * self.sample_freq
|
|
1175
|
+
# new seed for every source
|
|
965
1176
|
if self.coherence == 'incoherent':
|
|
966
|
-
self.signal.seed = s + abs(int(hash(self.digest)//10e12))
|
|
1177
|
+
self.signal.seed = s + abs(int(hash(self.digest) // 10e12))
|
|
967
1178
|
self.signal.rms = self.signal.rms * self.source_strength[s]
|
|
968
1179
|
signals[s] = self.signal.usignal(self.up)
|
|
969
1180
|
i = 0
|
|
970
|
-
n = self.numsamples
|
|
1181
|
+
n = self.numsamples
|
|
971
1182
|
while n:
|
|
972
1183
|
n -= 1
|
|
973
1184
|
try:
|
|
974
1185
|
for s in range(self.num_sources):
|
|
975
|
-
|
|
976
|
-
out[i] +=
|
|
977
|
-
|
|
978
|
-
inds += 1.
|
|
1186
|
+
# sum sources
|
|
1187
|
+
out[i] += signals[s, array(0.5 + inds[:, s].T * self.up, dtype=int64)] / rms[:, s]
|
|
1188
|
+
|
|
1189
|
+
inds += 1.0
|
|
979
1190
|
i += 1
|
|
980
1191
|
if i == num:
|
|
981
1192
|
yield out
|
|
@@ -983,139 +1194,141 @@ class LineSource( PointSource ):
|
|
|
983
1194
|
i = 0
|
|
984
1195
|
except IndexError:
|
|
985
1196
|
break
|
|
986
|
-
|
|
1197
|
+
|
|
987
1198
|
yield out[:i]
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
class MovingLineSource(LineSource, MovingPointSource):
|
|
991
1202
|
# internal identifier
|
|
992
|
-
digest = Property(
|
|
993
|
-
depends_on
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1203
|
+
digest = Property(
|
|
1204
|
+
depends_on=[
|
|
1205
|
+
'mics.digest',
|
|
1206
|
+
'signal.digest',
|
|
1207
|
+
'loc',
|
|
1208
|
+
'env.digest',
|
|
1209
|
+
'start_t',
|
|
1210
|
+
'start',
|
|
1211
|
+
'up',
|
|
1212
|
+
'direction',
|
|
1213
|
+
'__class__',
|
|
1214
|
+
],
|
|
1215
|
+
)
|
|
997
1216
|
|
|
998
1217
|
#: Reference vector, perpendicular to the x and y-axis of moving source.
|
|
999
1218
|
#: rotation source directivity around this axis
|
|
1000
|
-
rvec = CArray(
|
|
1001
|
-
|
|
1002
|
-
|
|
1219
|
+
rvec = CArray(dtype=float, shape=(3,), value=array((0, 0, 0)), desc='reference vector')
|
|
1220
|
+
|
|
1003
1221
|
@cached_property
|
|
1004
|
-
def _get_digest(
|
|
1005
|
-
return digest(self)
|
|
1006
|
-
|
|
1007
|
-
def get_moving_direction(self,direction,time=0):
|
|
1008
|
-
"""
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
trajg1 = array(self.trajectory.location( time, der=1))[:,0][:,newaxis]
|
|
1013
|
-
rflag = (self.rvec == 0).all() #flag translation vs. rotation
|
|
1222
|
+
def _get_digest(self):
|
|
1223
|
+
return digest(self)
|
|
1224
|
+
|
|
1225
|
+
def get_moving_direction(self, direction, time=0):
|
|
1226
|
+
"""Function that yields the moving coordinates along the trajectory."""
|
|
1227
|
+
trajg1 = array(self.trajectory.location(time, der=1))[:, 0][:, newaxis]
|
|
1228
|
+
rflag = (self.rvec == 0).all() # flag translation vs. rotation
|
|
1014
1229
|
if rflag:
|
|
1015
|
-
return direction
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
def get_emission_time(self,t,direction):
|
|
1230
|
+
return direction
|
|
1231
|
+
dx = array(trajg1.T) # direction vector (new x-axis)
|
|
1232
|
+
dy = cross(self.rvec, dx) # new y-axis
|
|
1233
|
+
dz = cross(dx, dy) # new z-axis
|
|
1234
|
+
RM = array((dx, dy, dz)).T # rotation matrix
|
|
1235
|
+
RM /= sqrt((RM * RM).sum(0)) # column normalized
|
|
1236
|
+
newdir = dot(RM, direction)
|
|
1237
|
+
return cross(newdir[:, 0].T, self.rvec.T).T
|
|
1238
|
+
|
|
1239
|
+
def get_emission_time(self, t, direction):
|
|
1026
1240
|
eps = ones(self.mics.num_mics)
|
|
1027
|
-
epslim = 0.1/self.up/self.sample_freq
|
|
1028
|
-
te = t.copy()
|
|
1241
|
+
epslim = 0.1 / self.up / self.sample_freq
|
|
1242
|
+
te = t.copy() # init emission time = receiving time
|
|
1029
1243
|
j = 0
|
|
1030
1244
|
# Newton-Rhapson iteration
|
|
1031
|
-
while abs(eps).max()>epslim and j<100:
|
|
1245
|
+
while abs(eps).max() > epslim and j < 100:
|
|
1032
1246
|
xs = array(self.trajectory.location(te))
|
|
1033
1247
|
loc = xs.copy()
|
|
1034
1248
|
loc += direction
|
|
1035
|
-
rm = loc-self.mics.mpos# distance vectors to microphones
|
|
1036
|
-
rm = sqrt((rm*rm).sum(0))# absolute distance
|
|
1037
|
-
loc /= sqrt((loc*loc).sum(0))# distance unit vector
|
|
1249
|
+
rm = loc - self.mics.mpos # distance vectors to microphones
|
|
1250
|
+
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
1251
|
+
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1038
1252
|
der = array(self.trajectory.location(te, der=1))
|
|
1039
|
-
Mr = (der*loc).sum(0)/self.env.c# radial Mach number
|
|
1040
|
-
eps = (te + rm/self.env.c - t)/(1+Mr)# discrepancy in time
|
|
1253
|
+
Mr = (der * loc).sum(0) / self.env.c # radial Mach number
|
|
1254
|
+
eps = (te + rm / self.env.c - t) / (1 + Mr) # discrepancy in time
|
|
1041
1255
|
te -= eps
|
|
1042
|
-
j += 1
|
|
1256
|
+
j += 1 # iteration count
|
|
1043
1257
|
return te, rm, Mr, xs
|
|
1044
|
-
|
|
1258
|
+
|
|
1045
1259
|
def result(self, num=128):
|
|
1046
|
-
"""
|
|
1047
|
-
|
|
1048
|
-
|
|
1260
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
1261
|
+
|
|
1049
1262
|
Parameters
|
|
1050
1263
|
----------
|
|
1051
1264
|
num : integer, defaults to 128
|
|
1052
1265
|
This parameter defines the size of the blocks to be yielded
|
|
1053
1266
|
(i.e. the number of samples per block) .
|
|
1054
|
-
|
|
1267
|
+
|
|
1055
1268
|
Returns
|
|
1056
1269
|
-------
|
|
1057
|
-
Samples in blocks of shape (num, numchannels).
|
|
1270
|
+
Samples in blocks of shape (num, numchannels).
|
|
1058
1271
|
The last block may be shorter than num.
|
|
1272
|
+
|
|
1059
1273
|
"""
|
|
1060
|
-
|
|
1061
|
-
#
|
|
1062
|
-
#from the end of the calculated signal.
|
|
1274
|
+
# If signal samples are needed for te < t_start, then samples are taken
|
|
1275
|
+
# from the end of the calculated signal.
|
|
1063
1276
|
mpos = self.mics.mpos
|
|
1064
|
-
|
|
1277
|
+
|
|
1065
1278
|
# direction vector from tuple
|
|
1066
|
-
direc = array(self.direction, dtype
|
|
1279
|
+
direc = array(self.direction, dtype=float)
|
|
1067
1280
|
# normed direction vector
|
|
1068
|
-
direc_n = direc/norm(direc)
|
|
1069
|
-
|
|
1070
|
-
# distance between monopoles in the line
|
|
1071
|
-
dist = self.length / self.num_sources
|
|
1281
|
+
direc_n = direc / norm(direc)
|
|
1282
|
+
|
|
1283
|
+
# distance between monopoles in the line
|
|
1284
|
+
dist = self.length / self.num_sources
|
|
1072
1285
|
dir2 = (direc_n * dist).reshape((3, 1))
|
|
1073
|
-
|
|
1074
|
-
#blocwise output
|
|
1286
|
+
|
|
1287
|
+
# blocwise output
|
|
1075
1288
|
out = zeros((num, self.numchannels))
|
|
1076
|
-
|
|
1289
|
+
|
|
1077
1290
|
# distances from monopoles in the line to microphones
|
|
1078
|
-
rms = empty((
|
|
1079
|
-
inds = empty((self.numchannels,self.num_sources))
|
|
1291
|
+
rms = empty((self.numchannels, self.num_sources))
|
|
1292
|
+
inds = empty((self.numchannels, self.num_sources))
|
|
1080
1293
|
signals = empty((self.num_sources, len(self.signal.usignal(self.up))))
|
|
1081
|
-
#coherence
|
|
1294
|
+
# coherence
|
|
1082
1295
|
for s in range(self.num_sources):
|
|
1083
|
-
#new seed for every source
|
|
1296
|
+
# new seed for every source
|
|
1084
1297
|
if self.coherence == 'incoherent':
|
|
1085
|
-
self.signal.seed = s + abs(int(hash(self.digest)//10e12))
|
|
1298
|
+
self.signal.seed = s + abs(int(hash(self.digest) // 10e12))
|
|
1086
1299
|
self.signal.rms = self.signal.rms * self.source_strength[s]
|
|
1087
1300
|
signals[s] = self.signal.usignal(self.up)
|
|
1088
1301
|
mpos = self.mics.mpos
|
|
1089
|
-
|
|
1302
|
+
|
|
1090
1303
|
# shortcuts and intial values
|
|
1091
1304
|
m = self.mics
|
|
1092
|
-
t = self.start*ones(m.num_mics)
|
|
1305
|
+
t = self.start * ones(m.num_mics)
|
|
1093
1306
|
i = 0
|
|
1094
|
-
n = self.numsamples
|
|
1307
|
+
n = self.numsamples
|
|
1095
1308
|
while n:
|
|
1096
|
-
n -= 1
|
|
1097
|
-
t += 1
|
|
1098
|
-
te1, rm1, Mr1, locs1 = self.get_emission_time(t,0)
|
|
1099
|
-
#trajg1 = array(self.trajectory.location( te1, der=1))[:,0][:,newaxis]
|
|
1100
|
-
|
|
1309
|
+
n -= 1
|
|
1310
|
+
t += 1.0 / self.sample_freq
|
|
1311
|
+
te1, rm1, Mr1, locs1 = self.get_emission_time(t, 0)
|
|
1312
|
+
# trajg1 = array(self.trajectory.location( te1, der=1))[:,0][:,newaxis]
|
|
1313
|
+
|
|
1101
1314
|
# get distance and ind for every source in the line
|
|
1102
1315
|
for s in range(self.num_sources):
|
|
1103
|
-
diff = self.get_moving_direction(dir2,te1)
|
|
1104
|
-
te, rm, Mr, locs = self.get_emission_time(t,tile((diff*s).T,(self.numchannels,1)).T)
|
|
1105
|
-
loc = array(self.trajectory.location(te), dtype
|
|
1106
|
-
diff = self.get_moving_direction(dir2,te)
|
|
1107
|
-
rms[:,s] = self.env._r((loc+diff*s), mpos)
|
|
1108
|
-
inds[:,s] = (te-self.start_t+self.start)*self.sample_freq
|
|
1109
|
-
|
|
1110
|
-
if self.conv_amp:
|
|
1111
|
-
rm *= (1-Mr)**2
|
|
1112
|
-
rms[:,s] *= (1-Mr)**2
|
|
1316
|
+
diff = self.get_moving_direction(dir2, te1)
|
|
1317
|
+
te, rm, Mr, locs = self.get_emission_time(t, tile((diff * s).T, (self.numchannels, 1)).T)
|
|
1318
|
+
loc = array(self.trajectory.location(te), dtype=float)[:, 0][:, newaxis]
|
|
1319
|
+
diff = self.get_moving_direction(dir2, te)
|
|
1320
|
+
rms[:, s] = self.env._r((loc + diff * s), mpos)
|
|
1321
|
+
inds[:, s] = (te - self.start_t + self.start) * self.sample_freq
|
|
1322
|
+
|
|
1323
|
+
if self.conv_amp:
|
|
1324
|
+
rm *= (1 - Mr) ** 2
|
|
1325
|
+
rms[:, s] *= (1 - Mr) ** 2 # assume that Mr is the same
|
|
1113
1326
|
try:
|
|
1114
1327
|
# subtract the second signal b/c of phase inversion
|
|
1115
1328
|
for s in range(self.num_sources):
|
|
1116
|
-
|
|
1117
|
-
out[i] +=
|
|
1118
|
-
|
|
1329
|
+
# sum sources
|
|
1330
|
+
out[i] += signals[s, array(0.5 + inds[:, s].T * self.up, dtype=int64)] / rms[:, s]
|
|
1331
|
+
|
|
1119
1332
|
i += 1
|
|
1120
1333
|
if i == num:
|
|
1121
1334
|
yield out
|
|
@@ -1126,212 +1339,201 @@ class MovingLineSource(LineSource,MovingPointSource):
|
|
|
1126
1339
|
yield out[:i]
|
|
1127
1340
|
|
|
1128
1341
|
|
|
1129
|
-
class UncorrelatedNoiseSource(
|
|
1130
|
-
"""
|
|
1131
|
-
Class to simulate white or pink noise as uncorrelated signal at each
|
|
1342
|
+
class UncorrelatedNoiseSource(SamplesGenerator):
|
|
1343
|
+
"""Class to simulate white or pink noise as uncorrelated signal at each
|
|
1132
1344
|
channel.
|
|
1133
|
-
|
|
1345
|
+
|
|
1134
1346
|
The output is being generated via the :meth:`result` generator.
|
|
1135
1347
|
"""
|
|
1136
|
-
|
|
1137
|
-
#: Type of noise to generate at the channels.
|
|
1138
|
-
#: The `~acoular.signals.SignalGenerator`-derived class has to
|
|
1348
|
+
|
|
1349
|
+
#: Type of noise to generate at the channels.
|
|
1350
|
+
#: The `~acoular.signals.SignalGenerator`-derived class has to
|
|
1139
1351
|
# feature the parameter "seed" (i.e. white or pink noise).
|
|
1140
|
-
signal = Trait(SignalGenerator,
|
|
1141
|
-
desc = "type of noise")
|
|
1352
|
+
signal = Trait(SignalGenerator, desc='type of noise')
|
|
1142
1353
|
|
|
1143
1354
|
#: Array with seeds for random number generator.
|
|
1144
|
-
#: When left empty, arange(:attr:`numchannels`) + :attr:`signal`.seed
|
|
1355
|
+
#: When left empty, arange(:attr:`numchannels`) + :attr:`signal`.seed
|
|
1145
1356
|
#: will be used.
|
|
1146
|
-
seed = CArray(dtype =
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
#: Number of channels in output; is set automatically /
|
|
1357
|
+
seed = CArray(dtype=uint32, desc='random seed values')
|
|
1358
|
+
|
|
1359
|
+
#: Number of channels in output; is set automatically /
|
|
1150
1360
|
#: depends on used microphone geometry.
|
|
1151
1361
|
numchannels = Delegate('mics', 'num_mics')
|
|
1152
1362
|
|
|
1153
1363
|
#: :class:`~acoular.microphones.MicGeom` object that provides the microphone locations.
|
|
1154
|
-
mics = Trait(MicGeom,
|
|
1155
|
-
desc="microphone geometry")
|
|
1364
|
+
mics = Trait(MicGeom, desc='microphone geometry')
|
|
1156
1365
|
|
|
1157
1366
|
# --- List of backwards compatibility traits and their setters/getters -----------
|
|
1158
1367
|
|
|
1159
1368
|
# Microphone locations.
|
|
1160
1369
|
# Deprecated! Use :attr:`mics` trait instead.
|
|
1161
1370
|
mpos = Property()
|
|
1162
|
-
|
|
1371
|
+
|
|
1163
1372
|
def _get_mpos(self):
|
|
1164
1373
|
return self.mics
|
|
1165
|
-
|
|
1374
|
+
|
|
1166
1375
|
def _set_mpos(self, mpos):
|
|
1167
|
-
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel
|
|
1376
|
+
warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2)
|
|
1168
1377
|
self.mics = mpos
|
|
1169
1378
|
|
|
1170
1379
|
# --- End of backwards compatibility traits --------------------------------------
|
|
1171
|
-
|
|
1380
|
+
|
|
1172
1381
|
#: Start time of the signal in seconds, defaults to 0 s.
|
|
1173
|
-
start_t = Float(0.0,
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
#: Start time of the data aquisition at microphones in seconds,
|
|
1382
|
+
start_t = Float(0.0, desc='signal start time')
|
|
1383
|
+
|
|
1384
|
+
#: Start time of the data aquisition at microphones in seconds,
|
|
1177
1385
|
#: defaults to 0 s.
|
|
1178
|
-
start = Float(0.0,
|
|
1179
|
-
desc="sample start time")
|
|
1386
|
+
start = Float(0.0, desc='sample start time')
|
|
1180
1387
|
|
|
1181
|
-
|
|
1182
|
-
#: Number of samples is set automatically /
|
|
1388
|
+
#: Number of samples is set automatically /
|
|
1183
1389
|
#: depends on :attr:`signal`.
|
|
1184
1390
|
numsamples = Delegate('signal')
|
|
1185
|
-
|
|
1186
|
-
#: Sampling frequency of the signal; is set automatically /
|
|
1391
|
+
|
|
1392
|
+
#: Sampling frequency of the signal; is set automatically /
|
|
1187
1393
|
#: depends on :attr:`signal`.
|
|
1188
|
-
sample_freq = Delegate('signal')
|
|
1189
|
-
|
|
1394
|
+
sample_freq = Delegate('signal')
|
|
1395
|
+
|
|
1190
1396
|
# internal identifier
|
|
1191
|
-
digest = Property(
|
|
1192
|
-
depends_on
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1397
|
+
digest = Property(
|
|
1398
|
+
depends_on=[
|
|
1399
|
+
'mics.digest',
|
|
1400
|
+
'signal.rms',
|
|
1401
|
+
'signal.numsamples',
|
|
1402
|
+
'signal.sample_freq',
|
|
1403
|
+
'signal.__class__',
|
|
1404
|
+
'seed',
|
|
1405
|
+
'loc',
|
|
1406
|
+
'start_t',
|
|
1407
|
+
'start',
|
|
1408
|
+
'__class__',
|
|
1409
|
+
],
|
|
1410
|
+
)
|
|
1196
1411
|
|
|
1197
1412
|
@cached_property
|
|
1198
|
-
def _get_digest(
|
|
1413
|
+
def _get_digest(self):
|
|
1199
1414
|
return digest(self)
|
|
1200
|
-
|
|
1201
|
-
def result
|
|
1202
|
-
"""
|
|
1203
|
-
|
|
1204
|
-
|
|
1415
|
+
|
|
1416
|
+
def result(self, num=128):
|
|
1417
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
1418
|
+
|
|
1205
1419
|
Parameters
|
|
1206
1420
|
----------
|
|
1207
1421
|
num : integer, defaults to 128
|
|
1208
1422
|
This parameter defines the size of the blocks to be yielded
|
|
1209
1423
|
(i.e. the number of samples per block) .
|
|
1210
|
-
|
|
1424
|
+
|
|
1211
1425
|
Returns
|
|
1212
1426
|
-------
|
|
1213
|
-
Samples in blocks of shape (num, numchannels).
|
|
1427
|
+
Samples in blocks of shape (num, numchannels).
|
|
1214
1428
|
The last block may be shorter than num.
|
|
1215
|
-
"""
|
|
1216
1429
|
|
|
1430
|
+
"""
|
|
1217
1431
|
Noise = self.signal.__class__
|
|
1218
1432
|
# create or get the array of random seeds
|
|
1219
|
-
if not self.seed:
|
|
1433
|
+
if not self.seed:
|
|
1220
1434
|
seed = arange(self.numchannels) + self.signal.seed
|
|
1221
1435
|
elif self.seed.shape == (self.numchannels,):
|
|
1222
1436
|
seed = self.seed
|
|
1223
1437
|
else:
|
|
1224
|
-
raise ValueError(
|
|
1225
|
-
|
|
1226
|
-
% (self.numchannels, str(self.seed.shape))
|
|
1227
|
-
|
|
1228
|
-
# create array with [numchannels] noise signal tracks
|
|
1229
|
-
signal = array([Noise(seed = s,
|
|
1230
|
-
numsamples = self.numsamples,
|
|
1231
|
-
sample_freq = self.sample_freq,
|
|
1232
|
-
rms = self.signal.rms).signal() \
|
|
1233
|
-
for s in seed]).T
|
|
1438
|
+
raise ValueError(
|
|
1439
|
+
'Seed array expected to be of shape (%i,), but has shape %s.'
|
|
1440
|
+
% (self.numchannels, str(self.seed.shape)),
|
|
1441
|
+
)
|
|
1234
1442
|
|
|
1235
|
-
|
|
1443
|
+
# create array with [numchannels] noise signal tracks
|
|
1444
|
+
signal = array(
|
|
1445
|
+
[
|
|
1446
|
+
Noise(seed=s, numsamples=self.numsamples, sample_freq=self.sample_freq, rms=self.signal.rms).signal()
|
|
1447
|
+
for s in seed
|
|
1448
|
+
],
|
|
1449
|
+
).T
|
|
1450
|
+
|
|
1451
|
+
n = num
|
|
1236
1452
|
while n <= self.numsamples:
|
|
1237
|
-
yield signal[n-num:n
|
|
1453
|
+
yield signal[n - num : n, :]
|
|
1238
1454
|
n += num
|
|
1239
1455
|
else:
|
|
1240
|
-
if (n-num) < self.numsamples:
|
|
1241
|
-
yield signal[n-num
|
|
1456
|
+
if (n - num) < self.numsamples:
|
|
1457
|
+
yield signal[n - num :, :]
|
|
1242
1458
|
else:
|
|
1243
1459
|
return
|
|
1244
1460
|
|
|
1245
1461
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
"""
|
|
1249
|
-
Mixes the signals from several sources.
|
|
1250
|
-
"""
|
|
1462
|
+
class SourceMixer(SamplesGenerator):
|
|
1463
|
+
"""Mixes the signals from several sources."""
|
|
1251
1464
|
|
|
1252
1465
|
#: List of :class:`~acoular.tprocess.SamplesGenerator` objects
|
|
1253
1466
|
#: to be mixed.
|
|
1254
|
-
sources = List(
|
|
1467
|
+
sources = List(Instance(SamplesGenerator, ()))
|
|
1255
1468
|
|
|
1256
1469
|
#: Sampling frequency of the signal.
|
|
1257
|
-
sample_freq = Property(
|
|
1258
|
-
|
|
1470
|
+
sample_freq = Property(depends_on=['sdigest'])
|
|
1471
|
+
|
|
1259
1472
|
#: Number of channels.
|
|
1260
|
-
numchannels = Property(
|
|
1261
|
-
|
|
1473
|
+
numchannels = Property(depends_on=['sdigest'])
|
|
1474
|
+
|
|
1262
1475
|
#: Number of samples.
|
|
1263
|
-
numsamples = Property(
|
|
1264
|
-
|
|
1265
|
-
#: Amplitude weight(s) for the sources as array. If not set,
|
|
1476
|
+
numsamples = Property(depends_on=['sdigest'])
|
|
1477
|
+
|
|
1478
|
+
#: Amplitude weight(s) for the sources as array. If not set,
|
|
1266
1479
|
#: all source signals are equally weighted.
|
|
1267
1480
|
#: Must match the number of sources in :attr:`sources`.
|
|
1268
|
-
weights = CArray(desc=
|
|
1481
|
+
weights = CArray(desc='channel weights')
|
|
1269
1482
|
|
|
1270
|
-
# internal identifier
|
|
1483
|
+
# internal identifier
|
|
1271
1484
|
sdigest = Str()
|
|
1272
1485
|
|
|
1273
1486
|
@observe('sources.items.digest')
|
|
1274
|
-
def _set_sources_digest(
|
|
1275
|
-
self.sdigest = ldigest(self.sources)
|
|
1487
|
+
def _set_sources_digest(self, event): # noqa ARG002
|
|
1488
|
+
self.sdigest = ldigest(self.sources)
|
|
1276
1489
|
|
|
1277
1490
|
# internal identifier
|
|
1278
|
-
digest = Property(
|
|
1491
|
+
digest = Property(depends_on=['sdigest', 'weights'])
|
|
1279
1492
|
|
|
1280
1493
|
@cached_property
|
|
1281
|
-
def _get_digest(
|
|
1494
|
+
def _get_digest(self):
|
|
1282
1495
|
return digest(self)
|
|
1283
1496
|
|
|
1284
1497
|
@cached_property
|
|
1285
|
-
def _get_sample_freq(
|
|
1286
|
-
if self.sources
|
|
1287
|
-
sample_freq = self.sources[0].sample_freq
|
|
1288
|
-
else:
|
|
1289
|
-
sample_freq = 0
|
|
1290
|
-
return sample_freq
|
|
1498
|
+
def _get_sample_freq(self):
|
|
1499
|
+
return self.sources[0].sample_freq if self.sources else 0
|
|
1291
1500
|
|
|
1292
1501
|
@cached_property
|
|
1293
|
-
def _get_numchannels(
|
|
1294
|
-
if self.sources
|
|
1295
|
-
numchannels = self.sources[0].numchannels
|
|
1296
|
-
else:
|
|
1297
|
-
numchannels = 0
|
|
1298
|
-
return numchannels
|
|
1502
|
+
def _get_numchannels(self):
|
|
1503
|
+
return self.sources[0].numchannels if self.sources else 0
|
|
1299
1504
|
|
|
1300
1505
|
@cached_property
|
|
1301
|
-
def _get_numsamples(
|
|
1302
|
-
if self.sources
|
|
1303
|
-
numsamples = self.sources[0].numsamples
|
|
1304
|
-
else:
|
|
1305
|
-
numsamples = 0
|
|
1306
|
-
return numsamples
|
|
1506
|
+
def _get_numsamples(self):
|
|
1507
|
+
return self.sources[0].numsamples if self.sources else 0
|
|
1307
1508
|
|
|
1308
|
-
def validate_sources(
|
|
1309
|
-
"""
|
|
1509
|
+
def validate_sources(self):
|
|
1510
|
+
"""Validates if sources fit together."""
|
|
1310
1511
|
if len(self.sources) < 1:
|
|
1311
|
-
|
|
1512
|
+
msg = 'Number of sources in SourceMixer should be at least 1.'
|
|
1513
|
+
raise ValueError(msg)
|
|
1312
1514
|
for s in self.sources[1:]:
|
|
1313
1515
|
if self.sample_freq != s.sample_freq:
|
|
1314
|
-
raise ValueError(
|
|
1516
|
+
raise ValueError('Sample frequency of %s does not fit' % s)
|
|
1315
1517
|
if self.numchannels != s.numchannels:
|
|
1316
|
-
raise ValueError(
|
|
1518
|
+
raise ValueError('Channel count of %s does not fit' % s)
|
|
1317
1519
|
if self.numsamples != s.numsamples:
|
|
1318
|
-
raise ValueError(
|
|
1520
|
+
raise ValueError('Number of samples of %s does not fit' % s)
|
|
1319
1521
|
|
|
1320
1522
|
def result(self, num):
|
|
1321
|
-
"""
|
|
1322
|
-
Python generator that yields the output block-wise.
|
|
1523
|
+
"""Python generator that yields the output block-wise.
|
|
1323
1524
|
The outputs from the sources in the list are being added.
|
|
1324
|
-
|
|
1525
|
+
|
|
1325
1526
|
Parameters
|
|
1326
1527
|
----------
|
|
1327
1528
|
num : integer
|
|
1328
1529
|
This parameter defines the size of the blocks to be yielded
|
|
1329
1530
|
(i.e. the number of samples per block).
|
|
1330
|
-
|
|
1531
|
+
|
|
1331
1532
|
Returns
|
|
1332
1533
|
-------
|
|
1333
|
-
Samples in blocks of shape (num, numchannels).
|
|
1534
|
+
Samples in blocks of shape (num, numchannels).
|
|
1334
1535
|
The last block may be shorter than num.
|
|
1536
|
+
|
|
1335
1537
|
"""
|
|
1336
1538
|
# check whether all sources fit together
|
|
1337
1539
|
self.validate_sources()
|
|
@@ -1339,63 +1541,58 @@ class SourceMixer( SamplesGenerator ):
|
|
|
1339
1541
|
gens = [i.result(num) for i in self.sources[1:]]
|
|
1340
1542
|
weights = self.weights.copy()
|
|
1341
1543
|
if weights.size == 0:
|
|
1342
|
-
weights = array([1. for j in range(len(
|
|
1544
|
+
weights = array([1.0 for j in range(len(self.sources))])
|
|
1343
1545
|
assert weights.shape[0] == len(self.sources)
|
|
1344
1546
|
for temp in self.sources[0].result(num):
|
|
1345
1547
|
temp *= weights[0]
|
|
1346
1548
|
sh = temp.shape[0]
|
|
1347
|
-
for j,g in enumerate(gens):
|
|
1348
|
-
temp1 = next(g)*weights[j+1]
|
|
1549
|
+
for j, g in enumerate(gens):
|
|
1550
|
+
temp1 = next(g) * weights[j + 1]
|
|
1349
1551
|
if temp.shape[0] > temp1.shape[0]:
|
|
1350
|
-
temp = temp[:temp1.shape[0]]
|
|
1351
|
-
temp += temp1[:temp.shape[0]]
|
|
1552
|
+
temp = temp[: temp1.shape[0]]
|
|
1553
|
+
temp += temp1[: temp.shape[0]]
|
|
1352
1554
|
yield temp
|
|
1353
1555
|
if sh > temp.shape[0]:
|
|
1354
1556
|
break
|
|
1355
1557
|
|
|
1356
1558
|
|
|
1357
|
-
class PointSourceConvolve(
|
|
1358
|
-
"""
|
|
1359
|
-
Class to blockwise convolve an arbitrary source signal with a spatial room impulse response
|
|
1360
|
-
"""
|
|
1559
|
+
class PointSourceConvolve(PointSource):
|
|
1560
|
+
"""Class to blockwise convolve an arbitrary source signal with a spatial room impulse response."""
|
|
1361
1561
|
|
|
1362
1562
|
#: Convolution kernel in the time domain.
|
|
1363
1563
|
#: The second dimension of the kernel array has to be either 1 or match :attr:`~SamplesGenerator.numchannels`.
|
|
1364
1564
|
#: If only a single kernel is supplied, it is applied to all channels.
|
|
1365
|
-
kernel = CArray(dtype=float, desc=
|
|
1565
|
+
kernel = CArray(dtype=float, desc='Convolution kernel.')
|
|
1366
1566
|
|
|
1367
1567
|
# ------------- overwrite traits that are not supported by this class -------------
|
|
1368
|
-
|
|
1568
|
+
|
|
1369
1569
|
#: Start time of the signal in seconds, defaults to 0 s.
|
|
1370
|
-
start_t = Enum(0.0,
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
#: Start time of the data aquisition at microphones in seconds,
|
|
1570
|
+
start_t = Enum(0.0, desc='signal start time')
|
|
1571
|
+
|
|
1572
|
+
#: Start time of the data aquisition at microphones in seconds,
|
|
1374
1573
|
#: defaults to 0 s.
|
|
1375
|
-
start = Enum(0.0,
|
|
1376
|
-
desc="sample start time")
|
|
1574
|
+
start = Enum(0.0, desc='sample start time')
|
|
1377
1575
|
|
|
1378
1576
|
#: Signal behaviour for negative time indices, i.e. if :attr:`start` < :attr:start_t.
|
|
1379
1577
|
#: `loop` take values from the end of :attr:`signal.signal()` array.
|
|
1380
1578
|
#: `zeros` set source signal to zero, advisable for deterministic signals.
|
|
1381
1579
|
#: defaults to `loop`.
|
|
1382
|
-
prepadding = Enum(None, desc=
|
|
1580
|
+
prepadding = Enum(None, desc='Behaviour for negative time indices.')
|
|
1383
1581
|
|
|
1384
1582
|
#: Upsampling factor, internal use, defaults to 16.
|
|
1385
|
-
up = Enum(None, desc=
|
|
1386
|
-
|
|
1583
|
+
up = Enum(None, desc='upsampling factor')
|
|
1584
|
+
|
|
1387
1585
|
# internal identifier
|
|
1388
|
-
digest = Property(
|
|
1389
|
-
depends_on
|
|
1390
|
-
|
|
1391
|
-
|
|
1586
|
+
digest = Property(
|
|
1587
|
+
depends_on=['mics.digest', 'signal.digest', 'loc', 'kernel', '__class__'],
|
|
1588
|
+
)
|
|
1589
|
+
|
|
1392
1590
|
@cached_property
|
|
1393
|
-
def _get_digest(
|
|
1591
|
+
def _get_digest(self):
|
|
1394
1592
|
return digest(self)
|
|
1395
1593
|
|
|
1396
1594
|
def result(self, num=128):
|
|
1397
|
-
"""
|
|
1398
|
-
Python generator that yields the output at microphones block-wise.
|
|
1595
|
+
"""Python generator that yields the output at microphones block-wise.
|
|
1399
1596
|
|
|
1400
1597
|
Parameters
|
|
1401
1598
|
----------
|
|
@@ -1405,11 +1602,11 @@ class PointSourceConvolve( PointSource ):
|
|
|
1405
1602
|
|
|
1406
1603
|
Returns
|
|
1407
1604
|
-------
|
|
1408
|
-
Samples in blocks of shape (num, numchannels).
|
|
1605
|
+
Samples in blocks of shape (num, numchannels).
|
|
1409
1606
|
The last block may be shorter than num.
|
|
1607
|
+
|
|
1410
1608
|
"""
|
|
1411
|
-
data = repeat(
|
|
1412
|
-
self.signal.signal()[:,newaxis],self.mics.num_mics,axis=1)
|
|
1609
|
+
data = repeat(self.signal.signal()[:, newaxis], self.mics.num_mics, axis=1)
|
|
1413
1610
|
source = TimeSamples(
|
|
1414
1611
|
data=data,
|
|
1415
1612
|
sample_freq=self.sample_freq,
|
|
@@ -1417,8 +1614,7 @@ class PointSourceConvolve( PointSource ):
|
|
|
1417
1614
|
numchannels=self.mics.num_mics,
|
|
1418
1615
|
)
|
|
1419
1616
|
time_convolve = TimeConvolve(
|
|
1420
|
-
source
|
|
1421
|
-
kernel
|
|
1617
|
+
source=source,
|
|
1618
|
+
kernel=self.kernel,
|
|
1422
1619
|
)
|
|
1423
|
-
|
|
1424
|
-
yield block
|
|
1620
|
+
yield from time_convolve.result(num)
|