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.
Files changed (139) hide show
  1. acoular/__init__.py +119 -54
  2. acoular/calib.py +29 -38
  3. acoular/configuration.py +132 -82
  4. acoular/demo/__init__.py +10 -4
  5. acoular/demo/acoular_demo.py +73 -55
  6. acoular/environments.py +270 -264
  7. acoular/fastFuncs.py +366 -196
  8. acoular/fbeamform.py +1797 -1934
  9. acoular/grids.py +504 -548
  10. acoular/h5cache.py +74 -83
  11. acoular/h5files.py +159 -142
  12. acoular/internal.py +13 -14
  13. acoular/microphones.py +57 -53
  14. acoular/sdinput.py +57 -53
  15. acoular/signals.py +180 -178
  16. acoular/sources.py +920 -724
  17. acoular/spectra.py +353 -363
  18. acoular/tbeamform.py +416 -416
  19. acoular/tfastfuncs.py +180 -104
  20. acoular/tools/__init__.py +25 -0
  21. acoular/tools/aiaa.py +185 -0
  22. acoular/tools/helpers.py +189 -0
  23. acoular/tools/metrics.py +165 -0
  24. acoular/tprocess.py +1240 -1182
  25. acoular/traitsviews.py +513 -501
  26. acoular/trajectory.py +50 -52
  27. acoular/version.py +5 -6
  28. acoular/xml/minidsp_uma-16.xml +20 -0
  29. acoular/xml/{minidsp_uma16.xml → minidsp_uma-16_mirrored.xml} +3 -0
  30. {acoular-24.3.dist-info → acoular-24.7.dist-info}/METADATA +58 -39
  31. acoular-24.7.dist-info/RECORD +50 -0
  32. {acoular-24.3.dist-info → acoular-24.7.dist-info}/WHEEL +1 -1
  33. acoular-24.7.dist-info/licenses/LICENSE +28 -0
  34. acoular/fileimport.py +0 -380
  35. acoular/nidaqimport.py +0 -273
  36. acoular/tests/reference_data/BeamformerBase.npy +0 -0
  37. acoular/tests/reference_data/BeamformerBaseFalse1.npy +0 -0
  38. acoular/tests/reference_data/BeamformerBaseFalse2.npy +0 -0
  39. acoular/tests/reference_data/BeamformerBaseFalse3.npy +0 -0
  40. acoular/tests/reference_data/BeamformerBaseFalse4.npy +0 -0
  41. acoular/tests/reference_data/BeamformerBaseTrue1.npy +0 -0
  42. acoular/tests/reference_data/BeamformerBaseTrue2.npy +0 -0
  43. acoular/tests/reference_data/BeamformerBaseTrue3.npy +0 -0
  44. acoular/tests/reference_data/BeamformerBaseTrue4.npy +0 -0
  45. acoular/tests/reference_data/BeamformerCMFLassoLarsBIC.npy +0 -0
  46. acoular/tests/reference_data/BeamformerCMFNNLS.npy +0 -0
  47. acoular/tests/reference_data/BeamformerCapon.npy +0 -0
  48. acoular/tests/reference_data/BeamformerClean.npy +0 -0
  49. acoular/tests/reference_data/BeamformerCleansc.npy +0 -0
  50. acoular/tests/reference_data/BeamformerCleant.npy +0 -0
  51. acoular/tests/reference_data/BeamformerCleantSq.npy +0 -0
  52. acoular/tests/reference_data/BeamformerCleantSqTraj.npy +0 -0
  53. acoular/tests/reference_data/BeamformerCleantTraj.npy +0 -0
  54. acoular/tests/reference_data/BeamformerDamas.npy +0 -0
  55. acoular/tests/reference_data/BeamformerDamasPlus.npy +0 -0
  56. acoular/tests/reference_data/BeamformerEig.npy +0 -0
  57. acoular/tests/reference_data/BeamformerEigFalse1.npy +0 -0
  58. acoular/tests/reference_data/BeamformerEigFalse2.npy +0 -0
  59. acoular/tests/reference_data/BeamformerEigFalse3.npy +0 -0
  60. acoular/tests/reference_data/BeamformerEigFalse4.npy +0 -0
  61. acoular/tests/reference_data/BeamformerEigTrue1.npy +0 -0
  62. acoular/tests/reference_data/BeamformerEigTrue2.npy +0 -0
  63. acoular/tests/reference_data/BeamformerEigTrue3.npy +0 -0
  64. acoular/tests/reference_data/BeamformerEigTrue4.npy +0 -0
  65. acoular/tests/reference_data/BeamformerFunctional.npy +0 -0
  66. acoular/tests/reference_data/BeamformerGIB.npy +0 -0
  67. acoular/tests/reference_data/BeamformerGridlessOrth.npy +0 -0
  68. acoular/tests/reference_data/BeamformerMusic.npy +0 -0
  69. acoular/tests/reference_data/BeamformerOrth.npy +0 -0
  70. acoular/tests/reference_data/BeamformerSODIX.npy +0 -0
  71. acoular/tests/reference_data/BeamformerTime.npy +0 -0
  72. acoular/tests/reference_data/BeamformerTimeSq.npy +0 -0
  73. acoular/tests/reference_data/BeamformerTimeSqTraj.npy +0 -0
  74. acoular/tests/reference_data/BeamformerTimeTraj.npy +0 -0
  75. acoular/tests/reference_data/Environment.npy +0 -0
  76. acoular/tests/reference_data/Example1_numerical_values_testsum.h5 +0 -0
  77. acoular/tests/reference_data/FiltFiltOctave__.npy +0 -0
  78. acoular/tests/reference_data/FiltFiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
  79. acoular/tests/reference_data/FiltFreqWeight_weight_A_.npy +0 -0
  80. acoular/tests/reference_data/FiltFreqWeight_weight_C_.npy +0 -0
  81. acoular/tests/reference_data/FiltFreqWeight_weight_Z_.npy +0 -0
  82. acoular/tests/reference_data/FiltOctave__.npy +0 -0
  83. acoular/tests/reference_data/FiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
  84. acoular/tests/reference_data/Filter__.npy +0 -0
  85. acoular/tests/reference_data/GeneralFlowEnvironment.npy +0 -0
  86. acoular/tests/reference_data/OctaveFilterBank__.npy +0 -0
  87. acoular/tests/reference_data/OpenJet.npy +0 -0
  88. acoular/tests/reference_data/PointSource.npy +0 -0
  89. acoular/tests/reference_data/PowerSpectra_csm.npy +0 -0
  90. acoular/tests/reference_data/PowerSpectra_ev.npy +0 -0
  91. acoular/tests/reference_data/RotatingFlow.npy +0 -0
  92. acoular/tests/reference_data/SlotJet.npy +0 -0
  93. acoular/tests/reference_data/TimeAverage__.npy +0 -0
  94. acoular/tests/reference_data/TimeCumAverage__.npy +0 -0
  95. acoular/tests/reference_data/TimeExpAverage_weight_F_.npy +0 -0
  96. acoular/tests/reference_data/TimeExpAverage_weight_I_.npy +0 -0
  97. acoular/tests/reference_data/TimeExpAverage_weight_S_.npy +0 -0
  98. acoular/tests/reference_data/TimeInOut__.npy +0 -0
  99. acoular/tests/reference_data/TimePower__.npy +0 -0
  100. acoular/tests/reference_data/TimeReverse__.npy +0 -0
  101. acoular/tests/reference_data/UniformFlowEnvironment.npy +0 -0
  102. acoular/tests/reference_data/beamformer_traj_time_data.h5 +0 -0
  103. acoular/tests/run_tests.sh +0 -18
  104. acoular/tests/run_tests_osx.sh +0 -16
  105. acoular/tests/test.npy +0 -0
  106. acoular/tests/test_beamformer_results.py +0 -213
  107. acoular/tests/test_classes.py +0 -60
  108. acoular/tests/test_digest.py +0 -125
  109. acoular/tests/test_environments.py +0 -73
  110. acoular/tests/test_example1.py +0 -124
  111. acoular/tests/test_grid.py +0 -92
  112. acoular/tests/test_integrate.py +0 -102
  113. acoular/tests/test_signals.py +0 -60
  114. acoular/tests/test_sources.py +0 -65
  115. acoular/tests/test_spectra.py +0 -38
  116. acoular/tests/test_timecache.py +0 -35
  117. acoular/tests/test_tprocess.py +0 -90
  118. acoular/tests/test_traj_beamformer_results.py +0 -164
  119. acoular/tests/unsupported/SpeedComparison/OvernightTestcasesBeamformer_nMics32_nGridPoints100_nFreqs4_nTrials10.png +0 -0
  120. acoular/tests/unsupported/SpeedComparison/cythonBeamformer.pyx +0 -237
  121. acoular/tests/unsupported/SpeedComparison/mainForCython.py +0 -103
  122. acoular/tests/unsupported/SpeedComparison/mainForParallelJit.py +0 -143
  123. acoular/tests/unsupported/SpeedComparison/setupCythonOpenMP.py +0 -63
  124. acoular/tests/unsupported/SpeedComparison/sharedFunctions.py +0 -153
  125. acoular/tests/unsupported/SpeedComparison/timeOverNMics_AllImportantMethods.png +0 -0
  126. acoular/tests/unsupported/SpeedComparison/timeOverNMics_faverage.png +0 -0
  127. acoular/tests/unsupported/SpeedComparison/vglOptimierungFAverage.py +0 -204
  128. acoular/tests/unsupported/SpeedComparison/vglOptimierungGaussSeidel.py +0 -182
  129. acoular/tests/unsupported/SpeedComparison/vglOptimierungR_BEAMFULL_INVERSE.py +0 -764
  130. acoular/tests/unsupported/SpeedComparison/vglOptimierungR_BEAM_OS.py +0 -231
  131. acoular/tests/unsupported/SpeedComparison/whatsFastestWayFor_absASquared.py +0 -48
  132. acoular/tests/unsupported/functionalBeamformer.py +0 -123
  133. acoular/tests/unsupported/precisionTest.py +0 -153
  134. acoular/tests/unsupported/validationOfBeamformerFuncsPOSTAcoularIntegration.py +0 -254
  135. acoular/tests/unsupported/validationOfBeamformerFuncsPREeAcoularIntegration.py +0 -531
  136. acoular/tools.py +0 -422
  137. acoular-24.3.dist-info/RECORD +0 -148
  138. acoular-24.3.dist-info/licenses/LICENSE +0 -29
  139. {acoular-24.3.dist-info → acoular-24.7.dist-info}/licenses/AUTHORS.rst +0 -0
