acoular 24.7__py3-none-any.whl → 25.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
acoular/sdinput.py CHANGED
@@ -9,16 +9,19 @@
9
9
  SoundDeviceSamplesGenerator
10
10
  """
11
11
 
12
- from traits.api import Any, Bool, Int, Long, Property, cached_property, observe
12
+ from traits.api import Any, Bool, Enum, Float, Int, Property, cached_property, observe
13
13
 
14
+ # acoular imports
15
+ from .base import SamplesGenerator
14
16
  from .configuration import config
17
+ from .deprecation import deprecated_alias
15
18
  from .internal import digest
16
- from .tprocess import SamplesGenerator
17
19
 
18
20
  if config.have_sounddevice:
19
21
  import sounddevice as sd
20
22
 
21
23
 
24
+ @deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples', 'collectsamples': 'collect_samples'})
22
25
  class SoundDeviceSamplesGenerator(SamplesGenerator):
23
26
  """Controller for sound card hardware using sounddevice library.
24
27
 
@@ -37,18 +40,23 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
37
40
  device = Int(0, desc='input device index')
38
41
 
39
42
  #: Number of input channels, maximum depends on device
40
- numchannels = Long(1, desc='number of analog input channels that collects data')
43
+ num_channels = Int(1, desc='number of analog input channels that collects data')
41
44
 
42
- #: Number of samples to collect; defaults to -1.
43
- # If is set to -1 device collects till user breaks streaming by setting Trait: collectsamples = False
44
- numsamples = Long(-1, desc='number of samples to collect')
45
+ #: Number of samples to collect; defaults to -1. If is set to -1 device collects until user
46
+ # breaks streaming by setting Trait: collect_samples = False.
47
+ num_samples = Int(-1, desc='number of samples to collect')
45
48
 
46
49
  #: Indicates if samples are collected, helper trait to break result loop
47
- collectsamples = Bool(True, desc='Indicates if samples are collected')
50
+ collect_samples = Bool(True, desc='Indicates if samples are collected')
48
51
 
49
52
  #: Sampling frequency of the signal, changes with sinusdevices
50
53
  sample_freq = Property(desc='sampling frequency')
51
54
 
55
+ _sample_freq = Float(default_value=None)
56
+
57
+ #: Datatype (resolution) of the signal, used as `dtype` in a sd `Stream` object
58
+ precision = Enum('float32', 'float16', 'int32', 'int16', 'int8', 'uint8', desc='precision (resolution) of signal')
59
+
52
60
  #: Indicates that the sounddevice buffer has overflown
53
61
  overflow = Bool(False, desc='Indicates if sounddevice buffer overflow')
54
62
 
@@ -59,19 +67,24 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
59
67
  stream = Any
60
68
 
61
69
  # internal identifier
62
- digest = Property(depends_on=['device', 'numchannels', 'numsamples'])
70
+ digest = Property(depends_on=['device', 'num_channels', 'num_samples'])
63
71
 
64
72
  @cached_property
65
73
  def _get_digest(self):
66
74
  return digest(self)
67
75
 
68
- # checks that numchannels are not more than device can provide
69
- @observe('device,numchannels')
70
- def _get_numchannels(self, event): # noqa ARG002
71
- self.numchannels = min(self.numchannels, sd.query_devices(self.device)['max_input_channels'])
76
+ # checks that num_channels are not more than device can provide
77
+ @observe('device, num_channels')
78
+ def _get_num_channels(self, event): # noqa ARG002
79
+ self.num_channels = min(self.num_channels, sd.query_devices(self.device)['max_input_channels'])
72
80
 
73
81
  def _get_sample_freq(self):
74
- return sd.query_devices(self.device)['default_samplerate']
82
+ if self._sample_freq is None:
83
+ self._sample_freq = sd.query_devices(self.device)['default_samplerate']
84
+ return self._sample_freq
85
+
86
+ def _set_sample_freq(self, f):
87
+ self._sample_freq = f
75
88
 
76
89
  def device_properties(self):
77
90
  """Returns
@@ -92,29 +105,30 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
92
105
 
93
106
  Returns
94
107
  -------
95
- Samples in blocks of shape (num, :attr:`numchannels`).
108
+ Samples in blocks of shape (num, :attr:`num_channels`).
96
109
  The last block may be shorter than num.
97
110
 
98
111
  """
99
112
  print(self.device_properties(), self.sample_freq)
