acoular 25.7__py3-none-any.whl → 25.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/aiaa/aiaa.py +7 -9
- acoular/base.py +6 -9
- acoular/calib.py +19 -18
- acoular/configuration.py +2 -2
- acoular/environments.py +102 -113
- acoular/fbeamform.py +296 -301
- acoular/fprocess.py +7 -4
- acoular/grids.py +98 -111
- acoular/h5cache.py +5 -1
- acoular/h5files.py +96 -9
- acoular/microphones.py +22 -27
- acoular/process.py +7 -11
- acoular/sdinput.py +0 -5
- acoular/signals.py +29 -27
- acoular/sources.py +189 -322
- acoular/spectra.py +33 -44
- acoular/tbeamform.py +217 -199
- acoular/tools/helpers.py +25 -33
- acoular/tools/metrics.py +5 -10
- acoular/tprocess.py +173 -209
- acoular/trajectory.py +5 -5
- acoular/version.py +2 -2
- {acoular-25.7.dist-info → acoular-25.10.dist-info}/METADATA +6 -2
- {acoular-25.7.dist-info → acoular-25.10.dist-info}/RECORD +27 -27
- {acoular-25.7.dist-info → acoular-25.10.dist-info}/WHEEL +0 -0
- {acoular-25.7.dist-info → acoular-25.10.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.7.dist-info → acoular-25.10.dist-info}/licenses/LICENSE +0 -0
acoular/h5files.py
CHANGED
|
@@ -11,25 +11,88 @@ class H5FileBase:
|
|
|
11
11
|
"""Base class for File objects that handle writing and reading of .h5 files."""
|
|
12
12
|
|
|
13
13
|
def create_extendable_array(self, nodename, shape, precision, group=None):
|
|
14
|
-
|
|
14
|
+
"""
|
|
15
|
+
Create an extendable array in the HDF5 file.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
nodename : :class:`str`
|
|
20
|
+
Name of the node (dataset) to create in the HDF5 file.
|
|
21
|
+
shape : :class:`tuple` of :class:`int`
|
|
22
|
+
Shape of the array to be created.
|
|
23
|
+
precision : :class:`str`
|
|
24
|
+
Data type/precision of the array (e.g., 'float32', 'int16').
|
|
25
|
+
group : object, optional
|
|
26
|
+
Group in which to create the array. If None, the root group is used.
|
|
27
|
+
"""
|
|
15
28
|
|
|
16
29
|
def get_data_by_reference(self, nodename, group=None):
|
|
17
|
-
|
|
30
|
+
"""
|
|
31
|
+
Get data by reference from the HDF5 file.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
nodename : :class:`str`
|
|
36
|
+
Name of the node (dataset or group) to retrieve from the HDF5 file.
|
|
37
|
+
group : object, optional
|
|
38
|
+
The parent group in which to look for the node. If None, the root group is used.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
object
|
|
43
|
+
A reference to the requested node (e.g., a dataset or group object) in the HDF5 file.
|
|
44
|
+
"""
|
|
18
45
|
|
|
19
46
|
def set_node_attribute(self, node, attrname, value):
|
|
20
|
-
|
|
47
|
+
"""
|
|
48
|
+
Set an attribute on a node.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
node : object
|
|
53
|
+
The node (e.g., group or dataset) to which the attribute will be set.
|
|
54
|
+
attrname : :class:`str`
|
|
55
|
+
The name of the attribute to set.
|
|
56
|
+
value : any
|
|
57
|
+
The value to assign to the attribute.
|
|
58
|
+
"""
|
|
21
59
|
|
|
22
60
|
def get_node_attribute(self, node, attrname):
|
|
23
|
-
|
|
61
|
+
"""
|
|
62
|
+
Get an attribute from a node.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
node : object
|
|
67
|
+
The node (e.g., group or dataset) from which to retrieve the attribute.
|
|
68
|
+
attrname : :class:`str`
|
|
69
|
+
The name of the attribute to retrieve.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
object
|
|
74
|
+
The value of the specified attribute.
|
|
75
|
+
"""
|
|
24
76
|
|
|
25
77
|
def append_data(self, node, data):
|
|
26
|
-
|
|
78
|
+
"""
|
|
79
|
+
Append data to an existing node.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
node : object
|
|
84
|
+
The node (e.g., array or dataset) in the HDF5 file to which data will be appended.
|
|
85
|
+
The expected type depends on the backend (e.g., PyTables node or h5py dataset).
|
|
86
|
+
data : array-like
|
|
87
|
+
The data to append. Should be compatible in shape and type with the existing node.
|
|
88
|
+
The format and type must match the node's requirements.
|
|
89
|
+
"""
|
|
27
90
|
|
|
28
91
|
def remove_data(self, nodename):
|
|
29
|
-
|
|
92
|
+
"""Remove data from the HDF5 file."""
|
|
30
93
|
|
|
31
94
|
def create_new_group(self, name, group=None):
|
|
32
|
-
|
|
95
|
+
"""Create a new group in the HDF5 file."""
|
|
33
96
|
|
|
34
97
|
|
|
35
98
|
class H5CacheFileBase:
|
|
@@ -38,10 +101,10 @@ class H5CacheFileBase:
|
|
|
38
101
|
compression_filter = None
|
|
39
102
|
|
|
40
103
|
def is_cached(self, nodename, group=None):
|
|
41
|
-
|
|
104
|
+
"""Check if data is cached in the HDF5 file."""
|
|
42
105
|
|
|
43
106
|
def create_compressible_array(self, nodename, shape, precision, group=None):
|
|
44
|
-
|
|
107
|
+
"""Create a compressible array in the HDF5 cache file."""
|
|
45
108
|
|
|
46
109
|
|
|
47
110
|
if config.have_tables:
|
|
@@ -62,34 +125,42 @@ if config.have_tables:
|
|
|
62
125
|
"""Hdf5 File based on PyTables."""
|
|
63
126
|
|
|
64
127
|
def create_extendable_array(self, nodename, shape, precision, group=None):
|
|
128
|
+
"""Create an extendable array using PyTables."""
|
|
65
129
|
if not group:
|
|
66
130
|
group = self.root
|
|
67
131
|
atom = precision_to_atom[precision]
|
|
68
132
|
self.create_earray(group, nodename, atom, shape)
|
|
69
133
|
|
|
70
134
|
def get_data_by_reference(self, nodename, group=None):
|
|
135
|
+
"""Get data by reference using PyTables."""
|
|
71
136
|
if not group:
|
|
72
137
|
group = self.root
|
|
73
138
|
return self.get_node(group, nodename)
|
|
74
139
|
|
|
75
140
|
def set_node_attribute(self, node, attrname, value):
|
|
141
|
+
"""Set an attribute on a PyTables node."""
|
|
76
142
|
node.set_attr(attrname, value)
|
|
77
143
|
|
|
78
144
|
def get_node_attribute(self, node, attrname):
|
|
145
|
+
"""Get an attribute from a PyTables node."""
|
|
79
146
|
return node._v_attrs[attrname] # noqa: SLF001
|
|
80
147
|
|
|
81
148
|
def append_data(self, node, data):
|
|
149
|
+
"""Append data to a PyTables node."""
|
|
82
150
|
node.append(data)
|
|
83
151
|
|
|
84
152
|
def remove_data(self, nodename):
|
|
153
|
+
"""Remove data from PyTables file."""
|
|
85
154
|
self.remove_node('/', nodename, recursive=True)
|
|
86
155
|
|
|
87
156
|
def create_new_group(self, name, group=None):
|
|
157
|
+
"""Create a new group in PyTables file."""
|
|
88
158
|
if not group:
|
|
89
159
|
group = self.root
|
|
90
160
|
return self.create_group(group, name)
|
|
91
161
|
|
|
92
162
|
def get_child_nodes(self, nodename):
|
|
163
|
+
"""Get child nodes from a PyTables group."""
|
|
93
164
|
for childnode in self.list_nodes(nodename):
|
|
94
165
|
yield (childnode.name, childnode)
|
|
95
166
|
|
|
@@ -115,11 +186,13 @@ if config.have_tables:
|
|
|
115
186
|
compression_filter = tables.Filters(complevel=5, complib='blosc')
|
|
116
187
|
|
|
117
188
|
def is_cached(self, nodename, group=None):
|
|
189
|
+
"""Check if data is cached in PyTables file."""
|
|
118
190
|
if not group:
|
|
119
191
|
group = self.root
|
|
120
192
|
return nodename in group
|
|
121
193
|
|
|
122
194
|
def create_compressible_array(self, nodename, shape, precision, group=None):
|
|
195
|
+
"""Create a compressible array in PyTables cache file."""
|
|
123
196
|
if not group:
|
|
124
197
|
group = self.root
|
|
125
198
|
atom = precision_to_atom[precision]
|
|
@@ -133,43 +206,53 @@ if config.have_h5py:
|
|
|
133
206
|
"""Hdf5 File based on h5py."""
|
|
134
207
|
|
|
135
208
|
def _get_in_file_path(self, nodename, group=None):
|
|
209
|
+
"""Get the in-file path for h5py operations."""
|
|
136
210
|
if not group:
|
|
137
211
|
return '/' + nodename
|
|
138
212
|
return group + '/' + nodename
|
|
139
213
|
|
|
140
214
|
def create_array(self, where, name, obj):
|
|
215
|
+
"""Create an array in h5py file."""
|
|
141
216
|
self.create_dataset(f'{where}/{name}', data=obj)
|
|
142
217
|
|
|
143
218
|
def create_extendable_array(self, nodename, shape, precision, group=None):
|
|
219
|
+
"""Create an extendable array using h5py."""
|
|
144
220
|
in_file_path = self._get_in_file_path(nodename, group)
|
|
145
221
|
self.create_dataset(in_file_path, shape=shape, dtype=precision, maxshape=(None, shape[1]))
|
|
146
222
|
|
|
147
223
|
def get_data_by_reference(self, nodename, group=None):
|
|
224
|
+
"""Get data by reference using h5py."""
|
|
148
225
|
in_file_path = self._get_in_file_path(nodename, group)
|
|
149
226
|
return self[in_file_path]
|
|
150
227
|
|
|
151
228
|
def set_node_attribute(self, node, attrname, value):
|
|
229
|
+
"""Set an attribute on an h5py node."""
|
|
152
230
|
node.attrs[attrname] = value
|
|
153
231
|
|
|
154
232
|
def get_node_attribute(self, node, attrname):
|
|
233
|
+
"""Get an attribute from an h5py node."""
|
|
155
234
|
return node.attrs[attrname]
|
|
156
235
|
|
|
157
236
|
def append_data(self, node, data):
|
|
237
|
+
"""Append data to an h5py dataset."""
|
|
158
238
|
old_shape = node.shape
|
|
159
239
|
new_shape = (old_shape[0] + data.shape[0], data.shape[1])
|
|
160
240
|
node.resize(new_shape)
|
|
161
241
|
node[old_shape[0] : new_shape[0], :] = data
|
|
162
242
|
|
|
163
243
|
def remove_data(self, nodename, group=None):
|
|
244
|
+
"""Remove data from h5py file."""
|
|
164
245
|
in_file_path = self._get_in_file_path(nodename, group)
|
|
165
246
|
del self[in_file_path]
|
|
166
247
|
|
|
167
248
|
def create_new_group(self, name, group=None):
|
|
249
|
+
"""Create a new group in h5py file."""
|
|
168
250
|
in_file_path = self._get_in_file_path(name, group)
|
|
169
251
|
self.create_group(in_file_path)
|
|
170
252
|
return in_file_path
|
|
171
253
|
|
|
172
254
|
def get_child_nodes(self, nodename):
|
|
255
|
+
"""Get child nodes from an h5py group."""
|
|
173
256
|
for childnode in self[nodename]:
|
|
174
257
|
yield (childnode, self[f'{nodename}/{childnode}'])
|
|
175
258
|
|
|
@@ -196,11 +279,13 @@ if config.have_h5py:
|
|
|
196
279
|
# compression_filter = 'blosc' # unavailable...
|
|
197
280
|
|
|
198
281
|
def is_cached(self, nodename, group=None):
|
|
282
|
+
"""Check if data is cached in h5py file."""
|
|
199
283
|
if not group:
|
|
200
284
|
group = '/'
|
|
201
285
|
return group + nodename in self
|
|
202
286
|
|
|
203
287
|
def create_compressible_array(self, nodename, shape, precision, group=None):
|
|
288
|
+
"""Create a compressible array in h5py cache file."""
|
|
204
289
|
in_file_path = self._get_in_file_path(nodename, group)
|
|
205
290
|
self.create_dataset(
|
|
206
291
|
in_file_path,
|
|
@@ -212,6 +297,7 @@ if config.have_h5py:
|
|
|
212
297
|
|
|
213
298
|
|
|
214
299
|
def _get_h5file_class():
|
|
300
|
+
"""Get the appropriate H5File class based on configuration."""
|
|
215
301
|
if config.h5library in ['pytables', 'tables']:
|
|
216
302
|
return H5FileTables
|
|
217
303
|
if config.h5library == 'h5py':
|
|
@@ -220,6 +306,7 @@ def _get_h5file_class():
|
|
|
220
306
|
|
|
221
307
|
|
|
222
308
|
def _get_cachefile_class():
|
|
309
|
+
"""Get the appropriate H5CacheFile class based on configuration."""
|
|
223
310
|
if config.h5library in ['pytables', 'tables']:
|
|
224
311
|
return H5CacheFileTables
|
|
225
312
|
if config.h5library == 'h5py':
|
acoular/microphones.py
CHANGED
|
@@ -14,7 +14,7 @@ Implements support for array microphone arrangements.
|
|
|
14
14
|
import xml.dom.minidom
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
import numpy as np
|
|
18
18
|
from scipy.spatial.distance import cdist
|
|
19
19
|
from traits.api import (
|
|
20
20
|
CArray,
|
|
@@ -24,17 +24,13 @@ from traits.api import (
|
|
|
24
24
|
Property,
|
|
25
25
|
Union,
|
|
26
26
|
cached_property,
|
|
27
|
-
|
|
27
|
+
observe,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
# acoular imports
|
|
31
|
-
from .deprecation import deprecated_alias
|
|
32
31
|
from .internal import digest
|
|
33
32
|
|
|
34
33
|
|
|
35
|
-
@deprecated_alias(
|
|
36
|
-
{'mpos_tot': 'pos_total', 'mpos': 'pos', 'from_file': 'file'}, read_only=['mpos'], removal_version='25.10'
|
|
37
|
-
)
|
|
38
34
|
class MicGeom(HasStrictTraits):
|
|
39
35
|
"""
|
|
40
36
|
Provide the geometric arrangement of microphones in an array.
|
|
@@ -43,23 +39,22 @@ class MicGeom(HasStrictTraits):
|
|
|
43
39
|
microphone array. The positions can be read from an XML file or set programmatically. Invalid
|
|
44
40
|
microphones can be excluded by specifying their indices via :attr:`invalid_channels`.
|
|
45
41
|
|
|
42
|
+
.. _units_note_microphones:
|
|
43
|
+
.. admonition:: Unit of length
|
|
44
|
+
|
|
45
|
+
The source code is agnostic to the unit of length. The microphone positions' coordinates are
|
|
46
|
+
assumed to be in meters. This is consistent with the standard
|
|
47
|
+
:class:`~acoular.environments.Environment` class which uses the speed of sound at 20°C at sea
|
|
48
|
+
level under standard atmosphere pressure in m/s. If the microphone positions' coordinates are
|
|
49
|
+
provided in a unit other than meter, it is advisable to change the
|
|
50
|
+
:attr:`~acoular.environments.Environment.c` attribute to match the given unit.
|
|
51
|
+
|
|
46
52
|
Notes
|
|
47
53
|
-----
|
|
48
54
|
- The microphone geometry as in :attr:`total_pos` is automatically changed if the :attr:`file`
|
|
49
55
|
attribute is updated.
|
|
50
56
|
- Small numerical values in the computed :attr:`center` are set to zero for numerical stability.
|
|
51
57
|
|
|
52
|
-
.. _units_note_microphones:
|
|
53
|
-
|
|
54
|
-
Unit System
|
|
55
|
-
-----------
|
|
56
|
-
The source code is agnostic to the unit of length. The microphone positions' coordinates are
|
|
57
|
-
assumed to be in meters. This is consistent with the standard
|
|
58
|
-
:class:`~acoular.environments.Environment` class which uses the speed of sound at 20°C at sea
|
|
59
|
-
level under standard atmosphere pressure in m/s. If the microphone positions' coordinates are
|
|
60
|
-
provided in a unit other than meter, it is advisable to change the
|
|
61
|
-
:attr:`~acoular.environments.Environment.c` attribute to match the given unit.
|
|
62
|
-
|
|
63
58
|
Examples
|
|
64
59
|
--------
|
|
65
60
|
To set a microphone geomerty for ``n`` programmatically, first a ``(3,n)`` array is needed. In
|
|
@@ -146,13 +141,13 @@ class MicGeom(HasStrictTraits):
|
|
|
146
141
|
|
|
147
142
|
#: Array containing the ``x, y, z`` positions of all microphones, including invalid ones, shape
|
|
148
143
|
#: ``(3,`` :attr:`num_mics` ``)``. This is set automatically when :attr:`file` changes or
|
|
149
|
-
#: explicitly by assigning an array of floats. All coordinates are in meters by default
|
|
150
|
-
#: :ref:`
|
|
144
|
+
#: explicitly by assigning an array of floats. All coordinates are in meters by default
|
|
145
|
+
#: (:ref:`see here <units_note_microphones>`).
|
|
151
146
|
pos_total = CArray(dtype=float, shape=(3, None), desc='x, y, z position of all microphones')
|
|
152
147
|
|
|
153
148
|
#: Array containing the ``x, y, z`` positions of valid microphones (i.e., excluding those in
|
|
154
149
|
#: :attr:`invalid_channels`), shape ``(3,`` :attr:`num_mics` ``)``. (read-only)
|
|
155
|
-
#: All coordinates are in meters by default (
|
|
150
|
+
#: All coordinates are in meters by default (:ref:`see here <units_note_microphones>`).
|
|
156
151
|
pos = Property(depends_on=['pos_total', 'invalid_channels'], desc='x, y, z position of used microphones')
|
|
157
152
|
|
|
158
153
|
#: List of indices indicating microphones to be excluded from calculations and results.
|
|
@@ -181,7 +176,7 @@ class MicGeom(HasStrictTraits):
|
|
|
181
176
|
if len(self.invalid_channels) == 0:
|
|
182
177
|
return self.pos_total
|
|
183
178
|
allr = [i for i in range(self.pos_total.shape[-1]) if i not in self.invalid_channels]
|
|
184
|
-
return self.pos_total[:, array(allr)]
|
|
179
|
+
return self.pos_total[:, np.array(allr)]
|
|
185
180
|
|
|
186
181
|
@cached_property
|
|
187
182
|
def _get_num_mics(self):
|
|
@@ -190,7 +185,7 @@ class MicGeom(HasStrictTraits):
|
|
|
190
185
|
@cached_property
|
|
191
186
|
def _get_center(self):
|
|
192
187
|
if self.pos.any():
|
|
193
|
-
center = average(self.pos, axis=1)
|
|
188
|
+
center = np.average(self.pos, axis=1)
|
|
194
189
|
# set very small values to zero
|
|
195
190
|
center[abs(center) < 1e-16] = 0.0
|
|
196
191
|
return center
|
|
@@ -202,8 +197,8 @@ class MicGeom(HasStrictTraits):
|
|
|
202
197
|
return cdist(self.pos.T, self.pos.T).max()
|
|
203
198
|
return None
|
|
204
199
|
|
|
205
|
-
@
|
|
206
|
-
def _import_mpos(self):
|
|
200
|
+
@observe('file')
|
|
201
|
+
def _import_mpos(self, event): # noqa ARG002
|
|
207
202
|
# Import the microphone positions from an XML file.
|
|
208
203
|
#
|
|
209
204
|
# This method parses the XML file specified in :attr:`file` and extracts the ``x``, ``y``,
|
|
@@ -237,7 +232,7 @@ class MicGeom(HasStrictTraits):
|
|
|
237
232
|
for el in doc.getElementsByTagName('pos'):
|
|
238
233
|
names.append(el.getAttribute('Name'))
|
|
239
234
|
xyz.append([float(el.getAttribute(a)) for a in 'xyz'])
|
|
240
|
-
self.pos_total = array(xyz, 'd').swapaxes(0, 1)
|
|
235
|
+
self.pos_total = np.array(xyz, 'd').swapaxes(0, 1)
|
|
241
236
|
|
|
242
237
|
def export_mpos(self, filename):
|
|
243
238
|
"""
|
|
@@ -266,8 +261,8 @@ class MicGeom(HasStrictTraits):
|
|
|
266
261
|
index of the microphone.
|
|
267
262
|
- This method only exports the positions of the valid microphones (those not listed in
|
|
268
263
|
:attr:`invalid_channels`).
|
|
269
|
-
- All coordinates (x, y, z) are exported in meters by default (see
|
|
270
|
-
|
|
264
|
+
- All coordinates (x, y, z) are exported in meters by default (:ref:`see here
|
|
265
|
+
<units_note_microphones>`).
|
|
271
266
|
"""
|
|
272
267
|
filepath = Path(filename)
|
|
273
268
|
basename = filepath.stem
|
acoular/process.py
CHANGED
|
@@ -19,12 +19,11 @@ from inspect import currentframe
|
|
|
19
19
|
from warnings import warn
|
|
20
20
|
|
|
21
21
|
import numpy as np
|
|
22
|
-
from traits.api import Any, Array, Bool, Dict, Enum, Instance, Int, Property, Union, cached_property,
|
|
22
|
+
from traits.api import Any, Array, Bool, Dict, Enum, Instance, Int, Property, Union, cached_property, observe
|
|
23
23
|
|
|
24
24
|
# acoular imports
|
|
25
25
|
from .base import Generator, InOut
|
|
26
26
|
from .configuration import config
|
|
27
|
-
from .deprecation import deprecated_alias
|
|
28
27
|
from .h5cache import H5cache
|
|
29
28
|
from .h5files import H5CacheFileBase
|
|
30
29
|
from .internal import digest
|
|
@@ -49,7 +48,7 @@ class LockedGenerator:
|
|
|
49
48
|
|
|
50
49
|
See Also
|
|
51
50
|
--------
|
|
52
|
-
:class
|
|
51
|
+
:class:`~acoular.process.SampleSplitter` :
|
|
53
52
|
Distribute data from a source to several following objects in a block-wise manner.
|
|
54
53
|
"""
|
|
55
54
|
|
|
@@ -63,9 +62,6 @@ class LockedGenerator:
|
|
|
63
62
|
return self.it.__next__()
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
@deprecated_alias(
|
|
67
|
-
{'naverage': 'num_per_average', 'numsamples': 'num_samples'}, read_only=['numsamples'], removal_version='25.10'
|
|
68
|
-
)
|
|
69
65
|
class Average(InOut):
|
|
70
66
|
"""
|
|
71
67
|
Calculate the average across consecutive time samples or frequency snapshots.
|
|
@@ -80,7 +76,7 @@ class Average(InOut):
|
|
|
80
76
|
|
|
81
77
|
See Also
|
|
82
78
|
--------
|
|
83
|
-
:class
|
|
79
|
+
:class:`~acoular.base.InOut` :
|
|
84
80
|
Receive data from any source domain and return signals in the same domain.
|
|
85
81
|
|
|
86
82
|
Examples
|
|
@@ -214,8 +210,8 @@ class Cache(InOut):
|
|
|
214
210
|
|
|
215
211
|
See Also
|
|
216
212
|
--------
|
|
217
|
-
:class
|
|
218
|
-
|
|
213
|
+
:class:`~acoular.base.InOut` : Receive data from any source domain and return signals in the
|
|
214
|
+
same domain.
|
|
219
215
|
|
|
220
216
|
Examples
|
|
221
217
|
--------
|
|
@@ -542,8 +538,8 @@ class SampleSplitter(InOut):
|
|
|
542
538
|
next_block = next(self._source_generator)
|
|
543
539
|
[self.block_buffer[obj].appendleft(next_block) for obj in self.block_buffer]
|
|
544
540
|
|
|
545
|
-
@
|
|
546
|
-
def _change_buffer_size(self): #
|
|
541
|
+
@observe('buffer_size')
|
|
542
|
+
def _change_buffer_size(self, event): # noqa: ARG002
|
|
547
543
|
for obj in self.block_buffer:
|
|
548
544
|
self._remove_block_buffer(obj)
|
|
549
545
|
self._create_block_buffer(obj)
|
acoular/sdinput.py
CHANGED
|
@@ -14,17 +14,12 @@ from traits.api import Any, Bool, Enum, Float, Int, Property, cached_property, o
|
|
|
14
14
|
# acoular imports
|
|
15
15
|
from .base import SamplesGenerator
|
|
16
16
|
from .configuration import config
|
|
17
|
-
from .deprecation import deprecated_alias
|
|
18
17
|
from .internal import digest
|
|
19
18
|
|
|
20
19
|
if config.have_sounddevice:
|
|
21
20
|
import sounddevice as sd
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
@deprecated_alias(
|
|
25
|
-
{'numchannels': 'num_channels', 'numsamples': 'num_samples', 'collectsamples': 'collect_samples'},
|
|
26
|
-
removal_version='25.10',
|
|
27
|
-
)
|
|
28
23
|
class SoundDeviceSamplesGenerator(SamplesGenerator):
|
|
29
24
|
"""Controller for sound card hardware using sounddevice library.
|
|
30
25
|
|
acoular/signals.py
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
"""
|
|
5
5
|
Implements signal generators for the simulation of acoustic sources.
|
|
6
6
|
|
|
7
|
+
.. inheritance-diagram::
|
|
8
|
+
acoular.signals
|
|
9
|
+
:top-classes:
|
|
10
|
+
acoular.signals.SignalGenerator
|
|
11
|
+
:parts: 1
|
|
12
|
+
|
|
7
13
|
.. autosummary::
|
|
8
14
|
:toctree: generated/
|
|
9
15
|
|
|
@@ -21,8 +27,7 @@ Implements signal generators for the simulation of acoustic sources.
|
|
|
21
27
|
from abc import abstractmethod
|
|
22
28
|
from warnings import warn
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
from numpy.random import RandomState
|
|
30
|
+
import numpy as np
|
|
26
31
|
from scipy.signal import resample, sosfilt, tf2sos
|
|
27
32
|
from traits.api import (
|
|
28
33
|
ABCHasStrictTraits,
|
|
@@ -39,11 +44,9 @@ from traits.api import (
|
|
|
39
44
|
|
|
40
45
|
# acoular imports
|
|
41
46
|
from .base import SamplesGenerator
|
|
42
|
-
from .deprecation import deprecated_alias
|
|
43
47
|
from .internal import digest
|
|
44
48
|
|
|
45
49
|
|
|
46
|
-
@deprecated_alias({'numsamples': 'num_samples'}, removal_version='25.10')
|
|
47
50
|
class SignalGenerator(ABCHasStrictTraits):
|
|
48
51
|
"""
|
|
49
52
|
ABC for a simple one-channel signal generator.
|
|
@@ -170,9 +173,9 @@ class NoiseGenerator(SignalGenerator):
|
|
|
170
173
|
|
|
171
174
|
See Also
|
|
172
175
|
--------
|
|
173
|
-
:class
|
|
174
|
-
:class
|
|
175
|
-
:class
|
|
176
|
+
:class:`~acoular.signals.PNoiseGenerator` : For pink noise generation.
|
|
177
|
+
:class:`~acoular.signals.WNoiseGenerator` : For pink white generation.
|
|
178
|
+
:class:`~acoular.sources.UncorrelatedNoiseSource` : For per-channel noise generation.
|
|
176
179
|
"""
|
|
177
180
|
|
|
178
181
|
#: Root mean square (RMS) amplitude of the signal. For a point source,
|
|
@@ -211,8 +214,8 @@ class WNoiseGenerator(NoiseGenerator):
|
|
|
211
214
|
--------
|
|
212
215
|
:obj:`numpy.random.RandomState.standard_normal` :
|
|
213
216
|
Used here to generate normally distributed noise.
|
|
214
|
-
:class
|
|
215
|
-
:class
|
|
217
|
+
:class:`~acoular.signals.PNoiseGenerator` : For pink noise generation.
|
|
218
|
+
:class:`~acoular.sources.UncorrelatedNoiseSource` : For per-channel noise generation.
|
|
216
219
|
|
|
217
220
|
Examples
|
|
218
221
|
--------
|
|
@@ -260,7 +263,7 @@ class WNoiseGenerator(NoiseGenerator):
|
|
|
260
263
|
A 1D array of floats containing the generated white noise signal.
|
|
261
264
|
The length of the array is equal to :attr:`~SignalGenerator.num_samples`.
|
|
262
265
|
"""
|
|
263
|
-
rnd_gen = RandomState(self.seed)
|
|
266
|
+
rnd_gen = np.random.RandomState(self.seed)
|
|
264
267
|
return self.rms * rnd_gen.standard_normal(self.num_samples)
|
|
265
268
|
|
|
266
269
|
|
|
@@ -278,8 +281,8 @@ class PNoiseGenerator(NoiseGenerator):
|
|
|
278
281
|
|
|
279
282
|
See Also
|
|
280
283
|
--------
|
|
281
|
-
:class
|
|
282
|
-
:class
|
|
284
|
+
:class:`~acoular.signals.WNoiseGenerator` : For white noise generation.
|
|
285
|
+
:class:`~acoular.sources.UncorrelatedNoiseSource` : For per-channel noise generation.
|
|
283
286
|
|
|
284
287
|
References
|
|
285
288
|
----------
|
|
@@ -320,26 +323,26 @@ class PNoiseGenerator(NoiseGenerator):
|
|
|
320
323
|
simulation. If the specified depth exceeds the maximum possible value based on
|
|
321
324
|
the number of samples, it is automatically adjusted, and a warning is printed.
|
|
322
325
|
- The output signal is scaled to have the same overall level as white noise by dividing
|
|
323
|
-
the result by ``sqrt(depth + 1.5)``.
|
|
326
|
+
the result by ``np.sqrt(depth + 1.5)``.
|
|
324
327
|
"""
|
|
325
328
|
nums = self.num_samples
|
|
326
329
|
depth = self.depth
|
|
327
330
|
# maximum depth depending on number of samples
|
|
328
|
-
max_depth = int(log(nums) / log(2))
|
|
331
|
+
max_depth = int(np.log(nums) / np.log(2))
|
|
329
332
|
|
|
330
333
|
if depth > max_depth:
|
|
331
334
|
depth = max_depth
|
|
332
335
|
print(f'Pink noise filter depth set to maximum possible value of {max_depth:d}.')
|
|
333
336
|
|
|
334
|
-
rnd_gen = RandomState(self.seed)
|
|
337
|
+
rnd_gen = np.random.RandomState(self.seed)
|
|
335
338
|
s = rnd_gen.standard_normal(nums)
|
|
336
339
|
for _ in range(depth):
|
|
337
340
|
ind = 2**_ - 1
|
|
338
341
|
lind = nums - ind
|
|
339
342
|
dind = 2 ** (_ + 1)
|
|
340
|
-
s[ind:] += repeat(rnd_gen.standard_normal(nums // dind + 1), dind)[:lind]
|
|
341
|
-
# divide by sqrt(depth+1.5) to get same overall level as white noise
|
|
342
|
-
return self.rms / sqrt(depth + 1.5) * s
|
|
343
|
+
s[ind:] += np.repeat(rnd_gen.standard_normal(nums // dind + 1), dind)[:lind]
|
|
344
|
+
# divide by np.sqrt(depth+1.5) to get same overall level as white noise
|
|
345
|
+
return self.rms / np.sqrt(depth + 1.5) * s
|
|
343
346
|
|
|
344
347
|
|
|
345
348
|
class FiltWNoiseGenerator(WNoiseGenerator):
|
|
@@ -399,11 +402,11 @@ class FiltWNoiseGenerator(WNoiseGenerator):
|
|
|
399
402
|
|
|
400
403
|
#: A :class:`numpy.ndarray` of autoregressive coefficients (denominator). Default is ``[]``,
|
|
401
404
|
#: which results in no AR filtering (i.e., all-pole filter is ``[1.0]``).
|
|
402
|
-
ar = CArray(value=array([]), dtype=float, desc='autoregressive coefficients (coefficients of the denominator)')
|
|
405
|
+
ar = CArray(value=np.array([]), dtype=float, desc='autoregressive coefficients (coefficients of the denominator)')
|
|
403
406
|
|
|
404
407
|
#: A :class:`numpy.ndarray` of moving-average coefficients (numerator). Default is ``[]``,
|
|
405
408
|
#: which results in no MA filtering (i.e., all-zero filter is ``[1.0]``).
|
|
406
|
-
ma = CArray(value=array([]), dtype=float, desc='moving-average coefficients (coefficients of the numerator)')
|
|
409
|
+
ma = CArray(value=np.array([]), dtype=float, desc='moving-average coefficients (coefficients of the numerator)')
|
|
407
410
|
|
|
408
411
|
#: A unique checksum identifier based on the object properties. (read-only)
|
|
409
412
|
digest = Property(depends_on=['rms', 'seed', 'sample_freq', 'num_samples', 'ar', 'ma'])
|
|
@@ -432,7 +435,7 @@ class FiltWNoiseGenerator(WNoiseGenerator):
|
|
|
432
435
|
if the input array is empty.
|
|
433
436
|
"""
|
|
434
437
|
if coefficients.size == 0:
|
|
435
|
-
return array([1.0])
|
|
438
|
+
return np.array([1.0])
|
|
436
439
|
return coefficients
|
|
437
440
|
|
|
438
441
|
def signal(self):
|
|
@@ -450,7 +453,7 @@ class FiltWNoiseGenerator(WNoiseGenerator):
|
|
|
450
453
|
An array representing the filtered white noise signal. The length of the returned array
|
|
451
454
|
is equal to :attr:`the number of samples<SignalGenerator.num_samples>`.
|
|
452
455
|
"""
|
|
453
|
-
rnd_gen = RandomState(self.seed)
|
|
456
|
+
rnd_gen = np.random.RandomState(self.seed)
|
|
454
457
|
ma = self.handle_empty_coefficients(self.ma)
|
|
455
458
|
ar = self.handle_empty_coefficients(self.ar)
|
|
456
459
|
sos = tf2sos(ma, ar)
|
|
@@ -552,11 +555,10 @@ class SineGenerator(PeriodicSignalGenerator):
|
|
|
552
555
|
The generator supports high-frequency and high-resolution signals,
|
|
553
556
|
limited by the Nyquist criterion.
|
|
554
557
|
"""
|
|
555
|
-
t = arange(self.num_samples, dtype=float) / self.sample_freq
|
|
556
|
-
return self.amplitude * sin(2 * pi * self.freq * t + self.phase)
|
|
558
|
+
t = np.arange(self.num_samples, dtype=float) / self.sample_freq
|
|
559
|
+
return self.amplitude * np.sin(2 * np.pi * self.freq * t + self.phase)
|
|
557
560
|
|
|
558
561
|
|
|
559
|
-
@deprecated_alias({'rms': 'amplitude'}, removal_version='25.10')
|
|
560
562
|
class GenericSignalGenerator(SignalGenerator):
|
|
561
563
|
"""
|
|
562
564
|
Generate signals from a :class:`~acoular.base.SamplesGenerator` or derived object.
|
|
@@ -648,7 +650,7 @@ class GenericSignalGenerator(SignalGenerator):
|
|
|
648
650
|
stacklevel=2,
|
|
649
651
|
)
|
|
650
652
|
nums = self.num_samples
|
|
651
|
-
track = zeros(nums)
|
|
653
|
+
track = np.zeros(nums)
|
|
652
654
|
|
|
653
655
|
# iterate through source generator to fill signal track
|
|
654
656
|
for i, temp in enumerate(self.source.result(block)):
|
|
@@ -665,7 +667,7 @@ class GenericSignalGenerator(SignalGenerator):
|
|
|
665
667
|
# fill up empty track with as many full source signals as possible
|
|
666
668
|
nloops = nums // stop
|
|
667
669
|
if nloops > 1:
|
|
668
|
-
track[stop : stop * nloops] = tile(track[:stop], nloops - 1)
|
|
670
|
+
track[stop : stop * nloops] = np.tile(track[:stop], nloops - 1)
|
|
669
671
|
# fill up remaining empty track
|
|
670
672
|
res = nums % stop # last part of unfinished loop
|
|
671
673
|
if res > 0:
|