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/tools/helpers.py
CHANGED
|
@@ -17,17 +17,7 @@
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from warnings import warn
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
array,
|
|
22
|
-
concatenate,
|
|
23
|
-
isscalar,
|
|
24
|
-
newaxis,
|
|
25
|
-
searchsorted,
|
|
26
|
-
sum, # noqa A004
|
|
27
|
-
where,
|
|
28
|
-
zeros_like,
|
|
29
|
-
)
|
|
30
|
-
from numpy.ma import masked_where
|
|
20
|
+
import numpy as np
|
|
31
21
|
|
|
32
22
|
from acoular.tools.utils import mole_fraction_of_water_vapor
|
|
33
23
|
|
|
@@ -79,20 +69,20 @@ def synthetic(data, freqs, f, num=3):
|
|
|
79
69
|
and used :attr:`FFT block size<acoular.spectra.PowerSpectra.block_size>`.
|
|
80
70
|
|
|
81
71
|
"""
|
|
82
|
-
if isscalar(f):
|
|
72
|
+
if np.isscalar(f):
|
|
83
73
|
f = (f,)
|
|
84
74
|
if num == 0:
|
|
85
75
|
# single frequency lines
|
|
86
76
|
res = []
|
|
87
77
|
for i in f:
|
|
88
|
-
ind = searchsorted(freqs, i)
|
|
78
|
+
ind = np.searchsorted(freqs, i)
|
|
89
79
|
if ind >= len(freqs):
|
|
90
80
|
warn(
|
|
91
81
|
f'Queried frequency ({i:g} Hz) not in resolved frequency range. Returning zeros.',
|
|
92
82
|
Warning,
|
|
93
83
|
stacklevel=2,
|
|
94
84
|
)
|
|
95
|
-
h = zeros_like(data[0])
|
|
85
|
+
h = np.zeros_like(data[0])
|
|
96
86
|
else:
|
|
97
87
|
if freqs[ind] != i:
|
|
98
88
|
warn(
|
|
@@ -110,8 +100,8 @@ def synthetic(data, freqs, f, num=3):
|
|
|
110
100
|
for i in f:
|
|
111
101
|
f1 = i * 2.0 ** (-0.5 / num)
|
|
112
102
|
f2 = i * 2.0 ** (+0.5 / num)
|
|
113
|
-
ind1 = searchsorted(freqs, f1)
|
|
114
|
-
ind2 = searchsorted(freqs, f2)
|
|
103
|
+
ind1 = np.searchsorted(freqs, f1)
|
|
104
|
+
ind2 = np.searchsorted(freqs, f2)
|
|
115
105
|
if ind1 == ind2:
|
|
116
106
|
warn(
|
|
117
107
|
f'Queried frequency band ({f1:g} to {f2:g} Hz) does not '
|
|
@@ -120,11 +110,11 @@ def synthetic(data, freqs, f, num=3):
|
|
|
120
110
|
Warning,
|
|
121
111
|
stacklevel=2,
|
|
122
112
|
)
|
|
123
|
-
h = zeros_like(data[0])
|
|
113
|
+
h = np.zeros_like(data[0])
|
|
124
114
|
else:
|
|
125
|
-
h = sum(data[ind1:ind2], 0)
|
|
115
|
+
h = np.sum(data[ind1:ind2], 0)
|
|
126
116
|
res += [h]
|
|
127
|
-
return array(res)
|
|
117
|
+
return np.array(res)
|
|
128
118
|
|
|
129
119
|
|
|
130
120
|
def return_result(source, nmax=-1, num=128):
|
|
@@ -154,8 +144,8 @@ def return_result(source, nmax=-1, num=128):
|
|
|
154
144
|
|
|
155
145
|
if nmax > 0:
|
|
156
146
|
nblocks = (nmax - 1) // num + 1
|
|
157
|
-
return concatenate([res for _, res in zip(range(nblocks), resulter)])[:nmax]
|
|
158
|
-
return concatenate(list(resulter))
|
|
147
|
+
return np.concatenate([res for _, res in zip(range(nblocks), resulter, strict=True)])[:nmax]
|
|
148
|
+
return np.concatenate(list(resulter))
|
|
159
149
|
|
|
160
150
|
|
|
161
151
|
def barspectrum(data, fftfreqs, num=3, bar=True, xoffset=0.0):
|
|
@@ -203,9 +193,9 @@ def barspectrum(data, fftfreqs, num=3, bar=True, xoffset=0.0):
|
|
|
203
193
|
return (0, 0, 0)
|
|
204
194
|
|
|
205
195
|
# preferred center freqs after din en iso 266 for third-octave bands
|
|
206
|
-
fcbase = array([31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250])
|
|
196
|
+
fcbase = np.array([31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250])
|
|
207
197
|
# DIN band center frequencies from 31.5 Hz to 25 kHz
|
|
208
|
-
fc = concatenate((fcbase, fcbase * 10.0, fcbase[:] * 100.0))[:: (3 // num)]
|
|
198
|
+
fc = np.concatenate((fcbase, fcbase * 10.0, fcbase[:] * 100.0))[:: (3 // num)]
|
|
209
199
|
|
|
210
200
|
# exponent for band width calculation
|
|
211
201
|
ep = 1.0 / (2.0 * num)
|
|
@@ -215,16 +205,16 @@ def barspectrum(data, fftfreqs, num=3, bar=True, xoffset=0.0):
|
|
|
215
205
|
f_low = fftfreqs[1] * 2**ep
|
|
216
206
|
f_high = fftfreqs[-1] * 2**-ep
|
|
217
207
|
# get possible index range
|
|
218
|
-
i_low = 0 if fc[0] >= f_low else where(fc < f_low)[0][-1]
|
|
208
|
+
i_low = 0 if fc[0] >= f_low else np.where(fc < f_low)[0][-1]
|
|
219
209
|
|
|
220
|
-
i_high = fc.shape[0] if fc[-1] <= f_high else where(fc > f_high)[0][0]
|
|
210
|
+
i_high = fc.shape[0] if fc[-1] <= f_high else np.where(fc > f_high)[0][0]
|
|
221
211
|
|
|
222
212
|
# synthesize sound pressure values
|
|
223
|
-
p = array([synthetic(data, fftfreqs, list(fc[i_low:i_high]), num)])
|
|
213
|
+
p = np.array([synthetic(data, fftfreqs, list(fc[i_low:i_high]), num)])
|
|
224
214
|
|
|
225
215
|
if bar:
|
|
226
216
|
# upper and lower band borders
|
|
227
|
-
flu = concatenate(
|
|
217
|
+
flu = np.concatenate(
|
|
228
218
|
(
|
|
229
219
|
fc[i_low : i_low + 1] * 2**-ep,
|
|
230
220
|
(fc[i_low : i_high - 1] * 2**ep + fc[i_low + 1 : i_high] * 2**-ep) / 2.0,
|
|
@@ -232,9 +222,9 @@ def barspectrum(data, fftfreqs, num=3, bar=True, xoffset=0.0):
|
|
|
232
222
|
),
|
|
233
223
|
)
|
|
234
224
|
# band borders as coordinates for bar plotting
|
|
235
|
-
flulist = 2 ** (2 * xoffset * ep) * (array([1, 1])[:, newaxis] * flu[newaxis, :]).T.reshape(-1)[1:-1]
|
|
225
|
+
flulist = 2 ** (2 * xoffset * ep) * (np.array([1, 1])[:, np.newaxis] * flu[np.newaxis, :]).T.reshape(-1)[1:-1]
|
|
236
226
|
# sound pressures as list for bar plotting
|
|
237
|
-
plist = (array([1, 1])[:, newaxis] * p[newaxis, :]).T.reshape(-1)
|
|
227
|
+
plist = (np.array([1, 1])[:, np.newaxis] * p[np.newaxis, :]).T.reshape(-1)
|
|
238
228
|
else:
|
|
239
229
|
flulist = fc[i_low:i_high]
|
|
240
230
|
plist = p[0, :]
|
|
@@ -280,17 +270,19 @@ def bardata(data, fc, num=3, bar=True, xoffset=0.0, masked=-360):
|
|
|
280
270
|
|
|
281
271
|
if bar:
|
|
282
272
|
# upper and lower band borders
|
|
283
|
-
flu = concatenate((fc[:1] * 2**-ep, (fc[:-1] * 2**ep + fc[1:] * 2**-ep) / 2.0, fc[-1:] * 2**ep))
|
|
273
|
+
flu = np.concatenate((fc[:1] * 2**-ep, (fc[:-1] * 2**ep + fc[1:] * 2**-ep) / 2.0, fc[-1:] * 2**ep))
|
|
284
274
|
# band borders as coordinates for bar plotting
|
|
285
|
-
flulist =
|
|
275
|
+
flulist = (
|
|
276
|
+
2 ** (xoffset * 1.0 / num) * (np.array([1, 1])[:, np.newaxis] * flu[np.newaxis, :]).T.reshape(-1)[1:-1]
|
|
277
|
+
)
|
|
286
278
|
# sound pressures as list for bar plotting
|
|
287
|
-
plist = (array([1, 1])[:, newaxis] * data[newaxis, :]).T.reshape(-1)
|
|
279
|
+
plist = (np.array([1, 1])[:, np.newaxis] * data[np.newaxis, :]).T.reshape(-1)
|
|
288
280
|
else:
|
|
289
281
|
flulist = fc
|
|
290
282
|
plist = data
|
|
291
283
|
# print(flulist.shape, plist.shape)
|
|
292
284
|
if masked > -360:
|
|
293
|
-
plist = masked_where(plist <= masked, plist)
|
|
285
|
+
plist = np.ma.masked_where(plist <= masked, plist)
|
|
294
286
|
return (flulist, plist)
|
|
295
287
|
|
|
296
288
|
|
acoular/tools/metrics.py
CHANGED
|
@@ -11,12 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
from copy import copy
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
empty,
|
|
16
|
-
inf,
|
|
17
|
-
minimum,
|
|
18
|
-
ones,
|
|
19
|
-
)
|
|
14
|
+
import numpy as np
|
|
20
15
|
from scipy.spatial.distance import cdist
|
|
21
16
|
from traits.api import Bool, CArray, HasStrictTraits, Instance, Property
|
|
22
17
|
|
|
@@ -82,13 +77,13 @@ class MetricEvaluator(HasStrictTraits):
|
|
|
82
77
|
|
|
83
78
|
def _get_sector_radii(self):
|
|
84
79
|
ns = self.target_data.shape[1]
|
|
85
|
-
radii = ones(ns) * self.sector.r
|
|
80
|
+
radii = np.ones(ns) * self.sector.r
|
|
86
81
|
if self.adaptive_sector_size:
|
|
87
82
|
locs = self.target_grid.pos.T
|
|
88
83
|
intersrcdist = cdist(locs, locs)
|
|
89
|
-
intersrcdist[intersrcdist == 0] = inf
|
|
84
|
+
intersrcdist[intersrcdist == 0] = np.inf
|
|
90
85
|
intersrcdist = intersrcdist.min(0) / 2
|
|
91
|
-
radii = minimum(radii, intersrcdist)
|
|
86
|
+
radii = np.minimum(radii, intersrcdist)
|
|
92
87
|
return radii
|
|
93
88
|
|
|
94
89
|
def _get_sectors(self):
|
|
@@ -115,7 +110,7 @@ class MetricEvaluator(HasStrictTraits):
|
|
|
115
110
|
|
|
116
111
|
"""
|
|
117
112
|
sectors = self.sectors
|
|
118
|
-
results = empty(shape=self.target_data.shape)
|
|
113
|
+
results = np.empty(shape=self.target_data.shape)
|
|
119
114
|
for f in range(self.target_data.shape[0]):
|
|
120
115
|
data = self.data[f]
|
|
121
116
|
for i in range(self.target_data.shape[1]):
|
acoular/tools/utils.py
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
get_file_basename
|
|
7
7
|
find_basename
|
|
8
8
|
mole_fraction_of_water_vapor
|
|
9
|
+
Polygon
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
12
|
from pathlib import Path
|
|
@@ -13,6 +14,173 @@ from pathlib import Path
|
|
|
13
14
|
import numpy as np
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
def _det(xvert, yvert):
|
|
18
|
+
xvert = np.asarray(xvert, dtype=float)
|
|
19
|
+
yvert = np.asarray(yvert, dtype=float)
|
|
20
|
+
x_prev = np.concatenate(([xvert[-1]], xvert[:-1]))
|
|
21
|
+
y_prev = np.concatenate(([yvert[-1]], yvert[:-1]))
|
|
22
|
+
return np.sum(yvert * x_prev - xvert * y_prev, axis=0)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Polygon:
|
|
26
|
+
"""
|
|
27
|
+
Create an object representing a general polygon in a 2D plane.
|
|
28
|
+
|
|
29
|
+
This class allows defining a polygon by specifying the coordinates of its vertices and provides
|
|
30
|
+
methods for checking whether a set of points lies inside the polygon, or if a point is closer to
|
|
31
|
+
a side or vertex of the polygon.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
x : array_like
|
|
36
|
+
Array of x-coordinates of the vertices that define the polygon. These coordinates should
|
|
37
|
+
form a closed shape (i.e., the last point should be the same as the first point).
|
|
38
|
+
|
|
39
|
+
y : array_like
|
|
40
|
+
Array of y-coordinates of the vertices that define the polygon. These coordinates should
|
|
41
|
+
correspond to the x-coordinates, forming a closed shape.
|
|
42
|
+
|
|
43
|
+
Attributes
|
|
44
|
+
----------
|
|
45
|
+
x : :class:`numpy.ndarray`
|
|
46
|
+
Array of x-coordinates of the polygon vertices.
|
|
47
|
+
|
|
48
|
+
y : :class:`numpy.ndarray`
|
|
49
|
+
Array of y-coordinates of the polygon vertices.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, x, y):
|
|
53
|
+
if len(x) != len(y):
|
|
54
|
+
msg = 'x and y must be equally sized.'
|
|
55
|
+
raise IndexError(msg)
|
|
56
|
+
self.x = np.asarray(x, dtype=float)
|
|
57
|
+
self.y = np.asarray(y, dtype=float)
|
|
58
|
+
# Closes the polygon if it were open
|
|
59
|
+
x1, y1 = x[0], y[0]
|
|
60
|
+
xn, yn = x[-1], y[-1]
|
|
61
|
+
if x1 != xn or y1 != yn:
|
|
62
|
+
self.x = np.concatenate((self.x, [x1]))
|
|
63
|
+
self.y = np.concatenate((self.y, [y1]))
|
|
64
|
+
# Anti-clockwise coordinates
|
|
65
|
+
if _det(self.x, self.y) < 0:
|
|
66
|
+
self.x = self.x[::-1]
|
|
67
|
+
self.y = self.y[::-1]
|
|
68
|
+
|
|
69
|
+
def is_inside(self, xpoint, ypoint, smalld=1e-12):
|
|
70
|
+
"""
|
|
71
|
+
Check if a point or set of points are inside the polygon.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
xpoint : :class:`float` or array_like
|
|
76
|
+
Array of x-coordinates of the points to be tested.
|
|
77
|
+
|
|
78
|
+
ypoint : :class:`float` or array_like
|
|
79
|
+
Array of y-coordinates of the points to be tested.
|
|
80
|
+
|
|
81
|
+
smalld : :class:`float`, optional
|
|
82
|
+
Tolerance used for floating point comparisons when checking if a point is exactly on a
|
|
83
|
+
polygon's edge. The default value is ``1e-12``.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
:class:`float` or array_like
|
|
88
|
+
The distance from the point to the nearest point on the polygon. The values returned
|
|
89
|
+
have the following meanings:
|
|
90
|
+
- ``mindst < 0``: Point is outside the polygon.
|
|
91
|
+
- ``mindst = 0``: Point is on an edge of the polygon.
|
|
92
|
+
- ``mindst > 0``: Point is inside the polygon.
|
|
93
|
+
|
|
94
|
+
Notes
|
|
95
|
+
-----
|
|
96
|
+
The method uses an improved algorithm based on Nordbeck and Rydstedt for determining
|
|
97
|
+
whether a point is inside a polygon :cite:`SLOAN198545`.
|
|
98
|
+
"""
|
|
99
|
+
xpoint = np.asarray(xpoint, dtype=float)
|
|
100
|
+
ypoint = np.asarray(ypoint, dtype=float)
|
|
101
|
+
# Scalar to array
|
|
102
|
+
if xpoint.shape == ():
|
|
103
|
+
xpoint = np.array([xpoint], dtype=float)
|
|
104
|
+
ypoint = np.array([ypoint], dtype=float)
|
|
105
|
+
scalar = True
|
|
106
|
+
else:
|
|
107
|
+
scalar = False
|
|
108
|
+
# Check consistency
|
|
109
|
+
if xpoint.shape != ypoint.shape:
|
|
110
|
+
msg = 'x and y have different shapes'
|
|
111
|
+
raise IndexError(msg)
|
|
112
|
+
# If snear = True: Dist to nearest side < nearest vertex
|
|
113
|
+
# If snear = False: Dist to nearest vertex < nearest side
|
|
114
|
+
snear = np.ma.masked_all(xpoint.shape, dtype=bool)
|
|
115
|
+
# Initialize arrays
|
|
116
|
+
mindst = np.ones_like(xpoint, dtype=float) * np.inf
|
|
117
|
+
j = np.ma.masked_all(xpoint.shape, dtype=int)
|
|
118
|
+
x = self.x
|
|
119
|
+
y = self.y
|
|
120
|
+
n = len(x) - 1 # Number of sides/vertices defining the polygon
|
|
121
|
+
# Loop over each side defining polygon
|
|
122
|
+
for i in range(n):
|
|
123
|
+
d = np.ones_like(xpoint, dtype=float) * np.inf
|
|
124
|
+
# Start of side has coords (x1, y1)
|
|
125
|
+
# End of side has coords (x2, y2)
|
|
126
|
+
# Point has coords (xpoint, ypoint)
|
|
127
|
+
x1 = x[i]
|
|
128
|
+
y1 = y[i]
|
|
129
|
+
x21 = x[i + 1] - x1
|
|
130
|
+
y21 = y[i + 1] - y1
|
|
131
|
+
x1p = x1 - xpoint
|
|
132
|
+
y1p = y1 - ypoint
|
|
133
|
+
# Points on infinite line defined by
|
|
134
|
+
# x = x1 + t * (x1 - x2)
|
|
135
|
+
# y = y1 + t * (y1 - y2)
|
|
136
|
+
# where
|
|
137
|
+
# t = 0 at (x1, y1)
|
|
138
|
+
# t = 1 at (x2, y2)
|
|
139
|
+
# Find where normal passing through (xpoint, ypoint) intersects
|
|
140
|
+
# infinite line
|
|
141
|
+
t = -(x1p * x21 + y1p * y21) / (x21**2 + y21**2)
|
|
142
|
+
tlt0 = t < 0
|
|
143
|
+
tle1 = (t >= 0) & (t <= 1)
|
|
144
|
+
# Normal intersects side
|
|
145
|
+
d[tle1] = (x1p[tle1] + t[tle1] * x21) ** 2 + (y1p[tle1] + t[tle1] * y21) ** 2
|
|
146
|
+
# Normal does not intersects side
|
|
147
|
+
# Point is closest to vertex (x1, y1)
|
|
148
|
+
# Compute square of distance to this vertex
|
|
149
|
+
d[tlt0] = x1p[tlt0] ** 2 + y1p[tlt0] ** 2
|
|
150
|
+
# Store distances
|
|
151
|
+
mask = d < mindst
|
|
152
|
+
mindst[mask] = d[mask]
|
|
153
|
+
j[mask] = i
|
|
154
|
+
# Point is closer to (x1, y1) than any other vertex or side
|
|
155
|
+
snear[mask & tlt0] = False
|
|
156
|
+
# Point is closer to this side than to any other side or vertex
|
|
157
|
+
snear[mask & tle1] = True
|
|
158
|
+
if np.ma.count(snear) != snear.size:
|
|
159
|
+
msg = 'Error computing distances'
|
|
160
|
+
raise IndexError(msg)
|
|
161
|
+
mindst **= 0.5
|
|
162
|
+
# Point is closer to its nearest vertex than its nearest side, check if
|
|
163
|
+
# nearest vertex is concave.
|
|
164
|
+
# If the nearest vertex is concave then point is inside the polygon,
|
|
165
|
+
# else the point is outside the polygon.
|
|
166
|
+
jo = j.copy()
|
|
167
|
+
jo[j == 0] -= 1
|
|
168
|
+
area = _det([x[j + 1], x[j], x[jo - 1]], [y[j + 1], y[j], y[jo - 1]])
|
|
169
|
+
mindst[~snear] = np.copysign(mindst, area)[~snear]
|
|
170
|
+
# Point is closer to its nearest side than to its nearest vertex, check
|
|
171
|
+
# if point is to left or right of this side.
|
|
172
|
+
# If point is to left of side it is inside polygon, else point is
|
|
173
|
+
# outside polygon.
|
|
174
|
+
area = _det([x[j], x[j + 1], xpoint], [y[j], y[j + 1], ypoint])
|
|
175
|
+
mindst[snear] = np.copysign(mindst, area)[snear]
|
|
176
|
+
# Point is on side of polygon
|
|
177
|
+
mindst[np.fabs(mindst) < smalld] = 0
|
|
178
|
+
# If input values were scalar then the output should be too
|
|
179
|
+
if scalar:
|
|
180
|
+
mindst = float(mindst[0])
|
|
181
|
+
return mindst
|
|
182
|
+
|
|
183
|
+
|
|
16
184
|
def get_file_basename(file, alternative_basename='void'):
|
|
17
185
|
"""Return the basename of the file.
|
|
18
186
|
|