acoular 24.10__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, Float, Int, Long, Property, Trait, cached_property, observe
12
+ from traits.api import Any, Bool, Enum, Float, Int, Property, cached_property, observe
13
13
 
14
+ # acoular imports
14
15
  from .base import SamplesGenerator
15
16
  from .configuration import config
17
+ from .deprecation import deprecated_alias
16
18
  from .internal import digest
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,14 +40,14 @@ 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')
@@ -52,7 +55,7 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
52
55
  _sample_freq = Float(default_value=None)
53
56
 
54
57
  #: Datatype (resolution) of the signal, used as `dtype` in a sd `Stream` object
55
- precision = Trait('float32', 'float16', 'int32', 'int16', 'int8', 'uint8', desc='precision (resolution) of signal')
58
+ precision = Enum('float32', 'float16', 'int32', 'int16', 'int8', 'uint8', desc='precision (resolution) of signal')
56
59
 
57
60
  #: Indicates that the sounddevice buffer has overflown
58
61
  overflow = Bool(False, desc='Indicates if sounddevice buffer overflow')
@@ -64,16 +67,16 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
64
67
  stream = Any
65
68
 
66
69
  # internal identifier
67
- digest = Property(depends_on=['device', 'numchannels', 'numsamples'])
70
+ digest = Property(depends_on=['device', 'num_channels', 'num_samples'])
68
71
 
69
72
  @cached_property
70
73
  def _get_digest(self):
71
74
  return digest(self)
72
75
 
73
- # checks that numchannels are not more than device can provide
74
- @observe('device,numchannels')
75
- def _get_numchannels(self, event): # noqa ARG002
76
- 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'])
77
80
 
78
81
  def _get_sample_freq(self):
79
82
  if self._sample_freq is None:
@@ -102,14 +105,14 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
102
105
 
103
106
  Returns
104
107
  -------
105
- Samples in blocks of shape (num, :attr:`numchannels`).
108
+ Samples in blocks of shape (num, :attr:`num_channels`).
106
109
  The last block may be shorter than num.
107
110
 
108
111
  """
109
112
  print(self.device_properties(), self.sample_freq)
110
113
  self.stream = stream_obj = sd.InputStream(
111
114
  device=self.device,
112
- channels=self.numchannels,
115
+ channels=self.num_channels,
113
116
  clip_off=True,
114
117
  samplerate=self.sample_freq,
115
118
  dtype=self.precision,
@@ -117,15 +120,15 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
117
120
 
118
121
  with stream_obj as stream:
119
122
  self.running = True
120
- if self.numsamples == -1:
121
- 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
122
125
  data, self.overflow = stream.read(num)
123
126
  yield data[:num]
124
127
 
125
- elif self.numsamples > 0: # amount of samples to collect is specified by user
126
- samples_count = 0 # numsamples counter
127
- while samples_count < self.numsamples:
128
- 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)
129
132
  data, self.overflow = stream.read(num)
130
133
  yield data[:anz]
131
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,19 +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
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
+ )
25
39
 
26
40
  # acoular imports
27
41
  from .base import SamplesGenerator
42
+ from .deprecation import deprecated_alias
28
43
  from .internal import digest
29
44
 
30
45
 
31
- class SignalGenerator(HasPrivateTraits):
46
+ @deprecated_alias({'numsamples': 'num_samples'})
47
+ class SignalGenerator(ABCHasStrictTraits):
32
48
  """Virtual base class for a simple one-channel signal generator.
33
49
 
34
50
  Defines the common interface for all SignalGenerator classes. This class
@@ -36,21 +52,20 @@ class SignalGenerator(HasPrivateTraits):
36
52
  should not be used directly as it contains no real functionality.
37
53
  """
38
54
 
39
- #: RMS amplitude of source signal (for point source: in 1 m distance).
40
- rms = Float(1.0, desc='rms amplitude')
41
-
42
55
  #: Sampling frequency of the signal.
43
56
  sample_freq = Float(1.0, desc='sampling frequency')
44
57
 
45
58
  #: Number of samples to generate.
46
- numsamples = CLong
59
+ num_samples = CInt
47
60
 
48
61
  # internal identifier
49
- digest = Property
62
+ digest = Property(depends_on=['sample_freq', 'num_samples'])
50
63
 
64
+ @abstractmethod
51
65
  def _get_digest(self):
52
- return ''
66
+ """Returns the internal identifier."""
53
67
 
68
+ @abstractmethod
54
69
  def signal(self):
55
70
  """Deliver the signal."""
56
71
 
@@ -68,14 +83,52 @@ class SignalGenerator(HasPrivateTraits):
68
83
  Returns
69
84
  -------
70
85
  array of floats
71
- The resulting signal of length `factor` * :attr:`numsamples`.
72
-
86
+ The resulting signal of length `factor` * :attr:`num_samples`.
73
87
  """
74
- return resample(self.signal(), factor * self.numsamples)
88
+ return resample(self.signal(), factor * self.num_samples)
75
89
 
76
90
 