100
113
  self.stream = stream_obj = sd.InputStream(
101
114
  device=self.device,
102
- channels=self.numchannels,
115
+ channels=self.num_channels,
103
116
  clip_off=True,
104
117
  samplerate=self.sample_freq,
118
+ dtype=self.precision,
105
119
  )
106
120
 
107
121
  with stream_obj as stream:
108
122
  self.running = True
109
- if self.numsamples == -1:
110
- while self.collectsamples: # yield data as long as collectsamples is True
123
+ if self.num_samples == -1:
124
+ while self.collect_samples: # yield data as long as collect_samples is True
111
125
  data, self.overflow = stream.read(num)
112
126
  yield data[:num]
113
127
 
114
- elif self.numsamples > 0: # amount of samples to collect is specified by user
115
- samples_count = 0 # numsamples counter
116
- while samples_count < self.numsamples:
117
- anz = min(num, self.numsamples - samples_count)
128
+ elif self.num_samples > 0: # amount of samples to collect is specified by user
129
+ samples_count = 0 # num_samples counter
130
+ while samples_count < self.num_samples:
131
+ anz = min(num, self.num_samples - samples_count)
118
132
  data, self.overflow = stream.read(num)
119
133
  yield data[:anz]
120
134
  samples_count += anz
acoular/signals.py CHANGED
@@ -7,6 +7,8 @@
7
7
  :toctree: generated/
8
8
 
9
9
  SignalGenerator
10
+ PeriodicSignalGenerator
11
+ NoiseGenerator
10
12
  WNoiseGenerator
11
13
  PNoiseGenerator
12
14
  FiltWNoiseGenerator
@@ -16,20 +18,33 @@
16
18
  """
17
19
 
18
20
  # imports from other packages
21
+ from abc import abstractmethod
19
22
  from warnings import warn
20
23
 
21
24
  from numpy import arange, array, log, pi, repeat, sin, sqrt, tile, zeros
22
25
  from numpy.random import RandomState
23
26
  from scipy.signal import resample, sosfilt, tf2sos
24
- from traits.api import Bool, CArray, CLong, Delegate, Float, HasPrivateTraits, Int, Property, Trait, cached_property
25
-
26
- from .internal import digest
27
+ from traits.api import (
28
+ ABCHasStrictTraits,
29
+ Bool,
30
+ CArray,
31
+ CInt,
32
+ Delegate,
33
+ Float,
34
+ Instance,
35
+ Int,
36
+ Property,
37
+ cached_property,
38
+ )
27
39
 
28
40
  # acoular imports
29
- from .tprocess import SamplesGenerator
41
+ from .base import SamplesGenerator
42
+ from .deprecation import deprecated_alias
43
+ from .internal import digest
30
44
 
31
45
 
32
- class SignalGenerator(HasPrivateTraits):
46
+ @deprecated_alias({'numsamples': 'num_samples'})
47
+ class SignalGenerator(ABCHasStrictTraits):
33
48
  """Virtual base class for a simple one-channel signal generator.
34
49
 
35
50
  Defines the common interface for all SignalGenerator classes. This class
@@ -37,21 +52,20 @@ class SignalGenerator(HasPrivateTraits):
37
52
  should not be used directly as it contains no real functionality.
38
53
  """
39
54
 
40
- #: RMS amplitude of source signal (for point source: in 1 m distance).
41
- rms = Float(1.0, desc='rms amplitude')
42
-
43
55
  #: Sampling frequency of the signal.
44
56
  sample_freq = Float(1.0, desc='sampling frequency')
45
57
 
46
58
  #: Number of samples to generate.
47
- numsamples = CLong
59
+ num_samples = CInt
48
60
 
49
61
  # internal identifier
50
- digest = Property
62
+ digest = Property(depends_on=['sample_freq', 'num_samples'])
51
63
 
64
+ @abstractmethod
52
65
  def _get_digest(self):
53
- return ''
66
+ """Returns the internal identifier."""
54
67
 
68
+ @abstractmethod
55
69
  def signal(self):
56
70
  """Deliver the signal."""
57
71
 
@@ -69,14 +83,52 @@ class SignalGenerator(HasPrivateTraits):
69
83
  Returns
70
84
  -------
71
85
  array of floats
72
- The resulting signal of length `factor` * :attr:`numsamples`.
73
-
86
+ The resulting signal of length `factor` * :attr:`num_samples`.
74
87
  """