acoular/tprocess.py CHANGED
@@ -1,8 +1,6 @@
1
- # -*- coding: utf-8 -*-
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
- from numpy import array, empty, empty_like, pi, sin, sqrt, zeros, newaxis, unique, \
41
- int16, nan, concatenate, sum, float64, identity, argsort, interp, arange, append, \
42
- linspace, flatnonzero, argmin, argmax, delete, mean, inf, asarray, stack, sinc, exp, \
43
- polymul, arange, cumsum, ceil, split, array_equal
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 scipy.interpolate import LinearNDInterpolator,splrep, splev, \
50
- CloughTocher2DInterpolator, CubicSpline, Rbf
51
- from traits.api import HasPrivateTraits, Float, Int, CLong, Bool, ListInt, \
52
- Constant, File, Property, Instance, Trait, Delegate, Str, \
53
- cached_property, on_trait_change, List, CArray, Dict, PrefixMap, Callable,\
54
- observe
55
-
56
- from scipy.fft import rfft, irfft
57
- import numba as nb
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 datetime import datetime
60
- from os import path
61
- import wave
62
- from scipy.signal import butter, filtfilt, bilinear, tf2sos, sosfilt, sosfiltfilt
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( HasPrivateTraits ):
78
- """
79
- Base class for any generating signal processing block
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
- desc="sampling frequency")
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 = ['sample_freq', 'numchannels', 'numsamples'])
99
-
100
- def _get_digest( self ):
101
- return digest( self )
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
- Python generator that yields the output block-wise.
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( SamplesGenerator ):
122
- """
123
- Base class for any time domain signal processing block,
124
- gets samples from :attr:`source` and generates output via the
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( depends_on = ['source.digest'])
182
+ digest = Property(depends_on=['source.digest'])
142
183
 
