acoular 24.5__py3-none-any.whl → 24.7__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 +3 -6
- acoular/configuration.py +20 -13
- acoular/demo/acoular_demo.py +28 -35
- acoular/environments.py +13 -10
- acoular/fastFuncs.py +40 -40
- acoular/fbeamform.py +895 -1088
- acoular/grids.py +5 -5
- acoular/h5cache.py +34 -34
- acoular/h5files.py +13 -13
- acoular/internal.py +3 -3
- acoular/sdinput.py +10 -1
- acoular/signals.py +15 -1
- acoular/sources.py +120 -49
- acoular/spectra.py +5 -5
- acoular/tbeamform.py +5 -6
- acoular/tfastfuncs.py +17 -18
- acoular/tools/aiaa.py +4 -5
- acoular/tprocess.py +82 -82
- acoular/version.py +2 -2
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/METADATA +24 -4
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/RECORD +24 -24
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/WHEEL +1 -1
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.5.dist-info → acoular-24.7.dist-info}/licenses/LICENSE +0 -0
acoular/grids.py
CHANGED
|
@@ -550,8 +550,8 @@ class RectGrid3D(RectGrid):
|
|
|
550
550
|
if isscalar(increment):
|
|
551
551
|
try:
|
|
552
552
|
self._increment = absolute(float(increment))
|
|
553
|
-
except:
|
|
554
|
-
raise TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment)
|
|
553
|
+
except ValueError as ve:
|
|
554
|
+
raise TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment) from ve
|
|
555
555
|
elif len(increment) == 3:
|
|
556
556
|
self._increment = array(increment, dtype=float)
|
|
557
557
|
else:
|
|
@@ -559,14 +559,14 @@ class RectGrid3D(RectGrid):
|
|
|
559
559
|
|
|
560
560
|
# Respective increments in x,y, and z-direction (in m).
|
|
561
561
|
# Deprecated: Use :attr:`~RectGrid.increment` for this functionality
|
|
562
|
-
increment3D = Property(desc='3D step sizes')
|
|
562
|
+
increment3D = Property(desc='3D step sizes') # noqa N815
|
|
563
563
|
|
|
564
|
-
def _get_increment3D(self):
|
|
564
|
+
def _get_increment3D(self): # noqa N802
|
|
565
565
|
if isscalar(self._increment):
|
|
566
566
|
return array([self._increment, self._increment, self._increment])
|
|
567
567
|
return self._increment
|
|
568
568
|
|
|
569
|
-
def _set_increment3D(self, inc):
|
|
569
|
+
def _set_increment3D(self, inc): # noqa N802
|
|
570
570
|
if not isscalar(inc) and len(inc) == 3:
|
|
571
571
|
self._increment = array(inc, dtype=float)
|
|
572
572
|
else:
|
acoular/h5cache.py
CHANGED
|
@@ -13,7 +13,7 @@ from .configuration import Config, config
|
|
|
13
13
|
from .h5files import _get_cachefile_class
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class HDF5Cache(HasPrivateTraits):
|
|
17
17
|
"""Cache class that handles opening and closing 'tables.File' objects."""
|
|
18
18
|
|
|
19
19
|
config = Instance(Config)
|
|
@@ -24,23 +24,23 @@ class H5cache_class(HasPrivateTraits):
|
|
|
24
24
|
|
|
25
25
|
open_files = WeakValueDictionary()
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
open_file_reference = Dict()
|
|
28
28
|
|
|
29
29
|
def _idle_if_busy(self):
|
|
30
30
|
while self.busy:
|
|
31
31
|
pass
|
|
32
32
|
|
|
33
|
-
def open_cachefile(self,
|
|
34
|
-
|
|
35
|
-
return
|
|
33
|
+
def open_cachefile(self, filename, mode):
|
|
34
|
+
file = _get_cachefile_class()
|
|
35
|
+
return file(path.join(self.cache_dir, filename), mode)
|
|
36
36
|
|
|
37
37
|
def close_cachefile(self, cachefile):
|
|
38
|
-
self.
|
|
38
|
+
self.open_file_reference.pop(get_basename(cachefile))
|
|
39
39
|
cachefile.close()
|
|
40
40
|
|
|
41
41
|
def get_filename(self, file):
|
|
42
|
-
|
|
43
|
-
if isinstance(file,
|
|
42
|
+
file_class = _get_cachefile_class()
|
|
43
|
+
if isinstance(file, file_class):
|
|
44
44
|
return get_basename(file)
|
|
45
45
|
return 0
|
|
46
46
|
|
|
@@ -51,13 +51,13 @@ class H5cache_class(HasPrivateTraits):
|
|
|
51
51
|
return iter(self.open_files.values())
|
|
52
52
|
|
|
53
53
|
def close_unreferenced_cachefiles(self):
|
|
54
|
-
for
|
|
55
|
-
if not self.is_reference_existent(
|
|
56
|
-
# print("close unreferenced File:",get_basename(
|
|
57
|
-
self.close_cachefile(
|
|
54
|
+
for cachefile in self.get_open_cachefiles():
|
|
55
|
+
if not self.is_reference_existent(cachefile):
|
|
56
|
+
# print("close unreferenced File:",get_basename(cachefile))
|
|
57
|
+
self.close_cachefile(cachefile)
|
|
58
58
|
|
|
59
59
|
def is_reference_existent(self, file):
|
|
60
|
-
|
|
60
|
+
exist_flag = False
|
|
61
61
|
# inspect all refererres to the file object
|
|
62
62
|
gc.collect() # clear garbage before collecting referrers
|
|
63
63
|
for ref in gc.get_referrers(file):
|
|
@@ -65,41 +65,41 @@ class H5cache_class(HasPrivateTraits):
|
|
|
65
65
|
# attribute?
|
|
66
66
|
if isinstance(ref, dict) and 'h5f' in ref:
|
|
67
67
|
# file is still referred, must not be closed
|
|
68
|
-
|
|
68
|
+
exist_flag = True
|
|
69
69
|
break
|
|
70
|
-
return
|
|
70
|
+
return exist_flag
|
|
71
71
|
|
|
72
|
-
def is_cachefile_existent(self,
|
|
73
|
-
if
|
|
72
|
+
def is_cachefile_existent(self, filename):
|
|
73
|
+
if filename in listdir(self.cache_dir):
|
|
74
74
|
return True
|
|
75
75
|
return False
|
|
76
76
|
|
|
77
|
-
def _increase_file_reference_counter(self,
|
|
78
|
-
self.
|
|
77
|
+
def _increase_file_reference_counter(self, filename):
|
|
78
|
+
self.open_file_reference[filename] = self.open_file_reference.get(filename, 0) + 1
|
|
79
79
|
|
|
80
|
-
def _decrease_file_reference_counter(self,
|
|
81
|
-
self.
|
|
80
|
+
def _decrease_file_reference_counter(self, filename):
|
|
81
|
+
self.open_file_reference[filename] = self.open_file_reference[filename] - 1
|
|
82
82
|
|
|
83
83
|
def _print_open_files(self):
|
|
84
|
-
print(list(self.
|
|
84
|
+
print(list(self.open_file_reference.items()))
|
|
85
85
|
|
|
86
86
|
def get_cache_file(self, obj, basename, mode='a'):
|
|
87
87
|
"""Returns pytables .h5 file to h5f trait of calling object for caching."""
|
|
88
88
|
self._idle_if_busy() #
|
|
89
89
|
self.busy = True
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
filename = basename + '_cache.h5'
|
|
92
|
+
obj_filename = self.get_filename(obj.h5f)
|
|
93
93
|
|
|
94
|
-
if
|
|
95
|
-
if
|
|
94
|
+
if obj_filename:
|
|
95
|
+
if obj_filename == filename:
|
|
96
96
|
self.busy = False
|
|
97
97
|
return
|
|
98
|
-
self._decrease_file_reference_counter(
|
|
98
|
+
self._decrease_file_reference_counter(obj_filename)
|
|
99
99
|
|
|
100
|
-
if
|
|
100
|
+
if filename not in self.open_files: # or tables.file._open_files.filenames
|
|
101
101
|
if config.global_caching == 'readonly' and not self.is_cachefile_existent(
|
|
102
|
-
|
|
102
|
+
filename,
|
|
103
103
|
): # condition ensures that cachefile is not created in readonly mode
|
|
104
104
|
obj.h5f = None
|
|
105
105
|
self.busy = False
|
|
@@ -107,11 +107,11 @@ class H5cache_class(HasPrivateTraits):
|
|
|
107
107
|
return
|
|
108
108
|
if config.global_caching == 'readonly':
|
|
109
109
|
mode = 'r'
|
|
110
|
-
f = self.open_cachefile(
|
|
111
|
-
self.open_files[
|
|
110
|
+
f = self.open_cachefile(filename, mode)
|
|
111
|
+
self.open_files[filename] = f
|
|
112
112
|
|
|
113
|
-
obj.h5f = self.open_files[
|
|
114
|
-
self._increase_file_reference_counter(
|
|
113
|
+
obj.h5f = self.open_files[filename]
|
|
114
|
+
self._increase_file_reference_counter(filename)
|
|
115
115
|
|
|
116
116
|
# garbage collection
|
|
117
117
|
self.close_unreferenced_cachefiles()
|
|
@@ -120,7 +120,7 @@ class H5cache_class(HasPrivateTraits):
|
|
|
120
120
|
self._print_open_files()
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
H5cache =
|
|
123
|
+
H5cache = HDF5Cache(config=config)
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
def get_basename(file):
|
acoular/h5files.py
CHANGED
|
@@ -33,7 +33,7 @@ class H5FileBase:
|
|
|
33
33
|
class H5CacheFileBase:
|
|
34
34
|
"""Base class for File objects that handle writing and reading of .h5 cache files."""
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
compression_filter = None
|
|
37
37
|
|
|
38
38
|
def is_cached(self, nodename, group=None):
|
|
39
39
|
pass
|
|
@@ -72,7 +72,7 @@ if config.have_tables:
|
|
|
72
72
|
node.set_attr(attrname, value)
|
|
73
73
|
|
|
74
74
|
def get_node_attribute(self, node, attrname):
|
|
75
|
-
return node._v_attrs[attrname]
|
|
75
|
+
return node._v_attrs[attrname] # noqa: SLF001
|
|
76
76
|
|
|
77
77
|
def append_data(self, node, data):
|
|
78
78
|
node.append(data)
|
|
@@ -93,10 +93,10 @@ if config.have_tables:
|
|
|
93
93
|
"""Recursively convert an HDF5 node to a dictionary."""
|
|
94
94
|
node = self.get_node(nodename)
|
|
95
95
|
# initialize node-dict with node's own attributes
|
|
96
|
-
result = {attr: node._v_attrs[attr] for attr in node._v_attrs._f_list()}
|
|
96
|
+
result = {attr: node._v_attrs[attr] for attr in node._v_attrs._f_list()} # noqa: SLF001
|
|
97
97
|
if isinstance(node, tables.Group):
|
|
98
98
|
# if node is a group, recursively add its children
|
|
99
|
-
for childname in node._v_children:
|
|
99
|
+
for childname in node._v_children: # noqa: SLF001
|
|
100
100
|
result[childname] = self.node_to_dict(f'{nodename}/{childname}')
|
|
101
101
|
elif isinstance(node, tables.Leaf):
|
|
102
102
|
# if node contains only data, add it
|
|
@@ -106,7 +106,7 @@ if config.have_tables:
|
|
|
106
106
|
return result
|
|
107
107
|
|
|
108
108
|
class H5CacheFileTables(H5FileTables, H5CacheFileBase):
|
|
109
|
-
|
|
109
|
+
compression_filter = tables.Filters(complevel=5, complib='blosc')
|
|
110
110
|
|
|
111
111
|
def is_cached(self, nodename, group=None):
|
|
112
112
|
if not group:
|
|
@@ -119,7 +119,7 @@ if config.have_tables:
|
|
|
119
119
|
if not group:
|
|
120
120
|
group = self.root
|
|
121
121
|
atom = precision_to_atom[precision]
|
|
122
|
-
self.create_carray(group, nodename, atom, shape, filters=self.
|
|
122
|
+
self.create_carray(group, nodename, atom, shape, filters=self.compression_filter)
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
if config.have_h5py:
|
|
@@ -149,10 +149,10 @@ if config.have_h5py:
|
|
|
149
149
|
return node.attrs[attrname]
|
|
150
150
|
|
|
151
151
|
def append_data(self, node, data):
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
node.resize(
|
|
155
|
-
node[
|
|
152
|
+
old_shape = node.shape
|
|
153
|
+
new_shape = (old_shape[0] + data.shape[0], data.shape[1])
|
|
154
|
+
node.resize(new_shape)
|
|
155
|
+
node[old_shape[0] : new_shape[0], :] = data
|
|
156
156
|
|
|
157
157
|
def remove_data(self, nodename, group=None):
|
|
158
158
|
in_file_path = self._get_in_file_path(nodename, group)
|
|
@@ -184,8 +184,8 @@ if config.have_h5py:
|
|
|
184
184
|
return result
|
|
185
185
|
|
|
186
186
|
class H5CacheFileH5py(H5CacheFileBase, H5FileH5py):
|
|
187
|
-
|
|
188
|
-
#
|
|
187
|
+
compression_filter = 'lzf'
|
|
188
|
+
# compression_filter = 'blosc' # unavailable...
|
|
189
189
|
|
|
190
190
|
def is_cached(self, nodename, group=None):
|
|
191
191
|
if not group:
|
|
@@ -200,7 +200,7 @@ if config.have_h5py:
|
|
|
200
200
|
in_file_path,
|
|
201
201
|
dtype=precision,
|
|
202
202
|
shape=shape,
|
|
203
|
-
compression=self.
|
|
203
|
+
compression=self.compression_filter,
|
|
204
204
|
chunks=True,
|
|
205
205
|
)
|
|
206
206
|
|
acoular/internal.py
CHANGED
|
@@ -13,13 +13,13 @@ def digest(obj, name='digest'):
|
|
|
13
13
|
for i in do_.split('.'):
|
|
14
14
|
vobj = list(vobj.trait_get(i.rstrip('[]')).values())[0]
|
|
15
15
|
str_.append(str(vobj).encode('UTF-8'))
|
|
16
|
-
except:
|
|
16
|
+
except: # noqa: E722
|
|
17
17
|
pass
|
|
18
18
|
return '_' + md5(b''.join(str_)).hexdigest()
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def ldigest(
|
|
21
|
+
def ldigest(obj_list):
|
|
22
22
|
str_ = []
|
|
23
|
-
for i in
|
|
23
|
+
for i in obj_list:
|
|
24
24
|
str_.append(str(i.digest).encode('UTF-8'))
|
|
25
25
|
return '_' + md5(b''.join(str_)).hexdigest()
|
acoular/sdinput.py
CHANGED
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
SoundDeviceSamplesGenerator
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
import sounddevice as sd
|
|
13
12
|
from traits.api import Any, Bool, Int, Long, Property, cached_property, observe
|
|
14
13
|
|
|
14
|
+
from .configuration import config
|
|
15
15
|
from .internal import digest
|
|
16
16
|
from .tprocess import SamplesGenerator
|
|
17
17
|
|
|
18
|
+
if config.have_sounddevice:
|
|
19
|
+
import sounddevice as sd
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
class SoundDeviceSamplesGenerator(SamplesGenerator):
|
|
20
23
|
"""Controller for sound card hardware using sounddevice library.
|
|
@@ -24,6 +27,12 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
|
|
|
24
27
|
:meth:`result`.
|
|
25
28
|
"""
|
|
26
29
|
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
super().__init__(*args, **kwargs)
|
|
32
|
+
if config.have_sounddevice is False:
|
|
33
|
+
msg = 'SoundDevice library not found but is required for using the SoundDeviceSamplesGenerator class.'
|
|
34
|
+
raise ImportError(msg)
|
|
35
|
+
|
|
27
36
|
#: input device index, refers to sounddevice list
|
|
28
37
|
device = Int(0, desc='input device index')
|
|
29
38
|
|
acoular/signals.py
CHANGED
|
@@ -279,7 +279,21 @@ class SineGenerator(SignalGenerator):
|
|
|
279
279
|
|
|
280
280
|
|
|
281
281
|
class GenericSignalGenerator(SignalGenerator):
|
|
282
|
-
"""Generate signal from output of :class:`~acoular.tprocess.SamplesGenerator` object.
|
|
282
|
+
"""Generate signal from output of :class:`~acoular.tprocess.SamplesGenerator` object.
|
|
283
|
+
|
|
284
|
+
This class can be used to inject arbitrary signals into Acoular processing
|
|
285
|
+
chains. For example, it can be used to read signals from a HDF5 file or create any signal
|
|
286
|
+
by using the :class:`acoular.sources.TimeSamples` class.
|
|
287
|
+
|
|
288
|
+
Example
|
|
289
|
+
-------
|
|
290
|
+
>>> import numpy as np
|
|
291
|
+
>>> from acoular import TimeSamples, GenericSignalGenerator
|
|
292
|
+
>>> data = np.random.rand(1000, 1)
|
|
293
|
+
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
294
|
+
>>> sig = GenericSignalGenerator(source=ts)
|
|
295
|
+
|
|
296
|
+
"""
|
|
283
297
|
|
|
284
298
|
#: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
|
|
285
299
|
source = Trait(SamplesGenerator)
|
acoular/sources.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ------------------------------------------------------------------------------
|
|
2
2
|
# Copyright (c) Acoular Development Team.
|
|
3
3
|
# ------------------------------------------------------------------------------
|
|
4
|
-
"""Measured multichannel data
|
|
4
|
+
"""Measured multichannel data management and simulation of acoustic sources.
|
|
5
5
|
|
|
6
6
|
.. autosummary::
|
|
7
7
|
:toctree: generated/
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
# imports from other packages
|
|
24
24
|
|
|
25
|
+
import contextlib
|
|
25
26
|
from os import path
|
|
26
27
|
from warnings import warn
|
|
27
28
|
|
|
@@ -104,7 +105,7 @@ def _fill_mic_signal_block(out, signal, rm, ind, blocksize, numchannels, up, pre
|
|
|
104
105
|
return out
|
|
105
106
|
|
|
106
107
|
|
|
107
|
-
def spherical_hn1(n, z
|
|
108
|
+
def spherical_hn1(n, z):
|
|
108
109
|
"""Spherical Hankel Function of the First Kind."""
|
|
109
110
|
return spherical_jn(n, z, derivative=False) + 1j * spherical_yn(n, z, derivative=False)
|
|
110
111
|
|
|
@@ -146,7 +147,7 @@ def get_radiation_angles(direction, mpos, sourceposition):
|
|
|
146
147
|
return azi, ele
|
|
147
148
|
|
|
148
149
|
|
|
149
|
-
def get_modes(lOrder, direction, mpos, sourceposition=None):
|
|
150
|
+
def get_modes(lOrder, direction, mpos, sourceposition=None): # noqa: N803
|
|
150
151
|
"""Returns Spherical Harmonic Radiation Pattern at the Microphones.
|
|
151
152
|
|
|
152
153
|
Parameters
|
|
@@ -170,9 +171,9 @@ def get_modes(lOrder, direction, mpos, sourceposition=None):
|
|
|
170
171
|
azi, ele = get_radiation_angles(direction, mpos, sourceposition) # angles between source and mics
|
|
171
172
|
modes = zeros((azi.shape[0], (lOrder + 1) ** 2), dtype=complex128)
|
|
172
173
|
i = 0
|
|
173
|
-
for
|
|
174
|
-
for m in range(-
|
|
175
|
-
modes[:, i] = sph_harm(m,
|
|
174
|
+
for lidx in range(lOrder + 1):
|
|
175
|
+
for m in range(-lidx, lidx + 1):
|
|
176
|
+
modes[:, i] = sph_harm(m, lidx, azi, ele)
|
|
176
177
|
if m < 0:
|
|
177
178
|
modes[:, i] = modes[:, i].conj() * 1j
|
|
178
179
|
i += 1
|
|
@@ -180,12 +181,44 @@ def get_modes(lOrder, direction, mpos, sourceposition=None):
|
|
|
180
181
|
|
|
181
182
|
|
|
182
183
|
class TimeSamples(SamplesGenerator):
|
|
183
|
-
"""Container for time data in `*.h5` format.
|
|
184
|
-
|
|
185
|
-
This class loads measured data from
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
"""Container for processing time data in `*.h5` or NumPy array format.
|
|
185
|
+
|
|
186
|
+
This class loads measured data from HDF5 files and provides information about this data.
|
|
187
|
+
It also serves as an interface where the data can be accessed (e.g. for use in a block chain) via the
|
|
188
|
+
:meth:`result` generator.
|
|
189
|
+
|
|
190
|
+
Examples
|
|
191
|
+
--------
|
|
192
|
+
Data can be loaded from a HDF5 file as follows:
|
|
193
|
+
|
|
194
|
+
>>> from acoular import TimeSamples
|
|
195
|
+
>>> name = <some_h5_file.h5> # doctest: +SKIP
|
|
196
|
+
>>> ts = TimeSamples(name=name) # doctest: +SKIP
|
|
197
|
+
>>> print(f'number of channels: {ts.numchannels}') # doctest: +SKIP
|
|
198
|
+
number of channels: 56 # doctest: +SKIP
|
|
199
|
+
|
|
200
|
+
Alternatively, the time data can be specified directly as a numpy array.
|
|
201
|
+
In this case, the :attr:`data` and :attr:`sample_freq` attributes must be set manually.
|
|
202
|
+
|
|
203
|
+
>>> import numpy as np
|
|
204
|
+
>>> data = np.random.rand(1000, 4)
|
|
205
|
+
>>> ts = TimeSamples(data=data, sample_freq=51200)
|
|
206
|
+
|
|
207
|
+
Chunks of the time data can be accessed iteratively via the :meth:`result` generator.
|
|
208
|
+
The last block will be shorter than the block size if the number of samples is not a multiple of the block size.
|
|
209
|
+
|
|
210
|
+
>>> blocksize = 512
|
|
211
|
+
>>> generator = ts.result(num=blocksize)
|
|
212
|
+
>>> for block in generator:
|
|
213
|
+
... print(block.shape)
|
|
214
|
+
(512, 4)
|
|
215
|
+
(488, 4)
|
|
216
|
+
|
|
217
|
+
See Also
|
|
218
|
+
--------
|
|
219
|
+
acoular.sources.MaskedTimeSamples :
|
|
220
|
+
Extends the functionality of class :class:`TimeSamples` by enabling the definition of start and stop samples
|
|
221
|
+
as well as the specification of invalid channels.
|
|
189
222
|
"""
|
|
190
223
|
|
|
191
224
|
#: Full name of the .h5 file with data.
|
|
@@ -233,31 +266,31 @@ class TimeSamples(SamplesGenerator):
|
|
|
233
266
|
return path.splitext(path.basename(self.name))[0]
|
|
234
267
|
|
|
235
268
|
@on_trait_change('basename')
|
|
236
|
-
def
|
|
269
|
+
def _load_data(self):
|
|
237
270
|
"""Open the .h5 file and set attributes."""
|
|
238
271
|
if not path.isfile(self.name):
|
|
239
|
-
# no file there
|
|
240
|
-
self.numsamples = 0
|
|
241
|
-
self.numchannels = 0
|
|
242
272
|
self.sample_freq = 0
|
|
243
273
|
raise OSError('No such file: %s' % self.name)
|
|
244
274
|
if self.h5f is not None:
|
|
245
|
-
|
|
275
|
+
with contextlib.suppress(OSError):
|
|
246
276
|
self.h5f.close()
|
|
247
|
-
except OSError:
|
|
248
|
-
pass
|
|
249
277
|
file = _get_h5file_class()
|
|
250
278
|
self.h5f = file(self.name)
|
|
251
|
-
self.
|
|
252
|
-
self.
|
|
279
|
+
self._load_timedata()
|
|
280
|
+
self._load_metadata()
|
|
253
281
|
|
|
254
|
-
|
|
282
|
+
@on_trait_change('data')
|
|
283
|
+
def _load_shapes(self):
|
|
284
|
+
"""Set numchannels and numsamples from data."""
|
|
285
|
+
if self.data is not None:
|
|
286
|
+
self.numsamples, self.numchannels = self.data.shape
|
|
287
|
+
|
|
288
|
+
def _load_timedata(self):
|
|
255
289
|
"""Loads timedata from .h5 file. Only for internal use."""
|
|
256
290
|
self.data = self.h5f.get_data_by_reference('time_data')
|
|
257
291
|
self.sample_freq = self.h5f.get_node_attribute(self.data, 'sample_freq')
|
|
258
|
-
(self.numsamples, self.numchannels) = self.data.shape
|
|
259
292
|
|
|
260
|
-
def
|
|
293
|
+
def _load_metadata(self):
|
|
261
294
|
"""Loads metadata from .h5 file. Only for internal use."""
|
|
262
295
|
self.metadata = {}
|
|
263
296
|
if '/metadata' in self.h5f:
|
|
@@ -266,15 +299,20 @@ class TimeSamples(SamplesGenerator):
|
|
|
266
299
|
def result(self, num=128):
|
|
267
300
|
"""Python generator that yields the output block-wise.
|
|
268
301
|
|
|
302
|
+
Reads the time data either from a HDF5 file or from a numpy array given
|
|
303
|
+
by :attr:`data` and iteratively returns a block of size `num` samples.
|
|
304
|
+
Calibrated data is returned if a calibration object is given by :attr:`calib`.
|
|
305
|
+
|
|
269
306
|
Parameters
|
|
270
307
|
----------
|
|
271
308
|
num : integer, defaults to 128
|
|
272
309
|
This parameter defines the size of the blocks to be yielded
|
|
273
|
-
(i.e. the number of samples per block)
|
|
310
|
+
(i.e. the number of samples per block).
|
|
274
311
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
312
|
+
Yields
|
|
313
|
+
------
|
|
314
|
+
numpy.ndarray
|
|
315
|
+
Samples in blocks of shape (num, numchannels).
|
|
278
316
|
The last block may be shorter than num.
|
|
279
317
|
|
|
280
318
|
"""
|
|
@@ -298,14 +336,40 @@ class TimeSamples(SamplesGenerator):
|
|
|
298
336
|
|
|
299
337
|
|
|
300
338
|
class MaskedTimeSamples(TimeSamples):
|
|
301
|
-
"""Container for time data in `*.h5` format.
|
|
302
|
-
|
|
303
|
-
This class loads measured data from
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
It also serves as an interface where the data can be accessed
|
|
307
|
-
|
|
308
|
-
|
|
339
|
+
"""Container for processing time data in `*.h5` or NumPy array format.
|
|
340
|
+
|
|
341
|
+
This class loads measured data from HDF5 files and provides information about this data.
|
|
342
|
+
It supports storing information about (in)valid samples and (in)valid channels and allows
|
|
343
|
+
to specify a start and stop index for the valid samples.
|
|
344
|
+
It also serves as an interface where the data can be accessed (e.g. for use in a block chain) via the
|
|
345
|
+
:meth:`result` generator.
|
|
346
|
+
|
|
347
|
+
Examples
|
|
348
|
+
--------
|
|
349
|
+
Data can be loaded from a HDF5 file and invalid channels can be specified as follows:
|
|
350
|
+
|
|
351
|
+
>>> from acoular import MaskedTimeSamples
|
|
352
|
+
>>> name = <some_h5_file.h5> # doctest: +SKIP
|
|
353
|
+
>>> ts = MaskedTimeSamples(name=name, invalid_channels=[0, 1]) # doctest: +SKIP
|
|
354
|
+
>>> print(f'number of valid channels: {ts.numchannels}') # doctest: +SKIP
|
|
355
|
+
number of valid channels: 54 # doctest: +SKIP
|
|
356
|
+
|
|
357
|
+
Alternatively, the time data can be specified directly as a numpy array.
|
|
358
|
+
In this case, the :attr:`data` and :attr:`sample_freq` attributes must be set manually.
|
|
359
|
+
|
|
360
|
+
>>> from acoular import MaskedTimeSamples
|
|
361
|
+
>>> import numpy as np
|
|
362
|
+
>>> data = np.random.rand(1000, 4)
|
|
363
|
+
>>> ts = MaskedTimeSamples(data=data, sample_freq=51200)
|
|
364
|
+
|
|
365
|
+
Chunks of the time data can be accessed iteratively via the :meth:`result` generator:
|
|
366
|
+
|
|
367
|
+
>>> blocksize = 512
|
|
368
|
+
>>> generator = ts.result(num=blocksize)
|
|
369
|
+
>>> for block in generator:
|
|
370
|
+
... print(block.shape)
|
|
371
|
+
(512, 4)
|
|
372
|
+
(488, 4)
|
|
309
373
|
"""
|
|
310
374
|
|
|
311
375
|
#: Index of the first sample to be considered valid.
|
|
@@ -362,26 +426,28 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
362
426
|
return sli[1] - sli[0]
|
|
363
427
|
|
|
364
428
|
@on_trait_change('basename')
|
|
365
|
-
def
|
|
429
|
+
def _load_data(self):
|
|
366
430
|
# """ open the .h5 file and set attributes
|
|
367
431
|
# """
|
|
368
432
|
if not path.isfile(self.name):
|
|
369
433
|
# no file there
|
|
370
|
-
self.numsamples_total = 0
|
|
371
|
-
self.numchannels_total = 0
|
|
372
434
|
self.sample_freq = 0
|
|
373
435
|
raise OSError('No such file: %s' % self.name)
|
|
374
436
|
if self.h5f is not None:
|
|
375
|
-
|
|
437
|
+
with contextlib.suppress(OSError):
|
|
376
438
|
self.h5f.close()
|
|
377
|
-
except OSError:
|
|
378
|
-
pass
|
|
379
439
|
file = _get_h5file_class()
|
|
380
440
|
self.h5f = file(self.name)
|
|
381
|
-
self.
|
|
382
|
-
self.
|
|
441
|
+
self._load_timedata()
|
|
442
|
+
self._load_metadata()
|
|
443
|
+
|
|
444
|
+
@on_trait_change('data')
|
|
445
|
+
def _load_shapes(self):
|
|
446
|
+
"""Set numchannels and numsamples from data."""
|
|
447
|
+
if self.data is not None:
|
|
448
|
+
self.numsamples_total, self.numchannels_total = self.data.shape
|
|
383
449
|
|
|
384
|
-
def
|
|
450
|
+
def _load_timedata(self):
|
|
385
451
|
"""Loads timedata from .h5 file. Only for internal use."""
|
|
386
452
|
self.data = self.h5f.get_data_by_reference('time_data')
|
|
387
453
|
self.sample_freq = self.h5f.get_node_attribute(self.data, 'sample_freq')
|
|
@@ -390,15 +456,20 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
390
456
|
def result(self, num=128):
|
|
391
457
|
"""Python generator that yields the output block-wise.
|
|
392
458
|
|
|
459
|
+
Reads the time data either from a HDF5 file or from a numpy array given
|
|
460
|
+
by :attr:`data` and iteratively returns a block of size `num` samples.
|
|
461
|
+
Calibrated data is returned if a calibration object is given by :attr:`calib`.
|
|
462
|
+
|
|
393
463
|
Parameters
|
|
394
464
|
----------
|
|
395
465
|
num : integer, defaults to 128
|
|
396
466
|
This parameter defines the size of the blocks to be yielded
|
|
397
467
|
(i.e. the number of samples per block).
|
|
398
468
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
469
|
+
Yields
|
|
470
|
+
------
|
|
471
|
+
numpy.ndarray
|
|
472
|
+
Samples in blocks of shape (num, numchannels).
|
|
402
473
|
The last block may be shorter than num.
|
|
403
474
|
|
|
404
475
|
"""
|
|
@@ -588,7 +659,7 @@ class SphericalHarmonicSource(PointSource):
|
|
|
588
659
|
"""
|
|
589
660
|
|
|
590
661
|
#: Order of spherical harmonic source
|
|
591
|
-
lOrder = Int(0, desc='Order of spherical harmonic')
|
|
662
|
+
lOrder = Int(0, desc='Order of spherical harmonic') # noqa: N815
|
|
592
663
|
|
|
593
664
|
alpha = CArray(desc='coefficients of the (lOrder,) spherical harmonic mode')
|
|
594
665
|
|
acoular/spectra.py
CHANGED
|
@@ -383,7 +383,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
383
383
|
wind = wind[newaxis, :].swapaxes(0, 1)
|
|
384
384
|
numfreq = int(self.block_size / 2 + 1)
|
|
385
385
|
csm_shape = (numfreq, t.numchannels, t.numchannels)
|
|
386
|
-
|
|
386
|
+
csm_upper = zeros(csm_shape, dtype=self.precision)
|
|
387
387
|
# print "num blocks", self.num_blocks
|
|
388
388
|
# for backward compatibility
|
|
389
389
|
if self.calib and self.calib.num_mics > 0:
|
|
@@ -394,11 +394,11 @@ class PowerSpectra(BaseSpectra):
|
|
|
394
394
|
# get time data blockwise
|
|
395
395
|
for data in self.get_source_data():
|
|
396
396
|
ft = fft.rfft(data * wind, None, 0).astype(self.precision)
|
|
397
|
-
calcCSM(
|
|
397
|
+
calcCSM(csm_upper, ft) # only upper triangular part of matrix is calculated (for speed reasons)
|
|
398
398
|
# create the full csm matrix via transposing and complex conj.
|
|
399
|
-
|
|
400
|
-
[fill_diagonal(
|
|
401
|
-
csm =
|
|
399
|
+
csm_lower = csm_upper.conj().transpose(0, 2, 1)
|
|
400
|
+
[fill_diagonal(csm_lower[cntFreq, :, :], 0) for cntFreq in range(csm_lower.shape[0])]
|
|
401
|
+
csm = csm_lower + csm_upper
|
|
402
402
|
# onesided spectrum: multiplication by 2.0=sqrt(2)^2
|
|
403
403
|
return csm * (2.0 / self.block_size / weight / self.num_blocks)
|
|
404
404
|
|