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/tprocess.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
|
"""Implements processing in the time domain.
|
|
7
5
|
|
|
8
6
|
.. autosummary::
|
|
@@ -37,92 +35,135 @@
|
|
|
37
35
|
"""
|
|
38
36
|
|
|
39
37
|
# imports from other packages
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
import threading
|
|
39
|
+
import wave
|
|
40
|
+
from collections import deque
|
|
41
|
+
from datetime import datetime, timezone
|
|
42
|
+
from inspect import currentframe
|
|
43
|
+
from os import path
|
|
44
|
+
from warnings import warn
|
|
44
45
|
|
|
46
|
+
import numba as nb
|
|
47
|
+
from numpy import (
|
|
48
|
+
append,
|
|
49
|
+
arange,
|
|
50
|
+
argmax,
|
|
51
|
+
argmin,
|
|
52
|
+
argsort,
|
|
53
|
+
array,
|
|
54
|
+
array_equal,
|
|
55
|
+
asarray,
|
|
56
|
+
ceil,
|
|
57
|
+
concatenate,
|
|
58
|
+
cumsum,
|
|
59
|
+
delete,
|
|
60
|
+
empty,
|
|
61
|
+
empty_like,
|
|
62
|
+
exp,
|
|
63
|
+
flatnonzero,
|
|
64
|
+
float64,
|
|
65
|
+
identity,
|
|
66
|
+
inf,
|
|
67
|
+
int16,
|
|
68
|
+
interp,
|
|
69
|
+
linspace,
|
|
70
|
+
mean,
|
|
71
|
+
nan,
|
|
72
|
+
newaxis,
|
|
73
|
+
pi,
|
|
74
|
+
polymul,
|
|
75
|
+
sin,
|
|
76
|
+
sinc,
|
|
77
|
+
split,
|
|
78
|
+
sqrt,
|
|
79
|
+
stack,
|
|
80
|
+
sum,
|
|
81
|
+
unique,
|
|
82
|
+
zeros,
|
|
83
|
+
)
|
|
45
84
|
from numpy.linalg import norm
|
|
46
85
|
from numpy.matlib import repmat
|
|
47
|
-
|
|
86
|
+
from scipy.fft import irfft, rfft
|
|
87
|
+
from scipy.interpolate import CloughTocher2DInterpolator, CubicSpline, LinearNDInterpolator, Rbf, splev, splrep
|
|
88
|
+
from scipy.signal import bilinear, butter, sosfilt, sosfiltfilt, tf2sos
|
|
48
89
|
from scipy.spatial import Delaunay
|
|
49
|
-
from
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
90
|
+
from traits.api import (
|
|
91
|
+
Bool,
|
|
92
|
+
CArray,
|
|
93
|
+
CLong,
|
|
94
|
+
Constant,
|
|
95
|
+
Delegate,
|
|
96
|
+
Dict,
|
|
97
|
+
File,
|
|
98
|
+
Float,
|
|
99
|
+
HasPrivateTraits,
|
|
100
|
+
Instance,
|
|
101
|
+
Int,
|
|
102
|
+
List,
|
|
103
|
+
ListInt,
|
|
104
|
+
Property,
|
|
105
|
+
Str,
|
|
106
|
+
Trait,
|
|
107
|
+
cached_property,
|
|
108
|
+
observe,
|
|
109
|
+
on_trait_change,
|
|
110
|
+
)
|
|
58
111
|
|
|
59
|
-
from
|
|
60
|
-
from
|
|
61
|
-
import
|
|
62
|
-
from
|
|
63
|
-
from warnings import warn
|
|
64
|
-
from collections import deque
|
|
65
|
-
from inspect import currentframe
|
|
66
|
-
import threading
|
|
112
|
+
from .configuration import config
|
|
113
|
+
from .environments import cartToCyl, cylToCart
|
|
114
|
+
from .h5cache import H5cache
|
|
115
|
+
from .h5files import H5CacheFileBase, _get_h5file_class
|
|
67
116
|
|
|
68
117
|
# acoular imports
|
|
69
118
|
from .internal import digest, ldigest
|
|
70
|
-
from .h5cache import H5cache
|
|
71
|
-
from .h5files import H5CacheFileBase, _get_h5file_class
|
|
72
|
-
from .environments import cartToCyl,cylToCart
|
|
73
119
|
from .microphones import MicGeom
|
|
74
|
-
from .configuration import config
|
|
75
120
|
|
|
76
121
|
|
|
77
|
-
class SamplesGenerator(
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
|
|
122
|
+
class SamplesGenerator(HasPrivateTraits):
|
|
123
|
+
"""Base class for any generating signal processing block.
|
|
124
|
+
|
|
81
125
|
It provides a common interface for all SamplesGenerator classes, which
|
|
82
126
|
generate an output via the generator :meth:`result`.
|
|
83
|
-
This class has no real functionality on its own and should not be
|
|
127
|
+
This class has no real functionality on its own and should not be
|
|
84
128
|
used directly.
|
|
85
129
|
"""
|
|
86
130
|
|
|
87
131
|
#: Sampling frequency of the signal, defaults to 1.0
|
|
88
|
-
sample_freq = Float(1.0,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
#: Number of channels
|
|
132
|
+
sample_freq = Float(1.0, desc='sampling frequency')
|
|
133
|
+
|
|
134
|
+
#: Number of channels
|
|
92
135
|
numchannels = CLong
|
|
93
|
-
|
|
94
|
-
#: Number of samples
|
|
136
|
+
|
|
137
|
+
#: Number of samples
|
|
95
138
|
numsamples = CLong
|
|
96
|
-
|
|
139
|
+
|
|
97
140
|
# internal identifier
|
|
98
|
-
digest = Property(depends_on
|
|
99
|
-
|
|
100
|
-
def _get_digest(
|
|
101
|
-
return digest(
|
|
102
|
-
|
|
141
|
+
digest = Property(depends_on=['sample_freq', 'numchannels', 'numsamples'])
|
|
142
|
+
|
|
143
|
+
def _get_digest(self):
|
|
144
|
+
return digest(self)
|
|
145
|
+
|
|
103
146
|
def result(self, num):
|
|
104
|
-
"""
|
|
105
|
-
|
|
106
|
-
|
|
147
|
+
"""Python generator that yields the output block-wise.
|
|
148
|
+
|
|
107
149
|
Parameters
|
|
108
150
|
----------
|
|
109
151
|
num : integer
|
|
110
152
|
This parameter defines the size of the blocks to be yielded
|
|
111
|
-
(i.e. the number of samples per block)
|
|
112
|
-
|
|
153
|
+
(i.e. the number of samples per block)
|
|
154
|
+
|
|
113
155
|
Returns
|
|
114
156
|
-------
|
|
115
157
|
No output since `SamplesGenerator` only represents a base class to derive
|
|
116
158
|
other classes from.
|
|
159
|
+
|
|
117
160
|
"""
|
|
118
|
-
pass
|
|
119
161
|
|
|
120
162
|
|
|
121
|
-
class TimeInOut(
|
|
122
|
-
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
generator :meth:`result`
|
|
163
|
+
class TimeInOut(SamplesGenerator):
|
|
164
|
+
"""Base class for any time domain signal processing block,
|
|
165
|
+
gets samples from :attr:`source` and generates output via the
|
|
166
|
+
generator :meth:`result`.
|
|
126
167
|
"""
|
|
127
168
|
|
|
128
169
|
#: Data source; :class:`~acoular.sources.SamplesGenerator` or derived object.
|
|
@@ -130,651 +171,671 @@ class TimeInOut( SamplesGenerator ):
|
|
|
130
171
|
|
|
131
172
|
#: Sampling frequency of output signal, as given by :attr:`source`.
|
|
132
173
|
sample_freq = Delegate('source')
|
|
133
|
-
|
|
174
|
+
|
|
134
175
|
#: Number of channels in output, as given by :attr:`source`.
|
|
135
176
|
numchannels = Delegate('source')
|
|
136
|
-
|
|
177
|
+
|
|
137
178
|
#: Number of samples in output, as given by :attr:`source`.
|
|
138
179
|
numsamples = Delegate('source')
|
|
139
|
-
|
|
180
|
+
|
|
140
181
|
# internal identifier
|
|
141
|
-
digest = Property(
|
|
182
|
+
digest = Property(depends_on=['source.digest'])
|
|
142
183
|
|
|
143
184
|
@cached_property
|
|
144
|
-
def _get_digest(
|
|
185
|
+
def _get_digest(self):
|
|
145
186
|
return digest(self)
|
|
146
187
|
|
|
147
188
|
def result(self, num):
|
|
148
|
-
"""
|
|
149
|
-
Python generator: dummy function, just echoes the output of source,
|
|
189
|
+
"""Python generator: dummy function, just echoes the output of source,
|
|
150
190
|
yields samples in blocks of shape (num, :attr:`numchannels`), the last block
|
|
151
191
|
may be shorter than num.
|
|
152
192
|
"""
|
|
153
|
-
|
|
154
|
-
# effectively no processing
|
|
155
|
-
yield temp
|
|
193
|
+
yield from self.source.result(num)
|
|
156
194
|
|
|
157
195
|
|
|
158
|
-
class MaskedTimeInOut
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
channels and samples for any
|
|
196
|
+
class MaskedTimeInOut(TimeInOut):
|
|
197
|
+
"""Signal processing block for channel and sample selection.
|
|
198
|
+
|
|
199
|
+
This class serves as intermediary to define (in)valid
|
|
200
|
+
channels and samples for any
|
|
164
201
|
:class:`~acoular.sources.SamplesGenerator` (or derived) object.
|
|
165
|
-
It gets samples from :attr:`~acoular.tprocess.TimeInOut.source`
|
|
202
|
+
It gets samples from :attr:`~acoular.tprocess.TimeInOut.source`
|
|
166
203
|
and generates output via the generator :meth:`result`.
|
|
167
204
|
"""
|
|
168
|
-
|
|
205
|
+
|
|
169
206
|
#: Index of the first sample to be considered valid.
|
|
170
|
-
start = CLong(0,
|
|
171
|
-
|
|
172
|
-
|
|
207
|
+
start = CLong(0, desc='start of valid samples')
|
|
208
|
+
|
|
173
209
|
#: Index of the last sample to be considered valid.
|
|
174
|
-
stop = Trait(None, None, CLong,
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
stop = Trait(None, None, CLong, desc='stop of valid samples')
|
|
211
|
+
|
|
177
212
|
#: Channels that are to be treated as invalid.
|
|
178
|
-
invalid_channels = ListInt(
|
|
179
|
-
|
|
180
|
-
|
|
213
|
+
invalid_channels = ListInt(desc='list of invalid channels')
|
|
214
|
+
|
|
181
215
|
#: Channel mask to serve as an index for all valid channels, is set automatically.
|
|
182
|
-
channels = Property(depends_on
|
|
183
|
-
|
|
184
|
-
|
|
216
|
+
channels = Property(depends_on=['invalid_channels', 'source.numchannels'], desc='channel mask')
|
|
217
|
+
|
|
185
218
|
#: Number of channels in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
|
|
186
219
|
numchannels_total = Delegate('source', 'numchannels')
|
|
187
|
-
|
|
220
|
+
|
|
188
221
|
#: Number of samples in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
|
|
189
222
|
numsamples_total = Delegate('source', 'numsamples')
|
|
190
223
|
|
|
191
224
|
#: Number of valid channels, is set automatically.
|
|
192
|
-
numchannels = Property(depends_on
|
|
193
|
-
'source.numchannels'], desc="number of valid input channels")
|
|
225
|
+
numchannels = Property(depends_on=['invalid_channels', 'source.numchannels'], desc='number of valid input channels')
|
|
194
226
|
|
|
195
227
|
#: Number of valid time samples, is set automatically.
|
|
196
|
-
numsamples = Property(depends_on
|
|
197
|
-
desc="number of valid samples per channel")
|
|
228
|
+
numsamples = Property(depends_on=['start', 'stop', 'source.numsamples'], desc='number of valid samples per channel')
|
|
198
229
|
|
|
199
230
|
#: Name of the cache file without extension, readonly.
|
|
200
|
-
basename = Property(
|
|
201
|
-
desc="basename for cache file")
|
|
231
|
+
basename = Property(depends_on='source.digest', desc='basename for cache file')
|
|
202
232
|
|
|
203
233
|
# internal identifier
|
|
204
|
-
digest = Property(
|
|
205
|
-
'invalid_channels'])
|
|
234
|
+
digest = Property(depends_on=['source.digest', 'start', 'stop', 'invalid_channels'])
|
|
206
235
|
|
|
207
236
|
@cached_property
|
|
208
|
-
def _get_digest(
|
|
237
|
+
def _get_digest(self):
|
|
209
238
|
return digest(self)
|
|
210
239
|
|
|
211
240
|
@cached_property
|
|
212
|
-
def _get_basename(
|
|
241
|
+
def _get_basename(self):
|
|
213
242
|
if 'basename' in self.source.all_trait_names():
|
|
214
243
|
return self.source.basename
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
244
|
+
return self.source.__class__.__name__ + self.source.digest
|
|
245
|
+
|
|
218
246
|
@cached_property
|
|
219
|
-
def _get_channels(
|
|
220
|
-
if len(self.invalid_channels)==0:
|
|
247
|
+
def _get_channels(self):
|
|
248
|
+
if len(self.invalid_channels) == 0:
|
|
221
249
|
return slice(0, None, None)
|
|
222
|
-
allr=[i for i in range(self.numchannels_total) if not
|
|
250
|
+
allr = [i for i in range(self.numchannels_total) if i not in self.invalid_channels]
|
|
223
251
|
return array(allr)
|
|
224
|
-
|
|
252
|
+
|
|
225
253
|
@cached_property
|
|
226
|
-
def _get_numchannels(
|
|
227
|
-
if len(self.invalid_channels)==0:
|
|
254
|
+
def _get_numchannels(self):
|
|
255
|
+
if len(self.invalid_channels) == 0:
|
|
228
256
|
return self.numchannels_total
|
|
229
257
|
return len(self.channels)
|
|
230
|
-
|
|
258
|
+
|
|
231
259
|
@cached_property
|
|
232
|
-
def _get_numsamples(
|
|
260
|
+
def _get_numsamples(self):
|
|
233
261
|
sli = slice(self.start, self.stop).indices(self.numsamples_total)
|
|
234
|
-
return sli[1]-sli[0]
|
|
262
|
+
return sli[1] - sli[0]
|
|
235
263
|
|
|
236
264
|
def result(self, num):
|
|
237
|
-
"""
|
|
238
|
-
|
|
239
|
-
|
|
265
|
+
"""Python generator that yields the output block-wise.
|
|
266
|
+
|
|
240
267
|
Parameters
|
|
241
268
|
----------
|
|
242
269
|
num : integer
|
|
243
270
|
This parameter defines the size of the blocks to be yielded
|
|
244
271
|
(i.e. the number of samples per block).
|
|
245
|
-
|
|
272
|
+
|
|
246
273
|
Returns
|
|
247
274
|
-------
|
|
248
|
-
Samples in blocks of shape (num, :attr:`numchannels`).
|
|
275
|
+
Samples in blocks of shape (num, :attr:`numchannels`).
|
|
249
276
|
The last block may be shorter than num.
|
|
277
|
+
|
|
250
278
|
"""
|
|
251
279
|
sli = slice(self.start, self.stop).indices(self.numsamples_total)
|
|
252
280
|
start = sli[0]
|
|
253
281
|
stop = sli[1]
|
|
254
282
|
if start >= stop:
|
|
255
|
-
|
|
256
|
-
|
|
283
|
+
msg = 'no samples available'
|
|
284
|
+
raise OSError(msg)
|
|
285
|
+
|
|
257
286
|
if start != 0 or stop != self.numsamples_total:
|
|
258
287
|
offset = -start % num
|
|
259
|
-
if offset == 0:
|
|
260
|
-
|
|
288
|
+
if offset == 0:
|
|
289
|
+
offset = num
|
|
290
|
+
buf = empty((num + offset, self.numchannels), dtype=float)
|
|
261
291
|
bsize = 0
|
|
262
292
|
i = 0
|
|
263
293
|
fblock = True
|
|
264
294
|
for block in self.source.result(num):
|
|
265
295
|
bs = block.shape[0]
|
|
266
296
|
i += bs
|
|
267
|
-
if fblock and i >= start
|
|
268
|
-
if i>= stop:
|
|
269
|
-
yield block[bs-(i-start):bs-(i-stop),self.channels]
|
|
297
|
+
if fblock and i >= start: # first block in the chosen interval
|
|
298
|
+
if i >= stop: # special case that start and stop are in one block
|
|
299
|
+
yield block[bs - (i - start) : bs - (i - stop), self.channels]
|
|
270
300
|
break
|
|
271
|
-
bsize +=
|
|
272
|
-
buf[:(i-start)
|
|
301
|
+
bsize += i - start
|
|
302
|
+
buf[: (i - start), :] = block[bs - (i - start) :, self.channels]
|
|
273
303
|
fblock = False
|
|
274
|
-
elif i >= stop:
|
|
275
|
-
buf[bsize:bsize+bs-(i-stop)
|
|
276
|
-
bsize += bs-(i-stop)
|
|
277
|
-
if bsize >num:
|
|
304
|
+
elif i >= stop: # last block
|
|
305
|
+
buf[bsize : bsize + bs - (i - stop), :] = block[: bs - (i - stop), self.channels]
|
|
306
|
+
bsize += bs - (i - stop)
|
|
307
|
+
if bsize > num:
|
|
278
308
|
yield buf[:num]
|
|
279
|
-
buf[:bsize-num
|
|
309
|
+
buf[: bsize - num, :] = buf[num:bsize, :]
|
|
280
310
|
bsize -= num
|
|
281
|
-
yield buf[:bsize
|
|
311
|
+
yield buf[:bsize, :]
|
|
282
312
|
break
|
|
283
|
-
elif i >=start
|
|
284
|
-
buf[bsize:bsize+bs
|
|
313
|
+
elif i >= start:
|
|
314
|
+
buf[bsize : bsize + bs, :] = block[:, self.channels]
|
|
285
315
|
bsize += bs
|
|
286
|
-
if bsize>=num:
|
|
316
|
+
if bsize >= num:
|
|
287
317
|
yield buf[:num]
|
|
288
|
-
buf[:bsize-num
|
|
318
|
+
buf[: bsize - num, :] = buf[num:bsize, :]
|
|
289
319
|
bsize -= num
|
|
290
|
-
|
|
291
|
-
else:
|
|
320
|
+
|
|
321
|
+
else: # if no start/stop given, don't do the resorting thing
|
|
292
322
|
for block in self.source.result(num):
|
|
293
323
|
yield block[:, self.channels]
|
|
294
324
|
|
|
295
325
|
|
|
296
|
-
class ChannelMixer(
|
|
297
|
-
"""
|
|
298
|
-
Class for directly mixing the channels of a multi-channel source.
|
|
326
|
+
class ChannelMixer(TimeInOut):
|
|
327
|
+
"""Class for directly mixing the channels of a multi-channel source.
|
|
299
328
|
Outputs a single channel.
|
|
300
329
|
"""
|
|
301
|
-
|
|
330
|
+
|
|
302
331
|
#: Amplitude weight(s) for the channels as array. If not set, all channels are equally weighted.
|
|
303
|
-
weights = CArray(desc=
|
|
304
|
-
|
|
332
|
+
weights = CArray(desc='channel weights')
|
|
333
|
+
|
|
305
334
|
# Number of channels is always one here.
|
|
306
335
|
numchannels = Constant(1)
|
|
307
|
-
|
|
336
|
+
|
|
308
337
|
# internal identifier
|
|
309
|
-
digest = Property(
|
|
338
|
+
digest = Property(depends_on=['source.digest', 'weights'])
|
|
310
339
|
|
|
311
340
|
@cached_property
|
|
312
|
-
def _get_digest(
|
|
313
|
-
return digest(self)
|
|
341
|
+
def _get_digest(self):
|
|
342
|
+
return digest(self)
|
|
314
343
|
|
|
315
344
|
def result(self, num):
|
|
316
|
-
"""
|
|
317
|
-
|
|
318
|
-
|
|
345
|
+
"""Python generator that yields the output block-wise.
|
|
346
|
+
|
|
319
347
|
Parameters
|
|
320
348
|
----------
|
|
321
349
|
num : integer
|
|
322
350
|
This parameter defines the size of the blocks to be yielded
|
|
323
351
|
(i.e. the number of samples per block).
|
|
324
|
-
|
|
352
|
+
|
|
325
353
|
Returns
|
|
326
354
|
-------
|
|
327
|
-
Samples in blocks of shape (num, 1).
|
|
355
|
+
Samples in blocks of shape (num, 1).
|
|
328
356
|
The last block may be shorter than num.
|
|
357
|
+
|
|
329
358
|
"""
|
|
330
359
|
if self.weights.size:
|
|
331
360
|
if self.weights.shape in {(self.source.numchannels,), (1,)}:
|
|
332
361
|
weights = self.weights
|
|
333
362
|
else:
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
else:
|
|
363
|
+
msg = f'Weight factors can not be broadcasted: {self.weights.shape}, {(self.source.numchannels,)}'
|
|
364
|
+
raise ValueError(msg)
|
|
365
|
+
else:
|
|
337
366
|
weights = 1
|
|
338
|
-
|
|
367
|
+
|
|
339
368
|
for block in self.source.result(num):
|
|
340
|
-
yield sum(weights*block, 1, keepdims=True)
|
|
341
|
-
|
|
342
|
-
|
|
369
|
+
yield sum(weights * block, 1, keepdims=True)
|
|
370
|
+
|
|
371
|
+
|
|
343
372
|
class Trigger(TimeInOut):
|
|
344
|
-
"""
|
|
345
|
-
Class for identifying trigger signals.
|
|
373
|
+
"""Class for identifying trigger signals.
|
|
346
374
|
Gets samples from :attr:`source` and stores the trigger samples in :meth:`trigger_data`.
|
|
347
|
-
|
|
375
|
+
|
|
348
376
|
The algorithm searches for peaks which are above/below a signed threshold.
|
|
349
377
|
A estimate for approximative length of one revolution is found via the greatest
|
|
350
378
|
number of samples between the adjacent peaks.
|
|
351
379
|
The algorithm then defines hunks as percentages of the estimated length of one revolution.
|
|
352
|
-
If there are multiple peaks within one hunk, the algorithm just takes one of them
|
|
380
|
+
If there are multiple peaks within one hunk, the algorithm just takes one of them
|
|
353
381
|
into account (e.g. the first peak, the peak with extremum value, ...).
|
|
354
382
|
In the end, the algorithm checks if the found peak locations result in rpm that don't
|
|
355
383
|
vary too much.
|
|
356
384
|
"""
|
|
385
|
+
|
|
357
386
|
#: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
|
|
358
387
|
source = Instance(SamplesGenerator)
|
|
359
|
-
|
|
360
|
-
#: Threshold of trigger. Has different meanings for different
|
|
388
|
+
|
|
389
|
+
#: Threshold of trigger. Has different meanings for different
|
|
361
390
|
#: :attr:`~acoular.tprocess.Trigger.trigger_type`. The sign is relevant.
|
|
362
|
-
#: If a sample of the signal is above/below the positive/negative threshold,
|
|
391
|
+
#: If a sample of the signal is above/below the positive/negative threshold,
|
|
363
392
|
#: it is assumed to be a peak.
|
|
364
393
|
#: Default is None, in which case a first estimate is used: The threshold
|
|
365
|
-
#: is assumed to be 75% of the max/min difference between all extremums and the
|
|
394
|
+
#: is assumed to be 75% of the max/min difference between all extremums and the
|
|
366
395
|
#: mean value of the trigger signal. E.g: the mean value is 0 and there are positive
|
|
367
|
-
#: extremums at 400 and negative extremums at -800. Then the estimated threshold would be
|
|
396
|
+
#: extremums at 400 and negative extremums at -800. Then the estimated threshold would be
|
|
368
397
|
#: 0.75 * -800 = -600.
|
|
369
398
|
threshold = Float(None)
|
|
370
|
-
|
|
399
|
+
|
|
371
400
|
#: Maximum allowable variation of length of each revolution duration. Default is
|
|
372
401
|
#: 2%. A warning is thrown, if any revolution length surpasses this value:
|
|
373
402
|
#: abs(durationEachRev - meanDuration) > 0.02 * meanDuration
|
|
374
403
|
max_variation_of_duration = Float(0.02)
|
|
375
|
-
|
|
404
|
+
|
|
376
405
|
#: Defines the length of hunks via lenHunk = hunk_length * maxOncePerRevDuration.
|
|
377
|
-
#: If there are multiple peaks within lenHunk, then the algorithm will
|
|
406
|
+
#: If there are multiple peaks within lenHunk, then the algorithm will
|
|
378
407
|
#: cancel all but one out (see :attr:`~acoular.tprocess.Trigger.multiple_peaks_in_hunk`).
|
|
379
408
|
#: Default is to 0.1.
|
|
380
409
|
hunk_length = Float(0.1)
|
|
381
|
-
|
|
410
|
+
|
|
382
411
|
#: Type of trigger.
|
|
383
412
|
#:
|
|
384
|
-
#: 'dirac': a single puls is assumed (sign of
|
|
413
|
+
#: 'dirac': a single puls is assumed (sign of
|
|
385
414
|
#: :attr:`~acoular.tprocess.Trigger.trigger_type` is important).
|
|
386
415
|
#: Sample will trigger if its value is above/below the pos/neg threshold.
|
|
387
|
-
#:
|
|
388
|
-
#: 'rect' : repeating rectangular functions. Only every second
|
|
389
|
-
#: edge is assumed to be a trigger. The sign of
|
|
416
|
+
#:
|
|
417
|
+
#: 'rect' : repeating rectangular functions. Only every second
|
|
418
|
+
#: edge is assumed to be a trigger. The sign of
|
|
390
419
|
#: :attr:`~acoular.tprocess.Trigger.trigger_type` gives information
|
|
391
420
|
#: on which edge should be used (+ for rising edge, - for falling edge).
|
|
392
421
|
#: Sample will trigger if the difference between its value and its predecessors value
|
|
393
422
|
#: is above/below the pos/neg threshold.
|
|
394
|
-
#:
|
|
423
|
+
#:
|
|
395
424
|
#: Default is 'dirac'.
|
|
396
425
|
trigger_type = Trait('dirac', 'rect')
|
|
397
|
-
|
|
426
|
+
|
|
398
427
|
#: Identifier which peak to consider, if there are multiple peaks in one hunk
|
|
399
|
-
#: (see :attr:`~acoular.tprocess.Trigger.hunk_length`). Default is to 'extremum',
|
|
428
|
+
#: (see :attr:`~acoular.tprocess.Trigger.hunk_length`). Default is to 'extremum',
|
|
400
429
|
#: in which case the extremal peak (maximum if threshold > 0, minimum if threshold < 0) is considered.
|
|
401
430
|
multiple_peaks_in_hunk = Trait('extremum', 'first')
|
|
402
|
-
|
|
403
|
-
#: Tuple consisting of 3 entries:
|
|
404
|
-
#:
|
|
431
|
+
|
|
432
|
+
#: Tuple consisting of 3 entries:
|
|
433
|
+
#:
|
|
405
434
|
#: 1.: -Vector with the sample indices of the 1/Rev trigger samples
|
|
406
|
-
#:
|
|
435
|
+
#:
|
|
407
436
|
#: 2.: -maximum of number of samples between adjacent trigger samples
|
|
408
|
-
#:
|
|
437
|
+
#:
|
|
409
438
|
#: 3.: -minimum of number of samples between adjacent trigger samples
|
|
410
|
-
trigger_data = Property(
|
|
411
|
-
|
|
412
|
-
|
|
439
|
+
trigger_data = Property(
|
|
440
|
+
depends_on=[
|
|
441
|
+
'source.digest',
|
|
442
|
+
'threshold',
|
|
443
|
+
'max_variation_of_duration',
|
|
444
|
+
'hunk_length',
|
|
445
|
+
'trigger_type',
|
|
446
|
+
'multiple_peaks_in_hunk',
|
|
447
|
+
],
|
|
448
|
+
)
|
|
449
|
+
|
|
413
450
|
# internal identifier
|
|
414
|
-
digest = Property(
|
|
415
|
-
|
|
416
|
-
|
|
451
|
+
digest = Property(
|
|
452
|
+
depends_on=[
|
|
453
|
+
'source.digest',
|
|
454
|
+
'threshold',
|
|
455
|
+
'max_variation_of_duration',
|
|
456
|
+
'hunk_length',
|
|
457
|
+
'trigger_type',
|
|
458
|
+
'multiple_peaks_in_hunk',
|
|
459
|
+
],
|
|
460
|
+
)
|
|
461
|
+
|
|
417
462
|
@cached_property
|
|
418
|
-
def _get_digest(
|
|
463
|
+
def _get_digest(self):
|
|
419
464
|
return digest(self)
|
|
420
|
-
|
|
465
|
+
|
|
421
466
|
@cached_property
|
|
422
467
|
def _get_trigger_data(self):
|
|
423
468
|
self._check_trigger_existence()
|
|
424
|
-
triggerFunc = {'dirac'
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
469
|
+
triggerFunc = {'dirac': self._trigger_dirac, 'rect': self._trigger_rect}[self.trigger_type]
|
|
470
|
+
num = 2048 # number samples for result-method of source
|
|
471
|
+
threshold = self._threshold(num)
|
|
472
|
+
|
|
429
473
|
# get all samples which surpasse the threshold
|
|
430
474
|
peakLoc = array([], dtype='int') # all indices which surpasse the threshold
|
|
431
|
-
|
|
475
|
+
trigger_data = array([])
|
|
432
476
|
x0 = []
|
|
433
477
|
dSamples = 0
|
|
434
|
-
for triggerSignal in self.source.result(
|
|
478
|
+
for triggerSignal in self.source.result(num):
|
|
435
479
|
localTrigger = flatnonzero(triggerFunc(x0, triggerSignal, threshold))
|
|
436
|
-
if
|
|
480
|
+
if len(localTrigger) != 0:
|
|
437
481
|
peakLoc = append(peakLoc, localTrigger + dSamples)
|
|
438
|
-
|
|
439
|
-
dSamples +=
|
|
482
|
+
trigger_data = append(trigger_data, triggerSignal[localTrigger])
|
|
483
|
+
dSamples += num
|
|
440
484
|
x0 = triggerSignal[-1]
|
|
441
485
|
if len(peakLoc) <= 1:
|
|
442
|
-
|
|
486
|
+
msg = 'Not enough trigger info. Check *threshold* sign and value!'
|
|
487
|
+
raise Exception(msg)
|
|
443
488
|
|
|
444
489
|
peakDist = peakLoc[1:] - peakLoc[:-1]
|
|
445
490
|
maxPeakDist = max(peakDist) # approximate distance between the revolutions
|
|
446
|
-
|
|
447
|
-
# if there are hunks which contain multiple peaks -> check for each hunk,
|
|
491
|
+
|
|
492
|
+
# if there are hunks which contain multiple peaks -> check for each hunk,
|
|
448
493
|
# which peak is the correct one -> delete the other one.
|
|
449
|
-
# if there are no multiple peaks in any hunk left -> leave the while
|
|
494
|
+
# if there are no multiple peaks in any hunk left -> leave the while
|
|
450
495
|
# loop and continue with program
|
|
451
496
|
multiplePeaksWithinHunk = flatnonzero(peakDist < self.hunk_length * maxPeakDist)
|
|
452
497
|
while len(multiplePeaksWithinHunk) > 0:
|
|
453
498
|
peakLocHelp = multiplePeaksWithinHunk[0]
|
|
454
499
|
indHelp = [peakLocHelp, peakLocHelp + 1]
|
|
455
500
|
if self.multiple_peaks_in_hunk == 'extremum':
|
|
456
|
-
values =
|
|
501
|
+
values = trigger_data[indHelp]
|
|
457
502
|
deleteInd = indHelp[argmin(abs(values))]
|
|
458
503
|
elif self.multiple_peaks_in_hunk == 'first':
|
|
459
504
|
deleteInd = indHelp[1]
|
|
460
505
|
peakLoc = delete(peakLoc, deleteInd)
|
|
461
|
-
|
|
506
|
+
trigger_data = delete(trigger_data, deleteInd)
|
|
462
507
|
peakDist = peakLoc[1:] - peakLoc[:-1]
|
|
463
508
|
multiplePeaksWithinHunk = flatnonzero(peakDist < self.hunk_length * maxPeakDist)
|
|
464
|
-
|
|
509
|
+
|
|
465
510
|
# check whether distances between peaks are evenly distributed
|
|
466
511
|
meanDist = mean(peakDist)
|
|
467
512
|
diffDist = abs(peakDist - meanDist)
|
|
468
513
|
faultyInd = flatnonzero(diffDist > self.max_variation_of_duration * meanDist)
|
|
469
514
|
if faultyInd.size != 0:
|
|
470
|
-
warn(
|
|
515
|
+
warn(
|
|
516
|
+
'In Trigger-Identification: The distances between the peaks (and therefor the lengths of the revolutions) vary too much (check samples %s).'
|
|
517
|
+
% str(peakLoc[faultyInd] + self.source.start),
|
|
518
|
+
Warning,
|
|
519
|
+
stacklevel=2,
|
|
520
|
+
)
|
|
471
521
|
return peakLoc, max(peakDist), min(peakDist)
|
|
472
|
-
|
|
473
|
-
def _trigger_dirac(self, x0, x, threshold):
|
|
522
|
+
|
|
523
|
+
def _trigger_dirac(self, x0, x, threshold): # noqa: ARG002
|
|
474
524
|
# x0 not needed here, but needed in _trigger_rect
|
|
475
525
|
return self._trigger_value_comp(x, threshold)
|
|
476
|
-
|
|
526
|
+
|
|
477
527
|
def _trigger_rect(self, x0, x, threshold):
|
|
478
528
|
# x0 stores the last value of the the last generator cycle
|
|
479
529
|
xNew = append(x0, x)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
indPeaks = triggerData < threshold
|
|
489
|
-
return indPeaks
|
|
490
|
-
|
|
491
|
-
def _threshold(self, nSamples):
|
|
492
|
-
if self.threshold == None: # take a guessed threshold
|
|
530
|
+
# indPeakHunk = abs(xNew[1:] - xNew[:-1]) > abs(threshold) # with this line: every edge would be located
|
|
531
|
+
return self._trigger_value_comp(xNew[1:] - xNew[:-1], threshold)
|
|
532
|
+
|
|
533
|
+
def _trigger_value_comp(self, trigger_data, threshold):
|
|
534
|
+
return trigger_data > threshold if threshold > 0.0 else trigger_data < threshold
|
|
535
|
+
|
|
536
|
+
def _threshold(self, num):
|
|
537
|
+
if self.threshold is None: # take a guessed threshold
|
|
493
538
|
# get max and min values of whole trigger signal
|
|
494
539
|
maxVal = -inf
|
|
495
540
|
minVal = inf
|
|
496
541
|
meanVal = 0
|
|
497
542
|
cntMean = 0
|
|
498
|
-
for
|
|
499
|
-
maxVal = max(maxVal,
|
|
500
|
-
minVal = min(minVal,
|
|
501
|
-
meanVal +=
|
|
543
|
+
for trigger_data in self.source.result(num):
|
|
544
|
+
maxVal = max(maxVal, trigger_data.max())
|
|
545
|
+
minVal = min(minVal, trigger_data.min())
|
|
546
|
+
meanVal += trigger_data.mean()
|
|
502
547
|
cntMean += 1
|
|
503
548
|
meanVal /= cntMean
|
|
504
|
-
|
|
549
|
+
|
|
505
550
|
# get 75% of maximum absolute value of trigger signal
|
|
506
551
|
maxTriggerHelp = [minVal, maxVal] - meanVal
|
|
507
552
|
argInd = argmax(abs(maxTriggerHelp))
|
|
508
553
|
thresh = maxTriggerHelp[argInd] * 0.75 # 0.75 for 75% of max trigger signal
|
|
509
|
-
warn('No threshold was passed. An estimated threshold of %s is assumed.' % thresh, Warning, stacklevel
|
|
554
|
+
warn('No threshold was passed. An estimated threshold of %s is assumed.' % thresh, Warning, stacklevel=2)
|
|
510
555
|
else: # take user defined threshold
|
|
511
556
|
thresh = self.threshold
|
|
512
557
|
return thresh
|
|
513
|
-
|
|
558
|
+
|
|
514
559
|
def _check_trigger_existence(self):
|
|
515
560
|
nChannels = self.source.numchannels
|
|
516
561
|
if not nChannels == 1:
|
|
517
562
|
raise Exception('Trigger signal must consist of ONE channel, instead %s channels are given!' % nChannels)
|
|
518
563
|
return 0
|
|
519
564
|
|
|
565
|
+
|
|
520
566
|
class AngleTracker(MaskedTimeInOut):
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
567
|
+
"""Calculates rotation angle and rpm per sample from a trigger signal
|
|
568
|
+
using spline interpolation in the time domain.
|
|
569
|
+
|
|
525
570
|
Gets samples from :attr:`trigger` and stores the angle and rpm samples in :meth:`angle` and :meth:`rpm`.
|
|
526
571
|
|
|
527
|
-
|
|
572
|
+
"""
|
|
528
573
|
|
|
529
574
|
#: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
|
|
530
|
-
source = Instance(SamplesGenerator)
|
|
531
|
-
|
|
575
|
+
source = Instance(SamplesGenerator)
|
|
576
|
+
|
|
532
577
|
#: Trigger data from :class:`acoular.tprocess.Trigger`.
|
|
533
|
-
trigger = Instance(Trigger)
|
|
534
|
-
|
|
578
|
+
trigger = Instance(Trigger)
|
|
579
|
+
|
|
535
580
|
# internal identifier
|
|
536
|
-
digest = Property(
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
581
|
+
digest = Property(
|
|
582
|
+
depends_on=[
|
|
583
|
+
'source.digest',
|
|
584
|
+
'trigger.digest',
|
|
585
|
+
'trigger_per_revo',
|
|
586
|
+
'rot_direction',
|
|
587
|
+
'interp_points',
|
|
588
|
+
'start_angle',
|
|
589
|
+
],
|
|
590
|
+
)
|
|
591
|
+
|
|
543
592
|
#: Trigger signals per revolution,
|
|
544
593
|
#: defaults to 1.
|
|
545
|
-
trigger_per_revo = Int(1,
|
|
546
|
-
|
|
547
|
-
|
|
594
|
+
trigger_per_revo = Int(1, desc='trigger signals per revolution')
|
|
595
|
+
|
|
548
596
|
#: Flag to set counter-clockwise (1) or clockwise (-1) rotation,
|
|
549
597
|
#: defaults to -1.
|
|
550
|
-
rot_direction = Int(-1,
|
|
551
|
-
|
|
552
|
-
|
|
598
|
+
rot_direction = Int(-1, desc='mathematical direction of rotation')
|
|
599
|
+
|
|
553
600
|
#: Points of interpolation used for spline,
|
|
554
601
|
#: defaults to 4.
|
|
555
|
-
interp_points = Int(4,
|
|
556
|
-
|
|
557
|
-
|
|
602
|
+
interp_points = Int(4, desc='Points of interpolation used for spline')
|
|
603
|
+
|
|
558
604
|
#: rotation angle in radians for first trigger position
|
|
559
|
-
start_angle = Float(0,
|
|
560
|
-
|
|
561
|
-
|
|
605
|
+
start_angle = Float(0, desc='rotation angle for trigger position')
|
|
606
|
+
|
|
562
607
|
#: revolutions per minute for each sample, read-only
|
|
563
|
-
rpm = Property(
|
|
564
|
-
|
|
608
|
+
rpm = Property(depends_on='digest', desc='revolutions per minute for each sample')
|
|
609
|
+
|
|
565
610
|
#: average revolutions per minute, read-only
|
|
566
|
-
average_rpm = Property(
|
|
567
|
-
|
|
611
|
+
average_rpm = Property(depends_on='digest', desc='average revolutions per minute')
|
|
612
|
+
|
|
568
613
|
#: rotation angle in radians for each sample, read-only
|
|
569
|
-
angle = Property(
|
|
570
|
-
|
|
614
|
+
angle = Property(depends_on='digest', desc='rotation angle for each sample')
|
|
615
|
+
|
|
571
616
|
# Internal flag to determine whether rpm and angle calculation has been processed,
|
|
572
617
|
# prevents recalculation
|
|
573
|
-
_calc_flag = Bool(False)
|
|
574
|
-
|
|
618
|
+
_calc_flag = Bool(False)
|
|
619
|
+
|
|
575
620
|
# Revolutions per minute, internal use
|
|
576
621
|
_rpm = CArray()
|
|
577
|
-
|
|
622
|
+
|
|
578
623
|
# Rotation angle in radians, internal use
|
|
579
624
|
_angle = CArray()
|
|
580
|
-
|
|
581
625
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
def _get_digest( self ):
|
|
626
|
+
@cached_property
|
|
627
|
+
def _get_digest(self):
|
|
585
628
|
return digest(self)
|
|
586
|
-
|
|
587
|
-
#helperfunction for trigger index detection
|
|
629
|
+
|
|
630
|
+
# helperfunction for trigger index detection
|
|
588
631
|
def _find_nearest_idx(self, peakarray, value):
|
|
589
632
|
peakarray = asarray(peakarray)
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
633
|
+
return (abs(peakarray - value)).argmin()
|
|
634
|
+
|
|
593
635
|
def _to_rpm_and_angle(self):
|
|
594
|
-
"""
|
|
595
|
-
Internal helper function
|
|
636
|
+
"""Internal helper function
|
|
596
637
|
Calculates angles in radians for one or more instants in time.
|
|
597
|
-
|
|
598
|
-
Current version supports only trigger and sources with the same samplefreq.
|
|
599
|
-
This behaviour may change in future releases
|
|
600
|
-
"""
|
|
601
638
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
639
|
+
Current version supports only trigger and sources with the same samplefreq.
|
|
640
|
+
This behaviour may change in future releases
|
|
641
|
+
"""
|
|
642
|
+
# init
|
|
643
|
+
ind = 0
|
|
644
|
+
# trigger data
|
|
645
|
+
peakloc, maxdist, mindist = self.trigger.trigger_data()
|
|
646
|
+
TriggerPerRevo = self.trigger_per_revo
|
|
607
647
|
rotDirection = self.rot_direction
|
|
608
|
-
|
|
609
|
-
samplerate =
|
|
610
|
-
self._rpm = zeros(
|
|
611
|
-
self._angle = zeros(
|
|
612
|
-
#number of spline points
|
|
613
|
-
InterpPoints=self.interp_points
|
|
614
|
-
|
|
615
|
-
#loop over alle timesamples
|
|
616
|
-
while ind <
|
|
617
|
-
#when starting spline forward
|
|
618
|
-
if ind<peakloc[InterpPoints]:
|
|
619
|
-
peakdist
|
|
620
|
-
|
|
621
|
-
|
|
648
|
+
num = self.source.numsamples
|
|
649
|
+
samplerate = self.source.sample_freq
|
|
650
|
+
self._rpm = zeros(num)
|
|
651
|
+
self._angle = zeros(num)
|
|
652
|
+
# number of spline points
|
|
653
|
+
InterpPoints = self.interp_points
|
|
654
|
+
|
|
655
|
+
# loop over alle timesamples
|
|
656
|
+
while ind < num:
|
|
657
|
+
# when starting spline forward
|
|
658
|
+
if ind < peakloc[InterpPoints]:
|
|
659
|
+
peakdist = (
|
|
660
|
+
peakloc[self._find_nearest_idx(peakarray=peakloc, value=ind) + 1]
|
|
661
|
+
- peakloc[self._find_nearest_idx(peakarray=peakloc, value=ind)]
|
|
662
|
+
)
|
|
663
|
+
splineData = stack(
|
|
664
|
+
(range(InterpPoints), peakloc[ind // peakdist : ind // peakdist + InterpPoints]),
|
|
665
|
+
axis=0,
|
|
666
|
+
)
|
|
667
|
+
# spline backwards
|
|
622
668
|
else:
|
|
623
|
-
peakdist
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
669
|
+
peakdist = (
|
|
670
|
+
peakloc[self._find_nearest_idx(peakarray=peakloc, value=ind)]
|
|
671
|
+
- peakloc[self._find_nearest_idx(peakarray=peakloc, value=ind) - 1]
|
|
672
|
+
)
|
|
673
|
+
splineData = stack(
|
|
674
|
+
(range(InterpPoints), peakloc[ind // peakdist - InterpPoints : ind // peakdist]),
|
|
675
|
+
axis=0,
|
|
676
|
+
)
|
|
677
|
+
# calc angles and rpm
|
|
678
|
+
Spline = splrep(splineData[:, :][1], splineData[:, :][0], k=3)
|
|
679
|
+
self._rpm[ind] = splev(ind, Spline, der=1, ext=0) * 60 * samplerate
|
|
680
|
+
self._angle[ind] = (
|
|
681
|
+
splev(ind, Spline, der=0, ext=0) * 2 * pi * rotDirection / TriggerPerRevo + self.start_angle
|
|
682
|
+
) % (2 * pi)
|
|
683
|
+
# next sample
|
|
684
|
+
ind += 1
|
|
685
|
+
# calculation complete
|
|
632
686
|
self._calc_flag = True
|
|
633
|
-
|
|
687
|
+
|
|
634
688
|
# reset calc flag if something has changed
|
|
635
689
|
@on_trait_change('digest')
|
|
636
|
-
def _reset_calc_flag(
|
|
690
|
+
def _reset_calc_flag(self):
|
|
637
691
|
self._calc_flag = False
|
|
638
|
-
|
|
639
|
-
#calc rpm from trigger data
|
|
692
|
+
|
|
693
|
+
# calc rpm from trigger data
|
|
640
694
|
@cached_property
|
|
641
|
-
def _get_rpm(
|
|
695
|
+
def _get_rpm(self):
|
|
642
696
|
if not self._calc_flag:
|
|
643
697
|
self._to_rpm_and_angle()
|
|
644
698
|
return self._rpm
|
|
645
699
|
|
|
646
|
-
#calc of angle from trigger data
|
|
700
|
+
# calc of angle from trigger data
|
|
647
701
|
@cached_property
|
|
648
702
|
def _get_angle(self):
|
|
649
703
|
if not self._calc_flag:
|
|
650
704
|
self._to_rpm_and_angle()
|
|
651
705
|
return self._angle
|
|
652
706
|
|
|
653
|
-
#calc average rpm from trigger data
|
|
707
|
+
# calc average rpm from trigger data
|
|
654
708
|
@cached_property
|
|
655
|
-
def _get_average_rpm(
|
|
656
|
-
"""
|
|
657
|
-
|
|
658
|
-
|
|
709
|
+
def _get_average_rpm(self):
|
|
710
|
+
"""Returns average revolutions per minute (rpm) over the source samples.
|
|
711
|
+
|
|
659
712
|
Returns
|
|
660
713
|
-------
|
|
661
714
|
rpm : float
|
|
662
715
|
rpm in 1/min.
|
|
716
|
+
|
|
663
717
|
"""
|
|
664
|
-
#trigger indices data
|
|
665
|
-
peakloc = self.trigger.
|
|
666
|
-
#calculation of average rpm in 1/min
|
|
667
|
-
return (len(peakloc)-1) / (peakloc[-1]-peakloc[0]) / self.trigger_per_revo * self.source.sample_freq * 60
|
|
718
|
+
# trigger indices data
|
|
719
|
+
peakloc = self.trigger.trigger_data()[0]
|
|
720
|
+
# calculation of average rpm in 1/min
|
|
721
|
+
return (len(peakloc) - 1) / (peakloc[-1] - peakloc[0]) / self.trigger_per_revo * self.source.sample_freq * 60
|
|
722
|
+
|
|
668
723
|
|
|
669
724
|
class SpatialInterpolator(TimeInOut):
|
|
725
|
+
"""Base class for spatial interpolation of microphone data.
|
|
726
|
+
Gets samples from :attr:`source` and generates output via the
|
|
727
|
+
generator :meth:`result`.
|
|
670
728
|
"""
|
|
671
|
-
|
|
672
|
-
Gets samples from :attr:`source` and generates output via the
|
|
673
|
-
generator :meth:`result`
|
|
674
|
-
"""
|
|
729
|
+
|
|
675
730
|
#: :class:`~acoular.microphones.MicGeom` object that provides the real microphone locations.
|
|
676
|
-
mics = Instance(MicGeom(),
|
|
677
|
-
|
|
678
|
-
|
|
731
|
+
mics = Instance(MicGeom(), desc='microphone geometry')
|
|
732
|
+
|
|
679
733
|
#: :class:`~acoular.microphones.MicGeom` object that provides the virtual microphone locations.
|
|
680
|
-
mics_virtual = Property(
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
desc="internal microphone geometry;internal usage, read only")
|
|
685
|
-
|
|
734
|
+
mics_virtual = Property(desc='microphone geometry')
|
|
735
|
+
|
|
736
|
+
_mics_virtual = Instance(MicGeom, desc='internal microphone geometry;internal usage, read only')
|
|
737
|
+
|
|
686
738
|
def _get_mics_virtual(self):
|
|
687
739
|
if not self._mics_virtual and self.mics:
|
|
688
740
|
self._mics_virtual = self.mics
|
|
689
741
|
return self._mics_virtual
|
|
690
|
-
|
|
742
|
+
|
|
691
743
|
def _set_mics_virtual(self, mics_virtual):
|
|
692
744
|
self._mics_virtual = mics_virtual
|
|
693
745
|
|
|
694
|
-
|
|
695
746
|
#: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
|
|
696
747
|
source = Instance(SamplesGenerator)
|
|
697
|
-
|
|
748
|
+
|
|
698
749
|
#: Interpolation method in spacial domain, defaults to linear
|
|
699
750
|
#: linear uses numpy linear interpolation
|
|
700
751
|
#: spline uses scipy CloughTocher algorithm
|
|
701
752
|
#: rbf is scipy radial basis function with multiquadric, cubic and sinc functions
|
|
702
|
-
#: idw refers to the inverse distance weighting algorithm
|
|
703
|
-
method = Trait(
|
|
704
|
-
'
|
|
705
|
-
|
|
753
|
+
#: idw refers to the inverse distance weighting algorithm
|
|
754
|
+
method = Trait(
|
|
755
|
+
'linear',
|
|
756
|
+
'spline',
|
|
757
|
+
'rbf-multiquadric',
|
|
758
|
+
'rbf-cubic',
|
|
759
|
+
'IDW',
|
|
760
|
+
'custom',
|
|
761
|
+
'sinc',
|
|
762
|
+
desc='method for interpolation used',
|
|
763
|
+
)
|
|
764
|
+
|
|
706
765
|
#: spacial dimensionality of the array geometry
|
|
707
|
-
array_dimension= Trait('1D', '2D',
|
|
708
|
-
|
|
709
|
-
|
|
766
|
+
array_dimension = Trait('1D', '2D', 'ring', '3D', 'custom', desc='spacial dimensionality of the array geometry')
|
|
767
|
+
|
|
710
768
|
#: Sampling frequency of output signal, as given by :attr:`source`.
|
|
711
769
|
sample_freq = Delegate('source', 'sample_freq')
|
|
712
|
-
|
|
770
|
+
|
|
713
771
|
#: Number of channels in output.
|
|
714
772
|
numchannels = Property()
|
|
715
|
-
|
|
773
|
+
|
|
716
774
|
#: Number of samples in output, as given by :attr:`source`.
|
|
717
775
|
numsamples = Delegate('source', 'numsamples')
|
|
718
|
-
|
|
719
776
|
|
|
720
|
-
#:Interpolate a point at the origin of the Array geometry
|
|
721
|
-
interp_at_zero =
|
|
777
|
+
#:Interpolate a point at the origin of the Array geometry
|
|
778
|
+
interp_at_zero = Bool(False)
|
|
722
779
|
|
|
723
780
|
#: The rotation must be around the z-axis, which means from x to y axis.
|
|
724
|
-
#: If the coordinates are not build like that, than this 3x3 orthogonal
|
|
781
|
+
#: If the coordinates are not build like that, than this 3x3 orthogonal
|
|
725
782
|
#: transformation matrix Q can be used to modify the coordinates.
|
|
726
|
-
#: It is assumed that with the modified coordinates the rotation is around the z-axis.
|
|
783
|
+
#: It is assumed that with the modified coordinates the rotation is around the z-axis.
|
|
727
784
|
#: The transformation is done via [x,y,z]_mod = Q * [x,y,z]. (default is Identity).
|
|
728
785
|
Q = CArray(dtype=float64, shape=(3, 3), value=identity(3))
|
|
729
|
-
|
|
730
|
-
num_IDW= Trait(3,dtype = int, \
|
|
731
|
-
desc='number of neighboring microphones, DEFAULT=3')
|
|
732
786
|
|
|
733
|
-
|
|
734
|
-
desc='used in interpolation for virtual microphone, weighting power exponent for IDW')
|
|
787
|
+
num_IDW = Trait(3, dtype=int, desc='number of neighboring microphones, DEFAULT=3') # noqa: N815
|
|
735
788
|
|
|
789
|
+
p_weight = Trait(
|
|
790
|
+
2,
|
|
791
|
+
dtype=float,
|
|
792
|
+
desc='used in interpolation for virtual microphone, weighting power exponent for IDW',
|
|
793
|
+
)
|
|
736
794
|
|
|
737
795
|
#: Stores the output of :meth:`_virtNewCoord_func`; Read-Only
|
|
738
|
-
_virtNewCoord_func = Property(
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
796
|
+
_virtNewCoord_func = Property( # noqa: N815
|
|
797
|
+
depends_on=['mics.digest', 'mics_virtual.digest', 'method', 'array_dimension', 'interp_at_zero'],
|
|
798
|
+
)
|
|
799
|
+
|
|
743
800
|
#: internal identifier
|
|
744
|
-
digest = Property(
|
|
745
|
-
|
|
746
|
-
|
|
801
|
+
digest = Property(
|
|
802
|
+
depends_on=[
|
|
803
|
+
'mics.digest',
|
|
804
|
+
'mics_virtual.digest',
|
|
805
|
+
'source.digest',
|
|
806
|
+
'method',
|
|
807
|
+
'array_dimension',
|
|
808
|
+
'Q',
|
|
809
|
+
'interp_at_zero',
|
|
810
|
+
],
|
|
811
|
+
)
|
|
812
|
+
|
|
747
813
|
def _get_numchannels(self):
|
|
748
814
|
return self.mics_virtual.num_mics
|
|
749
|
-
|
|
815
|
+
|
|
750
816
|
@cached_property
|
|
751
|
-
def _get_digest(
|
|
817
|
+
def _get_digest(self):
|
|
752
818
|
return digest(self)
|
|
753
|
-
|
|
819
|
+
|
|
754
820
|
@cached_property
|
|
755
|
-
def _get_virtNewCoord(self):
|
|
756
|
-
return self._virtNewCoord_func(self.mics.mpos, self.mics_virtual.mpos,self.method, self.array_dimension)
|
|
757
|
-
|
|
758
|
-
|
|
821
|
+
def _get_virtNewCoord(self): # noqa N802
|
|
822
|
+
return self._virtNewCoord_func(self.mics.mpos, self.mics_virtual.mpos, self.method, self.array_dimension)
|
|
823
|
+
|
|
759
824
|
def sinc_mic(self, r):
|
|
760
|
-
"""
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
def _virtNewCoord_func(self, mic, micVirt, method ,array_dimension, interp_at_zero = False):
|
|
767
|
-
"""
|
|
768
|
-
Core functionality for getting the interpolation .
|
|
769
|
-
|
|
825
|
+
"""Modified Sinc function for Radial Basis function approximation."""
|
|
826
|
+
return sinc((r * self.mics_virtual.mpos.shape[1]) / (pi))
|
|
827
|
+
|
|
828
|
+
def _virtNewCoord_func(self, mpos, mpos_virt, method, array_dimension): # noqa N802
|
|
829
|
+
"""Core functionality for getting the interpolation.
|
|
830
|
+
|
|
770
831
|
Parameters
|
|
771
832
|
----------
|
|
772
|
-
|
|
833
|
+
mpos : float[3, nPhysicalMics]
|
|
773
834
|
The mic positions of the physical (really existing) mics
|
|
774
|
-
|
|
835
|
+
mpos_virt : float[3, nVirtualMics]
|
|
775
836
|
The mic positions of the virtual mics
|
|
776
837
|
method : string
|
|
777
|
-
The Interpolation method to use
|
|
838
|
+
The Interpolation method to use
|
|
778
839
|
array_dimension : string
|
|
779
840
|
The Array Dimensions in cylinder coordinates
|
|
780
841
|
|
|
@@ -794,502 +855,530 @@ class SpatialInterpolator(TimeInOut):
|
|
|
794
855
|
2. item : int64[nMicsInArray]
|
|
795
856
|
same as 1d case, BUT with the difference, that here the rotational periodicy is handled, when constructing the mesh.
|
|
796
857
|
Therefor the mesh could have more vertices than the actual Array mics.
|
|
797
|
-
|
|
858
|
+
|
|
798
859
|
virtNewCoord : float64[3, nVirtualMics]
|
|
799
860
|
Projection of each virtual mic onto its new coordinates. The columns of virtNewCoord correspond to [phi, rho, z]
|
|
800
|
-
|
|
861
|
+
|
|
801
862
|
newCoord : float64[3, nMics]
|
|
802
863
|
Projection of each mic onto its new coordinates. The columns of newCoordinates correspond to [phi, rho, z]
|
|
803
|
-
|
|
864
|
+
|
|
865
|
+
"""
|
|
804
866
|
# init positions of virtual mics in cyl coordinates
|
|
805
|
-
nVirtMics =
|
|
867
|
+
nVirtMics = mpos_virt.shape[1]
|
|
806
868
|
virtNewCoord = zeros((3, nVirtMics))
|
|
807
869
|
virtNewCoord.fill(nan)
|
|
808
|
-
#init real positions in cyl coordinates
|
|
809
|
-
nMics =
|
|
870
|
+
# init real positions in cyl coordinates
|
|
871
|
+
nMics = mpos.shape[1]
|
|
810
872
|
newCoord = zeros((3, nMics))
|
|
811
873
|
newCoord.fill(nan)
|
|
812
|
-
#empty mesh object
|
|
874
|
+
# empty mesh object
|
|
813
875
|
mesh = []
|
|
814
|
-
|
|
815
|
-
if self.array_dimension =='1D' or self.array_dimension =='ring':
|
|
816
|
-
# get projections onto new coordinate, for real mics
|
|
817
|
-
projectionOnNewAxis = cartToCyl(mic,self.Q)[0]
|
|
818
|
-
indReorderHelp = argsort(projectionOnNewAxis)
|
|
819
|
-
mesh.append([projectionOnNewAxis[indReorderHelp], indReorderHelp])
|
|
820
|
-
|
|
821
|
-
#new coordinates of real mics
|
|
822
|
-
indReorderHelp = argsort(cartToCyl(mic,self.Q)[0])
|
|
823
|
-
newCoord = (cartToCyl(mic,self.Q).T)[indReorderHelp].T
|
|
824
|
-
|
|
825
|
-
# and for virtual mics
|
|
826
|
-
virtNewCoord = cartToCyl(micVirt)
|
|
827
|
-
|
|
828
|
-
elif self.array_dimension =='2D': # 2d case0
|
|
829
876
|
|
|
877
|
+
if self.array_dimension == '1D' or self.array_dimension == 'ring':
|
|
878
|
+
# get projections onto new coordinate, for real mics
|
|
879
|
+
projectionOnNewAxis = cartToCyl(mpos, self.Q)[0]
|
|
880
|
+
indReorderHelp = argsort(projectionOnNewAxis)
|
|
881
|
+
mesh.append([projectionOnNewAxis[indReorderHelp], indReorderHelp])
|
|
882
|
+
|
|
883
|
+
# new coordinates of real mics
|
|
884
|
+
indReorderHelp = argsort(cartToCyl(mpos, self.Q)[0])
|
|
885
|
+
newCoord = (cartToCyl(mpos, self.Q).T)[indReorderHelp].T
|
|
886
|
+
|
|
887
|
+
# and for virtual mics
|
|
888
|
+
virtNewCoord = cartToCyl(mpos_virt)
|
|
889
|
+
|
|
890
|
+
elif self.array_dimension == '2D': # 2d case0
|
|
830
891
|
# get virtual mic projections on new coord system
|
|
831
|
-
virtNewCoord = cartToCyl(
|
|
832
|
-
|
|
833
|
-
#new coordinates of real mics
|
|
834
|
-
indReorderHelp = argsort(cartToCyl(
|
|
835
|
-
newCoord = cartToCyl(
|
|
836
|
-
|
|
837
|
-
#scipy delauney triangulation
|
|
838
|
-
#Delaunay
|
|
839
|
-
tri = Delaunay(newCoord.T[
|
|
840
|
-
|
|
841
|
-
|
|
892
|
+
virtNewCoord = cartToCyl(mpos_virt, self.Q)
|
|
893
|
+
|
|
894
|
+
# new coordinates of real mics
|
|
895
|
+
indReorderHelp = argsort(cartToCyl(mpos, self.Q)[0])
|
|
896
|
+
newCoord = cartToCyl(mpos, self.Q)
|
|
897
|
+
|
|
898
|
+
# scipy delauney triangulation
|
|
899
|
+
# Delaunay
|
|
900
|
+
tri = Delaunay(newCoord.T[:, :2], incremental=True) #
|
|
901
|
+
|
|
842
902
|
if self.interp_at_zero:
|
|
843
|
-
#add a point at zero
|
|
844
|
-
tri.add_points(array([[0
|
|
845
|
-
|
|
846
|
-
# extend mesh with closest boundary points of repeating mesh
|
|
903
|
+
# add a point at zero
|
|
904
|
+
tri.add_points(array([[0], [0]]).T)
|
|
905
|
+
|
|
906
|
+
# extend mesh with closest boundary points of repeating mesh
|
|
847
907
|
pointsOriginal = arange(tri.points.shape[0])
|
|
848
908
|
hull = tri.convex_hull
|
|
849
909
|
hullPoints = unique(hull)
|
|
850
|
-
|
|
910
|
+
|
|
851
911
|
addRight = tri.points[hullPoints]
|
|
852
|
-
addRight[:, 0] += 2*pi
|
|
853
|
-
addLeft= tri.points[hullPoints]
|
|
854
|
-
addLeft[:, 0] -= 2*pi
|
|
855
|
-
|
|
912
|
+
addRight[:, 0] += 2 * pi
|
|
913
|
+
addLeft = tri.points[hullPoints]
|
|
914
|
+
addLeft[:, 0] -= 2 * pi
|
|
915
|
+
|
|
856
916
|
indOrigPoints = concatenate((pointsOriginal, pointsOriginal[hullPoints], pointsOriginal[hullPoints]))
|
|
857
|
-
# add all hull vertices to original mesh and check which of those
|
|
917
|
+
# add all hull vertices to original mesh and check which of those
|
|
858
918
|
# are actual neighbors of the original array. Cancel out all others.
|
|
859
919
|
tri.add_points(concatenate([addLeft, addRight]))
|
|
860
920
|
indices, indptr = tri.vertex_neighbor_vertices
|
|
861
921
|
hullNeighbor = empty((0), dtype='int32')
|
|
862
922
|
for currHull in hullPoints:
|
|
863
|
-
neighborOfHull = indptr[indices[currHull]:indices[currHull + 1]]
|
|
923
|
+
neighborOfHull = indptr[indices[currHull] : indices[currHull + 1]]
|
|
864
924
|
hullNeighbor = append(hullNeighbor, neighborOfHull)
|
|
865
925
|
hullNeighborUnique = unique(hullNeighbor)
|
|
866
926
|
pointsNew = unique(append(pointsOriginal, hullNeighborUnique))
|
|
867
927
|
tri = Delaunay(tri.points[pointsNew]) # re-meshing
|
|
868
928
|
mesh.append([tri, indOrigPoints[pointsNew]])
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
elif self.array_dimension =='3D': # 3d case
|
|
873
|
-
|
|
929
|
+
|
|
930
|
+
elif self.array_dimension == '3D': # 3d case
|
|
874
931
|
# get virtual mic projections on new coord system
|
|
875
|
-
virtNewCoord = cartToCyl(
|
|
932
|
+
virtNewCoord = cartToCyl(mpos_virt, self.Q)
|
|
876
933
|
# get real mic projections on new coord system
|
|
877
|
-
indReorderHelp = argsort(cartToCyl(
|
|
878
|
-
newCoord =
|
|
879
|
-
#Delaunay
|
|
880
|
-
tri =Delaunay(newCoord.T, incremental=True)
|
|
934
|
+
indReorderHelp = argsort(cartToCyl(mpos, self.Q)[0])
|
|
935
|
+
newCoord = cartToCyl(mpos, self.Q)
|
|
936
|
+
# Delaunay
|
|
937
|
+
tri = Delaunay(newCoord.T, incremental=True) # , incremental=True,qhull_options = "Qc QJ Q12"
|
|
881
938
|
|
|
882
939
|
if self.interp_at_zero:
|
|
883
|
-
#add a point at zero
|
|
884
|
-
tri.add_points(array([[0
|
|
940
|
+
# add a point at zero
|
|
941
|
+
tri.add_points(array([[0], [0], [0]]).T)
|
|
885
942
|
|
|
886
|
-
# extend mesh with closest boundary points of repeating mesh
|
|
943
|
+
# extend mesh with closest boundary points of repeating mesh
|
|
887
944
|
pointsOriginal = arange(tri.points.shape[0])
|
|
888
945
|
hull = tri.convex_hull
|
|
889
946
|
hullPoints = unique(hull)
|
|
890
|
-
|
|
947
|
+
|
|
891
948
|
addRight = tri.points[hullPoints]
|
|
892
|
-
addRight[:, 0] += 2*pi
|
|
893
|
-
addLeft= tri.points[hullPoints]
|
|
894
|
-
addLeft[:, 0] -= 2*pi
|
|
895
|
-
|
|
949
|
+
addRight[:, 0] += 2 * pi
|
|
950
|
+
addLeft = tri.points[hullPoints]
|
|
951
|
+
addLeft[:, 0] -= 2 * pi
|
|
952
|
+
|
|
896
953
|
indOrigPoints = concatenate((pointsOriginal, pointsOriginal[hullPoints], pointsOriginal[hullPoints]))
|
|
897
|
-
# add all hull vertices to original mesh and check which of those
|
|
954
|
+
# add all hull vertices to original mesh and check which of those
|
|
898
955
|
# are actual neighbors of the original array. Cancel out all others.
|
|
899
956
|
tri.add_points(concatenate([addLeft, addRight]))
|
|
900
957
|
indices, indptr = tri.vertex_neighbor_vertices
|
|
901
958
|
hullNeighbor = empty((0), dtype='int32')
|
|
902
959
|
for currHull in hullPoints:
|
|
903
|
-
neighborOfHull = indptr[indices[currHull]:indices[currHull + 1]]
|
|
960
|
+
neighborOfHull = indptr[indices[currHull] : indices[currHull + 1]]
|
|
904
961
|
hullNeighbor = append(hullNeighbor, neighborOfHull)
|
|
905
962
|
hullNeighborUnique = unique(hullNeighbor)
|
|
906
963
|
pointsNew = unique(append(pointsOriginal, hullNeighborUnique))
|
|
907
964
|
tri = Delaunay(tri.points[pointsNew]) # re-meshing
|
|
908
965
|
mesh.append([tri, indOrigPoints[pointsNew]])
|
|
909
|
-
|
|
910
|
-
return mesh, virtNewCoord , newCoord
|
|
911
|
-
|
|
912
966
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
967
|
+
return mesh, virtNewCoord, newCoord
|
|
968
|
+
|
|
969
|
+
def _result_core_func(self, p, phi_delay=None, period=None, Q=Q, interp_at_zero=False): # noqa: N803, ARG002 (see #226)
|
|
970
|
+
"""Performs the actual Interpolation.
|
|
971
|
+
|
|
917
972
|
Parameters
|
|
918
973
|
----------
|
|
919
|
-
p : float[
|
|
974
|
+
p : float[num, nMicsReal]
|
|
920
975
|
The pressure field of the yielded sample at real mics.
|
|
921
|
-
|
|
922
|
-
If passed (rotational case), this list contains the angular delay
|
|
976
|
+
phi_delay : empty list (default) or float[num]
|
|
977
|
+
If passed (rotational case), this list contains the angular delay
|
|
923
978
|
of each sample in rad.
|
|
924
979
|
period : None (default) or float
|
|
925
|
-
If periodicity can be assumed (rotational case)
|
|
980
|
+
If periodicity can be assumed (rotational case)
|
|
926
981
|
this parameter contains the periodicity length
|
|
927
|
-
|
|
982
|
+
|
|
928
983
|
Returns
|
|
929
984
|
-------
|
|
930
|
-
pInterp : float[
|
|
985
|
+
pInterp : float[num, nMicsVirtual]
|
|
931
986
|
The interpolated time data at the virtual mics
|
|
987
|
+
|
|
932
988
|
"""
|
|
933
|
-
|
|
934
|
-
|
|
989
|
+
if phi_delay is None:
|
|
990
|
+
phi_delay = []
|
|
991
|
+
# number of time samples
|
|
935
992
|
nTime = p.shape[0]
|
|
936
|
-
#number of virtual mixcs
|
|
993
|
+
# number of virtual mixcs
|
|
937
994
|
nVirtMics = self.mics_virtual.mpos.shape[1]
|
|
938
995
|
# mesh and projection onto polar Coordinates
|
|
939
996
|
meshList, virtNewCoord, newCoord = self._get_virtNewCoord()
|
|
940
|
-
# pressure interpolation init
|
|
941
|
-
pInterp = zeros((nTime,nVirtMics))
|
|
942
|
-
#Coordinates in cartesian CO - for IDW interpolation
|
|
943
|
-
newCoordCart=cylToCart(newCoord)
|
|
944
|
-
|
|
997
|
+
# pressure interpolation init
|
|
998
|
+
pInterp = zeros((nTime, nVirtMics))
|
|
999
|
+
# Coordinates in cartesian CO - for IDW interpolation
|
|
1000
|
+
newCoordCart = cylToCart(newCoord)
|
|
1001
|
+
|
|
945
1002
|
if self.interp_at_zero:
|
|
946
|
-
#interpolate point at 0 in Kartesian CO
|
|
947
|
-
interpolater = LinearNDInterpolator(
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1003
|
+
# interpolate point at 0 in Kartesian CO
|
|
1004
|
+
interpolater = LinearNDInterpolator(
|
|
1005
|
+
cylToCart(newCoord[:, argsort(newCoord[0])])[:2, :].T,
|
|
1006
|
+
p[:, (argsort(newCoord[0]))].T,
|
|
1007
|
+
fill_value=0,
|
|
1008
|
+
)
|
|
1009
|
+
pZero = interpolater((0, 0))
|
|
1010
|
+
# add the interpolated pressure at origin to pressure channels
|
|
951
1011
|
p = concatenate((p, pZero[:, newaxis]), axis=1)
|
|
952
1012
|
|
|
953
|
-
|
|
954
|
-
#helpfunction reordered for reordered pressure values
|
|
1013
|
+
# helpfunction reordered for reordered pressure values
|
|
955
1014
|
pHelp = p[:, meshList[0][1]]
|
|
956
|
-
|
|
957
|
-
# Interpolation for 1D Arrays
|
|
958
|
-
if self.array_dimension =='1D' or self.array_dimension =='ring':
|
|
959
|
-
#for rotation add
|
|
960
|
-
if not array_equal(
|
|
961
|
-
xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(
|
|
962
|
-
xInterp = ((xInterpHelp + pi
|
|
963
|
-
#if no rotation given
|
|
1015
|
+
|
|
1016
|
+
# Interpolation for 1D Arrays
|
|
1017
|
+
if self.array_dimension == '1D' or self.array_dimension == 'ring':
|
|
1018
|
+
# for rotation add phi_delay
|
|
1019
|
+
if not array_equal(phi_delay, []):
|
|
1020
|
+
xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
|
|
1021
|
+
xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
|
|
1022
|
+
# if no rotation given
|
|
964
1023
|
else:
|
|
965
1024
|
xInterp = repmat(virtNewCoord[0, :], nTime, 1)
|
|
966
|
-
#get ordered microphone posions in radiant
|
|
1025
|
+
# get ordered microphone posions in radiant
|
|
967
1026
|
x = newCoord[0]
|
|
968
1027
|
for cntTime in range(nTime):
|
|
969
|
-
|
|
970
1028
|
if self.method == 'linear':
|
|
971
|
-
#numpy 1-d interpolation
|
|
972
|
-
pInterp[cntTime] = interp(
|
|
973
|
-
|
|
974
|
-
|
|
1029
|
+
# numpy 1-d interpolation
|
|
1030
|
+
pInterp[cntTime] = interp(
|
|
1031
|
+
xInterp[cntTime, :],
|
|
1032
|
+
x,
|
|
1033
|
+
pHelp[cntTime, :],
|
|
1034
|
+
period=period,
|
|
1035
|
+
left=nan,
|
|
1036
|
+
right=nan,
|
|
1037
|
+
)
|
|
1038
|
+
|
|
975
1039
|
elif self.method == 'spline':
|
|
976
|
-
#scipy cubic spline interpolation
|
|
977
|
-
SplineInterp = CubicSpline(
|
|
978
|
-
|
|
979
|
-
|
|
1040
|
+
# scipy cubic spline interpolation
|
|
1041
|
+
SplineInterp = CubicSpline(
|
|
1042
|
+
append(x, (2 * pi) + x[0]),
|
|
1043
|
+
append(pHelp[cntTime, :], pHelp[cntTime, :][0]),
|
|
1044
|
+
axis=0,
|
|
1045
|
+
bc_type='periodic',
|
|
1046
|
+
extrapolate=None,
|
|
1047
|
+
)
|
|
1048
|
+
pInterp[cntTime] = SplineInterp(xInterp[cntTime, :])
|
|
1049
|
+
|
|
980
1050
|
elif self.method == 'sinc':
|
|
981
|
-
#compute using 3-D Rbfs for sinc
|
|
982
|
-
rbfi = Rbf(
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1051
|
+
# compute using 3-D Rbfs for sinc
|
|
1052
|
+
rbfi = Rbf(
|
|
1053
|
+
x,
|
|
1054
|
+
newCoord[1],
|
|
1055
|
+
newCoord[2],
|
|
1056
|
+
pHelp[cntTime, :],
|
|
1057
|
+
function=self.sinc_mic,
|
|
1058
|
+
) # radial basis function interpolator instance
|
|
1059
|
+
|
|
1060
|
+
pInterp[cntTime] = rbfi(xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2])
|
|
1061
|
+
|
|
990
1062
|
elif self.method == 'rbf-cubic':
|
|
991
|
-
#compute using 3-D Rbfs with multiquadratics
|
|
992
|
-
rbfi = Rbf(
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1063
|
+
# compute using 3-D Rbfs with multiquadratics
|
|
1064
|
+
rbfi = Rbf(
|
|
1065
|
+
x,
|
|
1066
|
+
newCoord[1],
|
|
1067
|
+
newCoord[2],
|
|
1068
|
+
pHelp[cntTime, :],
|
|
1069
|
+
function='cubic',
|
|
1070
|
+
) # radial basis function interpolator instance
|
|
1071
|
+
|
|
1072
|
+
pInterp[cntTime] = rbfi(xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2])
|
|
1073
|
+
|
|
1001
1074
|
# Interpolation for arbitrary 2D Arrays
|
|
1002
|
-
elif self.array_dimension =='2D':
|
|
1003
|
-
#check rotation
|
|
1004
|
-
if not array_equal(
|
|
1005
|
-
xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(
|
|
1006
|
-
xInterp = ((xInterpHelp + pi
|
|
1075
|
+
elif self.array_dimension == '2D':
|
|
1076
|
+
# check rotation
|
|
1077
|
+
if not array_equal(phi_delay, []):
|
|
1078
|
+
xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
|
|
1079
|
+
xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
|
|
1007
1080
|
else:
|
|
1008
|
-
xInterp = repmat(virtNewCoord[0, :], nTime, 1)
|
|
1009
|
-
|
|
1010
|
-
mesh = meshList[0][0]
|
|
1011
|
-
for cntTime in range(nTime):
|
|
1081
|
+
xInterp = repmat(virtNewCoord[0, :], nTime, 1)
|
|
1012
1082
|
|
|
1083
|
+
mesh = meshList[0][0]
|
|
1084
|
+
for cntTime in range(nTime):
|
|
1013
1085
|
# points for interpolation
|
|
1014
|
-
newPoint = concatenate((xInterp[cntTime, :][:, newaxis], virtNewCoord[1, :][:, newaxis]), axis=1)
|
|
1015
|
-
#scipy 1D interpolation
|
|
1086
|
+
newPoint = concatenate((xInterp[cntTime, :][:, newaxis], virtNewCoord[1, :][:, newaxis]), axis=1)
|
|
1087
|
+
# scipy 1D interpolation
|
|
1016
1088
|
if self.method == 'linear':
|
|
1017
|
-
interpolater = LinearNDInterpolator(mesh, pHelp[cntTime, :], fill_value
|
|
1018
|
-
pInterp[cntTime] = interpolater(newPoint)
|
|
1019
|
-
|
|
1089
|
+
interpolater = LinearNDInterpolator(mesh, pHelp[cntTime, :], fill_value=0)
|
|
1090
|
+
pInterp[cntTime] = interpolater(newPoint)
|
|
1091
|
+
|
|
1020
1092
|
elif self.method == 'spline':
|
|
1021
1093
|
# scipy CloughTocher interpolation
|
|
1022
|
-
f = CloughTocher2DInterpolator(mesh, pHelp[cntTime, :], fill_value
|
|
1023
|
-
pInterp[cntTime] = f(newPoint)
|
|
1024
|
-
|
|
1094
|
+
f = CloughTocher2DInterpolator(mesh, pHelp[cntTime, :], fill_value=0)
|
|
1095
|
+
pInterp[cntTime] = f(newPoint)
|
|
1096
|
+
|
|
1025
1097
|
elif self.method == 'sinc':
|
|
1026
|
-
#compute using 3-D Rbfs for sinc
|
|
1027
|
-
rbfi = Rbf(
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1098
|
+
# compute using 3-D Rbfs for sinc
|
|
1099
|
+
rbfi = Rbf(
|
|
1100
|
+
newCoord[0],
|
|
1101
|
+
newCoord[1],
|
|
1102
|
+
newCoord[2],
|
|
1103
|
+
pHelp[cntTime, : len(newCoord[0])],
|
|
1104
|
+
function=self.sinc_mic,
|
|
1105
|
+
) # radial basis function interpolator instance
|
|
1106
|
+
|
|
1107
|
+
pInterp[cntTime] = rbfi(xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2])
|
|
1108
|
+
|
|
1037
1109
|
elif self.method == 'rbf-cubic':
|
|
1038
|
-
#compute using 3-D Rbfs
|
|
1039
|
-
rbfi = Rbf(
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1110
|
+
# compute using 3-D Rbfs
|
|
1111
|
+
rbfi = Rbf(
|
|
1112
|
+
newCoord[0],
|
|
1113
|
+
newCoord[1],
|
|
1114
|
+
newCoord[2],
|
|
1115
|
+
pHelp[cntTime, : len(newCoord[0])],
|
|
1116
|
+
function='cubic',
|
|
1117
|
+
) # radial basis function interpolator instance
|
|
1118
|
+
|
|
1119
|
+
virtshiftcoord = array([xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2]])
|
|
1120
|
+
pInterp[cntTime] = rbfi(virtshiftcoord[0], virtshiftcoord[1], virtshiftcoord[2])
|
|
1121
|
+
|
|
1049
1122
|
elif self.method == 'rbf-multiquadric':
|
|
1050
|
-
#compute using 3-D Rbfs
|
|
1051
|
-
rbfi = Rbf(
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1123
|
+
# compute using 3-D Rbfs
|
|
1124
|
+
rbfi = Rbf(
|
|
1125
|
+
newCoord[0],
|
|
1126
|
+
newCoord[1],
|
|
1127
|
+
newCoord[2],
|
|
1128
|
+
pHelp[cntTime, : len(newCoord[0])],
|
|
1129
|
+
function='multiquadric',
|
|
1130
|
+
) # radial basis function interpolator instance
|
|
1131
|
+
|
|
1132
|
+
virtshiftcoord = array([xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2]])
|
|
1133
|
+
pInterp[cntTime] = rbfi(virtshiftcoord[0], virtshiftcoord[1], virtshiftcoord[2])
|
|
1060
1134
|
# using inverse distance weighting
|
|
1061
|
-
elif self.method=='IDW':
|
|
1135
|
+
elif self.method == 'IDW':
|
|
1062
1136
|
newPoint2_M = newPoint.T
|
|
1063
|
-
newPoint3_M = append(newPoint2_M,zeros([1,self.numchannels]),axis=0)
|
|
1137
|
+
newPoint3_M = append(newPoint2_M, zeros([1, self.numchannels]), axis=0)
|
|
1064
1138
|
newPointCart = cylToCart(newPoint3_M)
|
|
1065
|
-
for ind in arange(len(newPoint[:,0])):
|
|
1066
|
-
newPoint_Rep = repmat(newPointCart[:,ind], len(newPoint[:,0]),1).T
|
|
1139
|
+
for ind in arange(len(newPoint[:, 0])):
|
|
1140
|
+
newPoint_Rep = repmat(newPointCart[:, ind], len(newPoint[:, 0]), 1).T
|
|
1067
1141
|
subtract = newPoint_Rep - newCoordCart
|
|
1068
|
-
normDistance = norm(subtract,axis=0)
|
|
1069
|
-
index_norm = argsort(normDistance)[:self.num_IDW]
|
|
1070
|
-
pHelpNew = pHelp[cntTime,index_norm]
|
|
1142
|
+
normDistance = norm(subtract, axis=0)
|
|
1143
|
+
index_norm = argsort(normDistance)[: self.num_IDW]
|
|
1144
|
+
pHelpNew = pHelp[cntTime, index_norm]
|
|
1071
1145
|
normNew = normDistance[index_norm]
|
|
1072
1146
|
if normNew[0] < 1e-3:
|
|
1073
|
-
pInterp[cntTime,ind] = pHelpNew[0]
|
|
1147
|
+
pInterp[cntTime, ind] = pHelpNew[0]
|
|
1074
1148
|
else:
|
|
1075
|
-
wholeD = sum(
|
|
1076
|
-
weight = (1 / normNew
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
|
|
1086
|
-
xInterp = ((xInterpHelp + pi ) % (2 * pi)) - pi #shifting phi cootrdinate into feasible area [-pi, pi]
|
|
1149
|
+
wholeD = sum(1 / normNew**self.p_weight)
|
|
1150
|
+
weight = (1 / normNew**self.p_weight) / wholeD
|
|
1151
|
+
pInterp[cntTime, ind] = sum(pHelpNew * weight)
|
|
1152
|
+
|
|
1153
|
+
# Interpolation for arbitrary 3D Arrays
|
|
1154
|
+
elif self.array_dimension == '3D':
|
|
1155
|
+
# check rotation
|
|
1156
|
+
if not array_equal(phi_delay, []):
|
|
1157
|
+
xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phi_delay, virtNewCoord.shape[1], 1).T
|
|
1158
|
+
xInterp = ((xInterpHelp + pi) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
|
|
1087
1159
|
else:
|
|
1088
|
-
xInterp = repmat(virtNewCoord[0, :], nTime, 1)
|
|
1089
|
-
|
|
1160
|
+
xInterp = repmat(virtNewCoord[0, :], nTime, 1)
|
|
1161
|
+
|
|
1090
1162
|
mesh = meshList[0][0]
|
|
1091
1163
|
for cntTime in range(nTime):
|
|
1092
1164
|
# points for interpolation
|
|
1093
1165
|
newPoint = concatenate((xInterp[cntTime, :][:, newaxis], virtNewCoord[1:, :].T), axis=1)
|
|
1094
|
-
|
|
1095
|
-
if self.method == 'linear':
|
|
1096
|
-
interpolater = LinearNDInterpolator(mesh, pHelp[cntTime, :], fill_value
|
|
1166
|
+
|
|
1167
|
+
if self.method == 'linear':
|
|
1168
|
+
interpolater = LinearNDInterpolator(mesh, pHelp[cntTime, :], fill_value=0)
|
|
1097
1169
|
pInterp[cntTime] = interpolater(newPoint)
|
|
1098
|
-
|
|
1170
|
+
|
|
1099
1171
|
elif self.method == 'sinc':
|
|
1100
|
-
#compute using 3-D Rbfs for sinc
|
|
1101
|
-
rbfi = Rbf(
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1172
|
+
# compute using 3-D Rbfs for sinc
|
|
1173
|
+
rbfi = Rbf(
|
|
1174
|
+
newCoord[0],
|
|
1175
|
+
newCoord[1],
|
|
1176
|
+
newCoord[2],
|
|
1177
|
+
pHelp[cntTime, : len(newCoord[0])],
|
|
1178
|
+
function=self.sinc_mic,
|
|
1179
|
+
) # radial basis function interpolator instance
|
|
1180
|
+
|
|
1181
|
+
pInterp[cntTime] = rbfi(xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2])
|
|
1182
|
+
|
|
1110
1183
|
elif self.method == 'rbf-cubic':
|
|
1111
|
-
#compute using 3-D Rbfs
|
|
1112
|
-
rbfi = Rbf(
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1184
|
+
# compute using 3-D Rbfs
|
|
1185
|
+
rbfi = Rbf(
|
|
1186
|
+
newCoord[0],
|
|
1187
|
+
newCoord[1],
|
|
1188
|
+
newCoord[2],
|
|
1189
|
+
pHelp[cntTime, : len(newCoord[0])],
|
|
1190
|
+
function='cubic',
|
|
1191
|
+
) # radial basis function interpolator instance
|
|
1192
|
+
|
|
1193
|
+
pInterp[cntTime] = rbfi(xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2])
|
|
1194
|
+
|
|
1121
1195
|
elif self.method == 'rbf-multiquadric':
|
|
1122
|
-
#compute using 3-D Rbfs
|
|
1123
|
-
rbfi = Rbf(
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
#return interpolated pressure values
|
|
1196
|
+
# compute using 3-D Rbfs
|
|
1197
|
+
rbfi = Rbf(
|
|
1198
|
+
newCoord[0],
|
|
1199
|
+
newCoord[1],
|
|
1200
|
+
newCoord[2],
|
|
1201
|
+
pHelp[cntTime, : len(newCoord[0])],
|
|
1202
|
+
function='multiquadric',
|
|
1203
|
+
) # radial basis function interpolator instance
|
|
1204
|
+
|
|
1205
|
+
pInterp[cntTime] = rbfi(xInterp[cntTime, :], virtNewCoord[1], virtNewCoord[2])
|
|
1206
|
+
|
|
1207
|
+
# return interpolated pressure values
|
|
1134
1208
|
return pInterp
|
|
1135
1209
|
|
|
1136
|
-
|
|
1210
|
+
|
|
1137
1211
|
class SpatialInterpolatorRotation(SpatialInterpolator):
|
|
1212
|
+
"""Spatial Interpolation for rotating sources. Gets samples from :attr:`source`
|
|
1213
|
+
and angles from :attr:`AngleTracker`.Generates output via the generator :meth:`result`.
|
|
1214
|
+
|
|
1138
1215
|
"""
|
|
1139
|
-
|
|
1140
|
-
and angles from :attr:`AngleTracker`.Generates output via the generator :meth:`result`
|
|
1141
|
-
|
|
1142
|
-
"""
|
|
1216
|
+
|
|
1143
1217
|
#: Angle data from AngleTracker class
|
|
1144
1218
|
angle_source = Instance(AngleTracker)
|
|
1145
|
-
|
|
1219
|
+
|
|
1146
1220
|
#: Internal identifier
|
|
1147
|
-
digest = Property(
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1221
|
+
digest = Property(
|
|
1222
|
+
depends_on=[
|
|
1223
|
+
'source.digest',
|
|
1224
|
+
'angle_source.digest',
|
|
1225
|
+
'mics.digest',
|
|
1226
|
+
'mics_virtual.digest',
|
|
1227
|
+
'method',
|
|
1228
|
+
'array_dimension',
|
|
1229
|
+
'Q',
|
|
1230
|
+
'interp_at_zero',
|
|
1231
|
+
],
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1151
1234
|
@cached_property
|
|
1152
|
-
def _get_digest(
|
|
1153
|
-
return digest(self)
|
|
1154
|
-
|
|
1235
|
+
def _get_digest(self):
|
|
1236
|
+
return digest(self)
|
|
1237
|
+
|
|
1155
1238
|
def result(self, num=128):
|
|
1156
|
-
"""
|
|
1157
|
-
|
|
1158
|
-
|
|
1239
|
+
"""Python generator that yields the output block-wise.
|
|
1240
|
+
|
|
1159
1241
|
Parameters
|
|
1160
1242
|
----------
|
|
1161
1243
|
num : integer
|
|
1162
1244
|
This parameter defines the size of the blocks to be yielded
|
|
1163
1245
|
(i.e. the number of samples per block).
|
|
1164
|
-
|
|
1246
|
+
|
|
1165
1247
|
Returns
|
|
1166
1248
|
-------
|
|
1167
|
-
Samples in blocks of shape (num, :attr:`numchannels`).
|
|
1249
|
+
Samples in blocks of shape (num, :attr:`numchannels`).
|
|
1168
1250
|
The last block may be shorter than num.
|
|
1251
|
+
|
|
1169
1252
|
"""
|
|
1170
|
-
#period for rotation
|
|
1253
|
+
# period for rotation
|
|
1171
1254
|
period = 2 * pi
|
|
1172
|
-
#get angle
|
|
1173
|
-
angle = self.angle_source.
|
|
1174
|
-
#counter to track angle position in time for each block
|
|
1175
|
-
count=0
|
|
1255
|
+
# get angle
|
|
1256
|
+
angle = self.angle_source.angle()
|
|
1257
|
+
# counter to track angle position in time for each block
|
|
1258
|
+
count = 0
|
|
1176
1259
|
for timeData in self.source.result(num):
|
|
1177
|
-
|
|
1178
|
-
interpVal = self._result_core_func(timeData,
|
|
1260
|
+
phi_delay = angle[count : count + num]
|
|
1261
|
+
interpVal = self._result_core_func(timeData, phi_delay, period, self.Q, interp_at_zero=False)
|
|
1179
1262
|
yield interpVal
|
|
1180
|
-
count += num
|
|
1263
|
+
count += num
|
|
1264
|
+
|
|
1181
1265
|
|
|
1182
1266
|
class SpatialInterpolatorConstantRotation(SpatialInterpolator):
|
|
1267
|
+
"""Spatial linear Interpolation for constantly rotating sources.
|
|
1268
|
+
Gets samples from :attr:`source` and generates output via the
|
|
1269
|
+
generator :meth:`result`.
|
|
1183
1270
|
"""
|
|
1184
|
-
|
|
1185
|
-
Gets samples from :attr:`source` and generates output via the
|
|
1186
|
-
generator :meth:`result`
|
|
1187
|
-
"""
|
|
1271
|
+
|
|
1188
1272
|
#: Rotational speed in rps. Positive, if rotation is around positive z-axis sense,
|
|
1189
1273
|
#: which means from x to y axis.
|
|
1190
1274
|
rotational_speed = Float(0.0)
|
|
1191
|
-
|
|
1275
|
+
|
|
1192
1276
|
# internal identifier
|
|
1193
|
-
digest = Property(
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1277
|
+
digest = Property(
|
|
1278
|
+
depends_on=[
|
|
1279
|
+
'source.digest',
|
|
1280
|
+
'mics.digest',
|
|
1281
|
+
'mics_virtual.digest',
|
|
1282
|
+
'method',
|
|
1283
|
+
'array_dimension',
|
|
1284
|
+
'Q',
|
|
1285
|
+
'interp_at_zero',
|
|
1286
|
+
'rotational_speed',
|
|
1287
|
+
],
|
|
1288
|
+
)
|
|
1289
|
+
|
|
1197
1290
|
@cached_property
|
|
1198
|
-
def _get_digest(
|
|
1291
|
+
def _get_digest(self):
|
|
1199
1292
|
return digest(self)
|
|
1200
|
-
|
|
1201
|
-
|
|
1293
|
+
|
|
1202
1294
|
def result(self, num=1):
|
|
1203
|
-
"""
|
|
1204
|
-
|
|
1205
|
-
|
|
1295
|
+
"""Python generator that yields the output block-wise.
|
|
1296
|
+
|
|
1206
1297
|
Parameters
|
|
1207
1298
|
----------
|
|
1208
1299
|
num : integer
|
|
1209
1300
|
This parameter defines the size of the blocks to be yielded
|
|
1210
1301
|
(i.e. the number of samples per block).
|
|
1211
|
-
|
|
1302
|
+
|
|
1212
1303
|
Returns
|
|
1213
1304
|
-------
|
|
1214
|
-
Samples in blocks of shape (num, :attr:`numchannels`).
|
|
1305
|
+
Samples in blocks of shape (num, :attr:`numchannels`).
|
|
1215
1306
|
The last block may be shorter than num.
|
|
1307
|
+
|
|
1216
1308
|
"""
|
|
1217
1309
|
omega = 2 * pi * self.rotational_speed
|
|
1218
1310
|
period = 2 * pi
|
|
1219
1311
|
phiOffset = 0.0
|
|
1220
1312
|
for timeData in self.source.result(num):
|
|
1221
1313
|
nTime = timeData.shape[0]
|
|
1222
|
-
|
|
1223
|
-
interpVal = self._result_core_func(timeData,
|
|
1224
|
-
phiOffset =
|
|
1225
|
-
yield interpVal
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
class Mixer(
|
|
1229
|
-
"""
|
|
1230
|
-
Mixes the signals from several sources.
|
|
1231
|
-
"""
|
|
1314
|
+
phi_delay = phiOffset + linspace(0, nTime / self.sample_freq * omega, nTime, endpoint=False)
|
|
1315
|
+
interpVal = self._result_core_func(timeData, phi_delay, period, self.Q, interp_at_zero=False)
|
|
1316
|
+
phiOffset = phi_delay[-1] + omega / self.sample_freq
|
|
1317
|
+
yield interpVal
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
class Mixer(TimeInOut):
|
|
1321
|
+
"""Mixes the signals from several sources."""
|
|
1232
1322
|
|
|
1233
1323
|
#: Data source; :class:`~acoular.tprocess.SamplesGenerator` object.
|
|
1234
1324
|
source = Trait(SamplesGenerator)
|
|
1235
1325
|
|
|
1236
1326
|
#: List of additional :class:`~acoular.tprocess.SamplesGenerator` objects
|
|
1237
1327
|
#: to be mixed.
|
|
1238
|
-
sources = List(
|
|
1328
|
+
sources = List(Instance(SamplesGenerator, ()))
|
|
1239
1329
|
|
|
1240
1330
|
#: Sampling frequency of the signal as given by :attr:`source`.
|
|
1241
1331
|
sample_freq = Delegate('source')
|
|
1242
|
-
|
|
1332
|
+
|
|
1243
1333
|
#: Number of channels in output as given by :attr:`source`.
|
|
1244
1334
|
numchannels = Delegate('source')
|
|
1245
|
-
|
|
1335
|
+
|
|
1246
1336
|
#: Number of samples in output as given by :attr:`source`.
|
|
1247
1337
|
numsamples = Delegate('source')
|
|
1248
1338
|
|
|
1249
|
-
# internal identifier
|
|
1339
|
+
# internal identifier
|
|
1250
1340
|
sdigest = Str()
|
|
1251
1341
|
|
|
1252
1342
|
@observe('sources.items.digest')
|
|
1253
|
-
def _set_sources_digest(
|
|
1254
|
-
self.sdigest = ldigest(self.sources)
|
|
1255
|
-
|
|
1343
|
+
def _set_sources_digest(self, event): # noqa ARG002
|
|
1344
|
+
self.sdigest = ldigest(self.sources)
|
|
1345
|
+
|
|
1256
1346
|
# internal identifier
|
|
1257
|
-
digest = Property(
|
|
1347
|
+
digest = Property(depends_on=['source.digest', 'sdigest'])
|
|
1258
1348
|
|
|
1259
1349
|
@cached_property
|
|
1260
|
-
def _get_digest(
|
|
1350
|
+
def _get_digest(self):
|
|
1261
1351
|
return digest(self)
|
|
1262
1352
|
|
|
1263
|
-
def validate_sources(
|
|
1264
|
-
"""
|
|
1353
|
+
def validate_sources(self):
|
|
1354
|
+
"""Validates if sources fit together."""
|
|
1265
1355
|
if self.source:
|
|
1266
1356
|
for s in self.sources:
|
|
1267
1357
|
if self.sample_freq != s.sample_freq:
|
|
1268
|
-
raise ValueError(
|
|
1358
|
+
raise ValueError('Sample frequency of %s does not fit' % s)
|
|
1269
1359
|
if self.numchannels != s.numchannels:
|
|
1270
|
-
raise ValueError(
|
|
1360
|
+
raise ValueError('Channel count of %s does not fit' % s)
|
|
1271
1361
|
|
|
1272
1362
|
def result(self, num):
|
|
1273
|
-
"""
|
|
1274
|
-
|
|
1275
|
-
The output from the source and those in the list
|
|
1363
|
+
"""Python generator that yields the output block-wise.
|
|
1364
|
+
The output from the source and those in the list
|
|
1276
1365
|
sources are being added.
|
|
1277
|
-
|
|
1366
|
+
|
|
1278
1367
|
Parameters
|
|
1279
1368
|
----------
|
|
1280
1369
|
num : integer
|
|
1281
1370
|
This parameter defines the size of the blocks to be yielded
|
|
1282
1371
|
(i.e. the number of samples per block).
|
|
1283
|
-
|
|
1372
|
+
|
|
1284
1373
|
Returns
|
|
1285
1374
|
-------
|
|
1286
|
-
Samples in blocks of shape (num, numchannels).
|
|
1375
|
+
Samples in blocks of shape (num, numchannels).
|
|
1287
1376
|
The last block may be shorter than num.
|
|
1377
|
+
|
|
1288
1378
|
"""
|
|
1289
|
-
|
|
1290
1379
|
# check whether all sources fit together
|
|
1291
1380
|
self.validate_sources()
|
|
1292
|
-
|
|
1381
|
+
|
|
1293
1382
|
gens = [i.result(num) for i in self.sources]
|
|
1294
1383
|
for temp in self.source.result(num):
|
|
1295
1384
|
sh = temp.shape[0]
|
|
@@ -1299,297 +1388,293 @@ class Mixer( TimeInOut ):
|
|
|
1299
1388
|
except StopIteration:
|
|
1300
1389
|
return
|
|
1301
1390
|
if temp.shape[0] > temp1.shape[0]:
|
|
1302
|
-
temp = temp[:temp1.shape[0]]
|
|
1303
|
-
temp += temp1[:temp.shape[0]]
|
|
1391
|
+
temp = temp[: temp1.shape[0]]
|
|
1392
|
+
temp += temp1[: temp.shape[0]]
|
|
1304
1393
|
yield temp
|
|
1305
1394
|
if sh > temp.shape[0]:
|
|
1306
1395
|
break
|
|
1307
1396
|
|
|
1308
1397
|
|
|
1309
|
-
class TimePower(
|
|
1310
|
-
"""
|
|
1311
|
-
Calculates time-depended power of the signal.
|
|
1312
|
-
"""
|
|
1398
|
+
class TimePower(TimeInOut):
|
|
1399
|
+
"""Calculates time-depended power of the signal."""
|
|
1313
1400
|
|
|
1314
1401
|
def result(self, num):
|
|
1315
|
-
"""
|
|
1316
|
-
|
|
1317
|
-
|
|
1402
|
+
"""Python generator that yields the output block-wise.
|
|
1403
|
+
|
|
1318
1404
|
Parameters
|
|
1319
1405
|
----------
|
|
1320
1406
|
num : integer
|
|
1321
1407
|
This parameter defines the size of the blocks to be yielded
|
|
1322
1408
|
(i.e. the number of samples per block).
|
|
1323
|
-
|
|
1409
|
+
|
|
1324
1410
|
Returns
|
|
1325
1411
|
-------
|
|
1326
|
-
Squared output of source.
|
|
1327
|
-
Yields samples in blocks of shape (num, numchannels).
|
|
1412
|
+
Squared output of source.
|
|
1413
|
+
Yields samples in blocks of shape (num, numchannels).
|
|
1328
1414
|
The last block may be shorter than num.
|
|
1415
|
+
|
|
1329
1416
|
"""
|
|
1330
1417
|
for temp in self.source.result(num):
|
|
1331
|
-
yield temp*temp
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
Calculates time-dependent average of the signal
|
|
1336
|
-
|
|
1418
|
+
yield temp * temp
|
|
1419
|
+
|
|
1420
|
+
|
|
1421
|
+
class TimeAverage(TimeInOut):
|
|
1422
|
+
"""Calculates time-dependent average of the signal."""
|
|
1423
|
+
|
|
1337
1424
|
#: Number of samples to average over, defaults to 64.
|
|
1338
|
-
naverage = Int(64,
|
|
1339
|
-
|
|
1340
|
-
|
|
1425
|
+
naverage = Int(64, desc='number of samples to average over')
|
|
1426
|
+
|
|
1341
1427
|
#: Sampling frequency of the output signal, is set automatically.
|
|
1342
|
-
sample_freq = Property(
|
|
1343
|
-
|
|
1428
|
+
sample_freq = Property(depends_on='source.sample_freq, naverage')
|
|
1429
|
+
|
|
1344
1430
|
#: Number of samples of the output signal, is set automatically.
|
|
1345
|
-
numsamples = Property(
|
|
1346
|
-
|
|
1431
|
+
numsamples = Property(depends_on='source.numsamples, naverage')
|
|
1432
|
+
|
|
1347
1433
|
# internal identifier
|
|
1348
|
-
digest = Property(
|
|
1434
|
+
digest = Property(depends_on=['source.digest', '__class__', 'naverage'])
|
|
1349
1435
|
|
|
1350
1436
|
@cached_property
|
|
1351
|
-
def _get_digest(
|
|
1437
|
+
def _get_digest(self):
|
|
1352
1438
|
return digest(self)
|
|
1353
|
-
|
|
1439
|
+
|
|
1354
1440
|
@cached_property
|
|
1355
|
-
def _get_sample_freq
|
|
1441
|
+
def _get_sample_freq(self):
|
|
1356
1442
|
if self.source:
|
|
1357
1443
|
return 1.0 * self.source.sample_freq / self.naverage
|
|
1444
|
+
return None
|
|
1358
1445
|
|
|
1359
1446
|
@cached_property
|
|
1360
|
-
def _get_numsamples
|
|
1447
|
+
def _get_numsamples(self):
|
|
1361
1448
|
if self.source:
|
|
1362
1449
|
return self.source.numsamples / self.naverage
|
|
1450
|
+
return None
|
|
1363
1451
|
|
|
1364
1452
|
def result(self, num):
|
|
1365
|
-
"""
|
|
1366
|
-
Python generator that yields the output block-wise.
|
|
1453
|
+
"""Python generator that yields the output block-wise.
|
|
1367
1454
|
|
|
1368
|
-
|
|
1369
1455
|
Parameters
|
|
1370
1456
|
----------
|
|
1371
1457
|
num : integer
|
|
1372
1458
|
This parameter defines the size of the blocks to be yielded
|
|
1373
1459
|
(i.e. the number of samples per block).
|
|
1374
|
-
|
|
1460
|
+
|
|
1375
1461
|
Returns
|
|
1376
1462
|
-------
|
|
1377
|
-
Average of the output of source.
|
|
1378
|
-
Yields samples in blocks of shape (num, numchannels).
|
|
1463
|
+
Average of the output of source.
|
|
1464
|
+
Yields samples in blocks of shape (num, numchannels).
|
|
1379
1465
|
The last block may be shorter than num.
|
|
1466
|
+
|
|
1380
1467
|
"""
|
|
1381
1468
|
nav = self.naverage
|
|
1382
|
-
for temp in self.source.result(num*nav):
|
|
1469
|
+
for temp in self.source.result(num * nav):
|
|
1383
1470
|
ns, nc = temp.shape
|
|
1384
|
-
nso = int(ns/nav)
|
|
1471
|
+
nso = int(ns / nav)
|
|
1385
1472
|
if nso > 0:
|
|
1386
|
-
yield temp[:nso*nav].reshape((nso, -1, nc)).mean(axis=1)
|
|
1473
|
+
yield temp[: nso * nav].reshape((nso, -1, nc)).mean(axis=1)
|
|
1474
|
+
|
|
1475
|
+
|
|
1476
|
+
class TimeCumAverage(TimeInOut):
|
|
1477
|
+
"""Calculates cumulative average of the signal, useful for Leq."""
|
|
1387
1478
|
|
|
1388
|
-
class TimeCumAverage( TimeInOut):
|
|
1389
|
-
"""
|
|
1390
|
-
Calculates cumulative average of the signal, useful for Leq
|
|
1391
|
-
"""
|
|
1392
1479
|
def result(self, num):
|
|
1393
|
-
"""
|
|
1394
|
-
Python generator that yields the output block-wise.
|
|
1480
|
+
"""Python generator that yields the output block-wise.
|
|
1395
1481
|
|
|
1396
|
-
|
|
1397
1482
|
Parameters
|
|
1398
1483
|
----------
|
|
1399
1484
|
num : integer
|
|
1400
1485
|
This parameter defines the size of the blocks to be yielded
|
|
1401
1486
|
(i.e. the number of samples per block).
|
|
1402
|
-
|
|
1487
|
+
|
|
1403
1488
|
Returns
|
|
1404
1489
|
-------
|
|
1405
|
-
Cumulative average of the output of source.
|
|
1406
|
-
Yields samples in blocks of shape (num, numchannels).
|
|
1490
|
+
Cumulative average of the output of source.
|
|
1491
|
+
Yields samples in blocks of shape (num, numchannels).
|
|
1407
1492
|
The last block may be shorter than num.
|
|
1493
|
+
|
|
1408
1494
|
"""
|
|
1409
|
-
count = (arange(num) + 1)[:,newaxis]
|
|
1410
|
-
for i,temp in enumerate(self.source.result(num)):
|
|
1495
|
+
count = (arange(num) + 1)[:, newaxis]
|
|
1496
|
+
for i, temp in enumerate(self.source.result(num)):
|
|
1411
1497
|
ns, nc = temp.shape
|
|
1412
1498
|
if not i:
|
|
1413
|
-
accu = zeros((1,nc))
|
|
1414
|
-
temp = (accu*(count[0]-1) + cumsum(temp,axis=0))/count[:ns]
|
|
1499
|
+
accu = zeros((1, nc))
|
|
1500
|
+
temp = (accu * (count[0] - 1) + cumsum(temp, axis=0)) / count[:ns]
|
|
1415
1501
|
accu = temp[-1]
|
|
1416
1502
|
count += ns
|
|
1417
1503
|
yield temp
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
Calculates the time-reversed signal of a source.
|
|
1422
|
-
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
class TimeReverse(TimeInOut):
|
|
1507
|
+
"""Calculates the time-reversed signal of a source."""
|
|
1508
|
+
|
|
1423
1509
|
def result(self, num):
|
|
1424
|
-
"""
|
|
1425
|
-
Python generator that yields the output block-wise.
|
|
1510
|
+
"""Python generator that yields the output block-wise.
|
|
1426
1511
|
|
|
1427
|
-
|
|
1428
1512
|
Parameters
|
|
1429
1513
|
----------
|
|
1430
1514
|
num : integer
|
|
1431
1515
|
This parameter defines the size of the blocks to be yielded
|
|
1432
1516
|
(i.e. the number of samples per block).
|
|
1433
|
-
|
|
1517
|
+
|
|
1434
1518
|
Returns
|
|
1435
1519
|
-------
|
|
1436
|
-
Yields samples in blocks of shape (num, numchannels).
|
|
1437
|
-
Time-reversed output of source.
|
|
1520
|
+
Yields samples in blocks of shape (num, numchannels).
|
|
1521
|
+
Time-reversed output of source.
|
|
1438
1522
|
The last block may be shorter than num.
|
|
1523
|
+
|
|
1439
1524
|
"""
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
temp = empty_like(
|
|
1443
|
-
h =
|
|
1525
|
+
result_list = []
|
|
1526
|
+
result_list.extend(self.source.result(num))
|
|
1527
|
+
temp = empty_like(result_list[0])
|
|
1528
|
+
h = result_list.pop()
|
|
1444
1529
|
nsh = h.shape[0]
|
|
1445
1530
|
temp[:nsh] = h[::-1]
|
|
1446
|
-
for h in
|
|
1447
|
-
temp[nsh:] = h[:nsh-1
|
|
1531
|
+
for h in result_list[::-1]:
|
|
1532
|
+
temp[nsh:] = h[: nsh - 1 : -1]
|
|
1448
1533
|
yield temp
|
|
1449
|
-
temp[:nsh] = h[nsh-1
|
|
1534
|
+
temp[:nsh] = h[nsh - 1 :: -1]
|
|
1450
1535
|
yield temp[:nsh]
|
|
1451
|
-
|
|
1536
|
+
|
|
1537
|
+
|
|
1452
1538
|
class Filter(TimeInOut):
|
|
1453
|
-
"""
|
|
1454
|
-
Abstract base class for IIR filters based on scipy lfilter
|
|
1539
|
+
"""Abstract base class for IIR filters based on scipy lfilter
|
|
1455
1540
|
implements a filter with coefficients that may be changed
|
|
1456
|
-
during processing
|
|
1457
|
-
|
|
1541
|
+
during processing.
|
|
1542
|
+
|
|
1458
1543
|
Should not be instanciated by itself
|
|
1459
1544
|
"""
|
|
1545
|
+
|
|
1460
1546
|
#: Filter coefficients
|
|
1461
1547
|
sos = Property()
|
|
1462
1548
|
|
|
1463
|
-
def _get_sos(
|
|
1464
|
-
return tf2sos([1],[1])
|
|
1549
|
+
def _get_sos(self):
|
|
1550
|
+
return tf2sos([1], [1])
|
|
1465
1551
|
|
|
1466
1552
|
def result(self, num):
|
|
1467
|
-
"""
|
|
1468
|
-
Python generator that yields the output block-wise.
|
|
1553
|
+
"""Python generator that yields the output block-wise.
|
|
1469
1554
|
|
|
1470
|
-
|
|
1471
1555
|
Parameters
|
|
1472
1556
|
----------
|
|
1473
1557
|
num : integer
|
|
1474
1558
|
This parameter defines the size of the blocks to be yielded
|
|
1475
1559
|
(i.e. the number of samples per block).
|
|
1476
|
-
|
|
1560
|
+
|
|
1477
1561
|
Returns
|
|
1478
1562
|
-------
|
|
1479
|
-
Samples in blocks of shape (num, numchannels).
|
|
1563
|
+
Samples in blocks of shape (num, numchannels).
|
|
1480
1564
|
Delivers the bandpass filtered output of source.
|
|
1481
1565
|
The last block may be shorter than num.
|
|
1566
|
+
|
|
1482
1567
|
"""
|
|
1483
1568
|
sos = self.sos
|
|
1484
1569
|
zi = zeros((sos.shape[0], 2, self.source.numchannels))
|
|
1485
1570
|
for block in self.source.result(num):
|
|
1486
|
-
sos = self.sos
|
|
1487
|
-
|
|
1571
|
+
sos = self.sos # this line is useful in case of changes
|
|
1572
|
+
# to self.sos during generator lifetime
|
|
1488
1573
|
block, zi = sosfilt(sos, block, axis=0, zi=zi)
|
|
1489
1574
|
yield block
|
|
1490
1575
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
Octave or third-octave filter (causal, non-zero phase delay).
|
|
1494
|
-
|
|
1576
|
+
|
|
1577
|
+
class FiltOctave(Filter):
|
|
1578
|
+
"""Octave or third-octave filter (causal, non-zero phase delay)."""
|
|
1579
|
+
|
|
1495
1580
|
#: Band center frequency; defaults to 1000.
|
|
1496
|
-
band = Float(1000.0,
|
|
1497
|
-
|
|
1498
|
-
|
|
1581
|
+
band = Float(1000.0, desc='band center frequency')
|
|
1582
|
+
|
|
1499
1583
|
#: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
|
|
1500
|
-
fraction = Trait('Octave', {'Octave':1, 'Third octave':3},
|
|
1501
|
-
desc = "fraction of octave")
|
|
1584
|
+
fraction = Trait('Octave', {'Octave': 1, 'Third octave': 3}, desc='fraction of octave')
|
|
1502
1585
|
|
|
1503
1586
|
#: Filter order
|
|
1504
|
-
order = Int(3, desc
|
|
1505
|
-
|
|
1506
|
-
sos = Property(
|
|
1587
|
+
order = Int(3, desc='IIR filter order')
|
|
1588
|
+
|
|
1589
|
+
sos = Property(depends_on=['band', 'fraction', 'source.digest', 'order'])
|
|
1507
1590
|
|
|
1508
1591
|
# internal identifier
|
|
1509
|
-
digest = Property(
|
|
1510
|
-
'band', 'fraction','order'])
|
|
1592
|
+
digest = Property(depends_on=['source.digest', '__class__', 'band', 'fraction', 'order'])
|
|
1511
1593
|
|
|
1512
1594
|
@cached_property
|
|
1513
|
-
def _get_digest(
|
|
1595
|
+
def _get_digest(self):
|
|
1514
1596
|
return digest(self)
|
|
1515
|
-
|
|
1597
|
+
|
|
1516
1598
|
@cached_property
|
|
1517
|
-
def _get_sos(
|
|
1599
|
+
def _get_sos(self):
|
|
1518
1600
|
# filter design
|
|
1519
1601
|
fs = self.sample_freq
|
|
1520
1602
|
# adjust filter edge frequencies for correct power bandwidth (see ANSI 1.11 1987
|
|
1521
1603
|
# and Kalb,J.T.: "A thirty channel real time audio analyzer and its applications",
|
|
1522
1604
|
# PhD Thesis: Georgia Inst. of Techn., 1975
|
|
1523
|
-
beta = pi/(2*self.order)
|
|
1524
|
-
alpha = pow(2.0, 1.0/(2.0*self.fraction_))
|
|
1525
|
-
beta = 2 * beta / sin(beta) / (alpha-1/alpha)
|
|
1526
|
-
alpha = (1+sqrt(1+beta*beta))/beta
|
|
1527
|
-
fr = 2*self.band/fs
|
|
1528
|
-
if fr > 1/sqrt(2):
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1605
|
+
beta = pi / (2 * self.order)
|
|
1606
|
+
alpha = pow(2.0, 1.0 / (2.0 * self.fraction_))
|
|
1607
|
+
beta = 2 * beta / sin(beta) / (alpha - 1 / alpha)
|
|
1608
|
+
alpha = (1 + sqrt(1 + beta * beta)) / beta
|
|
1609
|
+
fr = 2 * self.band / fs
|
|
1610
|
+
if fr > 1 / sqrt(2):
|
|
1611
|
+
msg = f'band frequency too high:{self.band:f},{fs:f}'
|
|
1612
|
+
raise ValueError(msg)
|
|
1613
|
+
om1 = fr / alpha
|
|
1614
|
+
om2 = fr * alpha
|
|
1615
|
+
return butter(self.order, [om1, om2], 'bandpass', output='sos')
|
|
1616
|
+
|
|
1617
|
+
|
|
1618
|
+
class FiltFiltOctave(FiltOctave):
|
|
1619
|
+
"""Octave or third-octave filter with zero phase delay.
|
|
1620
|
+
|
|
1538
1621
|
This filter can be applied on time signals.
|
|
1539
|
-
It requires large amounts of memory!
|
|
1622
|
+
It requires large amounts of memory!
|
|
1540
1623
|
"""
|
|
1624
|
+
|
|
1541
1625
|
#: Filter order (applied for forward filter and backward filter)
|
|
1542
|
-
order = Int(2, desc
|
|
1626
|
+
order = Int(2, desc='IIR filter half order')
|
|
1543
1627
|
|
|
1544
1628
|
# internal identifier
|
|
1545
|
-
digest = Property(
|
|
1546
|
-
'band', 'fraction','order'])
|
|
1629
|
+
digest = Property(depends_on=['source.digest', '__class__', 'band', 'fraction', 'order'])
|
|
1547
1630
|
|
|
1548
1631
|
@cached_property
|
|
1549
|
-
def _get_digest(
|
|
1632
|
+
def _get_digest(self):
|
|
1550
1633
|
return digest(self)
|
|
1551
|
-
|
|
1634
|
+
|
|
1552
1635
|
@cached_property
|
|
1553
|
-
def _get_sos(
|
|
1636
|
+
def _get_sos(self):
|
|
1554
1637
|
# filter design
|
|
1555
1638
|
fs = self.sample_freq
|
|
1556
1639
|
# adjust filter edge frequencies for correct power bandwidth (see FiltOctave)
|
|
1557
|
-
beta = pi/(2*self.order)
|
|
1558
|
-
alpha = pow(2.0, 1.0/(2.0*self.fraction_))
|
|
1559
|
-
beta = 2 * beta / sin(beta) / (alpha-1/alpha)
|
|
1560
|
-
alpha = (1+sqrt(1+beta*beta))/beta
|
|
1640
|
+
beta = pi / (2 * self.order)
|
|
1641
|
+
alpha = pow(2.0, 1.0 / (2.0 * self.fraction_))
|
|
1642
|
+
beta = 2 * beta / sin(beta) / (alpha - 1 / alpha)
|
|
1643
|
+
alpha = (1 + sqrt(1 + beta * beta)) / beta
|
|
1561
1644
|
# additional bandwidth correction for double-pass
|
|
1562
|
-
alpha = alpha * {6:1.01,5:1.012,4:1.016,3:1.022,2:1.036,1:1.083}.get(self.order,1.0)**(
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1645
|
+
alpha = alpha * {6: 1.01, 5: 1.012, 4: 1.016, 3: 1.022, 2: 1.036, 1: 1.083}.get(self.order, 1.0) ** (
|
|
1646
|
+
3 / self.fraction_
|
|
1647
|
+
)
|
|
1648
|
+
fr = 2 * self.band / fs
|
|
1649
|
+
if fr > 1 / sqrt(2):
|
|
1650
|
+
msg = f'band frequency too high:{self.band:f},{fs:f}'
|
|
1651
|
+
raise ValueError(msg)
|
|
1652
|
+
om1 = fr / alpha
|
|
1653
|
+
om2 = fr * alpha
|
|
1654
|
+
return butter(self.order, [om1, om2], 'bandpass', output='sos')
|
|
1655
|
+
|
|
1570
1656
|
def result(self, num):
|
|
1571
|
-
"""
|
|
1572
|
-
Python generator that yields the output block-wise.
|
|
1657
|
+
"""Python generator that yields the output block-wise.
|
|
1573
1658
|
|
|
1574
|
-
|
|
1575
1659
|
Parameters
|
|
1576
1660
|
----------
|
|
1577
1661
|
num : integer
|
|
1578
1662
|
This parameter defines the size of the blocks to be yielded
|
|
1579
1663
|
(i.e. the number of samples per block).
|
|
1580
|
-
|
|
1664
|
+
|
|
1581
1665
|
Returns
|
|
1582
1666
|
-------
|
|
1583
|
-
Samples in blocks of shape (num, numchannels).
|
|
1667
|
+
Samples in blocks of shape (num, numchannels).
|
|
1584
1668
|
Delivers the zero-phase bandpass filtered output of source.
|
|
1585
1669
|
The last block may be shorter than num.
|
|
1670
|
+
|
|
1586
1671
|
"""
|
|
1587
|
-
sos = self.sos
|
|
1672
|
+
sos = self.sos
|
|
1588
1673
|
data = empty((self.source.numsamples, self.source.numchannels))
|
|
1589
1674
|
j = 0
|
|
1590
1675
|
for block in self.source.result(num):
|
|
1591
1676
|
ns, nc = block.shape
|
|
1592
|
-
data[j:j+ns] = block
|
|
1677
|
+
data[j : j + ns] = block
|
|
1593
1678
|
j += ns
|
|
1594
1679
|
# filter one channel at a time to save memory
|
|
1595
1680
|
for j in range(self.source.numchannels):
|
|
@@ -1597,83 +1682,79 @@ class FiltFiltOctave( FiltOctave ):
|
|
|
1597
1682
|
j = 0
|
|
1598
1683
|
ns = data.shape[0]
|
|
1599
1684
|
while j < ns:
|
|
1600
|
-
yield data[j:j+num]
|
|
1685
|
+
yield data[j : j + num]
|
|
1601
1686
|
j += num
|
|
1602
1687
|
|
|
1688
|
+
|
|
1603
1689
|
class TimeExpAverage(Filter):
|
|
1604
|
-
"""
|
|
1605
|
-
Computes exponential averaging according to IEC 61672-1
|
|
1690
|
+
"""Computes exponential averaging according to IEC 61672-1
|
|
1606
1691
|
time constant: F -> 125 ms, S -> 1 s
|
|
1607
|
-
I (non-standard) -> 35 ms
|
|
1692
|
+
I (non-standard) -> 35 ms.
|
|
1608
1693
|
"""
|
|
1609
1694
|
|
|
1610
1695
|
#: time weighting
|
|
1611
|
-
weight = Trait('F', {'F':0.125, 'S':1.0, 'I':0.035},
|
|
1612
|
-
|
|
1696
|
+
weight = Trait('F', {'F': 0.125, 'S': 1.0, 'I': 0.035}, desc='time weighting')
|
|
1697
|
+
|
|
1698
|
+
sos = Property(depends_on=['weight', 'source.digest'])
|
|
1613
1699
|
|
|
1614
|
-
sos = Property( depends_on = ['weight', 'source.digest'])
|
|
1615
|
-
|
|
1616
1700
|
# internal identifier
|
|
1617
|
-
digest = Property(
|
|
1618
|
-
'weight'])
|
|
1701
|
+
digest = Property(depends_on=['source.digest', '__class__', 'weight'])
|
|
1619
1702
|
|
|
1620
1703
|
@cached_property
|
|
1621
|
-
def _get_digest(
|
|
1704
|
+
def _get_digest(self):
|
|
1622
1705
|
return digest(self)
|
|
1623
|
-
|
|
1706
|
+
|
|
1624
1707
|
@cached_property
|
|
1625
|
-
def _get_sos(
|
|
1626
|
-
alpha = 1-exp(-1/self.weight_/self.sample_freq)
|
|
1627
|
-
a = [1, alpha-1]
|
|
1708
|
+
def _get_sos(self):
|
|
1709
|
+
alpha = 1 - exp(-1 / self.weight_ / self.sample_freq)
|
|
1710
|
+
a = [1, alpha - 1]
|
|
1628
1711
|
b = [alpha]
|
|
1629
|
-
return tf2sos(b,a)
|
|
1712
|
+
return tf2sos(b, a)
|
|
1713
|
+
|
|
1714
|
+
|
|
1715
|
+
class FiltFreqWeight(Filter):
|
|
1716
|
+
"""Frequency weighting filter accoring to IEC 61672."""
|
|
1630
1717
|
|
|
1631
|
-
class FiltFreqWeight( Filter ):
|
|
1632
|
-
"""
|
|
1633
|
-
Frequency weighting filter accoring to IEC 61672
|
|
1634
|
-
"""
|
|
1635
1718
|
#: weighting characteristics
|
|
1636
|
-
weight = Trait('A',('A','C','Z'), desc=
|
|
1719
|
+
weight = Trait('A', ('A', 'C', 'Z'), desc='frequency weighting')
|
|
1637
1720
|
|
|
1638
|
-
sos = Property(
|
|
1721
|
+
sos = Property(depends_on=['weight', 'source.digest'])
|
|
1639
1722
|
|
|
1640
1723
|
# internal identifier
|
|
1641
|
-
digest = Property(
|
|
1642
|
-
'weight'])
|
|
1724
|
+
digest = Property(depends_on=['source.digest', '__class__', 'weight'])
|
|
1643
1725
|
|
|
1644
1726
|
@cached_property
|
|
1645
|
-
def _get_digest(
|
|
1727
|
+
def _get_digest(self):
|
|
1646
1728
|
return digest(self)
|
|
1647
1729
|
|
|
1648
1730
|
@cached_property
|
|
1649
|
-
def _get_sos(
|
|
1731
|
+
def _get_sos(self):
|
|
1650
1732
|
# s domain coefficients
|
|
1651
1733
|
f1 = 20.598997
|
|
1652
1734
|
f2 = 107.65265
|
|
1653
1735
|
f3 = 737.86223
|
|
1654
1736
|
f4 = 12194.217
|
|
1655
|
-
a = polymul([1, 4*pi * f4, (2*pi * f4)**2],
|
|
1656
|
-
[1, 4*pi * f1, (2*pi * f1)**2])
|
|
1737
|
+
a = polymul([1, 4 * pi * f4, (2 * pi * f4) ** 2], [1, 4 * pi * f1, (2 * pi * f1) ** 2])
|
|
1657
1738
|
if self.weight == 'A':
|
|
1658
|
-
a = polymul(polymul(a, [1, 2*pi * f3]), [1, 2*pi * f2])
|
|
1659
|
-
b = [(2*pi * f4)**2 * 10**(1.9997/20)
|
|
1660
|
-
b,a = bilinear(b,a,self.sample_freq)
|
|
1739
|
+
a = polymul(polymul(a, [1, 2 * pi * f3]), [1, 2 * pi * f2])
|
|
1740
|
+
b = [(2 * pi * f4) ** 2 * 10 ** (1.9997 / 20), 0, 0, 0, 0]
|
|
1741
|
+
b, a = bilinear(b, a, self.sample_freq)
|
|
1661
1742
|
elif self.weight == 'C':
|
|
1662
|
-
b = [(2*pi * f4)**2 * 10**(0.0619/20)
|
|
1663
|
-
b,a = bilinear(b,a,self.sample_freq)
|
|
1664
|
-
b = append(b,zeros(2))
|
|
1665
|
-
a = append(a,zeros(2))
|
|
1743
|
+
b = [(2 * pi * f4) ** 2 * 10 ** (0.0619 / 20), 0, 0]
|
|
1744
|
+
b, a = bilinear(b, a, self.sample_freq)
|
|
1745
|
+
b = append(b, zeros(2)) # make 6th order
|
|
1746
|
+
a = append(a, zeros(2))
|
|
1666
1747
|
else:
|
|
1667
1748
|
b = zeros(7)
|
|
1668
1749
|
b[0] = 1.0
|
|
1669
|
-
a = b
|
|
1670
|
-
return tf2sos(b,a)
|
|
1750
|
+
a = b # 6th order flat response
|
|
1751
|
+
return tf2sos(b, a)
|
|
1752
|
+
|
|
1671
1753
|
|
|
1672
1754
|
class FilterBank(TimeInOut):
|
|
1673
|
-
"""
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1755
|
+
"""Abstract base class for IIR filter banks based on scipy lfilter
|
|
1756
|
+
implements a bank of parallel filters.
|
|
1757
|
+
|
|
1677
1758
|
Should not be instanciated by itself
|
|
1678
1759
|
"""
|
|
1679
1760
|
|
|
@@ -1689,198 +1770,187 @@ class FilterBank(TimeInOut):
|
|
|
1689
1770
|
#: Number of bands
|
|
1690
1771
|
numchannels = Property()
|
|
1691
1772
|
|
|
1692
|
-
def _get_sos(
|
|
1693
|
-
return [tf2sos([1],[1])]
|
|
1773
|
+
def _get_sos(self):
|
|
1774
|
+
return [tf2sos([1], [1])]
|
|
1694
1775
|
|
|
1695
|
-
def _get_bands(
|
|
1776
|
+
def _get_bands(self):
|
|
1696
1777
|
return ['']
|
|
1697
1778
|
|
|
1698
|
-
def _get_numbands(
|
|
1779
|
+
def _get_numbands(self):
|
|
1699
1780
|
return 0
|
|
1700
1781
|
|
|
1701
|
-
def _get_numchannels(
|
|
1702
|
-
return self.numbands*self.source.numchannels
|
|
1782
|
+
def _get_numchannels(self):
|
|
1783
|
+
return self.numbands * self.source.numchannels
|
|
1703
1784
|
|
|
1704
1785
|
def result(self, num):
|
|
1705
|
-
"""
|
|
1706
|
-
|
|
1707
|
-
|
|
1786
|
+
"""Python generator that yields the output block-wise.
|
|
1787
|
+
|
|
1708
1788
|
Parameters
|
|
1709
1789
|
----------
|
|
1710
1790
|
num : integer
|
|
1711
1791
|
This parameter defines the size of the blocks to be yielded
|
|
1712
1792
|
(i.e. the number of samples per block).
|
|
1713
|
-
|
|
1793
|
+
|
|
1714
1794
|
Returns
|
|
1715
1795
|
-------
|
|
1716
|
-
Samples in blocks of shape (num, numchannels).
|
|
1796
|
+
Samples in blocks of shape (num, numchannels).
|
|
1717
1797
|
Delivers the bandpass filtered output of source.
|
|
1718
1798
|
The last block may be shorter than num.
|
|
1799
|
+
|
|
1719
1800
|
"""
|
|
1720
1801
|
numbands = self.numbands
|
|
1721
1802
|
snumch = self.source.numchannels
|
|
1722
1803
|
sos = self.sos
|
|
1723
|
-
zi = [zeros(
|
|
1724
|
-
res = zeros((num,self.numchannels),dtype='float')
|
|
1804
|
+
zi = [zeros((sos[0].shape[0], 2, snumch)) for _ in range(numbands)]
|
|
1805
|
+
res = zeros((num, self.numchannels), dtype='float')
|
|
1725
1806
|
for block in self.source.result(num):
|
|
1726
|
-
bl = block.shape[0]
|
|
1727
1807
|
for i in range(numbands):
|
|
1728
|
-
res[:,i*snumch:(i+1)*snumch], zi[i] = sosfilt(sos[i], block, axis=0, zi=zi[i])
|
|
1808
|
+
res[:, i * snumch : (i + 1) * snumch], zi[i] = sosfilt(sos[i], block, axis=0, zi=zi[i])
|
|
1729
1809
|
yield res
|
|
1730
1810
|
|
|
1811
|
+
|
|
1731
1812
|
class OctaveFilterBank(FilterBank):
|
|
1732
|
-
"""
|
|
1733
|
-
|
|
1734
|
-
"""
|
|
1813
|
+
"""Octave or third-octave filter bank."""
|
|
1814
|
+
|
|
1735
1815
|
#: Lowest band center frequency index; defaults to 21 (=125 Hz).
|
|
1736
|
-
lband = Int(21,
|
|
1737
|
-
desc = "lowest band center frequency index")
|
|
1816
|
+
lband = Int(21, desc='lowest band center frequency index')
|
|
1738
1817
|
|
|
1739
1818
|
#: Lowest band center frequency index + 1; defaults to 40 (=8000 Hz).
|
|
1740
|
-
hband = Int(40,
|
|
1741
|
-
|
|
1742
|
-
|
|
1819
|
+
hband = Int(40, desc='lowest band center frequency index')
|
|
1820
|
+
|
|
1743
1821
|
#: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
|
|
1744
|
-
fraction = Trait('Octave', {'Octave':1, 'Third octave':3},
|
|
1745
|
-
desc = "fraction of octave")
|
|
1822
|
+
fraction = Trait('Octave', {'Octave': 1, 'Third octave': 3}, desc='fraction of octave')
|
|
1746
1823
|
|
|
1747
1824
|
#: List of filter coefficients for all filters
|
|
1748
|
-
ba = Property(
|
|
1825
|
+
ba = Property(depends_on=['lband', 'hband', 'fraction', 'source.digest'])
|
|
1749
1826
|
|
|
1750
1827
|
#: List of labels for bands
|
|
1751
|
-
bands = Property(depends_on
|
|
1828
|
+
bands = Property(depends_on=['lband', 'hband', 'fraction'])
|
|
1752
1829
|
|
|
1753
1830
|
#: Number of bands
|
|
1754
|
-
numbands = Property(depends_on
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
digest = Property(
|
|
1758
|
-
'lband','hband','fraction','order'])
|
|
1831
|
+
numbands = Property(depends_on=['lband', 'hband', 'fraction'])
|
|
1832
|
+
|
|
1833
|
+
# internal identifier
|
|
1834
|
+
digest = Property(depends_on=['source.digest', '__class__', 'lband', 'hband', 'fraction', 'order'])
|
|
1759
1835
|
|
|
1760
1836
|
@cached_property
|
|
1761
|
-
def _get_digest(
|
|
1837
|
+
def _get_digest(self):
|
|
1762
1838
|
return digest(self)
|
|
1763
1839
|
|
|
1764
1840
|
@cached_property
|
|
1765
|
-
def _get_bands(
|
|
1766
|
-
return [10**(i/10) for i in range(self.lband,self.hband,4-self.fraction_)]
|
|
1841
|
+
def _get_bands(self):
|
|
1842
|
+
return [10 ** (i / 10) for i in range(self.lband, self.hband, 4 - self.fraction_)]
|
|
1767
1843
|
|
|
1768
1844
|
@cached_property
|
|
1769
|
-
def _get_numbands(
|
|
1845
|
+
def _get_numbands(self):
|
|
1770
1846
|
return len(self.bands)
|
|
1771
1847
|
|
|
1772
1848
|
@cached_property
|
|
1773
|
-
def _get_sos(
|
|
1849
|
+
def _get_sos(self):
|
|
1774
1850
|
of = FiltOctave(source=self.source, fraction=self.fraction)
|
|
1775
1851
|
sos = []
|
|
1776
|
-
for i in range(self.lband,self.hband,4-self.fraction_):
|
|
1777
|
-
of.band = 10**(i/10)
|
|
1852
|
+
for i in range(self.lband, self.hband, 4 - self.fraction_):
|
|
1853
|
+
of.band = 10 ** (i / 10)
|
|
1778
1854
|
sos_ = of.sos
|
|
1779
1855
|
sos.append(sos_)
|
|
1780
1856
|
return sos
|
|
1781
1857
|
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
Caches time signal in cache file.
|
|
1785
|
-
|
|
1858
|
+
|
|
1859
|
+
class TimeCache(TimeInOut):
|
|
1860
|
+
"""Caches time signal in cache file."""
|
|
1861
|
+
|
|
1786
1862
|
# basename for cache
|
|
1787
|
-
basename = Property(
|
|
1788
|
-
|
|
1863
|
+
basename = Property(depends_on='digest')
|
|
1864
|
+
|
|
1789
1865
|
# hdf5 cache file
|
|
1790
|
-
h5f = Instance(
|
|
1791
|
-
|
|
1866
|
+
h5f = Instance(H5CacheFileBase, transient=True)
|
|
1867
|
+
|
|
1792
1868
|
# internal identifier
|
|
1793
|
-
digest = Property(
|
|
1869
|
+
digest = Property(depends_on=['source.digest', '__class__'])
|
|
1794
1870
|
|
|
1795
1871
|
@cached_property
|
|
1796
|
-
def _get_digest(
|
|
1872
|
+
def _get_digest(self):
|
|
1797
1873
|
return digest(self)
|
|
1798
1874
|
|
|
1799
1875
|
@cached_property
|
|
1800
|
-
def _get_basename
|
|
1801
|
-
obj = self.source
|
|
1802
|
-
basename = 'void'
|
|
1876
|
+
def _get_basename(self):
|
|
1877
|
+
obj = self.source # start with source
|
|
1878
|
+
basename = 'void' # if no file source is found
|
|
1803
1879
|
while obj:
|
|
1804
|
-
if 'basename' in obj.all_trait_names():
|
|
1805
|
-
basename = obj.basename
|
|
1880
|
+
if 'basename' in obj.all_trait_names(): # at original source?
|
|
1881
|
+
basename = obj.basename # get the name
|
|
1806
1882
|
break
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
obj = None
|
|
1883
|
+
try:
|
|
1884
|
+
obj = obj.source # traverse down until original data source
|
|
1885
|
+
except AttributeError:
|
|
1886
|
+
obj = None
|
|
1812
1887
|
return basename
|
|
1813
1888
|
|
|
1814
|
-
def _pass_data(self,num):
|
|
1815
|
-
|
|
1816
|
-
yield data
|
|
1889
|
+
def _pass_data(self, num):
|
|
1890
|
+
yield from self.source.result(num)
|
|
1817
1891
|
|
|
1818
|
-
def _write_data_to_cache(self,num):
|
|
1892
|
+
def _write_data_to_cache(self, num):
|
|
1819
1893
|
nodename = 'tc_' + self.digest
|
|
1820
|
-
self.h5f.create_extendable_array(
|
|
1821
|
-
nodename, (0, self.numchannels), "float32")
|
|
1894
|
+
self.h5f.create_extendable_array(nodename, (0, self.numchannels), 'float32')
|
|
1822
1895
|
ac = self.h5f.get_data_by_reference(nodename)
|
|
1823
|
-
self.h5f.set_node_attribute(ac,'sample_freq',self.sample_freq)
|
|
1824
|
-
self.h5f.set_node_attribute(ac,'complete',False)
|
|
1896
|
+
self.h5f.set_node_attribute(ac, 'sample_freq', self.sample_freq)
|
|
1897
|
+
self.h5f.set_node_attribute(ac, 'complete', False)
|
|
1825
1898
|
for data in self.source.result(num):
|
|
1826
|
-
self.h5f.append_data(ac,data)
|
|
1899
|
+
self.h5f.append_data(ac, data)
|
|
1827
1900
|
yield data
|
|
1828
|
-
self.h5f.set_node_attribute(ac,'complete',True)
|
|
1829
|
-
|
|
1830
|
-
def _get_data_from_cache(self,num):
|
|
1901
|
+
self.h5f.set_node_attribute(ac, 'complete', True)
|
|
1902
|
+
|
|
1903
|
+
def _get_data_from_cache(self, num):
|
|
1831
1904
|
nodename = 'tc_' + self.digest
|
|
1832
1905
|
ac = self.h5f.get_data_by_reference(nodename)
|
|
1833
1906
|
i = 0
|
|
1834
1907
|
while i < ac.shape[0]:
|
|
1835
|
-
yield ac[i:i+num]
|
|
1908
|
+
yield ac[i : i + num]
|
|
1836
1909
|
i += num
|
|
1837
1910
|
|
|
1838
|
-
def _get_data_from_incomplete_cache(self,num):
|
|
1911
|
+
def _get_data_from_incomplete_cache(self, num):
|
|
1839
1912
|
nodename = 'tc_' + self.digest
|
|
1840
1913
|
ac = self.h5f.get_data_by_reference(nodename)
|
|
1841
1914
|
i = 0
|
|
1842
1915
|
nblocks = 0
|
|
1843
|
-
while i+num <= ac.shape[0]:
|
|
1844
|
-
yield ac[i:i+num]
|
|
1916
|
+
while i + num <= ac.shape[0]:
|
|
1917
|
+
yield ac[i : i + num]
|
|
1845
1918
|
nblocks += 1
|
|
1846
1919
|
i += num
|
|
1847
1920
|
self.h5f.remove_data(nodename)
|
|
1848
|
-
self.h5f.create_extendable_array(
|
|
1849
|
-
nodename, (0, self.numchannels), "float32")
|
|
1921
|
+
self.h5f.create_extendable_array(nodename, (0, self.numchannels), 'float32')
|
|
1850
1922
|
ac = self.h5f.get_data_by_reference(nodename)
|
|
1851
|
-
self.h5f.set_node_attribute(ac,'sample_freq',self.sample_freq)
|
|
1852
|
-
self.h5f.set_node_attribute(ac,'complete',False)
|
|
1853
|
-
for j,data in enumerate(self.source.result(num)):
|
|
1854
|
-
self.h5f.append_data(ac,data)
|
|
1855
|
-
if j>=nblocks:
|
|
1923
|
+
self.h5f.set_node_attribute(ac, 'sample_freq', self.sample_freq)
|
|
1924
|
+
self.h5f.set_node_attribute(ac, 'complete', False)
|
|
1925
|
+
for j, data in enumerate(self.source.result(num)):
|
|
1926
|
+
self.h5f.append_data(ac, data)
|
|
1927
|
+
if j >= nblocks:
|
|
1856
1928
|
yield data
|
|
1857
|
-
self.h5f.set_node_attribute(ac,'complete',True)
|
|
1929
|
+
self.h5f.set_node_attribute(ac, 'complete', True)
|
|
1858
1930
|
|
|
1859
1931
|
# result generator: delivers input, possibly from cache
|
|
1860
1932
|
def result(self, num):
|
|
1861
|
-
"""
|
|
1862
|
-
Python generator that yields the output from cache block-wise.
|
|
1933
|
+
"""Python generator that yields the output from cache block-wise.
|
|
1863
1934
|
|
|
1864
|
-
|
|
1865
1935
|
Parameters
|
|
1866
1936
|
----------
|
|
1867
1937
|
num : integer
|
|
1868
1938
|
This parameter defines the size of the blocks to be yielded
|
|
1869
1939
|
(i.e. the number of samples per block).
|
|
1870
|
-
|
|
1940
|
+
|
|
1871
1941
|
Returns
|
|
1872
1942
|
-------
|
|
1873
|
-
Samples in blocks of shape (num, numchannels).
|
|
1943
|
+
Samples in blocks of shape (num, numchannels).
|
|
1874
1944
|
The last block may be shorter than num.
|
|
1875
1945
|
Echos the source output, but reads it from cache
|
|
1876
1946
|
when available and prevents unnecassary recalculation.
|
|
1947
|
+
|
|
1877
1948
|
"""
|
|
1878
|
-
|
|
1879
1949
|
if config.global_caching == 'none':
|
|
1880
1950
|
generator = self._pass_data
|
|
1881
|
-
else:
|
|
1951
|
+
else:
|
|
1882
1952
|
nodename = 'tc_' + self.digest
|
|
1883
|
-
H5cache.get_cache_file(
|
|
1953
|
+
H5cache.get_cache_file(self, self.basename)
|
|
1884
1954
|
if not self.h5f:
|
|
1885
1955
|
generator = self._pass_data
|
|
1886
1956
|
elif self.h5f.is_cached(nodename):
|
|
@@ -1889,13 +1959,18 @@ class TimeCache( TimeInOut ):
|
|
|
1889
1959
|
self.h5f.remove_data(nodename)
|
|
1890
1960
|
generator = self._write_data_to_cache
|
|
1891
1961
|
elif not self.h5f.get_data_by_reference(nodename).attrs.__contains__('complete'):
|
|
1892
|
-
if config.global_caching =='readonly':
|
|
1962
|
+
if config.global_caching == 'readonly':
|
|
1893
1963
|
generator = self._pass_data
|
|
1894
1964
|
else:
|
|
1895
1965
|
generator = self._get_data_from_incomplete_cache
|
|
1896
1966
|
elif not self.h5f.get_data_by_reference(nodename).attrs['complete']:
|
|
1897
|
-
if config.global_caching =='readonly':
|
|
1898
|
-
warn(
|
|
1967
|
+
if config.global_caching == 'readonly':
|
|
1968
|
+
warn(
|
|
1969
|
+
"Cache file is incomplete for nodename %s. With config.global_caching='readonly', the cache file will not be used!"
|
|
1970
|
+
% str(nodename),
|
|
1971
|
+
Warning,
|
|
1972
|
+
stacklevel=1,
|
|
1973
|
+
)
|
|
1899
1974
|
generator = self._pass_data
|
|
1900
1975
|
else:
|
|
1901
1976
|
generator = self._get_data_from_incomplete_cache
|
|
@@ -1903,59 +1978,54 @@ class TimeCache( TimeInOut ):
|
|
|
1903
1978
|
generator = self._write_data_to_cache
|
|
1904
1979
|
if config.global_caching == 'readonly':
|
|
1905
1980
|
generator = self._pass_data
|
|
1906
|
-
|
|
1907
|
-
yield temp
|
|
1981
|
+
yield from generator(num)
|
|
1908
1982
|
|
|
1909
1983
|
|
|
1910
|
-
class WriteWAV(
|
|
1911
|
-
"""
|
|
1912
|
-
Saves time signal from one or more channels as mono/stereo/multi-channel
|
|
1984
|
+
class WriteWAV(TimeInOut):
|
|
1985
|
+
"""Saves time signal from one or more channels as mono/stereo/multi-channel
|
|
1913
1986
|
`*.wav` file.
|
|
1914
1987
|
"""
|
|
1915
|
-
|
|
1988
|
+
|
|
1916
1989
|
#: Name of the file to be saved. If none is given, the name will be
|
|
1917
1990
|
#: automatically generated from the sources.
|
|
1918
|
-
name = File(filter=['*.wav'],
|
|
1919
|
-
|
|
1920
|
-
|
|
1991
|
+
name = File(filter=['*.wav'], desc='name of wave file')
|
|
1992
|
+
|
|
1921
1993
|
#: Basename for cache, readonly.
|
|
1922
|
-
basename = Property(
|
|
1923
|
-
|
|
1994
|
+
basename = Property(depends_on='digest')
|
|
1995
|
+
|
|
1924
1996
|
#: Channel(s) to save. List can only contain one or two channels.
|
|
1925
|
-
channels = ListInt(desc=
|
|
1926
|
-
|
|
1997
|
+
channels = ListInt(desc='channel to save')
|
|
1998
|
+
|
|
1927
1999
|
# internal identifier
|
|
1928
|
-
digest = Property(
|
|
2000
|
+
digest = Property(depends_on=['source.digest', 'channels', '__class__'])
|
|
1929
2001
|
|
|
1930
2002
|
@cached_property
|
|
1931
|
-
def _get_digest(
|
|
2003
|
+
def _get_digest(self):
|
|
1932
2004
|
return digest(self)
|
|
1933
2005
|
|
|
1934
2006
|
@cached_property
|
|
1935
|
-
def _get_basename
|
|
1936
|
-
obj = self.source
|
|
2007
|
+
def _get_basename(self):
|
|
2008
|
+
obj = self.source # start with source
|
|
1937
2009
|
try:
|
|
1938
2010
|
while obj:
|
|
1939
|
-
if 'basename' in obj.all_trait_names():
|
|
1940
|
-
basename = obj.basename
|
|
2011
|
+
if 'basename' in obj.all_trait_names(): # at original source?
|
|
2012
|
+
basename = obj.basename # get the name
|
|
1941
2013
|
break
|
|
1942
|
-
|
|
1943
|
-
obj = obj.source # traverse down until original data source
|
|
2014
|
+
obj = obj.source # traverse down until original data source
|
|
1944
2015
|
else:
|
|
1945
2016
|
basename = 'void'
|
|
1946
2017
|
except AttributeError:
|
|
1947
|
-
basename = 'void'
|
|
2018
|
+
basename = 'void' # if no file source is found
|
|
1948
2019
|
return basename
|
|
1949
2020
|
|
|
1950
2021
|
def save(self):
|
|
1951
|
-
"""
|
|
1952
|
-
Saves source output to one- or multiple-channel `*.wav` file.
|
|
1953
|
-
"""
|
|
2022
|
+
"""Saves source output to one- or multiple-channel `*.wav` file."""
|
|
1954
2023
|
nc = len(self.channels)
|
|
1955
2024
|
if nc == 0:
|
|
1956
|
-
|
|
2025
|
+
msg = 'No channels given for output.'
|
|
2026
|
+
raise ValueError(msg)
|
|
1957
2027
|
if nc > 2:
|
|
1958
|
-
warn(
|
|
2028
|
+
warn('More than two channels given for output, exported file will have %i channels' % nc, stacklevel=1)
|
|
1959
2029
|
if self.name == '':
|
|
1960
2030
|
name = self.basename
|
|
1961
2031
|
for nr in self.channels:
|
|
@@ -1963,7 +2033,7 @@ class WriteWAV( TimeInOut ):
|
|
|
1963
2033
|
name += '.wav'
|
|
1964
2034
|
else:
|
|
1965
2035
|
name = self.name
|
|
1966
|
-
wf = wave.open(name,'w')
|
|
2036
|
+
wf = wave.open(name, 'w')
|
|
1967
2037
|
wf.setnchannels(nc)
|
|
1968
2038
|
wf.setsampwidth(2)
|
|
1969
2039
|
wf.setframerate(self.source.sample_freq)
|
|
@@ -1972,149 +2042,141 @@ class WriteWAV( TimeInOut ):
|
|
|
1972
2042
|
ind = array(self.channels)
|
|
1973
2043
|
for data in self.source.result(1024):
|
|
1974
2044
|
mx = max(abs(data[:, ind]).max(), mx)
|
|
1975
|
-
scale = 0.9*2**15/mx
|
|
2045
|
+
scale = 0.9 * 2**15 / mx
|
|
1976
2046
|
for data in self.source.result(1024):
|
|
1977
|
-
wf.writeframesraw(array(data[:, ind]*scale, dtype=int16).tostring())
|
|
2047
|
+
wf.writeframesraw(array(data[:, ind] * scale, dtype=int16).tostring())
|
|
1978
2048
|
wf.close()
|
|
1979
2049
|
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
Saves time signal as `*.h5` file
|
|
1983
|
-
|
|
2050
|
+
|
|
2051
|
+
class WriteH5(TimeInOut):
|
|
2052
|
+
"""Saves time signal as `*.h5` file."""
|
|
2053
|
+
|
|
1984
2054
|
#: Name of the file to be saved. If none is given, the name will be
|
|
1985
2055
|
#: automatically generated from a time stamp.
|
|
1986
|
-
name = File(filter=['*.h5'],
|
|
1987
|
-
desc="name of data file")
|
|
2056
|
+
name = File(filter=['*.h5'], desc='name of data file')
|
|
1988
2057
|
|
|
1989
|
-
#: Number of samples to write to file by `result` method.
|
|
1990
|
-
#: defaults to -1 (write as long as source yields data).
|
|
2058
|
+
#: Number of samples to write to file by `result` method.
|
|
2059
|
+
#: defaults to -1 (write as long as source yields data).
|
|
1991
2060
|
numsamples_write = Int(-1)
|
|
1992
|
-
|
|
2061
|
+
|
|
1993
2062
|
# flag that can be raised to stop file writing
|
|
1994
2063
|
writeflag = Bool(True)
|
|
1995
|
-
|
|
2064
|
+
|
|
1996
2065
|
# internal identifier
|
|
1997
|
-
digest = Property(
|
|
2066
|
+
digest = Property(depends_on=['source.digest', '__class__'])
|
|
1998
2067
|
|
|
1999
|
-
#: The floating-number-precision of entries of H5 File corresponding
|
|
2068
|
+
#: The floating-number-precision of entries of H5 File corresponding
|
|
2000
2069
|
#: to numpy dtypes. Default is 32 bit.
|
|
2001
|
-
precision = Trait('float32', 'float64',
|
|
2002
|
-
desc="precision of H5 File")
|
|
2070
|
+
precision = Trait('float32', 'float64', desc='precision of H5 File')
|
|
2003
2071
|
|
|
2004
2072
|
#: Metadata to be stored in HDF5 file object
|
|
2005
|
-
metadata = Dict(
|
|
2006
|
-
desc="metadata to be stored in .h5 file")
|
|
2073
|
+
metadata = Dict(desc='metadata to be stored in .h5 file')
|
|
2007
2074
|
|
|
2008
2075
|
@cached_property
|
|
2009
|
-
def _get_digest(
|
|
2076
|
+
def _get_digest(self):
|
|
2010
2077
|
return digest(self)
|
|
2011
2078
|
|
|
2012
2079
|
def create_filename(self):
|
|
2013
2080
|
if self.name == '':
|
|
2014
|
-
name = datetime.now().isoformat('_').replace(':','-').replace('.','_')
|
|
2015
|
-
self.name = path.join(config.td_dir,name+'.h5')
|
|
2081
|
+
name = datetime.now(tz=timezone.utc).isoformat('_').replace(':', '-').replace('.', '_')
|
|
2082
|
+
self.name = path.join(config.td_dir, name + '.h5')
|
|
2016
2083
|
|
|
2017
2084
|
def get_initialized_file(self):
|
|
2018
2085
|
file = _get_h5file_class()
|
|
2019
2086
|
self.create_filename()
|
|
2020
|
-
f5h = file(self.name, mode
|
|
2021
|
-
f5h.create_extendable_array(
|
|
2022
|
-
'time_data', (0, self.numchannels), self.precision)
|
|
2087
|
+
f5h = file(self.name, mode='w')
|
|
2088
|
+
f5h.create_extendable_array('time_data', (0, self.numchannels), self.precision)
|
|
2023
2089
|
ac = f5h.get_data_by_reference('time_data')
|
|
2024
|
-
f5h.set_node_attribute(ac,'sample_freq',self.sample_freq)
|
|
2090
|
+
f5h.set_node_attribute(ac, 'sample_freq', self.sample_freq)
|
|
2025
2091
|
self.add_metadata(f5h)
|
|
2026
2092
|
return f5h
|
|
2027
|
-
|
|
2093
|
+
|
|
2028
2094
|
def save(self):
|
|
2029
|
-
"""
|
|
2030
|
-
Saves source output to `*.h5` file
|
|
2031
|
-
"""
|
|
2032
|
-
|
|
2095
|
+
"""Saves source output to `*.h5` file."""
|
|
2033
2096
|
f5h = self.get_initialized_file()
|
|
2034
2097
|
ac = f5h.get_data_by_reference('time_data')
|
|
2035
2098
|
for data in self.source.result(4096):
|
|
2036
|
-
f5h.append_data(ac,data)
|
|
2099
|
+
f5h.append_data(ac, data)
|
|
2037
2100
|
f5h.close()
|
|
2038
2101
|
|
|
2039
2102
|
def add_metadata(self, f5h):
|
|
2040
|
-
"""
|
|
2103
|
+
"""Adds metadata to .h5 file."""
|
|
2041
2104
|
nitems = len(self.metadata.items())
|
|
2042
2105
|
if nitems > 0:
|
|
2043
|
-
f5h.create_new_group(
|
|
2106
|
+
f5h.create_new_group('metadata', '/')
|
|
2044
2107
|
for key, value in self.metadata.items():
|
|
2045
|
-
f5h.create_array('/metadata',key, value)
|
|
2108
|
+
f5h.create_array('/metadata', key, value)
|
|
2046
2109
|
|
|
2047
2110
|
def result(self, num):
|
|
2048
|
-
"""
|
|
2049
|
-
Python generator that saves source output to `*.h5` file and
|
|
2111
|
+
"""Python generator that saves source output to `*.h5` file and
|
|
2050
2112
|
yields the source output block-wise.
|
|
2051
2113
|
|
|
2052
|
-
|
|
2114
|
+
|
|
2053
2115
|
Parameters
|
|
2054
2116
|
----------
|
|
2055
2117
|
num : integer
|
|
2056
2118
|
This parameter defines the size of the blocks to be yielded
|
|
2057
2119
|
(i.e. the number of samples per block).
|
|
2058
|
-
|
|
2120
|
+
|
|
2059
2121
|
Returns
|
|
2060
2122
|
-------
|
|
2061
|
-
Samples in blocks of shape (num, numchannels).
|
|
2123
|
+
Samples in blocks of shape (num, numchannels).
|
|
2062
2124
|
The last block may be shorter than num.
|
|
2063
2125
|
Echos the source output, but reads it from cache
|
|
2064
2126
|
when available and prevents unnecassary recalculation.
|
|
2127
|
+
|
|
2065
2128
|
"""
|
|
2066
|
-
|
|
2067
2129
|
self.writeflag = True
|
|
2068
2130
|
f5h = self.get_initialized_file()
|
|
2069
2131
|
ac = f5h.get_data_by_reference('time_data')
|
|
2070
2132
|
scount = 0
|
|
2071
2133
|
stotal = self.numsamples_write
|
|
2072
2134
|
source_gen = self.source.result(num)
|
|
2073
|
-
while self.writeflag:
|
|
2074
|
-
sleft = stotal-scount
|
|
2075
|
-
if
|
|
2076
|
-
anz = min(num,sleft)
|
|
2135
|
+
while self.writeflag:
|
|
2136
|
+
sleft = stotal - scount
|
|
2137
|
+
if stotal != -1 and sleft > 0:
|
|
2138
|
+
anz = min(num, sleft)
|
|
2077
2139
|
elif stotal == -1:
|
|
2078
2140
|
anz = num
|
|
2079
2141
|
else:
|
|
2080
2142
|
break
|
|
2081
2143
|
try:
|
|
2082
2144
|
data = next(source_gen)
|
|
2083
|
-
except:
|
|
2145
|
+
except StopIteration:
|
|
2084
2146
|
break
|
|
2085
|
-
f5h.append_data(ac,data[:anz])
|
|
2147
|
+
f5h.append_data(ac, data[:anz])
|
|
2086
2148
|
yield data
|
|
2087
2149
|
f5h.flush()
|
|
2088
2150
|
scount += anz
|
|
2089
2151
|
f5h.close()
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
Creates a Thread Safe Iterator.
|
|
2152
|
+
|
|
2153
|
+
|
|
2154
|
+
class LockedGenerator:
|
|
2155
|
+
"""Creates a Thread Safe Iterator.
|
|
2094
2156
|
Takes an iterator/generator and makes it thread-safe by
|
|
2095
2157
|
serializing call to the `next` method of given iterator/generator.
|
|
2096
2158
|
"""
|
|
2097
|
-
|
|
2159
|
+
|
|
2098
2160
|
def __init__(self, it):
|
|
2099
2161
|
self.it = it
|
|
2100
2162
|
self.lock = threading.Lock()
|
|
2101
2163
|
|
|
2102
|
-
def __next__(self):
|
|
2164
|
+
def __next__(self): # this function implementation is not python 2 compatible!
|
|
2103
2165
|
with self.lock:
|
|
2104
2166
|
return self.it.__next__()
|
|
2105
2167
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
A separate block buffer is created for each registered object in
|
|
2168
|
+
|
|
2169
|
+
class SampleSplitter(TimeInOut):
|
|
2170
|
+
"""Distributes data blocks from source to several following objects.
|
|
2171
|
+
A separate block buffer is created for each registered object in
|
|
2110
2172
|
(:attr:`block_buffer`) .
|
|
2111
|
-
|
|
2173
|
+
"""
|
|
2112
2174
|
|
|
2113
2175
|
#: dictionary with block buffers (dict values) of registered objects (dict
|
|
2114
|
-
#: keys).
|
|
2115
|
-
block_buffer = Dict(key_trait=Instance(SamplesGenerator))
|
|
2176
|
+
#: keys).
|
|
2177
|
+
block_buffer = Dict(key_trait=Instance(SamplesGenerator))
|
|
2116
2178
|
|
|
2117
|
-
#: max elements/blocks in block buffers.
|
|
2179
|
+
#: max elements/blocks in block buffers.
|
|
2118
2180
|
buffer_size = Int(100)
|
|
2119
2181
|
|
|
2120
2182
|
#: defines behaviour in case of block_buffer overflow. Can be set individually
|
|
@@ -2123,148 +2185,140 @@ class SampleSplitter(TimeInOut):
|
|
|
2123
2185
|
#: * 'error': an IOError is thrown by the class
|
|
2124
2186
|
#: * 'warning': a warning is displayed. Possibly leads to lost blocks of data
|
|
2125
2187
|
#: * 'none': nothing happens. Possibly leads to lost blocks of data
|
|
2126
|
-
buffer_overflow_treatment = Dict(
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2188
|
+
buffer_overflow_treatment = Dict(
|
|
2189
|
+
key_trait=Instance(SamplesGenerator),
|
|
2190
|
+
value_trait=Trait('error', 'warning', 'none'),
|
|
2191
|
+
desc='defines buffer overflow behaviour.',
|
|
2192
|
+
)
|
|
2193
|
+
|
|
2130
2194
|
# shadow trait to monitor if source deliver samples or is empty
|
|
2131
|
-
_source_generator_exist = Bool(False)
|
|
2195
|
+
_source_generator_exist = Bool(False)
|
|
2132
2196
|
|
|
2133
|
-
# shadow trait to monitor if buffer of objects with overflow treatment = 'error'
|
|
2197
|
+
# shadow trait to monitor if buffer of objects with overflow treatment = 'error'
|
|
2134
2198
|
# or warning is overfilled. Error will be raised in all threads.
|
|
2135
2199
|
_buffer_overflow = Bool(False)
|
|
2136
2200
|
|
|
2137
|
-
# Helper Trait holds source generator
|
|
2201
|
+
# Helper Trait holds source generator
|
|
2138
2202
|
_source_generator = Trait()
|
|
2139
|
-
|
|
2140
|
-
def _create_block_buffer(self,obj):
|
|
2141
|
-
self.block_buffer[obj] = deque([],maxlen=self.buffer_size)
|
|
2142
|
-
|
|
2143
|
-
def _create_buffer_overflow_treatment(self,obj):
|
|
2144
|
-
self.buffer_overflow_treatment[obj] = 'error'
|
|
2145
|
-
|
|
2146
|
-
def _clear_block_buffer(self,obj):
|
|
2203
|
+
|
|
2204
|
+
def _create_block_buffer(self, obj):
|
|
2205
|
+
self.block_buffer[obj] = deque([], maxlen=self.buffer_size)
|
|
2206
|
+
|
|
2207
|
+
def _create_buffer_overflow_treatment(self, obj):
|
|
2208
|
+
self.buffer_overflow_treatment[obj] = 'error'
|
|
2209
|
+
|
|
2210
|
+
def _clear_block_buffer(self, obj):
|
|
2147
2211
|
self.block_buffer[obj].clear()
|
|
2148
|
-
|
|
2149
|
-
def _remove_block_buffer(self,obj):
|
|
2212
|
+
|
|
2213
|
+
def _remove_block_buffer(self, obj):
|
|
2150
2214
|
del self.block_buffer[obj]
|
|
2151
2215
|
|
|
2152
|
-
def _remove_buffer_overflow_treatment(self,obj):
|
|
2216
|
+
def _remove_buffer_overflow_treatment(self, obj):
|
|
2153
2217
|
del self.buffer_overflow_treatment[obj]
|
|
2154
|
-
|
|
2155
|
-
def _assert_obj_registered(self,obj):
|
|
2156
|
-
if not
|
|
2157
|
-
raise
|
|
2218
|
+
|
|
2219
|
+
def _assert_obj_registered(self, obj):
|
|
2220
|
+
if obj not in self.block_buffer:
|
|
2221
|
+
raise OSError('calling object %s is not registered.' % obj)
|
|
2158
2222
|
|
|
2159
2223
|
def _get_objs_to_inspect(self):
|
|
2160
|
-
return [obj for obj in self.buffer_overflow_treatment.
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
def _inspect_buffer_levels(self,inspect_objs):
|
|
2224
|
+
return [obj for obj in self.buffer_overflow_treatment if self.buffer_overflow_treatment[obj] != 'none']
|
|
2225
|
+
|
|
2226
|
+
def _inspect_buffer_levels(self, inspect_objs):
|
|
2164
2227
|
for obj in inspect_objs:
|
|
2165
2228
|
if len(self.block_buffer[obj]) == self.buffer_size:
|
|
2166
|
-
if self.buffer_overflow_treatment[obj] == 'error':
|
|
2229
|
+
if self.buffer_overflow_treatment[obj] == 'error':
|
|
2167
2230
|
self._buffer_overflow = True
|
|
2168
2231
|
elif self.buffer_overflow_treatment[obj] == 'warning':
|
|
2169
|
-
warn(
|
|
2170
|
-
'overfilled buffer for object: %s data will get lost' %obj,
|
|
2171
|
-
UserWarning)
|
|
2232
|
+
warn('overfilled buffer for object: %s data will get lost' % obj, UserWarning, stacklevel=1)
|
|
2172
2233
|
|
|
2173
|
-
def _create_source_generator(self,num):
|
|
2174
|
-
for obj in self.block_buffer
|
|
2175
|
-
|
|
2234
|
+
def _create_source_generator(self, num):
|
|
2235
|
+
for obj in self.block_buffer:
|
|
2236
|
+
self._clear_block_buffer(obj)
|
|
2237
|
+
self._buffer_overflow = False # reset overflow bool
|
|
2176
2238
|
self._source_generator = LockedGenerator(self.source.result(num))
|
|
2177
|
-
self._source_generator_exist = True
|
|
2239
|
+
self._source_generator_exist = True # indicates full generator
|
|
2178
2240
|
|
|
2179
|
-
def _fill_block_buffers(self):
|
|
2180
|
-
next_block = next(self._source_generator)
|
|
2181
|
-
[self.block_buffer[obj].appendleft(next_block) for obj in self.block_buffer
|
|
2241
|
+
def _fill_block_buffers(self):
|
|
2242
|
+
next_block = next(self._source_generator)
|
|
2243
|
+
[self.block_buffer[obj].appendleft(next_block) for obj in self.block_buffer]
|
|
2182
2244
|
|
|
2183
2245
|
@on_trait_change('buffer_size')
|
|
2184
|
-
def _change_buffer_size(self):
|
|
2185
|
-
for obj in self.block_buffer
|
|
2246
|
+
def _change_buffer_size(self): #
|
|
2247
|
+
for obj in self.block_buffer:
|
|
2186
2248
|
self._remove_block_buffer(obj)
|
|
2187
|
-
self._create_block_buffer(obj)
|
|
2249
|
+
self._create_block_buffer(obj)
|
|
2188
2250
|
|
|
2189
|
-
def register_object(self
|
|
2190
|
-
"""
|
|
2191
|
-
Function that can be used to register objects that receive blocks from
|
|
2192
|
-
this class.
|
|
2193
|
-
"""
|
|
2251
|
+
def register_object(self, *objects_to_register):
|
|
2252
|
+
"""Function that can be used to register objects that receive blocks from this class."""
|
|
2194
2253
|
for obj in objects_to_register:
|
|
2195
|
-
if obj not in self.block_buffer
|
|
2254
|
+
if obj not in self.block_buffer:
|
|
2196
2255
|
self._create_block_buffer(obj)
|
|
2197
2256
|
self._create_buffer_overflow_treatment(obj)
|
|
2198
2257
|
|
|
2199
|
-
def remove_object(self
|
|
2200
|
-
"""
|
|
2201
|
-
Function that can be used to remove registered objects.
|
|
2202
|
-
"""
|
|
2258
|
+
def remove_object(self, *objects_to_remove):
|
|
2259
|
+
"""Function that can be used to remove registered objects."""
|
|
2203
2260
|
for obj in objects_to_remove:
|
|
2204
2261
|
self._remove_block_buffer(obj)
|
|
2205
2262
|
self._remove_buffer_overflow_treatment(obj)
|
|
2206
|
-
|
|
2207
|
-
def result(self,num):
|
|
2208
|
-
"""
|
|
2209
|
-
Python generator that yields the output block-wise from block-buffer.
|
|
2210
2263
|
|
|
2211
|
-
|
|
2264
|
+
def result(self, num):
|
|
2265
|
+
"""Python generator that yields the output block-wise from block-buffer.
|
|
2266
|
+
|
|
2212
2267
|
Parameters
|
|
2213
2268
|
----------
|
|
2214
2269
|
num : integer
|
|
2215
2270
|
This parameter defines the size of the blocks to be yielded
|
|
2216
2271
|
(i.e. the number of samples per block).
|
|
2217
|
-
|
|
2272
|
+
|
|
2218
2273
|
Returns
|
|
2219
2274
|
-------
|
|
2220
|
-
Samples in blocks of shape (num, numchannels).
|
|
2275
|
+
Samples in blocks of shape (num, numchannels).
|
|
2221
2276
|
Delivers a block of samples to the calling object.
|
|
2222
2277
|
The last block may be shorter than num.
|
|
2223
|
-
"""
|
|
2224
2278
|
|
|
2225
|
-
|
|
2279
|
+
"""
|
|
2280
|
+
calling_obj = currentframe().f_back.f_locals['self']
|
|
2226
2281
|
self._assert_obj_registered(calling_obj)
|
|
2227
|
-
objs_to_inspect = self._get_objs_to_inspect()
|
|
2228
|
-
|
|
2229
|
-
if not self._source_generator_exist:
|
|
2230
|
-
self._create_source_generator(num)
|
|
2282
|
+
objs_to_inspect = self._get_objs_to_inspect()
|
|
2283
|
+
|
|
2284
|
+
if not self._source_generator_exist:
|
|
2285
|
+
self._create_source_generator(num)
|
|
2231
2286
|
|
|
2232
2287
|
while not self._buffer_overflow:
|
|
2233
2288
|
if self.block_buffer[calling_obj]:
|
|
2234
2289
|
yield self.block_buffer[calling_obj].pop()
|
|
2235
2290
|
else:
|
|
2236
2291
|
self._inspect_buffer_levels(objs_to_inspect)
|
|
2237
|
-
try:
|
|
2292
|
+
try:
|
|
2238
2293
|
self._fill_block_buffers()
|
|
2239
2294
|
except StopIteration:
|
|
2240
2295
|
self._source_generator_exist = False
|
|
2241
2296
|
return
|
|
2242
|
-
else:
|
|
2243
|
-
|
|
2297
|
+
else:
|
|
2298
|
+
msg = 'Maximum size of block buffer is reached!'
|
|
2299
|
+
raise OSError(msg)
|
|
2300
|
+
|
|
2244
2301
|
|
|
2245
|
-
|
|
2246
2302
|
class TimeConvolve(TimeInOut):
|
|
2247
|
-
"""
|
|
2248
|
-
Uniformly partitioned overlap-save method (UPOLS) for fast convolution in the frequency domain, see :ref:`Wefers, 2015<Wefers2015>`.
|
|
2249
|
-
"""
|
|
2303
|
+
"""Uniformly partitioned overlap-save method (UPOLS) for fast convolution in the frequency domain, see :ref:`Wefers, 2015<Wefers2015>`."""
|
|
2250
2304
|
|
|
2251
2305
|
#: Convolution kernel in the time domain.
|
|
2252
2306
|
#: The second dimension of the kernel array has to be either 1 or match :attr:`~SamplesGenerator.numchannels`.
|
|
2253
2307
|
#: If only a single kernel is supplied, it is applied to all channels.
|
|
2254
|
-
kernel = CArray(dtype=float, desc=
|
|
2308
|
+
kernel = CArray(dtype=float, desc='Convolution kernel.')
|
|
2255
2309
|
|
|
2256
|
-
_block_size = Int(desc=
|
|
2310
|
+
_block_size = Int(desc='Block size')
|
|
2257
2311
|
|
|
2258
2312
|
_kernel_blocks = Property(
|
|
2259
|
-
depends_on=[
|
|
2260
|
-
desc=
|
|
2313
|
+
depends_on=['kernel', '_block_size'],
|
|
2314
|
+
desc='Frequency domain Kernel blocks',
|
|
2261
2315
|
)
|
|
2262
2316
|
|
|
2263
2317
|
# internal identifier
|
|
2264
|
-
digest = Property(
|
|
2318
|
+
digest = Property(depends_on=['source.digest', 'kernel', '__class__'])
|
|
2265
2319
|
|
|
2266
2320
|
@cached_property
|
|
2267
|
-
def _get_digest(
|
|
2321
|
+
def _get_digest(self):
|
|
2268
2322
|
return digest(self)
|
|
2269
2323
|
|
|
2270
2324
|
def _validate_kernel(self):
|
|
@@ -2273,12 +2327,13 @@ class TimeConvolve(TimeInOut):
|
|
|
2273
2327
|
self.kernel = self.kernel.reshape([-1, 1])
|
|
2274
2328
|
return
|
|
2275
2329
|
# check dimensionality
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2330
|
+
if self.kernel.ndim > 2:
|
|
2331
|
+
msg = 'Only one or two dimensional kernels accepted.'
|
|
2332
|
+
raise ValueError(msg)
|
|
2279
2333
|
# check if number of kernels matches numchannels
|
|
2280
2334
|
if self.kernel.shape[1] not in (1, self.source.numchannels):
|
|
2281
|
-
|
|
2335
|
+
msg = 'Number of kernels must be either `numchannels` or one.'
|
|
2336
|
+
raise ValueError(msg)
|
|
2282
2337
|
|
|
2283
2338
|
# compute the rfft of the kernel blockwise
|
|
2284
2339
|
@cached_property
|
|
@@ -2287,21 +2342,20 @@ class TimeConvolve(TimeInOut):
|
|
|
2287
2342
|
num = self._block_size
|
|
2288
2343
|
P = int(ceil(L / num))
|
|
2289
2344
|
trim = num * (P - 1)
|
|
2290
|
-
blocks = zeros([P, num + 1, N], dtype=
|
|
2345
|
+
blocks = zeros([P, num + 1, N], dtype='complex128')
|
|
2291
2346
|
|
|
2292
2347
|
if P > 1:
|
|
2293
2348
|
for i, block in enumerate(split(self.kernel[:trim], P - 1, axis=0)):
|
|
2294
|
-
blocks[i] = rfft(concatenate([block, zeros([num, N])], axis=0),axis=0)
|
|
2349
|
+
blocks[i] = rfft(concatenate([block, zeros([num, N])], axis=0), axis=0)
|
|
2295
2350
|
|
|
2296
2351
|
blocks[-1] = rfft(
|
|
2297
|
-
concatenate([self.kernel[trim:], zeros([2 * num - L + trim, N])], axis=0),
|
|
2352
|
+
concatenate([self.kernel[trim:], zeros([2 * num - L + trim, N])], axis=0),
|
|
2353
|
+
axis=0,
|
|
2298
2354
|
)
|
|
2299
2355
|
return blocks
|
|
2300
2356
|
|
|
2301
|
-
|
|
2302
2357
|
def result(self, num=128):
|
|
2303
|
-
"""
|
|
2304
|
-
Python generator that yields the output block-wise.
|
|
2358
|
+
"""Python generator that yields the output block-wise.
|
|
2305
2359
|
The source output is convolved with the kernel.
|
|
2306
2360
|
|
|
2307
2361
|
Parameters
|
|
@@ -2314,71 +2368,75 @@ class TimeConvolve(TimeInOut):
|
|
|
2314
2368
|
-------
|
|
2315
2369
|
Samples in blocks of shape (num, numchannels).
|
|
2316
2370
|
The last block may be shorter than num.
|
|
2317
|
-
"""
|
|
2318
2371
|
|
|
2372
|
+
"""
|
|
2319
2373
|
self._validate_kernel()
|
|
2320
2374
|
# initialize variables
|
|
2321
2375
|
self._block_size = num
|
|
2322
2376
|
L = self.kernel.shape[0]
|
|
2323
2377
|
N = self.source.numchannels
|
|
2324
2378
|
M = self.source.numsamples
|
|
2325
|
-
|
|
2379
|
+
numblocks_kernel = int(ceil(L / num)) # number of kernel blocks
|
|
2326
2380
|
Q = int(ceil(M / num)) # number of signal blocks
|
|
2327
2381
|
R = int(ceil((L + M - 1) / num)) # number of output blocks
|
|
2328
|
-
last_size = (L + M - 1) % num
|
|
2382
|
+
last_size = (L + M - 1) % num # size of final block
|
|
2329
2383
|
|
|
2330
2384
|
idx = 0
|
|
2331
|
-
|
|
2385
|
+
fdl = zeros([numblocks_kernel, num + 1, N], dtype='complex128')
|
|
2332
2386
|
buff = zeros([2 * num, N]) # time-domain input buffer
|
|
2333
|
-
spec_sum = zeros([num+1,N],dtype=
|
|
2387
|
+
spec_sum = zeros([num + 1, N], dtype='complex128')
|
|
2334
2388
|
|
|
2335
2389
|
signal_blocks = self.source.result(num)
|
|
2336
2390
|
temp = next(signal_blocks)
|
|
2337
|
-
buff[num : num + temp.shape[0]] = temp
|
|
2391
|
+
buff[num : num + temp.shape[0]] = temp # append new time-data
|
|
2338
2392
|
|
|
2339
2393
|
# for very short signals, we are already done
|
|
2340
2394
|
if R == 1:
|
|
2341
|
-
|
|
2342
|
-
spec_sum = _spectral_sum(spec_sum,
|
|
2395
|
+
_append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
|
|
2396
|
+
spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
|
|
2343
2397
|
# truncate s.t. total length is L+M-1 (like numpy convolve w/ mode="full")
|
|
2344
|
-
yield irfft(spec_sum,axis=0)[num: last_size + num]
|
|
2398
|
+
yield irfft(spec_sum, axis=0)[num : last_size + num]
|
|
2345
2399
|
return
|
|
2346
2400
|
|
|
2347
2401
|
# stream processing of source signal
|
|
2348
2402
|
for temp in signal_blocks:
|
|
2349
|
-
|
|
2350
|
-
spec_sum = _spectral_sum(spec_sum,
|
|
2351
|
-
yield irfft(spec_sum,axis=0)[num:]
|
|
2403
|
+
_append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
|
|
2404
|
+
spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
|
|
2405
|
+
yield irfft(spec_sum, axis=0)[num:]
|
|
2352
2406
|
buff = concatenate(
|
|
2353
|
-
[buff[num:], zeros([num, N])],
|
|
2407
|
+
[buff[num:], zeros([num, N])],
|
|
2408
|
+
axis=0,
|
|
2354
2409
|
) # shift input buffer to the left
|
|
2355
|
-
buff[num : num + temp.shape[0]] = temp
|
|
2410
|
+
buff[num : num + temp.shape[0]] = temp # append new time-data
|
|
2356
2411
|
|
|
2357
|
-
for _ in range(R-Q):
|
|
2358
|
-
|
|
2359
|
-
spec_sum = _spectral_sum(spec_sum,
|
|
2360
|
-
yield irfft(spec_sum,axis=0)[num:]
|
|
2412
|
+
for _ in range(R - Q):
|
|
2413
|
+
_append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
|
|
2414
|
+
spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
|
|
2415
|
+
yield irfft(spec_sum, axis=0)[num:]
|
|
2361
2416
|
buff = concatenate(
|
|
2362
|
-
[buff[num:], zeros([num, N])],
|
|
2417
|
+
[buff[num:], zeros([num, N])],
|
|
2418
|
+
axis=0,
|
|
2363
2419
|
) # shift input buffer to the left
|
|
2364
2420
|
|
|
2365
|
-
|
|
2366
|
-
spec_sum = _spectral_sum(spec_sum,
|
|
2421
|
+
_append_to_fdl(fdl, idx, numblocks_kernel, rfft(buff, axis=0))
|
|
2422
|
+
spec_sum = _spectral_sum(spec_sum, fdl, self._kernel_blocks)
|
|
2367
2423
|
# truncate s.t. total length is L+M-1 (like numpy convolve w/ mode="full")
|
|
2368
|
-
yield irfft(spec_sum, axis=0)[num: last_size + num]
|
|
2424
|
+
yield irfft(spec_sum, axis=0)[num : last_size + num]
|
|
2425
|
+
|
|
2369
2426
|
|
|
2370
2427
|
@nb.jit(nopython=True, cache=True)
|
|
2371
|
-
def
|
|
2372
|
-
|
|
2373
|
-
idx = int(idx +1 %
|
|
2428
|
+
def _append_to_fdl(fdl, idx, numblocks_kernel, buff):
|
|
2429
|
+
fdl[idx] = buff
|
|
2430
|
+
idx = int(idx + 1 % numblocks_kernel)
|
|
2431
|
+
|
|
2374
2432
|
|
|
2375
2433
|
@nb.jit(nopython=True, cache=True)
|
|
2376
|
-
def _spectral_sum(out,
|
|
2377
|
-
P,B,N =
|
|
2434
|
+
def _spectral_sum(out, fdl, kb):
|
|
2435
|
+
P, B, N = kb.shape
|
|
2378
2436
|
for n in range(N):
|
|
2379
2437
|
for b in range(B):
|
|
2380
|
-
out[b,n] = 0
|
|
2438
|
+
out[b, n] = 0
|
|
2381
2439
|
for i in range(P):
|
|
2382
|
-
out[b,n] +=
|
|
2440
|
+
out[b, n] += fdl[i, b, n] * kb[i, b, n]
|
|
2383
2441
|
|
|
2384
2442
|
return out
|