143
184
  @cached_property
144
- def _get_digest( self ):
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
- for temp in self.source.result(num):
154
- # effectively no processing
155
- yield temp
193
+ yield from self.source.result(num)
156
194
 
157
195
 
158
- class MaskedTimeInOut ( TimeInOut ):
159
- """
160
- Signal processing block for channel and sample selection.
161
-
162
- This class serves as intermediary to define (in)valid
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
- desc="start of valid samples")
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
- desc="stop of valid samples")
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
- desc="list of invalid channels")
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 = ['invalid_channels', 'source.numchannels'],
183
- desc="channel mask")
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 = ['invalid_channels', \
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 = ['start', 'stop', 'source.numsamples'],
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( depends_on = 'source.digest',
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( depends_on = ['source.digest', 'start', 'stop', \
205
- 'invalid_channels'])
234
+ digest = Property(depends_on=['source.digest', 'start', 'stop', 'invalid_channels'])
206
235
 
207
236
  @cached_property
208
- def _get_digest( self ):
237
+ def _get_digest(self):
209
238
  return digest(self)
210
239
 
211
240
  @cached_property
212
- def _get_basename( self ):
241
+ def _get_basename(self):
213
242
  if 'basename' in self.source.all_trait_names():
214
243
  return self.source.basename
215
- else:
216
- return self.source.__class__.__name__ + self.source.digest
217
-
244
+ return self.source.__class__.__name__ + self.source.digest
245
+
218
246
  @cached_property
219
- def _get_channels( self ):
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 (i in self.invalid_channels)]
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( self ):
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( self ):
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
- Python generator that yields the output block-wise.
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
- raise IOError("no samples available")
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: offset = num
260
- buf = empty((num + offset , self.numchannels), dtype=float)
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 : # first block in the chosen interval
268
- if i>= stop: # special case that start and stop are in one block
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 += (i-start)
272
- buf[:(i-start),:] = block[bs-(i-start):,self.channels]
301
+ bsize += i - start
302
+ buf[: (i - start), :] = block[bs - (i - start) :, self.channels]
273
303
  fblock = False
274
- elif i >= stop: # last block
275
- buf[bsize:bsize+bs-(i-stop),:] = block[:bs-(i-stop),self.channels]
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,:] = buf[num:bsize,:]
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,:] = block[:,self.channels]
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,:] = buf[num:bsize,:]
318
+ buf[: bsize - num, :] = buf[num:bsize, :]
289
319
  bsize -= num
290
-
291
- else: # if no start/stop given, don't do the resorting thing
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( TimeInOut ):
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="channel weights")
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( depends_on = ['source.digest', 'weights'])
338
+ digest = Property(depends_on=['source.digest', 'weights'])
310
339
 
311
340
  @cached_property
312
- def _get_digest( self ):
313
- return digest(self)
341
+ def _get_digest(self):
342
+ return digest(self)
314
343
 
315
344
  def result(self, num):
316
- """
317
- Python generator that yields the output block-wise.
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
- raise ValueError("Weight factors can not be broadcasted: %s, %s" % \
335
- (self.weights.shape, (self.source.numchannels,)))
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(depends_on=['source.digest', 'threshold', 'max_variation_of_duration', \
411
- 'hunk_length', 'trigger_type', 'multiple_peaks_in_hunk'])
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(depends_on=['source.digest', 'threshold', 'max_variation_of_duration', \
415
- 'hunk_length', 'trigger_type', 'multiple_peaks_in_hunk'])
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( self ):
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' : self._trigger_dirac,
425
- 'rect' : self._trigger_rect}[self.trigger_type]
426
- nSamples = 2048 # number samples for result-method of source
427
- threshold = self._threshold(nSamples)
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
- triggerData = array([])
475
+ trigger_data = array([])
432
476
  x0 = []
433
477
  dSamples = 0