75
- return resample(self.signal(), factor * self.numsamples)
88
+ return resample(self.signal(), factor * self.num_samples)
76
89
 
77
90
 
78
- class WNoiseGenerator(SignalGenerator):
79
- """White noise signal generator."""
91
+ class PeriodicSignalGenerator(SignalGenerator):
92
+ """
93
+ Abstract base class for periodic signal generators.
94
+
95
+ Defines the common interface for all :class:`SignalGenerator`-derived classes with periodic
96
+ signals. This class may be used as a base for class handling periodic signals that can be
97
+ characterized by their frequency, phase and amplitude. It should not be used directly as it
98
+ contains no real functionality.
99
+ """
100
+
101
+ #: Frequency of the signal, float, defaults to 1000.0.
102
+ freq = Float(1000.0, desc='Frequency')
103
+
104
+ #: Phase of the signal (in radians), float, defaults to 0.0.
105
+ phase = Float(0.0, desc='Phase')
106
+
107
+ #: Amplitude of the signal. Defaults to 1.0.
108
+ amplitude = Float(1.0)
109
+
110
+ # internal identifier
111
+ digest = Property(depends_on=['amplitude', 'num_samples', 'sample_freq', 'freq', 'phase'])
112
+
113
+ @abstractmethod
114
+ def _get_digest(self):
115
+ """Returns the internal identifier."""
116
+
117
+ @abstractmethod
118
+ def signal(self):
119
+ """Deliver the signal."""
120
+
121
+
122
+ class NoiseGenerator(SignalGenerator):
123
+ """Abstract base class for noise signal generators.
124
+
125
+ Defines the common interface for all :class:`SignalGenerator` classes with noise signals. This
126
+ class may be used as a base for class handling noise signals that can be characterized by their
127
+ RMS amplitude. It should not be used directly as it contains no real functionality.
128
+ """
129
+
130
+ #: RMS amplitude of the signal.
131
+ rms = Float(1.0, desc='rms amplitude')
80
132
 
81
133
  #: Seed for random number generator, defaults to 0.
82
134
  #: This parameter should be set differently for different instances
@@ -84,9 +136,22 @@ class WNoiseGenerator(SignalGenerator):
84
136
  seed = Int(0, desc='random seed value')
85
137
 
86
138
  # internal identifier
87
- digest = Property(
88
- depends_on=['rms', 'numsamples', 'sample_freq', 'seed', '__class__'],
89
- )
139
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples'])
140
+
141
+ @abstractmethod
142
+ def _get_digest(self):
143
+ """Returns the internal identifier."""
144
+
145
+ @abstractmethod
146
+ def signal(self):
147
+ """Deliver the signal."""
148
+
149
+
150
+ class WNoiseGenerator(NoiseGenerator):
151
+ """White noise signal generator."""
152
+
153
+ # internal identifier
154
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples'])
90
155
 
91
156
  @cached_property
92
157
  def _get_digest(self):
@@ -98,14 +163,13 @@ class WNoiseGenerator(SignalGenerator):
98
163
  Returns
99
164
  -------
100
165
  Array of floats
101
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
102
-
166
+ The resulting signal as an array of length :attr:`~SignalGenerator.num_samples`.
103
167
  """
104
168
  rnd_gen = RandomState(self.seed)
105
- return self.rms * rnd_gen.standard_normal(self.numsamples)
169
+ return self.rms * rnd_gen.standard_normal(self.num_samples)
106
170
 
107
171
 
108
- class PNoiseGenerator(SignalGenerator):
172
+ class PNoiseGenerator(NoiseGenerator):
109
173
  """Pink noise signal generator.
110
174
 
111
175
  Simulation of pink noise is based on the Voss-McCartney algorithm.
@@ -118,33 +182,26 @@ class PNoiseGenerator(SignalGenerator):
118
182
  characteristic.
