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