434
- for triggerSignal in self.source.result(nSamples):
478
+ for triggerSignal in self.source.result(num):
435
479
  localTrigger = flatnonzero(triggerFunc(x0, triggerSignal, threshold))
436
- if not len(localTrigger) == 0:
480
+ if len(localTrigger) != 0:
437
481
  peakLoc = append(peakLoc, localTrigger + dSamples)
438
- triggerData = append(triggerData, triggerSignal[localTrigger])
439
- dSamples += nSamples
482
+ trigger_data = append(trigger_data, triggerSignal[localTrigger])
483
+ dSamples += num
440
484
  x0 = triggerSignal[-1]
441
485
  if len(peakLoc) <= 1:
442
- raise Exception('Not enough trigger info. Check *threshold* sign and value!')
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 = triggerData[indHelp]
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
- triggerData = delete(triggerData, deleteInd)
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('In Trigger-Identification: The distances between the peaks (and therefor the lengths of the revolutions) vary too much (check samples %s).' % str(peakLoc[faultyInd] + self.source.start), Warning, stacklevel = 2)
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
- #indPeakHunk = abs(xNew[1:] - xNew[:-1]) > abs(threshold) # with this line: every edge would be located
481
- indPeakHunk = self._trigger_value_comp(xNew[1:] - xNew[:-1], threshold)
482
- return indPeakHunk
483
-
484
- def _trigger_value_comp(self, triggerData, threshold):
485
- if threshold > 0.0:
486
- indPeaks= triggerData > threshold
487
- else:
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 triggerData in self.source.result(nSamples):
499
- maxVal = max(maxVal, triggerData.max())
500
- minVal = min(minVal, triggerData.min())
501
- meanVal += triggerData.mean()
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 = 2)
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
- Calculates rotation angle and rpm per sample from a trigger signal
523
- using spline interpolation in the time domain.
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(depends_on=['source.digest',
537
- 'trigger.digest',
538
- 'trigger_per_revo',
539
- 'rot_direction',
540
- 'interp_points',
541
- 'start_angle'])
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
- desc ="trigger signals per revolution")
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
- desc ="mathematical direction of rotation")
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
- desc ="Points of interpolation used for spline")
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
- desc ="rotation angle for trigger position")
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( depends_on = 'digest', desc ="revolutions per minute for each sample")
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( depends_on = 'digest', desc ="average revolutions per minute")
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( depends_on = 'digest', desc ="rotation angle for each sample")
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
- @cached_property
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
- idx = (abs(peakarray - value)).argmin()
591
- return idx
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
- #init
603
- ind=0
604
- #trigger data
605
- peakloc,maxdist,mindist= self.trigger._get_trigger_data()
606
- TriggerPerRevo= self.trigger_per_revo
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
- nSamples = self.source.numsamples
609
- samplerate = self.source.sample_freq
610
- self._rpm = zeros(nSamples)
611
- self._angle = zeros(nSamples)
612
- #number of spline points
613
- InterpPoints=self.interp_points
614
-
615
- #loop over alle timesamples
616
- while ind < nSamples :
617
- #when starting spline forward
618
- if ind<peakloc[InterpPoints]:
619
- peakdist=peakloc[self._find_nearest_idx(peakarray= peakloc,value=ind)+1] - peakloc[self._find_nearest_idx(peakarray= peakloc,value=ind)]
620
- splineData = stack((range(InterpPoints), peakloc[ind//peakdist:ind//peakdist+InterpPoints]), axis=0)
621
- #spline backwards
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=peakloc[self._find_nearest_idx(peakarray= peakloc,value=ind)] - peakloc[self._find_nearest_idx(peakarray= peakloc,value=ind)-1]
624
- splineData = stack((range(InterpPoints), peakloc[ind//peakdist-InterpPoints:ind//peakdist]), axis=0)
625
- #calc angles and rpm
626
- Spline = splrep(splineData[:,:][1], splineData[:,:][0], k=3)
627
- self._rpm[ind]=splev(ind, Spline, der=1, ext=0)*60*samplerate
628
- self._angle[ind] = (splev(ind, Spline, der=0, ext=0)*2*pi*rotDirection/TriggerPerRevo + self.start_angle) % (2*pi)
629
- #next sample
630
- ind+=1
631
- #calculation complete
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( self ):
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( self ):
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( self ):
656
- """
657
- Returns average revolutions per minute (rpm) over the source samples.
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._get_trigger_data()[0]
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
- Base class for spatial interpolation of microphone data.
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
- desc="microphone geometry")
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
- desc="microphone geometry")
682
-
683
- _mics_virtual = Instance(MicGeom,
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('linear', 'spline', 'rbf-multiquadric', 'rbf-cubic','IDW',\
704
- 'custom', 'sinc', desc="method for interpolation used")
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
- 'ring', '3D', 'custom', desc="spacial dimensionality of the array geometry")
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 = Bool(False)
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
- p_weight = Trait(2,dtype = float,\
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(depends_on=['mics.digest',
739
- 'mics_virtual.digest',
740
- 'method','array_dimension',
741
- 'interp_at_zero'])
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(depends_on=['mics.digest', 'mics_virtual.digest', 'source.digest', \
745
- 'method','array_dimension', 'Q', 'interp_at_zero'])
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( self ):
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
- Modified Sinc function for Radial Basis function approximation
762
-
763
- """
764
- return sinc((r*self.mics_virtual.mpos.shape[1])/(pi))
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
- mic : float[3, nPhysicalMics]
833
+ mpos : float[3, nPhysicalMics]
773
834
  The mic positions of the physical (really existing) mics
774
- micVirt : float[3, nVirtualMics]
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 = micVirt.shape[1]
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 = mic.shape[1]
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(micVirt,self.Q)
832
-
833
- #new coordinates of real mics
834
- indReorderHelp = argsort(cartToCyl(mic,self.Q)[0])
835
- newCoord = cartToCyl(mic,self.Q)
836
-
837
- #scipy delauney triangulation
838
- #Delaunay
839
- tri = Delaunay(newCoord.T[:,:2], incremental=True) #
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 ], [0]]).T)
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(micVirt,self.Q)
932
+ virtNewCoord = cartToCyl(mpos_virt, self.Q)
876
933
  # get real mic projections on new coord system
877
- indReorderHelp = argsort(cartToCyl(mic,self.Q)[0])
878
- newCoord = (cartToCyl(mic,self.Q))
879
- #Delaunay
880
- tri =Delaunay(newCoord.T, incremental=True) #, incremental=True,qhull_options = "Qc QJ Q12"
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 ], [0], [0]]).T)
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
- def _result_core_func(self, p, phiDelay=[], period=None, Q=Q, interp_at_zero = False):
914
- """
915
- Performs the actual Interpolation
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[nSamples, nMicsReal]
974
+ p : float[num, nMicsReal]
920
975
  The pressure field of the yielded sample at real mics.
921
- phiDelay : empty list (default) or float[nSamples]
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[nSamples, nMicsVirtual]
985
+ pInterp : float[num, nMicsVirtual]
931
986
  The interpolated time data at the virtual mics
987
+
932
988
  """
