acoular 24.5__py3-none-any.whl → 24.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
acoular/__init__.py CHANGED
@@ -4,15 +4,21 @@
4
4
 
5
5
  """The Acoular library: several classes for the implementation of acoustic beamforming."""
6
6
 
7
- import os
7
+ import os # noqa: I001
8
8
 
9
+ # config must be imported before any submodules containing numpy, see #322.
9
10
  from .configuration import config
10
- from .version import __author__, __date__, __version__
11
-
12
- if config.have_sounddevice:
13
- from .sdinput import SoundDeviceSamplesGenerator
14
11
 
15
12
  from . import demo, tools
13
+ from .base import (
14
+ Generator,
15
+ InOut,
16
+ SamplesGenerator,
17
+ SpectraGenerator,
18
+ SpectraOut,
19
+ TimeInOut,
20
+ TimeOut,
21
+ )
16
22
  from .calib import Calib
17
23
  from .environments import (
18
24
  Environment,
@@ -46,6 +52,7 @@ from .fbeamform import (
46
52
  SteeringVector,
47
53
  integrate,
48
54
  )
55
+ from .fprocess import IRFFT, RFFT, AutoPowerSpectra, CrossPowerSpectra, FFTSpectra
49
56
  from .grids import (
50
57
  CircSector,
51
58
  ConvexSector,
@@ -62,6 +69,8 @@ from .grids import (
62
69
  Sector,
63
70
  )
64
71
  from .microphones import MicGeom
72
+ from .process import Average, Cache, SampleSplitter, TimeAverage, TimeCache
73
+ from .sdinput import SoundDeviceSamplesGenerator
65
74
  from .signals import (
66
75
  FiltWNoiseGenerator,
67
76
  GenericSignalGenerator,
@@ -84,7 +93,7 @@ from .sources import (
84
93
  TimeSamples,
85
94
  UncorrelatedNoiseSource,
86
95
  )
87
- from .spectra import BaseSpectra, FFTSpectra, PowerSpectra, PowerSpectraImport, synthetic
96
+ from .spectra import BaseSpectra, PowerSpectra, PowerSpectraImport, synthetic
88
97
  from .spectra import PowerSpectra as EigSpectra
89
98
  from .tbeamform import (
90
99
  BeamformerCleant,
@@ -106,19 +115,15 @@ from .tprocess import (
106
115
  FiltFreqWeight,
107
116
  FiltOctave,
108
117
  MaskedTimeInOut,
118
+ MaskedTimeOut,
109
119
  Mixer,
110
120
  OctaveFilterBank,
111
- SamplesGenerator,
112
- SampleSplitter,
113
121
  SpatialInterpolator,
114
122
  SpatialInterpolatorConstantRotation,
115
123
  SpatialInterpolatorRotation,
116
- TimeAverage,
117
- TimeCache,
118
124
  TimeConvolve,
119
125
  TimeCumAverage,
120
126
  TimeExpAverage,
121
- TimeInOut,
122
127
  TimePower,
123
128
  TimeReverse,
124
129
  Trigger,
@@ -126,3 +131,4 @@ from .tprocess import (
126
131
  WriteWAV,
127
132
  )
128
133
  from .trajectory import Trajectory
134
+ from .version import __author__, __date__, __version__
acoular/base.py ADDED
@@ -0,0 +1,312 @@
1
+ # ------------------------------------------------------------------------------
2
+ # Copyright (c) Acoular Development Team.
3
+ # ------------------------------------------------------------------------------
4
+ """Implements base classes for signal processing blocks in Acoular.
5
+
6
+ The classes in this module are abstract base classes that provide a common interface for all classes that generate an
7
+ output via the generator :meth:`result` in block-wise manner. They are not intended to be used directly, but to be
8
+ subclassed by classes that implement the actual signal processing.
9
+
10
+ .. autosummary::
11
+ :toctree: generated/
12
+
13
+ Generator
14
+ SamplesGenerator
15
+ SpectraGenerator
16
+ InOut
17
+ TimeOut
18
+ SpectraOut
19
+ TimeInOut
20
+ """
21
+
22
+ from traits.api import (
23
+ CArray,
24
+ CLong,
25
+ Delegate,
26
+ Float,
27
+ HasPrivateTraits,
28
+ Instance,
29
+ Property,
30
+ cached_property,
31
+ )
32
+
33
+ from acoular.internal import digest
34
+
35
+
36
+ class Generator(HasPrivateTraits):
37
+ """Interface for any generating signal processing block.
38
+
39
+ It provides a common interface for all classes, which generate an output via the generator :meth:`result`
40
+ in block-wise manner. It has a common set of traits that are used by all classes that implement this interface.
41
+ This includes the sampling frequency of the signal (:attr:`sample_freq`) and the number of samples (:attr:`numsamples`).
42
+ A private trait :attr:`digest` is used to store the internal identifier of the object, which is a hash of the object's
43
+ attributes. This is used to check if the object's internal state has changed.
44
+ """
45
+
46
+ #: Sampling frequency of the signal, defaults to 1.0
47
+ sample_freq = Float(1.0, desc='sampling frequency')
48
+
49
+ #: Number of signal samples
50
+ numsamples = CLong
51
+
52
+ # internal identifier
53
+ digest = Property(depends_on=['sample_freq', 'numsamples'])
54
+
55
+ def _get_digest(self):
56
+ return digest(self)
57
+
58
+ def result(self, num):
59
+ """Python generator that yields the output block-wise.
60
+
61
+ This method needs to be implemented by the derived classes.
62
+
63
+ Parameters
64
+ ----------
65
+ num : int
66
+ The size of the first dimension of the blocks to be yielded
67
+
68
+ Yields
69
+ ------
70
+ numpy.ndarray
71
+ Two-dimensional output data block of shape (num, ...)
72
+ """
73
+
74
+
75
+ class SamplesGenerator(Generator):
76
+ """Interface for any generating multi-channel time domain signal processing block.
77
+
78
+ It provides a common interface for all SamplesGenerator classes, which generate an output via the generator :meth:`result`
79
+ in block-wise manner. This class has no real functionality on its own and should not be used directly.
80
+ """
81
+
82
+ #: Number of channels
83
+ numchannels = CLong
84
+
85
+ # internal identifier
86
+ digest = Property(depends_on=['sample_freq', 'numchannels', 'numsamples'])
87
+
88
+ def _get_digest(self):
89
+ return digest(self)
90
+
91
+ def result(self, num):
92
+ """Python generator that yields the output block-wise.
93
+
94
+ Parameters
95
+ ----------
96
+ num : int
97
+ This parameter defines the size of the blocks to be yielded
98
+ (i.e. the number of samples per block)
99
+
100
+ Yields
101
+ ------
102
+ numpy.ndarray
103
+ The two-dimensional time-data block of shape (num, numchannels).
104
+ """
105
+
106
+
107
+ class SpectraGenerator(Generator):
108
+ """Interface for any generating multi-channel signal frequency domain processing block.
109
+
110
+ It provides a common interface for all SpectraGenerator classes, which generate an output via the generator :meth:`result`
111
+ in block-wise manner. This class has no real functionality on its own and should not be used directly.
112
+ """
113
+
114
+ #: Number of channels
115
+ numchannels = CLong
116
+
117
+ #: Number of frequencies
118
+ numfreqs = CLong
119
+
120
+ #: 1-D array of frequencies
121
+ freqs = CArray
122
+
123
+ #: The length of the block used to calculate the spectra
124
+ block_size = CLong
125
+
126
+ # internal identifier
127
+ digest = Property(depends_on=['sample_freq', 'numchannels', 'numsamples', 'numfreqs', 'block_size'])
128
+
129
+ def _get_digest(self):
130
+ return digest(self)
131
+
132
+ def result(self, num=1):
133
+ """Python generator that yields the output block-wise.
134
+
135
+ Parameters
136
+ ----------
137
+ num : integer
138
+ This parameter defines the size of the number of snapshots to be yielded.
139
+ Defaults to 1.
140
+
141
+ Yields
142
+ ------
143
+ numpy.ndarray
144
+ A two-dimensional block of shape (num, numchannels*numfreqs).
145
+ """
146
+
147
+
148
+ class TimeOut(SamplesGenerator):
149
+ """Abstract base class for any signal processing block that receives data from any :attr:`source` domain and returns
150
+ time domain signals.
151
+
152
+ It provides a base class that can be used to create signal processing blocks that receive data from any
153
+ generating :attr:`source` and generates a time signal output via the generator :meth:`result` in block-wise manner.
154
+ """
155
+
156
+ #: Data source; :class:`~acoular.base.Generator` or derived object.
157
+ source = Instance(Generator)
158
+
159
+ #: Sampling frequency of output signal, as given by :attr:`source`.
160
+ sample_freq = Delegate('source')
161
+
162
+ #: Number of channels in output, as given by :attr:`source`.
163
+ numchannels = Delegate('source')
164
+
165
+ #: Number of samples in output, as given by :attr:`source`.
166
+ numsamples = Delegate('source')
167
+
168
+ # internal identifier
169
+ digest = Property(depends_on=['source.digest'])
170
+
171
+ @cached_property
172
+ def _get_digest(self):
173
+ return digest(self)
174
+
175
+ def result(self, num):
176
+ """Python generator that processes the source data and yields the time-signal block-wise.
177
+
178
+ This method needs to be implemented by the derived classes.
179
+
180
+ Parameters
181
+ ----------
182
+ num : int
183
+ This parameter defines the size of the blocks to be yielded
184
+ (i.e. the number of samples per block)
185
+
186
+ Yields
187
+ ------
188
+ numpy.ndarray
189
+ Two-dimensional output data block of shape (num, numchannels)
190
+ """
191
+ yield from self.source.result(num)
192
+
193
+
194
+ class SpectraOut(SpectraGenerator):
195
+ """Abstract base class for any signal processing block that receives data from any :attr:`source` domain and
196
+ returns frequency domain signals.
197
+
198
+ It provides a base class that can be used to create signal processing blocks that receive data from any
199
+ generating :attr:`source` domain and generates a frequency domain output via the generator :meth:`result`
200
+ in block-wise manner.
201
+ """
202
+
203
+ #: Data source; :class:`~acoular.base.Generator` or derived object.
204
+ source = Instance(Generator)
205
+
206
+ #: Sampling frequency of output signal, as given by :attr:`source`.
207
+ sample_freq = Delegate('source')
208
+
209
+ #: Number of channels in output, as given by :attr:`source`.
210
+ numchannels = Delegate('source')
211
+
212
+ #: Number of snapshots in output, as given by :attr:`source`.
213
+ numsamples = Delegate('source')
214
+
215
+ #: Number of frequencies in output, as given by :attr:`source`.
216
+ numfreqs = Delegate('source')
217
+
218
+ #: 1-D array of frequencies, as given by :attr:`source`.
219
+ freqs = Delegate('source')
220
+
221
+ #: The size of the block used to calculate the spectra
222
+ block_size = Delegate('source')
223
+
224
+ # internal identifier
225
+ digest = Property(depends_on=['source.digest'])
226
+
227
+ @cached_property
228
+ def _get_digest(self):
229
+ return digest(self)
230
+
231
+ def result(self, num=1):
232
+ """Python generator that processes the source data and yields the output block-wise.
233
+
234
+ This method needs to be implemented by the derived classes.
235
+
236
+ num : integer
237
+ This parameter defines the the number of snapshots to be yielded.
238
+ Defaults to 1.
239
+
240
+ Yields
241
+ ------
242
+ numpy.ndarray
243
+ A two-dimensional block of shape (num, numchannels*numfreqs).
244
+ """
245
+ yield from self.source.result(num)
246
+
247
+
248
+ class InOut(SamplesGenerator, SpectraGenerator):
249
+ """Abstract base class for any signal processing block that receives data from any :attr:`source` domain and returns
250
+ signals in the same domain.
251
+
252
+ It provides a base class that can be used to create signal processing blocks that receive data from any
253
+ generating :attr:`source` and generates an output via the generator :meth:`result` in block-wise manner.
254
+ """
255
+
256
+ #: Data source; :class:`~acoular.base.Generator` or derived object.
257
+ source = Instance(Generator)
258
+
259
+ #: Sampling frequency of output signal, as given by :attr:`source`.
260
+ sample_freq = Delegate('source')
261
+
262
+ #: Number of channels in output, as given by :attr:`source`.
263
+ numchannels = Delegate('source')
264
+
265
+ #: Number of samples / snapshots in output, as given by :attr:`source`.
266
+ numsamples = Delegate('source')
267
+
268
+ # internal identifier
269
+ digest = Property(depends_on=['source.digest'])
270
+
271
+ @cached_property
272
+ def _get_digest(self):
273
+ return digest(self)
274
+
275
+ def result(self, num):
276
+ """Python generator that processes the source data and yields the output block-wise.
277
+
278
+ This method needs to be implemented by the derived classes.
279
+
280
+ Parameters
281
+ ----------
282
+ num : int
283
+ The size of the first dimension of the blocks to be yielded
284
+
285
+ Yields
286
+ ------
287
+ numpy.ndarray
288
+ Two-dimensional output data block of shape (num, ...)
289
+ """
290
+ yield from self.source.result(num)
291
+
292
+
293
+ class TimeInOut(TimeOut):
294
+ """Deprecated alias for :class:`~acoular.base.TimeOut`.
295
+
296
+ .. deprecated:: 24.10
297
+ Using :class:`~acoular.base.TimeInOut` is deprecated and will be removed in Acoular 25.07.
298
+ Use :class:`~acoular.base.TimeOut` instead.
299
+ """
300
+
301
+ #: Data source; :class:`~acoular.base.SamplesGenerator` or derived object.
302
+ source = Instance(SamplesGenerator)
303
+
304
+ def __init__(self, *args, **kwargs):
305
+ super().__init__(*args, **kwargs)
306
+ import warnings
307
+
308
+ warnings.warn(
309
+ 'TimeInOut is deprecated and will be removed in Acoular 25.07. Use TimeOut instead.',
310
+ DeprecationWarning,
311
+ stacklevel=2,
312
+ )
acoular/configuration.py CHANGED
@@ -35,7 +35,7 @@ if 'numpy' in sys.modules:
35
35
  np.show_config()
36
36
  sys.stdout = orig_stdout
37
37
  # check if it uses OpenBLAS or another library
38
- if 'openblas' in temp_stdout.getvalue().lower():
38
+ if 'openblas' in temp_stdout.getvalue().lower() and environ.get('OPENBLAS_NUM_THREADS') != '1':
39
39
  # it's OpenBLAS, set numba threads=1 to avoid overcommittment
40
40
  import numba
41
41
 
@@ -54,7 +54,7 @@ else:
54
54
  environ['OPENBLAS_NUM_THREADS'] = '1'
55
55
 
56
56
  # this loads numpy, so we have to defer loading until OpenBLAS check is done
57
- from traits.api import Bool, Either, HasStrictTraits, Property, Str, Trait, cached_property
57
+ from traits.api import Bool, Enum, HasStrictTraits, Property, Str, Trait, cached_property
58
58
 
59
59
 
60
60
  class Config(HasStrictTraits):
@@ -70,9 +70,9 @@ class Config(HasStrictTraits):
70
70
  --------
71
71
  For using Acoular with h5py package and overwrite existing cache:
72
72
 
73
- >>> import acoular
74
- >>> acoular.config.h5library = "h5py"
75
- >>> acoular.config.global_caching = "overwrite"
73
+ >>> import acoular
74
+ >>> acoular.config.h5library = 'h5py'
75
+ >>> acoular.config.global_caching = 'overwrite'
76
76
 
77
77
  """
78
78
 
@@ -97,7 +97,7 @@ class Config(HasStrictTraits):
97
97
  #: If 'pytables' can not be imported, 'h5py' is used.
98
98
  h5library = Property()
99
99
 
100
- _h5library = Either('pytables', 'tables', 'h5py', default='pytables')
100
+ _h5library = Enum('pytables', 'tables', 'h5py')
101
101
 
102
102
  #: Defines the path to the directory containing Acoulars cache files.
103
103
  #: If the specified :attr:`cache_dir` directory does not exist,
@@ -134,25 +134,28 @@ class Config(HasStrictTraits):
134
134
  #: Boolean Flag that determines whether sounddevice is installed.
135
135
  have_sounddevice = Property()
136
136
 
137
+ #: Boolean Flag that determines whether the package is installed.
138
+ have_traitsui = Property()
139
+
137
140
  def _get_global_caching(self):
138
141
  return self._global_caching
139
142
 
140
- def _set_global_caching(self, globalCachingValue):
141
- self._global_caching = globalCachingValue
143
+ def _set_global_caching(self, value):
144
+ self._global_caching = value
142
145
 
143
146
  def _get_h5library(self):
144
147
  return self._h5library
145
148
 
146
- def _set_h5library(self, libraryName):
147
- self._h5library = libraryName
149
+ def _set_h5library(self, name):
150
+ self._h5library = name
148
151
 
149
152
  def _get_use_traitsui(self):
150
153
  return self._use_traitsui
151
154
 
152
155
  def _set_use_traitsui(self, use_tui):
153
- if use_tui:
154
- from . import traitsviews
155
- # If user tries to use traitsuis and it's not installed, this will throw an error.
156
+ if use_tui and not self.have_traitsui:
157
+ error_msg = 'TraitsUI package is not installed!'
158
+ raise ImportError(error_msg)
156
159
  self._use_traitsui = use_tui
157
160
 
158
161
  def _assert_h5library(self):
@@ -205,6 +208,10 @@ class Config(HasStrictTraits):
205
208
  def _get_have_h5py(self):
206
209
  return self._have_module('h5py')
207
210
 
211
+ @cached_property
212
+ def _get_have_traitsui(self):
213
+ return self._have_module('traitsui')
214
+
208
215
 
209
216
  config = Config()
210
217
  """
@@ -232,7 +239,7 @@ Note: this is independent from the GUI tools implemented in the spectAcoular pac
232
239
  Example:
233
240
  For using Acoular with h5py package and overwrite existing cache:
234
241
 
235
- >>> import acoular
236
- >>> acoular.config.h5library = "h5py"
237
- >>> acoular.config.global_caching = "overwrite"
242
+ >>> import acoular
243
+ >>> acoular.config.h5library = "h5py"
244
+ >>> acoular.config.global_caching = "overwrite"
238
245
  """
@@ -19,10 +19,10 @@ The simulation generates the sound pressure at 64 microphones that are
19
19
  arrangend in the 'array64' geometry, which is part of the package. The sound
20
20
  pressure signals are sampled at 51200 Hz for a duration of 1 second.
21
21
 
22
- Source location (relative to array center) and levels:
22
+ Source location (relative to array center) and RMS in 1 m distance:
23
23
 
24
24
  ====== =============== ======
25
- Source Location Level
25
+ Source Location RMS
26
26
  ====== =============== ======
27
27
  1 (-0.1,-0.1,0.3) 1.0 Pa
28
28
  2 (0.15,0,0.3) 0.7 Pa
@@ -36,58 +36,45 @@ def run():
36
36
  """Run the Acoular demo."""
37
37
  from pathlib import Path
38
38
 
39
- from acoular import (
40
- BeamformerBase,
41
- L_p,
42
- MicGeom,
43
- Mixer,
44
- PointSource,
45
- PowerSpectra,
46
- RectGrid,
47
- SteeringVector,
48
- TimeSamples,
49
- WNoiseGenerator,
50
- WriteH5,
51
- config,
52
- )
53
- from acoular import __file__ as bpath
39
+ import acoular as ac
40
+
41
+ ac.config.global_caching = 'none'
54
42
 
55
43
  # set up the parameters
56
44
  sfreq = 51200
57
45
  duration = 1
58
46
  nsamples = duration * sfreq
59
- micgeofile = Path(bpath).parent / 'xml' / 'array_64.xml'
47
+ micgeofile = Path(ac.__file__).parent / 'xml' / 'array_64.xml'
60
48
  h5savefile = 'three_sources.h5'
61
49
 
62
50
  # generate test data, in real life this would come from an array measurement
63
- mg = MicGeom(from_file=micgeofile)
64
- n1 = WNoiseGenerator(sample_freq=sfreq, numsamples=nsamples, seed=1)
65
- n2 = WNoiseGenerator(sample_freq=sfreq, numsamples=nsamples, seed=2, rms=0.7)
66
- n3 = WNoiseGenerator(sample_freq=sfreq, numsamples=nsamples, seed=3, rms=0.5)
67
- p1 = PointSource(signal=n1, mics=mg, loc=(-0.1, -0.1, 0.3))
68
- p2 = PointSource(signal=n2, mics=mg, loc=(0.15, 0, 0.3))
69
- p3 = PointSource(signal=n3, mics=mg, loc=(0, 0.1, 0.3))
70
- pa = Mixer(source=p1, sources=[p2, p3])
71
- wh5 = WriteH5(source=pa, name=h5savefile)
51
+ mg = ac.MicGeom(from_file=micgeofile)
52
+ n1 = ac.WNoiseGenerator(sample_freq=sfreq, numsamples=nsamples, seed=1)
53
+ n2 = ac.WNoiseGenerator(sample_freq=sfreq, numsamples=nsamples, seed=2, rms=0.7)
54
+ n3 = ac.WNoiseGenerator(sample_freq=sfreq, numsamples=nsamples, seed=3, rms=0.5)
55
+ p1 = ac.PointSource(signal=n1, mics=mg, loc=(-0.1, -0.1, 0.3))
56
+ p2 = ac.PointSource(signal=n2, mics=mg, loc=(0.15, 0, 0.3))
57
+ p3 = ac.PointSource(signal=n3, mics=mg, loc=(0, 0.1, 0.3))
58
+ pa = ac.Mixer(source=p1, sources=[p2, p3])
59
+ wh5 = ac.WriteH5(source=pa, name=h5savefile)
72
60
  wh5.save()
73
61
 
74
62
  # analyze the data and generate map
75
63
 
76
- ts = TimeSamples(name=h5savefile)
77
- ps = PowerSpectra(time_data=ts, block_size=128, window='Hanning')
64
+ ps = ac.PowerSpectra(source=pa, block_size=128, window='Hanning')
78
65
 
79
- rg = RectGrid(x_min=-0.2, x_max=0.2, y_min=-0.2, y_max=0.2, z=0.3, increment=0.01)
80
- st = SteeringVector(grid=rg, mics=mg)
66
+ rg = ac.RectGrid(x_min=-0.2, x_max=0.2, y_min=-0.2, y_max=0.2, z=0.3, increment=0.01)
67
+ st = ac.SteeringVector(grid=rg, mics=mg)
81
68
 
82
- bb = BeamformerBase(freq_data=ps, steer=st)
69
+ bb = ac.BeamformerBase(freq_data=ps, steer=st)
83
70
  pm = bb.synthetic(8000, 3)
84
- Lm = L_p(pm)
71
+ spl = ac.L_p(pm)
85
72
 
86
- if config.have_matplotlib:
73
+ if ac.config.have_matplotlib:
87
74
  from pylab import axis, colorbar, figure, imshow, plot, show
88
75
 
89
76
  # show map
90
- imshow(Lm.T, origin='lower', vmin=Lm.max() - 10, extent=rg.extend(), interpolation='bicubic')
77
+ imshow(spl.T, origin='lower', vmin=spl.max() - 10, extent=rg.extend(), interpolation='bicubic')
91
78
  colorbar()
92
79
 
93
80
  # plot microphone geometry
@@ -99,6 +86,12 @@ def run():
99
86
 
100
87
  else:
101
88
  print('Matplotlib not found! Please install matplotlib if you want to plot the results.')
89
+ print('For consolation we do an ASCII map plot of the results here.')
90
+ grayscale = '@%#*+=-:. '[::-1]
91
+ ind = ((spl.T - spl.max() + 9).clip(0, 9)).astype(int)[::-1]
92
+ print(78 * '-')
93
+ print('|\n'.join([' '.join(['|'] + [grayscale[i] for i in row[2:-1]]) for row in ind]) + '|')
94
+ print(7 * '-', ''.join([f'{grayscale[i]}={int(spl.max())-9+i}dB ' for i in range(1, 10)]), 6 * '-')
102
95
 
103
96
 
104
97
  if __name__ == '__main__':