acoular 25.7__py3-none-any.whl → 26.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/aiaa/aiaa.py +8 -10
- acoular/base.py +13 -16
- acoular/calib.py +25 -24
- acoular/configuration.py +2 -2
- acoular/demo/__init__.py +97 -9
- acoular/demo/__main__.py +37 -0
- acoular/environments.py +119 -130
- acoular/fbeamform.py +438 -440
- acoular/fprocess.py +18 -13
- acoular/grids.py +122 -301
- acoular/h5cache.py +5 -1
- acoular/h5files.py +96 -9
- acoular/microphones.py +30 -35
- acoular/process.py +14 -25
- acoular/sdinput.py +9 -14
- acoular/signals.py +36 -34
- acoular/sources.py +263 -380
- acoular/spectra.py +60 -80
- acoular/tbeamform.py +242 -224
- acoular/tools/helpers.py +25 -33
- acoular/tools/metrics.py +5 -10
- acoular/tools/utils.py +168 -0
- acoular/tprocess.py +248 -271
- acoular/trajectory.py +5 -6
- acoular/version.py +2 -2
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/METADATA +54 -105
- acoular-26.1.dist-info/RECORD +56 -0
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/WHEEL +1 -1
- acoular/demo/acoular_demo.py +0 -135
- acoular-25.7.dist-info/RECORD +0 -56
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.7.dist-info → acoular-26.1.dist-info}/licenses/LICENSE +0 -0
acoular/h5cache.py
CHANGED
|
@@ -32,23 +32,27 @@ class HDF5Cache(HasStrictTraits):
|
|
|
32
32
|
pass
|
|
33
33
|
|
|
34
34
|
def close_cachefile(self, cachefile):
|
|
35
|
+
"""Close a cache file and remove it from the reference counter."""
|
|
35
36
|
self.open_file_reference.pop(Path(cachefile.filename))
|
|
36
37
|
cachefile.close()
|
|
37
38
|
|
|
38
39
|
def get_open_cachefiles(self):
|
|
40
|
+
"""Get an iterator over all open cache files."""
|
|
39
41
|
try:
|
|
40
42
|
return self.open_files.itervalues()
|
|
41
43
|
except AttributeError:
|
|
42
44
|
return iter(self.open_files.values())
|
|
43
45
|
|
|
44
46
|
def close_unreferenced_cachefiles(self):
|
|
47
|
+
"""Close cache files that are no longer referenced by any objects."""
|
|
45
48
|
for cachefile in self.get_open_cachefiles():
|
|
46
49
|
if not self.is_reference_existent(cachefile):
|
|
47
50
|
self.close_cachefile(cachefile)
|
|
48
51
|
|
|
49
52
|
def is_reference_existent(self, file):
|
|
53
|
+
"""Check if a file object still has active references."""
|
|
50
54
|
exist_flag = False
|
|
51
|
-
# inspect all
|
|
55
|
+
# inspect all referrers to the file object
|
|
52
56
|
gc.collect() # clear garbage before collecting referrers
|
|
53
57
|
for ref in gc.get_referrers(file):
|
|
54
58
|
# does the file object have a referrer that has a 'h5f'
|
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
|
|
@@ -142,32 +137,32 @@ class MicGeom(HasStrictTraits):
|
|
|
142
137
|
|
|
143
138
|
#: Path to the XML file containing microphone positions. The XML file should have elements with
|
|
144
139
|
#: the tag ``pos`` and attributes ``Name``, ``x``, ``y``, and ``z``.
|
|
145
|
-
file = Union(None, File(filter=['*.xml'], exists=True)
|
|
140
|
+
file = Union(None, File(filter=['*.xml'], exists=True))
|
|
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:`
|
|
151
|
-
pos_total = CArray(dtype=float, shape=(3, None)
|
|
144
|
+
#: explicitly by assigning an array of floats. All coordinates are in meters by default
|
|
145
|
+
#: (:ref:`see here <units_note_microphones>`).
|
|
146
|
+
pos_total = CArray(dtype=float, shape=(3, None))
|
|
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 (
|
|
156
|
-
pos = Property(depends_on=['pos_total', 'invalid_channels']
|
|
150
|
+
#: All coordinates are in meters by default (:ref:`see here <units_note_microphones>`).
|
|
151
|
+
pos = Property(depends_on=['pos_total', 'invalid_channels'])
|
|
157
152
|
|
|
158
153
|
#: List of indices indicating microphones to be excluded from calculations and results.
|
|
159
154
|
#: Default is ``[]``.
|
|
160
|
-
invalid_channels = List(int
|
|
155
|
+
invalid_channels = List(int)
|
|
161
156
|
|
|
162
157
|
#: Number of valid microphones in the array. (read-only)
|
|
163
|
-
num_mics = Property(depends_on=['pos']
|
|
158
|
+
num_mics = Property(depends_on=['pos'])
|
|
164
159
|
|
|
165
160
|
#: The geometric center of the array, calculated as the arithmetic mean of the positions of all
|
|
166
161
|
#: valid microphones. (read-only)
|
|
167
|
-
center = Property(depends_on=['pos']
|
|
162
|
+
center = Property(depends_on=['pos'])
|
|
168
163
|
|
|
169
164
|
#: The maximum distance between any two valid microphones in the array. (read-only)
|
|
170
|
-
aperture = Property(depends_on=['pos']
|
|
165
|
+
aperture = Property(depends_on=['pos'])
|
|
171
166
|
|
|
172
167
|
#: A unique identifier for the geometry, based on its properties. (read-only)
|
|
173
168
|
digest = Property(depends_on=['pos'])
|
|
@@ -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
|
|
@@ -275,6 +270,6 @@ class MicGeom(HasStrictTraits):
|
|
|
275
270
|
f.write(f'<?xml version="1.1" encoding="utf-8"?><MicArray name="{basename}">\n')
|
|
276
271
|
for i in range(self.pos.shape[-1]):
|
|
277
272
|
f.write(
|
|
278
|
-
f' <pos Name="Point {i+1}" x="{self.pos[0, i]}" y="{self.pos[1, i]}" z="{self.pos[2, i]}"/>\n',
|
|
273
|
+
f' <pos Name="Point {i + 1}" x="{self.pos[0, i]}" y="{self.pos[1, i]}" z="{self.pos[2, i]}"/>\n',
|
|
279
274
|
)
|
|
280
275
|
f.write('</MicArray>')
|
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
|
|
@@ -120,7 +116,7 @@ class Average(InOut):
|
|
|
120
116
|
|
|
121
117
|
#: The number of samples (time domain source) or snapshots (frequency domain source)
|
|
122
118
|
#: to average over. Default is ``64``.
|
|
123
|
-
num_per_average = Int(64
|
|
119
|
+
num_per_average = Int(64)
|
|
124
120
|
|
|
125
121
|
#: The sampling frequency of the output signal. It is set automatically as
|
|
126
122
|
#: (:attr:`~acoular.base.Generator.sample_freq` ``/`` :attr:`num_per_average`).
|
|
@@ -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
|
--------
|
|
@@ -483,7 +479,6 @@ class SampleSplitter(InOut):
|
|
|
483
479
|
buffer_overflow_treatment = Dict(
|
|
484
480
|
key_trait=Instance(Generator),
|
|
485
481
|
value_trait=Enum('error', 'warning', 'none'),
|
|
486
|
-
desc='defines buffer overflow behaviour.',
|
|
487
482
|
)
|
|
488
483
|
|
|
489
484
|
# A shadow trait to monitor if source deliver samples or is empty.
|
|
@@ -542,8 +537,8 @@ class SampleSplitter(InOut):
|
|
|
542
537
|
next_block = next(self._source_generator)
|
|
543
538
|
[self.block_buffer[obj].appendleft(next_block) for obj in self.block_buffer]
|
|
544
539
|
|
|
545
|
-
@
|
|
546
|
-
def _change_buffer_size(self): #
|
|
540
|
+
@observe('buffer_size')
|
|
541
|
+
def _change_buffer_size(self, event): # noqa: ARG002
|
|
547
542
|
for obj in self.block_buffer:
|
|
548
543
|
self._remove_block_buffer(obj)
|
|
549
544
|
self._create_block_buffer(obj)
|
|
@@ -732,7 +727,7 @@ class SamplesBuffer(InOut):
|
|
|
732
727
|
""" # noqa: W505
|
|
733
728
|
|
|
734
729
|
#: The number of samples that the buffer can hold.
|
|
735
|
-
length = Int(
|
|
730
|
+
length = Int()
|
|
736
731
|
|
|
737
732
|
#: The number of samples per block to obtain from the source. If set to ``None``, the number of
|
|
738
733
|
#: samples will be determined by the ``num`` argument of the :meth:`result` method.
|
|
@@ -740,7 +735,6 @@ class SamplesBuffer(InOut):
|
|
|
740
735
|
None,
|
|
741
736
|
Int(),
|
|
742
737
|
default_value=None,
|
|
743
|
-
desc='number of samples to return from the source. If "None", use "num" argument of result method',
|
|
744
738
|
)
|
|
745
739
|
|
|
746
740
|
#: The number of samples to return from the buffer. If set to ``None``, the number of
|
|
@@ -749,7 +743,6 @@ class SamplesBuffer(InOut):
|
|
|
749
743
|
None,
|
|
750
744
|
Int(),
|
|
751
745
|
default_value=None,
|
|
752
|
-
desc="number of samples to return from the buffer. If 'None', use 'num' argument of result method",
|
|
753
746
|
)
|
|
754
747
|
|
|
755
748
|
#: Index shift value for the buffer.
|
|
@@ -759,26 +752,22 @@ class SamplesBuffer(InOut):
|
|
|
759
752
|
#: samples.
|
|
760
753
|
shift_index_by = Enum(
|
|
761
754
|
('result_num', 'num'),
|
|
762
|
-
desc=(
|
|
763
|
-
'index shift value for the buffer. If "result_num", use "result_num" trait.'
|
|
764
|
-
' If "num", use "num" argument of result method'
|
|
765
|
-
),
|
|
766
755
|
)
|
|
767
756
|
|
|
768
757
|
#: The current filling level of the buffer, i.e., how many samples are currently available.
|
|
769
|
-
level = Property(
|
|
758
|
+
level = Property()
|
|
770
759
|
|
|
771
760
|
#: The data type of the elements in the buffer.
|
|
772
|
-
dtype = Any(
|
|
761
|
+
dtype = Any()
|
|
773
762
|
|
|
774
763
|
# Flag indicating if the source is empty (for internal use).
|
|
775
|
-
_empty_source = Bool(False
|
|
764
|
+
_empty_source = Bool(False)
|
|
776
765
|
|
|
777
766
|
# The actual buffer holding the samples for processing.
|
|
778
|
-
_buffer = Array(shape=(None, None)
|
|
767
|
+
_buffer = Array(shape=(None, None))
|
|
779
768
|
|
|
780
769
|
# The current index position in the buffer.
|
|
781
|
-
_index = Int(
|
|
770
|
+
_index = Int()
|
|
782
771
|
|
|
783
772
|
def _get_level(self):
|
|
784
773
|
return self._buffer.shape[0] - self._index
|
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
|
|
|
@@ -40,36 +35,36 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
|
|
|
40
35
|
raise ImportError(msg)
|
|
41
36
|
|
|
42
37
|
#: input device index, refers to sounddevice list
|
|
43
|
-
device = Int(0
|
|
38
|
+
device = Int(0)
|
|
44
39
|
|
|
45
40
|
#: Number of input channels, maximum depends on device
|
|
46
|
-
num_channels = Int(1
|
|
41
|
+
num_channels = Int(1)
|
|
47
42
|
|
|
48
43
|
#: Number of samples to collect; defaults to -1. If is set to -1 device collects until user
|
|
49
44
|
# breaks streaming by setting Trait: collect_samples = False.
|
|
50
|
-
num_samples = Int(-1
|
|
45
|
+
num_samples = Int(-1)
|
|
51
46
|
|
|
52
47
|
#: Indicates if samples are collected, helper trait to break result loop
|
|
53
|
-
collect_samples = Bool(True
|
|
48
|
+
collect_samples = Bool(True)
|
|
54
49
|
|
|
55
50
|
#: Sampling frequency of the signal, changes with sinusdevices
|
|
56
|
-
sample_freq = Property(
|
|
51
|
+
sample_freq = Property()
|
|
57
52
|
|
|
58
53
|
_sample_freq = Float(default_value=None)
|
|
59
54
|
|
|
60
55
|
#: Datatype (resolution) of the signal, used as `dtype` in a sd `Stream` object
|
|
61
|
-
precision = Enum('float32', 'float16', 'int32', 'int16', 'int8', 'uint8'
|
|
56
|
+
precision = Enum('float32', 'float16', 'int32', 'int16', 'int8', 'uint8')
|
|
62
57
|
|
|
63
58
|
#: Indicates that the sounddevice buffer has overflown
|
|
64
|
-
overflow = Bool(False
|
|
59
|
+
overflow = Bool(False)
|
|
65
60
|
|
|
66
61
|
#: Indicates that the stream is collecting samples
|
|
67
|
-
running = Bool(False
|
|
62
|
+
running = Bool(False)
|
|
68
63
|
|
|
69
64
|
#: The sounddevice InputStream object for inspection
|
|
70
65
|
stream = Any
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
#: A unique identifier for the generator, based on its properties. (read-only)
|
|
73
68
|
digest = Property(depends_on=['device', 'num_channels', 'num_samples'])
|
|
74
69
|
|
|
75
70
|
@cached_property
|