933
-
934
- #number of time samples
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(cylToCart(newCoord[:,argsort(newCoord[0])])[:2,:].T,
948
- p[:, (argsort(newCoord[0]))].T, fill_value = 0)
949
- pZero = interpolater((0,0))
950
- #add the interpolated pressure at origin to pressure channels
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 phidelay
960
- if not array_equal(phiDelay,[]):
961
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
962
- xInterp = ((xInterpHelp + pi ) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, 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(xInterp[cntTime, :], x, pHelp[cntTime, :], period=period, left=nan, right=nan)
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(append(x,(2*pi)+x[0]), append(pHelp[cntTime, :],pHelp[cntTime, :][0]), axis=0, bc_type='periodic', extrapolate=None)
978
- pInterp[cntTime] = SplineInterp(xInterp[cntTime, :])
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(x,newCoord[1],
983
- newCoord[2] ,
984
- pHelp[cntTime, :], function=self.sinc_mic) # radial basis function interpolator instance
985
-
986
- pInterp[cntTime] = rbfi(xInterp[cntTime, :],
987
- virtNewCoord[1],
988
- virtNewCoord[2])
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(x,newCoord[1],
993
- newCoord[2] ,
994
- pHelp[cntTime, :], function='cubic') # radial basis function interpolator instance
995
-
996
- pInterp[cntTime] = rbfi(xInterp[cntTime, :],
997
- virtNewCoord[1],
998
- virtNewCoord[2])
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(phiDelay,[]):
1005
- xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
1006
- xInterp = ((xInterpHelp + pi ) % (2 * pi)) - pi #shifting phi cootrdinate into feasible area [-pi, 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 = 0)
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 = 0)
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(newCoord[0],
1028
- newCoord[1],
1029
- newCoord[2] ,
1030
- pHelp[cntTime, :len(newCoord[0])], function=self.sinc_mic) # radial basis function interpolator instance
1031
-
1032
- pInterp[cntTime] = rbfi(xInterp[cntTime, :],
1033
- virtNewCoord[1],
1034
- virtNewCoord[2])
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( newCoord[0],
1040
- newCoord[1],
1041
- newCoord[2],
1042
- pHelp[cntTime, :len(newCoord[0])], function='cubic') # radial basis function interpolator instance
1043
-
1044
- virtshiftcoord= array([xInterp[cntTime, :],virtNewCoord[1], virtNewCoord[2]])
1045
- pInterp[cntTime] = rbfi(virtshiftcoord[0],
1046
- virtshiftcoord[1],
1047
- virtshiftcoord[2])
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(newCoord[0],
1052
- newCoord[1],
1053
- newCoord[2],
1054
- pHelp[cntTime, :len(newCoord[0])], function='multiquadric') # radial basis function interpolator instance
1055
-
1056
- virtshiftcoord= array([xInterp[cntTime, :],virtNewCoord[1], virtNewCoord[2]])
1057
- pInterp[cntTime] = rbfi(virtshiftcoord[0],
1058
- virtshiftcoord[1],
1059
- virtshiftcoord[2])
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((1 / normNew ** self.p_weight))
1076
- weight = (1 / normNew ** self.p_weight) / wholeD
1077
- weight_sum = sum(weight)
1078
- pInterp[cntTime,ind] = sum(pHelpNew * weight)
1079
-
1080
-
1081
- # Interpolation for arbitrary 3D Arrays
1082
- elif self.array_dimension =='3D':
1083
- #check rotation
1084
- if not array_equal(phiDelay,[]):
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 = 0)
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(newCoord[0],
1102
- newCoord[1],
1103
- newCoord[2],
1104
- pHelp[cntTime, :len(newCoord[0])], function=self.sinc_mic) # radial basis function interpolator instance
1105
-
1106
- pInterp[cntTime] = rbfi(xInterp[cntTime, :],
1107
- virtNewCoord[1],
1108
- virtNewCoord[2])
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(newCoord[0],
1113
- newCoord[1],
1114
- newCoord[2],
1115
- pHelp[cntTime, :len(newCoord[0])], function='cubic') # radial basis function interpolator instance
1116
-
1117
- pInterp[cntTime] = rbfi(xInterp[cntTime, :],
1118
- virtNewCoord[1],
1119
- virtNewCoord[2])
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(newCoord[0],
1124
- newCoord[1],
1125
- newCoord[2],
1126
- pHelp[cntTime, :len(newCoord[0])], function='multiquadric') # radial basis function interpolator instance
1127
-
1128
- pInterp[cntTime] = rbfi(xInterp[cntTime, :],
1129
- virtNewCoord[1],
1130
- virtNewCoord[2])
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
- Spatial Interpolation for rotating sources. Gets samples from :attr:`source`
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( depends_on = ['source.digest', 'angle_source.digest',\
1148
- 'mics.digest', 'mics_virtual.digest', \
1149
- 'method','array_dimension', 'Q', 'interp_at_zero'])
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( self ):
1153
- return digest(self)
1154
-
1235
+ def _get_digest(self):
1236
+ return digest(self)
1237
+
1155
1238
  def result(self, num=128):
