acoular 25.3.post1__py3-none-any.whl → 25.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 +2 -4
- acoular/aiaa/aiaa.py +9 -4
- acoular/base.py +14 -33
- acoular/calib.py +4 -4
- acoular/configuration.py +1 -1
- acoular/demo/__init__.py +6 -1
- acoular/demo/acoular_demo.py +34 -10
- acoular/deprecation.py +14 -2
- acoular/environments.py +6 -5
- acoular/fastFuncs.py +16 -10
- acoular/fbeamform.py +12 -107
- acoular/fprocess.py +3 -32
- acoular/grids.py +145 -38
- acoular/h5cache.py +5 -3
- acoular/h5files.py +10 -0
- acoular/internal.py +4 -0
- acoular/microphones.py +21 -3
- acoular/process.py +3 -45
- acoular/sdinput.py +12 -4
- acoular/signals.py +2 -2
- acoular/sources.py +28 -25
- acoular/spectra.py +17 -17
- acoular/tbeamform.py +3 -0
- acoular/tools/helpers.py +27 -0
- acoular/tools/utils.py +1 -2
- acoular/tprocess.py +1251 -470
- acoular/traitsviews.py +1 -3
- acoular/version.py +4 -3
- {acoular-25.3.post1.dist-info → acoular-25.7.dist-info}/METADATA +3 -3
- acoular-25.7.dist-info/RECORD +56 -0
- acoular-25.3.post1.dist-info/RECORD +0 -56
- {acoular-25.3.post1.dist-info → acoular-25.7.dist-info}/WHEEL +0 -0
- {acoular-25.3.post1.dist-info → acoular-25.7.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-25.3.post1.dist-info → acoular-25.7.dist-info}/licenses/LICENSE +0 -0
acoular/internal.py
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
# Copyright (c) Acoular Development Team.
|
|
3
3
|
# ------------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
"""Implements a digest function for caching of traits based on a unique identifier."""
|
|
6
|
+
|
|
5
7
|
from hashlib import md5
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
def digest(obj, name='digest'):
|
|
11
|
+
"""Generate a unique digest for the given object based on its traits."""
|
|
9
12
|
str_ = [str(obj.__class__).encode('UTF-8')]
|
|
10
13
|
for do_ in obj.trait(name).depends_on:
|
|
11
14
|
vobj = obj
|
|
@@ -19,6 +22,7 @@ def digest(obj, name='digest'):
|
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
def ldigest(obj_list):
|
|
25
|
+
"""Generate a unique digest for a list of objects based on their traits."""
|
|
22
26
|
str_ = []
|
|
23
27
|
for i in obj_list:
|
|
24
28
|
str_.append(str(i.digest).encode('UTF-8'))
|
acoular/microphones.py
CHANGED
|
@@ -22,6 +22,7 @@ from traits.api import (
|
|
|
22
22
|
HasStrictTraits,
|
|
23
23
|
List,
|
|
24
24
|
Property,
|
|
25
|
+
Union,
|
|
25
26
|
cached_property,
|
|
26
27
|
on_trait_change,
|
|
27
28
|
)
|
|
@@ -31,7 +32,9 @@ from .deprecation import deprecated_alias
|
|
|
31
32
|
from .internal import digest
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
@deprecated_alias(
|
|
35
|
+
@deprecated_alias(
|
|
36
|
+
{'mpos_tot': 'pos_total', 'mpos': 'pos', 'from_file': 'file'}, read_only=['mpos'], removal_version='25.10'
|
|
37
|
+
)
|
|
35
38
|
class MicGeom(HasStrictTraits):
|
|
36
39
|
"""
|
|
37
40
|
Provide the geometric arrangement of microphones in an array.
|
|
@@ -46,6 +49,17 @@ class MicGeom(HasStrictTraits):
|
|
|
46
49
|
attribute is updated.
|
|
47
50
|
- Small numerical values in the computed :attr:`center` are set to zero for numerical stability.
|
|
48
51
|
|
|
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
|
+
|
|
49
63
|
Examples
|
|
50
64
|
--------
|
|
51
65
|
To set a microphone geomerty for ``n`` programmatically, first a ``(3,n)`` array is needed. In
|
|
@@ -128,15 +142,17 @@ class MicGeom(HasStrictTraits):
|
|
|
128
142
|
|
|
129
143
|
#: Path to the XML file containing microphone positions. The XML file should have elements with
|
|
130
144
|
#: the tag ``pos`` and attributes ``Name``, ``x``, ``y``, and ``z``.
|
|
131
|
-
file = File(filter=['*.xml'], exists=True, desc='name of the xml file to import')
|
|
145
|
+
file = Union(None, File(filter=['*.xml'], exists=True), desc='name of the xml file to import')
|
|
132
146
|
|
|
133
147
|
#: Array containing the ``x, y, z`` positions of all microphones, including invalid ones, shape
|
|
134
148
|
#: ``(3,`` :attr:`num_mics` ``)``. This is set automatically when :attr:`file` changes or
|
|
135
|
-
#: explicitly by assigning an array of floats.
|
|
149
|
+
#: explicitly by assigning an array of floats. All coordinates are in meters by default (see
|
|
150
|
+
#: :ref:`notes <units_note_micophones>`).
|
|
136
151
|
pos_total = CArray(dtype=float, shape=(3, None), desc='x, y, z position of all microphones')
|
|
137
152
|
|
|
138
153
|
#: Array containing the ``x, y, z`` positions of valid microphones (i.e., excluding those in
|
|
139
154
|
#: :attr:`invalid_channels`), shape ``(3,`` :attr:`num_mics` ``)``. (read-only)
|
|
155
|
+
#: All coordinates are in meters by default (see :ref:`notes <units_note>`).
|
|
140
156
|
pos = Property(depends_on=['pos_total', 'invalid_channels'], desc='x, y, z position of used microphones')
|
|
141
157
|
|
|
142
158
|
#: List of indices indicating microphones to be excluded from calculations and results.
|
|
@@ -250,6 +266,8 @@ class MicGeom(HasStrictTraits):
|
|
|
250
266
|
index of the microphone.
|
|
251
267
|
- This method only exports the positions of the valid microphones (those not listed in
|
|
252
268
|
:attr:`invalid_channels`).
|
|
269
|
+
- All coordinates (x, y, z) are exported in meters by default (see
|
|
270
|
+
:ref:`notes <units_note_micophones>`).
|
|
253
271
|
"""
|
|
254
272
|
filepath = Path(filename)
|
|
255
273
|
basename = filepath.stem
|
acoular/process.py
CHANGED
|
@@ -10,8 +10,6 @@ General purpose blockwise processing methods independent of the domain (time or
|
|
|
10
10
|
Average
|
|
11
11
|
Cache
|
|
12
12
|
SampleSplitter
|
|
13
|
-
TimeAverage
|
|
14
|
-
TimeCache
|
|
15
13
|
SamplesBuffer
|
|
16
14
|
"""
|
|
17
15
|
|
|
@@ -65,7 +63,9 @@ class LockedGenerator:
|
|
|
65
63
|
return self.it.__next__()
|
|
66
64
|
|
|
67
65
|
|
|
68
|
-
@deprecated_alias(
|
|
66
|
+
@deprecated_alias(
|
|
67
|
+
{'naverage': 'num_per_average', 'numsamples': 'num_samples'}, read_only=['numsamples'], removal_version='25.10'
|
|
68
|
+
)
|
|
69
69
|
class Average(InOut):
|
|
70
70
|
"""
|
|
71
71
|
Calculate the average across consecutive time samples or frequency snapshots.
|
|
@@ -675,48 +675,6 @@ class SampleSplitter(InOut):
|
|
|
675
675
|
raise OSError(msg)
|
|
676
676
|
|
|
677
677
|
|
|
678
|
-
class TimeAverage(Average):
|
|
679
|
-
"""
|
|
680
|
-
Calculate the average of the signal.
|
|
681
|
-
|
|
682
|
-
.. deprecated:: 24.10
|
|
683
|
-
The use of :class:`~acoular.process.TimeAverage` is deprecated
|
|
684
|
-
and will be removed in Acoular version 25.07.
|
|
685
|
-
Please use :class:`~acoular.process.Average` instead for future compatibility.
|
|
686
|
-
|
|
687
|
-
Alias for :class:`~acoular.process.Average`.
|
|
688
|
-
"""
|
|
689
|
-
|
|
690
|
-
def __init__(self, *args, **kwargs):
|
|
691
|
-
super().__init__(*args, **kwargs)
|
|
692
|
-
warn(
|
|
693
|
-
'Using TimeAverage is deprecated and will be removed in Acoular version 25.07. Use Average instead.',
|
|
694
|
-
DeprecationWarning,
|
|
695
|
-
stacklevel=2,
|
|
696
|
-
)
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
class TimeCache(Cache):
|
|
700
|
-
"""
|
|
701
|
-
Cache source signals in cache file.
|
|
702
|
-
|
|
703
|
-
.. deprecated:: 24.10
|
|
704
|
-
The use of :class:`~acoular.process.TimeCache` is deprecated
|
|
705
|
-
and will be removed in Acoular version 25.07.
|
|
706
|
-
Please use :class:`~acoular.process.Cache` instead for future compatibility.
|
|
707
|
-
|
|
708
|
-
Alias for :class:`~acoular.process.Cache`.
|
|
709
|
-
"""
|
|
710
|
-
|
|
711
|
-
def __init__(self, *args, **kwargs):
|
|
712
|
-
super().__init__(*args, **kwargs)
|
|
713
|
-
warn(
|
|
714
|
-
'Using TimeCache is deprecated and will be removed in Acoular version 25.07. Use Cache instead.',
|
|
715
|
-
DeprecationWarning,
|
|
716
|
-
stacklevel=2,
|
|
717
|
-
)
|
|
718
|
-
|
|
719
|
-
|
|
720
678
|
class SamplesBuffer(InOut):
|
|
721
679
|
"""
|
|
722
680
|
Handle buffering of samples from a source.
|
acoular/sdinput.py
CHANGED
|
@@ -21,7 +21,10 @@ if config.have_sounddevice:
|
|
|
21
21
|
import sounddevice as sd
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
@deprecated_alias(
|
|
24
|
+
@deprecated_alias(
|
|
25
|
+
{'numchannels': 'num_channels', 'numsamples': 'num_samples', 'collectsamples': 'collect_samples'},
|
|
26
|
+
removal_version='25.10',
|
|
27
|
+
)
|
|
25
28
|
class SoundDeviceSamplesGenerator(SamplesGenerator):
|
|
26
29
|
"""Controller for sound card hardware using sounddevice library.
|
|
27
30
|
|
|
@@ -87,15 +90,20 @@ class SoundDeviceSamplesGenerator(SamplesGenerator):
|
|
|
87
90
|
self._sample_freq = f
|
|
88
91
|
|
|
89
92
|
def device_properties(self):
|
|
90
|
-
"""
|
|
93
|
+
"""
|
|
94
|
+
Display the properties of the sounddevice input device.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
91
97
|
-------
|
|
92
98
|
Dictionary of device properties according to sounddevice
|
|
93
99
|
"""
|
|
94
100
|
return sd.query_devices(self.device)
|
|
95
101
|
|
|
96
102
|
def result(self, num):
|
|
97
|
-
"""
|
|
98
|
-
|
|
103
|
+
"""
|
|
104
|
+
Python generator that yields the output block-wise.
|
|
105
|
+
|
|
106
|
+
Use at least a block-size of one ring cache block.
|
|
99
107
|
|
|
100
108
|
Parameters
|
|
101
109
|
----------
|
acoular/signals.py
CHANGED
|
@@ -43,7 +43,7 @@ from .deprecation import deprecated_alias
|
|
|
43
43
|
from .internal import digest
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
@deprecated_alias({'numsamples': 'num_samples'})
|
|
46
|
+
@deprecated_alias({'numsamples': 'num_samples'}, removal_version='25.10')
|
|
47
47
|
class SignalGenerator(ABCHasStrictTraits):
|
|
48
48
|
"""
|
|
49
49
|
ABC for a simple one-channel signal generator.
|
|
@@ -556,7 +556,7 @@ class SineGenerator(PeriodicSignalGenerator):
|
|
|
556
556
|
return self.amplitude * sin(2 * pi * self.freq * t + self.phase)
|
|
557
557
|
|
|
558
558
|
|
|
559
|
-
@deprecated_alias({'rms': 'amplitude'})
|
|
559
|
+
@deprecated_alias({'rms': 'amplitude'}, removal_version='25.10')
|
|
560
560
|
class GenericSignalGenerator(SignalGenerator):
|
|
561
561
|
"""
|
|
562
562
|
Generate signals from a :class:`~acoular.base.SamplesGenerator` or derived object.
|
acoular/sources.py
CHANGED
|
@@ -45,6 +45,7 @@ from numpy import (
|
|
|
45
45
|
mod,
|
|
46
46
|
newaxis,
|
|
47
47
|
ones,
|
|
48
|
+
ones_like,
|
|
48
49
|
pi,
|
|
49
50
|
real,
|
|
50
51
|
repeat,
|
|
@@ -317,7 +318,7 @@ def get_modes(lOrder, direction, mpos, sourceposition=None): # noqa: N803
|
|
|
317
318
|
return modes
|
|
318
319
|
|
|
319
320
|
|
|
320
|
-
@deprecated_alias({'name': 'file'})
|
|
321
|
+
@deprecated_alias({'name': 'file'}, removal_version='25.10')
|
|
321
322
|
class TimeSamples(SamplesGenerator):
|
|
322
323
|
"""
|
|
323
324
|
Container for processing time data in ``*.h5`` or NumPy array format.
|
|
@@ -369,7 +370,7 @@ class TimeSamples(SamplesGenerator):
|
|
|
369
370
|
"""
|
|
370
371
|
|
|
371
372
|
#: Full path to the ``.h5`` file containing time-domain data.
|
|
372
|
-
file = File(filter=['*.h5'], exists=True, desc='name of data file')
|
|
373
|
+
file = Union(None, File(filter=['*.h5'], exists=True), desc='name of data file')
|
|
373
374
|
|
|
374
375
|
#: Basename of the ``.h5`` file, set automatically from the :attr:`file` attribute.
|
|
375
376
|
basename = Property(depends_on=['file'], desc='basename of data file')
|
|
@@ -746,7 +747,7 @@ class MaskedTimeSamples(TimeSamples):
|
|
|
746
747
|
i += num
|
|
747
748
|
|
|
748
749
|
|
|
749
|
-
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True)
|
|
750
|
+
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True, removal_version='25.10')
|
|
750
751
|
class PointSource(SamplesGenerator):
|
|
751
752
|
"""
|
|
752
753
|
Define a fixed point source emitting a signal, intended for simulations.
|
|
@@ -1164,46 +1165,48 @@ class MovingPointSource(PointSource):
|
|
|
1164
1165
|
# from the end of the calculated signal.
|
|
1165
1166
|
|
|
1166
1167
|
signal = self.signal.usignal(self.up)
|
|
1167
|
-
out = empty((num, self.num_channels))
|
|
1168
1168
|
# shortcuts and initial values
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1169
|
+
num_mics = self.num_channels
|
|
1170
|
+
mpos = self.mics.pos[:, :, newaxis]
|
|
1171
|
+
t = self.start + ones(num_mics)[:, newaxis] * arange(num) / self.sample_freq
|
|
1172
1172
|
epslim = 0.1 / self.up / self.sample_freq
|
|
1173
1173
|
c0 = self.env.c
|
|
1174
1174
|
tr = self.trajectory
|
|
1175
1175
|
n = self.num_samples
|
|
1176
|
-
while n:
|
|
1177
|
-
|
|
1178
|
-
eps = ones(m.num_mics)
|
|
1176
|
+
while n > 0:
|
|
1177
|
+
eps = ones_like(t) # init discrepancy in time
|
|
1179
1178
|
te = t.copy() # init emission time = receiving time
|
|
1180
1179
|
j = 0
|
|
1181
1180
|
# Newton-Rhapson iteration
|
|
1182
1181
|
while abs(eps).max() > epslim and j < 100:
|
|
1183
|
-
loc = array(tr.location(te))
|
|
1184
|
-
rm = loc -
|
|
1182
|
+
loc = array(tr.location(te.flatten())).reshape((3, num_mics, -1))
|
|
1183
|
+
rm = loc - mpos # distance vectors to microphones
|
|
1185
1184
|
rm = sqrt((rm * rm).sum(0)) # absolute distance
|
|
1186
1185
|
loc /= sqrt((loc * loc).sum(0)) # distance unit vector
|
|
1187
|
-
der = array(tr.location(te, der=1))
|
|
1186
|
+
der = array(tr.location(te.flatten(), der=1)).reshape((3, num_mics, -1))
|
|
1188
1187
|
Mr = (der * loc).sum(0) / c0 # radial Mach number
|
|
1189
|
-
eps = (te + rm / c0 - t) / (1 + Mr) # discrepancy in time
|
|
1188
|
+
eps[:] = (te + rm / c0 - t) / (1 + Mr) # discrepancy in time
|
|
1190
1189
|
te -= eps
|
|
1191
1190
|
j += 1 # iteration count
|
|
1192
|
-
t +=
|
|
1191
|
+
t += num / self.sample_freq
|
|
1193
1192
|
# emission time relative to start time
|
|
1194
1193
|
ind = (te - self.start_t + self.start) * self.sample_freq
|
|
1195
1194
|
if self.conv_amp:
|
|
1196
1195
|
rm *= (1 - Mr) ** 2
|
|
1197
1196
|
try:
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1197
|
+
ind = array(0.5 + ind * self.up, dtype=int64)
|
|
1198
|
+
out = (signal[ind] / rm).T
|
|
1199
|
+
yield out[:n]
|
|
1200
|
+
except IndexError: # last incomplete frame
|
|
1201
|
+
signal_length = signal.shape[0]
|
|
1202
|
+
# Filter ind to exclude columns containing values greater than signal_length
|
|
1203
|
+
mask = (ind < signal_length).all(axis=0)
|
|
1204
|
+
out = (signal[ind[:, mask]] / rm[:, mask]).T
|
|
1205
|
+
# If out is not empty, yield it
|
|
1206
|
+
if out.size > 0:
|
|
1207
|
+
yield out[:n]
|
|
1204
1208
|
break
|
|
1205
|
-
|
|
1206
|
-
yield out[:i]
|
|
1209
|
+
n -= num
|
|
1207
1210
|
|
|
1208
1211
|
|
|
1209
1212
|
class PointSourceDipole(PointSource):
|
|
@@ -1947,7 +1950,7 @@ class MovingLineSource(LineSource, MovingPointSource):
|
|
|
1947
1950
|
yield out[:i]
|
|
1948
1951
|
|
|
1949
1952
|
|
|
1950
|
-
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True)
|
|
1953
|
+
@deprecated_alias({'numchannels': 'num_channels'}, read_only=True, removal_version='25.10')
|
|
1951
1954
|
class UncorrelatedNoiseSource(SamplesGenerator):
|
|
1952
1955
|
"""
|
|
1953
1956
|
Simulate uncorrelated white or pink noise signals at multiple channels.
|
|
@@ -2111,7 +2114,7 @@ class UncorrelatedNoiseSource(SamplesGenerator):
|
|
|
2111
2114
|
return
|
|
2112
2115
|
|
|
2113
2116
|
|
|
2114
|
-
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True)
|
|
2117
|
+
@deprecated_alias({'numchannels': 'num_channels', 'numsamples': 'num_samples'}, read_only=True, removal_version='25.10')
|
|
2115
2118
|
class SourceMixer(SamplesGenerator):
|
|
2116
2119
|
"""
|
|
2117
2120
|
Combine signals from multiple sources by mixing their outputs.
|
acoular/spectra.py
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
from abc import abstractmethod
|
|
15
|
-
from warnings import warn
|
|
16
15
|
|
|
17
16
|
from numpy import (
|
|
18
17
|
arange,
|
|
@@ -62,8 +61,9 @@ from .internal import digest
|
|
|
62
61
|
from .tools.utils import find_basename
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
@deprecated_alias(
|
|
66
|
-
|
|
64
|
+
@deprecated_alias(
|
|
65
|
+
{'numchannels': 'num_channels', 'time_data': 'source'}, read_only=['numchannels'], removal_version='25.10'
|
|
66
|
+
)
|
|
67
67
|
class BaseSpectra(ABCHasStrictTraits):
|
|
68
68
|
"""
|
|
69
69
|
Base class for handling spectral data in Acoular.
|
|
@@ -298,9 +298,10 @@ class PowerSpectra(BaseSpectra):
|
|
|
298
298
|
return None
|
|
299
299
|
|
|
300
300
|
def _set_freq_range(self, freq_range): # by setting this the user sets _freqlc and _freqhc
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
if freq_range is not None:
|
|
302
|
+
self._index_set_last = False
|
|
303
|
+
self._freqlc = freq_range[0]
|
|
304
|
+
self._freqhc = freq_range[1]
|
|
304
305
|
|
|
305
306
|
@property_depends_on(['source.sample_freq', 'block_size', '_ind_low', '_freqlc'])
|
|
306
307
|
def _get_ind_low(self):
|
|
@@ -309,7 +310,7 @@ class PowerSpectra(BaseSpectra):
|
|
|
309
310
|
if self._index_set_last:
|
|
310
311
|
return min(self._ind_low, fftfreq.shape[0] - 1)
|
|
311
312
|
return searchsorted(fftfreq[:-1], self._freqlc)
|
|
312
|
-
return
|
|
313
|
+
return 0
|
|
313
314
|
|
|
314
315
|
@property_depends_on(['source.sample_freq', 'block_size', '_ind_high', '_freqhc'])
|
|
315
316
|
def _get_ind_high(self):
|
|
@@ -631,7 +632,7 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
631
632
|
|
|
632
633
|
#: The frequencies included in the CSM in ascending order. Accepts list, array, or a single
|
|
633
634
|
#: float value.
|
|
634
|
-
frequencies = Union(CArray, Float, desc='frequencies included in the cross-spectral matrix')
|
|
635
|
+
frequencies = Union(None, CArray, Float, desc='frequencies included in the cross-spectral matrix')
|
|
635
636
|
|
|
636
637
|
#: Number of time data channels, inferred from the shape of the CSM.
|
|
637
638
|
num_channels = Property(depends_on=['digest'])
|
|
@@ -670,7 +671,7 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
670
671
|
basename = Property(depends_on=['digest'], desc='basename for cache file')
|
|
671
672
|
|
|
672
673
|
# Shadow trait for storing the CSM, for internal use only.
|
|
673
|
-
_csm = CArray()
|
|
674
|
+
_csm = Union(None, CArray(shape=(None, None, None)), desc='cross spectral matrix')
|
|
674
675
|
|
|
675
676
|
# Checksum for the CSM to trigger digest calculation, for internal use only.
|
|
676
677
|
_csmsum = Float()
|
|
@@ -689,12 +690,13 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
689
690
|
return self._csm
|
|
690
691
|
|
|
691
692
|
def _set_csm(self, csm):
|
|
692
|
-
if
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
693
|
+
if csm is not None:
|
|
694
|
+
if csm.shape[1] != csm.shape[2]:
|
|
695
|
+
msg = 'The cross spectral matrix must have the following shape: \
|
|
696
|
+
(number of frequencies, num_channels, num_channels)!'
|
|
697
|
+
raise ValueError(msg)
|
|
698
|
+
self._csm = csm
|
|
699
|
+
self._csmsum = real(self._csm).sum() + (imag(self._csm) ** 2).sum() # to trigger new digest creation
|
|
698
700
|
|
|
699
701
|
@property_depends_on(['digest'])
|
|
700
702
|
def _get_eva(self):
|
|
@@ -720,6 +722,4 @@ class PowerSpectraImport(PowerSpectra):
|
|
|
720
722
|
return array([self.frequencies])
|
|
721
723
|
if isinstance(self.frequencies, ndarray):
|
|
722
724
|
return self.frequencies
|
|
723
|
-
if self.frequencies is None:
|
|
724
|
-
warn('No frequencies defined for PowerSpectraImport object!', stacklevel=1)
|
|
725
725
|
return self.frequencies
|
acoular/tbeamform.py
CHANGED
|
@@ -174,6 +174,9 @@ class BeamformerTime(TimeOut):
|
|
|
174
174
|
p_res *= weights
|
|
175
175
|
if p_res.shape[0] < buffer.result_num: # last block shorter
|
|
176
176
|
num = p_res.shape[0] - max_sample_delay
|
|
177
|
+
# exit loop if there is not enough data left to be processed
|
|
178
|
+
if num <= 0:
|
|
179
|
+
break
|
|
177
180
|
n_index = arange(0, num + 1)[:, newaxis]
|
|
178
181
|
# init step
|
|
179
182
|
Phi, autopow = self._delay_and_sum(num, p_res, d_interp2, d_index, amp)
|
acoular/tools/helpers.py
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
barspectrum
|
|
12
12
|
bardata
|
|
13
13
|
c_air
|
|
14
|
+
get_data_file
|
|
14
15
|
"""
|
|
15
16
|
|
|
17
|
+
from pathlib import Path
|
|
16
18
|
from warnings import warn
|
|
17
19
|
|
|
18
20
|
from numpy import (
|
|
@@ -401,3 +403,28 @@ def c_air(t, h, p=101325, co2=0.04):
|
|
|
401
403
|
+ a14 * x_c**2
|
|
402
404
|
+ a15 * x_w * p * x_c
|
|
403
405
|
)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def get_data_file(file):
|
|
409
|
+
"""
|
|
410
|
+
Ensures a file is available locally.
|
|
411
|
+
|
|
412
|
+
If the file does not exist in ``'../data/'`` or the current directory,
|
|
413
|
+
it is downloaded from the Acoular GitHub repository.
|
|
414
|
+
|
|
415
|
+
Returns
|
|
416
|
+
-------
|
|
417
|
+
:class:`pathlib.Path`
|
|
418
|
+
Path to the file.
|
|
419
|
+
"""
|
|
420
|
+
data_file = Path('../data') / file
|
|
421
|
+
if not data_file.exists():
|
|
422
|
+
data_file = Path().cwd() / file
|
|
423
|
+
if not data_file.exists():
|
|
424
|
+
import urllib.request
|
|
425
|
+
|
|
426
|
+
url = 'https://github.com/acoular/acoular/raw/master/examples/data/' + file
|
|
427
|
+
urllib.request.urlretrieve(url, data_file)
|
|
428
|
+
print(f'Calibration file location: {data_file}')
|
|
429
|
+
|
|
430
|
+
return data_file
|
acoular/tools/utils.py
CHANGED
|
@@ -26,8 +26,7 @@ def get_file_basename(file, alternative_basename='void'):
|
|
|
26
26
|
str
|
|
27
27
|
Basename of the file.
|
|
28
28
|
"""
|
|
29
|
-
|
|
30
|
-
return basename if basename else alternative_basename
|
|
29
|
+
return Path(file).stem if file else alternative_basename
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
def find_basename(source, alternative_basename='void'):
|