77
- class WNoiseGenerator(SignalGenerator):
78
- """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')
79
132
 
80
133
  #: Seed for random number generator, defaults to 0.
81
134
  #: This parameter should be set differently for different instances
@@ -83,9 +136,22 @@ class WNoiseGenerator(SignalGenerator):
83
136
  seed = Int(0, desc='random seed value')
84
137
 
85
138
  # internal identifier
86
- digest = Property(
87
- depends_on=['rms', 'numsamples', 'sample_freq', 'seed', '__class__'],
88
- )
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'])
89
155
 
90
156
  @cached_property
91
157
  def _get_digest(self):
@@ -97,14 +163,13 @@ class WNoiseGenerator(SignalGenerator):
97
163
  Returns
98
164
  -------
99
165
  Array of floats
100
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
101
-
166
+ The resulting signal as an array of length :attr:`~SignalGenerator.num_samples`.
102
167
  """
103
168
  rnd_gen = RandomState(self.seed)
104
- return self.rms * rnd_gen.standard_normal(self.numsamples)
169
+ return self.rms * rnd_gen.standard_normal(self.num_samples)
105
170
 
106
171
 
107
- class PNoiseGenerator(SignalGenerator):
172
+ class PNoiseGenerator(NoiseGenerator):
108
173
  """Pink noise signal generator.
109
174
 
110
175
  Simulation of pink noise is based on the Voss-McCartney algorithm.
@@ -117,33 +182,26 @@ class PNoiseGenerator(SignalGenerator):
117
182
  characteristic.
118
183
  """
119
184
 
120
- #: Seed for random number generator, defaults to 0.
121
- #: This parameter should be set differently for different instances
122
- #: to guarantee statistically independent (non-correlated) outputs.
123
- seed = Int(0, desc='random seed value')
124
-
125
185
  #: "Octave depth" -- higher values for 1/f spectrum at low frequencies,
126
186
  #: but longer calculation, defaults to 16.
127
187
  depth = Int(16, desc='octave depth')
128
188
 
129
189
  # internal identifier
130
- digest = Property(
131
- depends_on=['rms', 'numsamples', 'sample_freq', 'seed', 'depth', '__class__'],
132
- )
190
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'depth'])
133
191
 
134
192
  @cached_property
135
193
  def _get_digest(self):
136
194
  return digest(self)
137
195
 
138
196
  def signal(self):
139
- nums = self.numsamples
197
+ nums = self.num_samples
140
198
  depth = self.depth
141
199
  # maximum depth depending on number of samples
142
200
  max_depth = int(log(nums) / log(2))
143
201
 
144
202
  if depth > max_depth:
145
203
  depth = max_depth
146
- 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}.')
147
205
 
148
206
  rnd_gen = RandomState(self.seed)
149
207
  s = rnd_gen.standard_normal(nums)
@@ -173,17 +231,7 @@ class FiltWNoiseGenerator(WNoiseGenerator):
173
231
  ma = CArray(value=array([]), dtype=float, desc='moving-average coefficients (coefficients of the numerator)')
174
232
 
175
233
  # internal identifier
176
- digest = Property(
177
- depends_on=[
178
- 'ar',
179
- 'ma',
180
- 'rms',
181
- 'numsamples',
182
- 'sample_freq',
183
- 'seed',
184
- '__class__',
185
- ],
186
- )
234
+ digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'ar', 'ma'])
187
235
 
188
236
  @cached_property
189
237
  def _get_digest(self):
@@ -200,8 +248,7 @@ class FiltWNoiseGenerator(WNoiseGenerator):
200
248
  Returns
201
249
  -------
202
250
  Array of floats
203
- The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`.
204
-
251
+ The resulting signal as an array of length :attr:`~SignalGenerator.num_samples`.
205
252
  """
206
253
  rnd_gen = RandomState(self.seed)
207
254
  ma = self.handle_empty_coefficients(self.ma)
@@ -210,56 +257,16 @@ class FiltWNoiseGenerator(WNoiseGenerator):
210
257
  ntaps = ma.shape[0]
211
258
  sdelay = round(0.5 * (ntaps - 1))
212
259
  wnoise = self.rms * rnd_gen.standard_normal(
213
- self.numsamples + sdelay,
260
+ self.num_samples + sdelay,
214
261
  ) # create longer signal to compensate delay
215
262
  return sosfilt(sos, x=wnoise)[sdelay:]
216
263
 
217
264
 
218
- class SineGenerator(SignalGenerator):
219
- """Sine signal generator with adjustable frequency and phase."""
220
-
221
- #: Sine wave frequency, float, defaults to 1000.0.
222
- freq = Float(1000.0, desc='Frequency')
223
-
224
- #: Sine wave phase (in radians), float, defaults to 0.0.
225
- phase = Float(0.0, desc='Phase')
226
-
227
- # Internal shadow trait for rms/amplitude values.
228
- # Do not set directly.
229
- _amp = Float(1.0)
230
-
231
- #: RMS of source signal (for point source: in 1 m distance).
232
- #: Deprecated. For amplitude use :attr:`amplitude`.
233
- rms = Property(desc='rms amplitude')
234
-
235
- def _get_rms(self):
236
- return self._amp / 2**0.5
237
-
238
- def _set_rms(self, rms):
239
- warn(
240
- 'Using rms to set amplitude is deprecated and will be removed in version 25.01. '
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
- DeprecationWarning,
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,13 +278,13 @@ 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
289
  """Generate signal from output of :class:`~acoular.base.SamplesGenerator` object.
283
290
 
@@ -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
304
  #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
299
- source = Trait(SamplesGenerator)
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