1156
- """
1157
- Python generator that yields the output block-wise.
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._get_angle()
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
- phiDelay = angle[count:count+num]
1178
- interpVal = self._result_core_func(timeData, phiDelay, period, self.Q, interp_at_zero = False)
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
- Spatial linear Interpolation for constantly rotating sources.
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( depends_on = ['source.digest','mics.digest', \
1194
- 'mics_virtual.digest','method','array_dimension', \
1195
- 'Q', 'interp_at_zero','rotational_speed'])
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( self ):
1291
+ def _get_digest(self):
1199
1292
  return digest(self)
1200
-
1201
-
1293
+
1202
1294
  def result(self, num=1):
1203
- """
1204
- Python generator that yields the output block-wise.
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
- phiDelay = phiOffset + linspace(0, nTime / self.sample_freq * omega, nTime, endpoint=False)
1223
- interpVal = self._result_core_func(timeData, phiDelay, period, self.Q, interp_at_zero = False)
1224
- phiOffset = phiDelay[-1] + omega / self.sample_freq
1225
- yield interpVal
1226
-
1227
-
1228
- class Mixer( TimeInOut ):
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( Instance(SamplesGenerator, ()) )
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( self, event ):
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( depends_on = ['source.digest','sdigest'])
1347
+ digest = Property(depends_on=['source.digest', 'sdigest'])
1258
1348
 
1259
1349
  @cached_property
1260
- def _get_digest( self ):
1350
+ def _get_digest(self):
1261
1351
  return digest(self)
1262
1352
 
1263
- def validate_sources( self ):
1264
- """ validates if sources fit together """
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("Sample frequency of %s does not fit" % s)
1358
+ raise ValueError('Sample frequency of %s does not fit' % s)
1269
1359
  if self.numchannels != s.numchannels:
1270
- raise ValueError("Channel count of %s does not fit" % s)
1360
+ raise ValueError('Channel count of %s does not fit' % s)
1271
1361
 
1272
1362
  def result(self, num):
1273
- """
1274
- Python generator that yields the output block-wise.
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( TimeInOut ):
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
- Python generator that yields the output block-wise.
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
- class TimeAverage( TimeInOut ) :
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
- desc = "number of samples to average over")
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( depends_on = 'source.sample_freq, naverage')
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( depends_on = 'source.numsamples, naverage')
1346
-
1431
+ numsamples = Property(depends_on='source.numsamples, naverage')
1432
+
1347
1433
  # internal identifier
1348
- digest = Property( depends_on = ['source.digest', '__class__', 'naverage'])
1434
+ digest = Property(depends_on=['source.digest', '__class__', 'naverage'])
1349
1435
 
1350
1436
  @cached_property
1351
- def _get_digest( self ):
1437
+ def _get_digest(self):
1352
1438
  return digest(self)
1353
-
1439
+
1354
1440
  @cached_property
1355
- def _get_sample_freq ( self ):
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 ( self ):
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
- class TimeReverse( TimeInOut ):
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
- l = []
1441
- l.extend(self.source.result(num))
1442
- temp = empty_like(l[0])
1443
- h = l.pop()
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 l[::-1]:
1447
- temp[nsh:] = h[:nsh-1:-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::-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( self ):
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 # this line is useful in case of changes
1487
- # to self.sos during generator lifetime
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
- class FiltOctave( Filter ):
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
- desc = "band center frequency")
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 = "IIR filter order")
1505
-
1506
- sos = Property( depends_on = ['band', 'fraction', 'source.digest', 'order'])
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( depends_on = ['source.digest', '__class__', \
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( self ):
1595
+ def _get_digest(self):
1514
1596
  return digest(self)
1515
-
1597
+
1516
1598
  @cached_property
1517
- def _get_sos( self ):
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
- raise ValueError("band frequency too high:%f,%f" % (self.band, fs))
1530
- om1 = fr/alpha
1531
- om2 = fr*alpha
1532
- return butter(self.order, [om1, om2], 'bandpass', output = 'sos')
1533
-
1534
- class FiltFiltOctave( FiltOctave ):
1535
- """
1536
- Octave or third-octave filter with zero phase delay.
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 = "IIR filter half order")
1626
+ order = Int(2, desc='IIR filter half order')
1543
1627
 