119
183
  """
120
184
 
121
- #: Seed for random number generator, defaults to 0.
122
- #: This parameter should be set differently for different instances
123
- #: to guarantee statistically independent (non-correlated) outputs.
124
- seed = Int(0, desc='random seed value')
125
-
126
185
  #: "Octave depth" -- higher values for 1/f spectrum at low frequencies,
127
186
  #: but longer calculation, defaults to 16.
128
187
  depth = Int(16, desc='octave depth')
129
188
 
130
189
  # internal identifier
131
- digest = Property(
132
- depends_on=['rms', 'numsamples', 'sample_freq', 'seed', 'depth', '__class__'],
133
- )
190
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'depth'])
134
191
 
135
192
  @cached_property
136
193
  def _get_digest(self):
137
194
  return digest(self)
138
195
 
139
196
  def signal(self):
140
- nums = self.numsamples
197
+ nums = self.num_samples
141
198
  depth = self.depth
142
199
  # maximum depth depending on number of samples
143
200
  max_depth = int(log(nums) / log(2))
144
201
 
145
202
  if depth > max_depth:
146
203
  depth = max_depth
147
- print('Pink noise filter depth set to maximum possible value of %d.' % max_depth)
204
+ print(f'Pink noise filter depth set to maximum possible value of {max_depth:d}.')
148
205
 
149
206
  rnd_gen = RandomState(self.seed)
150
207
  s = rnd_gen.standard_normal(nums)
@@ -174,17 +231,7 @@ class FiltWNoiseGenerator(WNoiseGenerator):
174
231
  ma = CArray(value=array([]), dtype=float, desc='moving-average coefficients (coefficients of the numerator)')
175
232
 
176
233
  # internal identifier
177
- digest = Property(
178
- depends_on=[
179
- 'ar',
180
- 'ma',
181
- 'rms',
182
- 'numsamples',
183
- 'sample_freq',
184
- 'seed',
185
- '__class__',
186
- ],
187
- )
234
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'ar', 'ma'])
188
235
 
189
236
  @cached_property
190
237
  def _get_digest(self):
@@ -201,8 +248,7 @@ class FiltWNoiseGenerator(WNoiseGenerator):
201
248
  Returns
202
249
  -------
203
250
  Array of floats
204
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
205
-
251
+ The resulting signal as an array of length :attr:`~SignalGenerator.num_samples`.
206
252
  """
207
253
  rnd_gen = RandomState(self.seed)
208
254
  ma = self.handle_empty_coefficients(self.ma)
@@ -211,55 +257,16 @@ class FiltWNoiseGenerator(WNoiseGenerator):
211
257
  ntaps = ma.shape[0]
212
258
  sdelay = round(0.5 * (ntaps - 1))
213
259
  wnoise = self.rms * rnd_gen.standard_normal(
214
- self.numsamples + sdelay,
260
+ self.num_samples + sdelay,
215
261
  ) # create longer signal to compensate delay
216
262
  return sosfilt(sos, x=wnoise)[sdelay:]
217
263
 
218
264
 
219
- class SineGenerator(SignalGenerator):
220
- """Sine signal generator with adjustable frequency and phase."""
221
-
222
- #: Sine wave frequency, float, defaults to 1000.0.
223
- freq = Float(1000.0, desc='Frequency')
224
-
225
- #: Sine wave phase (in radians), float, defaults to 0.0.
226
- phase = Float(0.0, desc='Phase')
227
-
228
- # Internal shadow trait for rms/amplitude values.
229
- # Do not set directly.
230
- _amp = Float(1.0)
231
-
232
- #: RMS of source signal (for point source: in 1 m distance).
233
- #: Deprecated. For amplitude use :attr:`amplitude`.
234
- rms = Property(desc='rms amplitude')
235
-
236
- def _get_rms(self):
237
- return self._amp / 2**0.5
238
-
239
- def _set_rms(self, rms):
240
- warn(
241
- 'Up to Acoular 20.02, rms is interpreted as sine amplitude. '
242
- 'This has since been corrected (rms now is 1/sqrt(2) of amplitude). '
243
- "Use 'amplitude' trait to directly set the ampltiude.",
244
- Warning,
245
- stacklevel=2,
246
- )
247
- self._amp = rms * 2**0.5
248
-
249
- #: Amplitude of source signal (for point source: in 1 m distance).
250
- #: Defaults to 1.0.
251
- amplitude = Property(desc='amplitude')
252
-
253
- def _get_amplitude(self):
254
- return self._amp
255
-
256
- def _set_amplitude(self, amp):
257
- self._amp = amp
265
+ class SineGenerator(PeriodicSignalGenerator):
266
+ """Sine signal generator with adjustable amplitude, frequency and phase."""
258
267
 
259
268
  # internal identifier
260
- digest = Property(
261
- depends_on=['_amp', 'numsamples', 'sample_freq', 'freq', 'phase', '__class__'],
262
- )
269
+ digest = Property(depends_on=['num_samples', 'sample_freq', 'amplitude', 'freq', 'phase'])
263
270
 
264
271
  @cached_property