1544
1628
  # internal identifier
1545
- digest = Property( depends_on = ['source.digest', '__class__', \
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( self ):
1632
+ def _get_digest(self):
1550
1633
  return digest(self)
1551
-
1634
+
1552
1635
  @cached_property
1553
- def _get_sos( self ):
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)**(3/self.fraction_)
1563
- fr = 2*self.band/fs
1564
- if fr > 1/sqrt(2):
1565
- raise ValueError("band frequency too high:%f,%f" % (self.band, fs))
1566
- om1 = fr/alpha
1567
- om2 = fr*alpha
1568
- return butter(self.order, [om1, om2], 'bandpass', output = 'sos')
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
- desc = "time weighting")
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( depends_on = ['source.digest', '__class__', \
1618
- 'weight'])
1701
+ digest = Property(depends_on=['source.digest', '__class__', 'weight'])
1619
1702
 
1620
1703
  @cached_property
1621
- def _get_digest( self ):
1704
+ def _get_digest(self):
1622
1705
  return digest(self)
1623
-
1706
+
1624
1707
  @cached_property
1625
- def _get_sos( self ):
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="frequency weighting")
1719
+ weight = Trait('A', ('A', 'C', 'Z'), desc='frequency weighting')
1637
1720
 
1638
- sos = Property( depends_on = ['weight', 'source.digest'])
1721
+ sos = Property(depends_on=['weight', 'source.digest'])
1639
1722
 
1640
1723
  # internal identifier
1641
- digest = Property( depends_on = ['source.digest', '__class__', \
1642
- 'weight'])
1724
+ digest = Property(depends_on=['source.digest', '__class__', 'weight'])
1643
1725
 
1644
1726
  @cached_property
1645
- def _get_digest( self ):
1727
+ def _get_digest(self):
1646
1728
  return digest(self)
1647
1729
 
1648
1730
  @cached_property
1649
- def _get_sos( self ):
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) , 0, 0, 0, 0]
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) , 0, 0]
1663
- b,a = bilinear(b,a,self.sample_freq)
1664
- b = append(b,zeros(2)) # make 6th order
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 # 6th order flat response
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
- Abstract base class for IIR filter banks based on scipy lfilter
1675
- implements a bank of parallel filters
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( self ):
1693
- return [tf2sos([1],[1])]
1773
+ def _get_sos(self):
1774
+ return [tf2sos([1], [1])]
1694
1775
 
1695
- def _get_bands( self ):
1776
+ def _get_bands(self):
1696
1777
  return ['']
1697
1778
 
1698
- def _get_numbands( self ):
1779
+ def _get_numbands(self):
1699
1780
  return 0
1700
1781
 
1701
- def _get_numchannels( self ):
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
- Python generator that yields the output block-wise.
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( (sos[0].shape[0],2, snumch)) for _ in range(numbands)]
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
- Octave or third-octave filter bank
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
- desc = "lowest band center frequency index")
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( depends_on = ['lband', 'hband', 'fraction', 'source.digest'])
1825
+ ba = Property(depends_on=['lband', 'hband', 'fraction', 'source.digest'])
1749
1826
 
1750
1827
  #: List of labels for bands
1751
- bands = Property(depends_on = ['lband', 'hband', 'fraction'])
1828
+ bands = Property(depends_on=['lband', 'hband', 'fraction'])
1752
1829
 
1753
1830
  #: Number of bands
1754
- numbands = Property(depends_on = ['lband', 'hband', 'fraction'])
1755
-
1756
- # internal identifier
1757
- digest = Property( depends_on = ['source.digest', '__class__', \
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( self ):
1837
+ def _get_digest(self):
1762
1838
  return digest(self)
1763
1839
 
1764
1840
  @cached_property
1765
- def _get_bands( self ):
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( self ):
1845
+ def _get_numbands(self):
1770
1846
  return len(self.bands)
1771
1847
 
1772
1848
  @cached_property
1773
- def _get_sos( self ):
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
- class TimeCache( TimeInOut ):
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( depends_on = 'digest')
1788
-
1863
+ basename = Property(depends_on='digest')
1864
+
1789
1865
  # hdf5 cache file
1790
- h5f = Instance( H5CacheFileBase, transient = True )
1791
-
1866
+ h5f = Instance(H5CacheFileBase, transient=True)
1867
+
1792
1868
  # internal identifier
1793
- digest = Property( depends_on = ['source.digest', '__class__'])
1869
+ digest = Property(depends_on=['source.digest', '__class__'])
1794
1870
 
1795
1871
  @cached_property
1796
- def _get_digest( self ):
1872
+ def _get_digest(self):
1797
1873
  return digest(self)
1798
1874
 
1799
1875
  @cached_property
1800
- def _get_basename ( self ):
1801
- obj = self.source # start with source
1802
- basename = 'void' # if no file source is found
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(): # at original source?
1805
- basename = obj.basename # get the name
1880
+ if 'basename' in obj.all_trait_names(): # at original source?
1881
+ basename = obj.basename # get the name
1806
1882
  break
1807
- else:
1808
- try:
1809
- obj = obj.source # traverse down until original data source
1810
- except AttributeError:
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
- for data in self.source.result(num):
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( self, self.basename )
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("Cache file is incomplete for nodename %s. With config.global_caching='readonly', the cache file will not be used!" %str(nodename), Warning, stacklevel = 1)
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
- for temp in generator(num):
1907
- yield temp
1981
+ yield from generator(num)
1908
1982
 
1909
1983
 
1910
- class WriteWAV( TimeInOut ):
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
- desc="name of wave file")
1920
-
1991
+ name = File(filter=['*.wav'], desc='name of wave file')
1992
+
1921
1993
  #: Basename for cache, readonly.
1922
- basename = Property( depends_on = 'digest')
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="channel to save")
1926
-
1997
+ channels = ListInt(desc='channel to save')
1998
+
1927
1999
  # internal identifier
1928
- digest = Property( depends_on = ['source.digest', 'channels', '__class__'])
2000
+ digest = Property(depends_on=['source.digest', 'channels', '__class__'])
1929
2001
 
1930
2002
  @cached_property
1931
- def _get_digest( self ):
2003
+ def _get_digest(self):
1932
2004
  return digest(self)
1933
2005
 
1934
2006
  @cached_property
1935
- def _get_basename ( self ):
1936
- obj = self.source # start with 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(): # at original source?
1940
- basename = obj.basename # get the name
2011
+ if 'basename' in obj.all_trait_names(): # at original source?
2012
+ basename = obj.basename # get the name
1941
2013
  break
1942
- else:
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' # if no file source is found
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
- raise ValueError("No channels given for output.")
2025
+ msg = 'No channels given for output.'
2026
+ raise ValueError(msg)
1957
2027
  if nc > 2:
1958
- warn("More than two channels given for output, exported file will have %i channels" % nc)
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
- class WriteH5( TimeInOut ):
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( depends_on = ['source.digest', '__class__'])
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( self ):
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 = 'w')
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
- """ adds metadata to .h5 file """
2103
+ """Adds metadata to .h5 file."""
2041
2104
  nitems = len(self.metadata.items())
2042
2105
  if nitems > 0:
2043
- f5h.create_new_group("metadata","/")
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 not stotal == -1 and sleft > 0:
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
- class LockedGenerator():
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): # this function implementation is not python 2 compatible!
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
- class SampleSplitter(TimeInOut):
2107
- '''
2108
- This class distributes data blocks from source to several following objects.
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(key_trait=Instance(SamplesGenerator),
2127
- value_trait=Trait('error','warning','none'),
2128
- desc='defines buffer overflow behaviour.')
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 obj in self.block_buffer.keys():
2157
- raise IOError("calling object %s is not registered." %obj)
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.keys()
2161
- if not self.buffer_overflow_treatment[obj] == 'none']
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.keys(): self._clear_block_buffer(obj)
2175
- self._buffer_overflow = False # reset overflow bool
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 # indicates full generator
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.keys()]
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.keys():
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,*objects_to_register):
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.keys():
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,*objects_to_remove):
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
- calling_obj = currentframe().f_back.f_locals['self']
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
- raise IOError('Maximum size of block buffer is reached!')
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="Convolution kernel.")
2308
+ kernel = CArray(dtype=float, desc='Convolution kernel.')
2255
2309
 
2256
- _block_size = Int(desc="Block size")
2310
+ _block_size = Int(desc='Block size')
2257
2311
 
2258
2312
  _kernel_blocks = Property(
2259
- depends_on=["kernel", "_block_size"],
2260
- desc="Frequency domain Kernel blocks",
2313
+ depends_on=['kernel', '_block_size'],
2314
+ desc='Frequency domain Kernel blocks',
2261
2315
  )
2262
2316
 
2263
2317
  # internal identifier
2264
- digest = Property( depends_on = ['source.digest', 'kernel', '__class__'])
2318
+ digest = Property(depends_on=['source.digest', 'kernel', '__class__'])
2265
2319
 
2266
2320
  @cached_property
2267
- def _get_digest( self ):
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
- elif self.kernel.ndim > 2:
2277
- raise ValueError("Only one or two dimensional kernels accepted.")
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
- raise ValueError("Number of kernels must be either `numchannels` or one.")
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="complex128")
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),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
- P = int(ceil(L / num)) # number of kernel blocks
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 # size of final block
2382
+ last_size = (L + M - 1) % num # size of final block
2329
2383
 
2330
2384
  idx = 0
2331
- FDL = zeros([P, num + 1, N], dtype="complex128")
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="complex128")
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 # append new time-data
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
- _append_to_FDL(FDL, idx, P, rfft(buff,axis=0))
2342
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks)
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
- _append_to_FDL(FDL, idx, P, rfft(buff,axis=0))
2350
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks )
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])], axis=0
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 # append new time-data
2410
+ buff[num : num + temp.shape[0]] = temp # append new time-data
2356
2411
 
2357
- for _ in range(R-Q):
2358
- _append_to_FDL(FDL, idx, P, rfft(buff,axis=0))
2359
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks )
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])], axis=0
2417
+ [buff[num:], zeros([num, N])],
2418
+ axis=0,
2363
2419
  ) # shift input buffer to the left
2364
2420
 
2365
- _append_to_FDL(FDL, idx, P, rfft(buff,axis=0))
2366
- spec_sum = _spectral_sum(spec_sum, FDL, self._kernel_blocks )
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 _append_to_FDL(FDL,idx,P,buff):
2372
- FDL[idx] = buff
2373
- idx = int(idx +1 % P)
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,FDL,KB):
2377
- P,B,N = KB.shape
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] += FDL[i,b,n]*KB[i,b,n]
2440
+ out[b, n] += fdl[i, b, n] * kb[i, b, n]
2383
2441
 
2384
2442
  return out