265
272
  def _get_digest(self):
@@ -271,15 +278,15 @@ class SineGenerator(SignalGenerator):
271
278
  Returns
272
279
  -------
273
280
  array of floats
274
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
275
-
281
+ The resulting signal as an array of length :attr:`~SignalGenerator.num_samples`.
276
282
  """
277
- t = arange(self.numsamples, dtype=float) / self.sample_freq
283
+ t = arange(self.num_samples, dtype=float) / self.sample_freq
278
284
  return self.amplitude * sin(2 * pi * self.freq * t + self.phase)
279
285
 
280
286
 
287
+ @deprecated_alias({'rms': 'amplitude'})
281
288
  class GenericSignalGenerator(SignalGenerator):
282
- """Generate signal from output of :class:`~acoular.tprocess.SamplesGenerator` object.
289
+ """Generate signal from output of :class:`~acoular.base.SamplesGenerator` object.
283
290
 
284
291
  This class can be used to inject arbitrary signals into Acoular processing
285
292
  chains. For example, it can be used to read signals from a HDF5 file or create any signal
@@ -292,35 +299,37 @@ class GenericSignalGenerator(SignalGenerator):
292
299
  >>> data = np.random.rand(1000, 1)
293
300
  >>> ts = TimeSamples(data=data, sample_freq=51200)
294
301
  >>> sig = GenericSignalGenerator(source=ts)
295
-
296
302
  """
297
303
 
298
- #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
299
- source = Trait(SamplesGenerator)
304
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
305
+ source = Instance(SamplesGenerator)
306
+
307
+ #: Amplitude of the signal. Defaults to 1.0.
308
+ amplitude = Float(1.0)
300
309
 
301
310
  #: Sampling frequency of output signal, as given by :attr:`source`.
302
311
  sample_freq = Delegate('source')
303
312
 
304
- _numsamples = CLong(0)
313
+ _num_samples = CInt(0)
305
314
 
306
- #: Number of samples to generate. Is set to source.numsamples by default.
307
- numsamples = Property()
315
+ #: Number of samples to generate. Is set to source.num_samples by default.
316
+ num_samples = Property()
308
317
 
309
- def _get_numsamples(self):
310
- if self._numsamples:
311
- return self._numsamples
312
- return self.source.numsamples
318
+ def _get_num_samples(self):
319
+ if self._num_samples:
320
+ return self._num_samples
321
+ return self.source.num_samples
313
322
 
314
- def _set_numsamples(self, numsamples):
315
- self._numsamples = numsamples
323
+ def _set_num_samples(self, num_samples):
324
+ self._num_samples = num_samples
316
325
 
317
326
  #: Boolean flag, if 'True' (default), signal track is repeated if requested
318
- #: :attr:`numsamples` is higher than available sample number
327
+ #: :attr:`num_samples` is higher than available sample number
319
328
  loop_signal = Bool(True)
320
329
 
321
330
  # internal identifier
322
331
  digest = Property(
323
- depends_on=['source.digest', 'loop_signal', 'numsamples', 'rms', '__class__'],
332
+ depends_on=['source.digest', 'loop_signal', 'num_samples', 'amplitude'],
324
333
  )
325
334
 
326
335
  @cached_property
@@ -333,17 +342,17 @@ class GenericSignalGenerator(SignalGenerator):
333
342
  Returns
334
343
  -------
335
344
  array of floats
336
- The resulting signal as an array of length :attr:`~GenericSignalGenerator.numsamples`.
345
+ The resulting signal as an array of length :attr:`~GenericSignalGenerator.num_samples`.
337
346
 
338
347
  """
339
348
  block = 1024
340
- if self.source.numchannels > 1:
349
+ if self.source.num_channels > 1:
341
350
  warn(
342
351
  'Signal source has more than one channel. Only channel 0 will be used for signal.',
343
352
  Warning,
344
353
  stacklevel=2,
345
354
  )
346
- nums = self.numsamples
355
+ nums = self.num_samples
347
356
  track = zeros(nums)
348
357
 
349
358
  # iterate through source generator to fill signal track
@@ -366,6 +375,4 @@ class GenericSignalGenerator(SignalGenerator):
366
375
  res = nums % stop # last part of unfinished loop
367
376
  if res > 0:
368
377
  track[stop * nloops :] = track[:res]
369
-
370
- # The rms value is just an amplification here
371
- return self.rms * track
378
+ return self.amplitude * track