acoular 24.10__py3-none-any.whl → 25.3__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 +5 -2
- acoular/aiaa/__init__.py +12 -0
- acoular/{tools → aiaa}/aiaa.py +23 -28
- acoular/base.py +75 -55
- acoular/calib.py +129 -34
- acoular/configuration.py +11 -9
- acoular/demo/__init__.py +1 -0
- acoular/demo/acoular_demo.py +31 -18
- acoular/deprecation.py +85 -0
- acoular/environments.py +481 -229
- acoular/fastFuncs.py +90 -84
- acoular/fbeamform.py +203 -411
- acoular/fprocess.py +233 -123
- acoular/grids.py +793 -424
- acoular/h5cache.py +29 -40
- acoular/h5files.py +2 -6
- acoular/microphones.py +197 -74
- acoular/process.py +660 -149
- acoular/sdinput.py +23 -20
- acoular/signals.py +461 -159
- acoular/sources.py +1311 -489
- acoular/spectra.py +328 -352
- acoular/tbeamform.py +79 -202
- acoular/tfastfuncs.py +21 -21
- acoular/tools/__init__.py +2 -8
- acoular/tools/helpers.py +216 -2
- acoular/tools/metrics.py +4 -4
- acoular/tools/utils.py +106 -200
- acoular/tprocess.py +348 -309
- acoular/traitsviews.py +10 -10
- acoular/trajectory.py +126 -53
- acoular/version.py +2 -2
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/METADATA +39 -17
- acoular-25.3.dist-info/RECORD +56 -0
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/WHEEL +1 -1
- acoular-24.10.dist-info/RECORD +0 -54
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/licenses/AUTHORS.rst +0 -0
- {acoular-24.10.dist-info → acoular-25.3.dist-info}/licenses/LICENSE +0 -0
acoular/grids.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# ------------------------------------------------------------------------------
|
|
2
2
|
# Copyright (c) Acoular Development Team.
|
|
3
3
|
# ------------------------------------------------------------------------------
|
|
4
|
-
"""
|
|
4
|
+
"""
|
|
5
|
+
Implement support for multidimensional grids and integration sectors.
|
|
5
6
|
|
|
6
7
|
.. autosummary::
|
|
7
8
|
:toctree: generated/
|
|
@@ -20,12 +21,13 @@
|
|
|
20
21
|
PolySector
|
|
21
22
|
ConvexSector
|
|
22
23
|
MultiSector
|
|
23
|
-
|
|
24
|
+
Polygon
|
|
25
|
+
in_hull
|
|
24
26
|
"""
|
|
25
27
|
|
|
26
28
|
# imports from other packages
|
|
27
|
-
|
|
28
|
-
from
|
|
29
|
+
import xml.dom.minidom
|
|
30
|
+
from abc import abstractmethod
|
|
29
31
|
|
|
30
32
|
from numpy import (
|
|
31
33
|
absolute,
|
|
@@ -45,7 +47,7 @@ from numpy import (
|
|
|
45
47
|
ones,
|
|
46
48
|
ones_like,
|
|
47
49
|
s_,
|
|
48
|
-
sum,
|
|
50
|
+
sum, # noqa: A004
|
|
49
51
|
tile,
|
|
50
52
|
unique,
|
|
51
53
|
where,
|
|
@@ -56,61 +58,83 @@ from scipy.linalg import norm
|
|
|
56
58
|
# from matplotlib.path import Path
|
|
57
59
|
from scipy.spatial import Delaunay
|
|
58
60
|
from traits.api import (
|
|
61
|
+
ABCHasStrictTraits,
|
|
59
62
|
Bool,
|
|
60
63
|
CArray,
|
|
61
64
|
File,
|
|
62
65
|
Float,
|
|
63
|
-
HasPrivateTraits,
|
|
64
66
|
Instance,
|
|
65
67
|
Int,
|
|
66
68
|
List,
|
|
67
69
|
Property,
|
|
70
|
+
Str,
|
|
68
71
|
Tuple,
|
|
69
72
|
Union,
|
|
70
73
|
cached_property,
|
|
74
|
+
observe,
|
|
71
75
|
on_trait_change,
|
|
72
76
|
property_depends_on,
|
|
73
77
|
)
|
|
74
78
|
from traits.trait_errors import TraitError
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
# acoular imports
|
|
81
|
+
from .deprecation import deprecated_alias
|
|
82
|
+
from .internal import digest, ldigest
|
|
77
83
|
|
|
78
84
|
|
|
79
85
|
def in_hull(p, hull, border=True, tol=0):
|
|
80
|
-
"""Test if points in `p` are in `hull`
|
|
81
|
-
`p` should be a `NxK` coordinates of `N` points in `K` dimensions
|
|
82
|
-
`hull` is either a scipy.spatial.Delaunay object or the `MxK` array of the
|
|
83
|
-
coordinates of `M` points in `K`dimensions for which Delaunay triangulation
|
|
84
|
-
will be computed.
|
|
85
86
|
"""
|
|
86
|
-
if
|
|
87
|
-
hull = Delaunay(hull)
|
|
88
|
-
|
|
89
|
-
if border:
|
|
90
|
-
return hull.find_simplex(p, tol=tol) >= 0
|
|
91
|
-
return hull.find_simplex(p, tol=tol) > 0
|
|
87
|
+
Test if points in ``p`` are in ``hull``, in- or excluding the border.
|
|
92
88
|
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
p : :class:`numpy.ndarray` of :class:`floats<float>`, shape `(N, K)`
|
|
92
|
+
Coordinates of `N` points in `K` dimensions.
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
hull : :class:`numpy.ndarray` of :class:`floats<float>`, shape `(M, K)`, or :class:`~scipy.spatial.Delaunay` object
|
|
95
|
+
Coordinates of `M` points in `K` dimensions for which Delaunay triangulation will be
|
|
96
|
+
computed.
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
border : bool, optional
|
|
99
|
+
Points in :attr:`p` on the border of :attr:`hull` will be kept in the return if `True`. If
|
|
100
|
+
`False`, only points inside :attr:`hull` will be kept. Default is `True`.
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
tol : :class:`float`, optional
|
|
103
|
+
Tolerance allowed in the :meth:`inside-triangle check<scipy.spatial.Delaunay.find_simplex>`.
|
|
104
|
+
Default is ``0``.
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
109
|
+
An array of boolean values indicating which points in ``p`` are inside the hull, same
|
|
110
|
+
shape as ``p``. Each entry is ``True`` if the corresponding point is inside the hull (or
|
|
111
|
+
on the border, if ``border=True``), and ``False`` otherwise.
|
|
105
112
|
|
|
106
113
|
Notes
|
|
107
114
|
-----
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
This function uses Delaunay triangulation to determine if a point is inside the convex hull,
|
|
116
|
+
which is efficient and robust for arbitrary shapes in higher-dimensional spaces.
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
>>> from acoular.grids import in_hull
|
|
121
|
+
>>> import numpy as np
|
|
122
|
+
>>> from scipy.spatial import Delaunay
|
|
123
|
+
>>> points = np.array([[0, 0], [1, 0], [0, 1], [1, 1]])
|
|
124
|
+
>>> hull = Delaunay(points)
|
|
125
|
+
>>> p = np.array([[0.5, 0.5], [2, 2]])
|
|
126
|
+
>>> in_hull(p, hull)
|
|
127
|
+
array([ True, False])
|
|
128
|
+
""" # noqa W505
|
|
129
|
+
if not isinstance(hull, Delaunay):
|
|
130
|
+
hull = Delaunay(hull)
|
|
112
131
|
|
|
113
|
-
|
|
132
|
+
if border:
|
|
133
|
+
return hull.find_simplex(p, tol=tol) >= 0
|
|
134
|
+
return hull.find_simplex(p, tol=tol) > 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _det(xvert, yvert):
|
|
114
138
|
xvert = asarray(xvert, dtype=float)
|
|
115
139
|
yvert = asarray(yvert, dtype=float)
|
|
116
140
|
x_prev = concatenate(([xvert[-1]], xvert[:-1]))
|
|
@@ -119,10 +143,30 @@ def _det(xvert, yvert):
|
|
|
119
143
|
|
|
120
144
|
|
|
121
145
|
class Polygon:
|
|
122
|
-
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
146
|
+
"""
|
|
147
|
+
Create an object representing a general polygon in a 2D plane.
|
|
148
|
+
|
|
149
|
+
This class allows defining a polygon by specifying the coordinates of its vertices and provides
|
|
150
|
+
methods for checking whether a set of points lies inside the polygon, or if a point is closer to
|
|
151
|
+
a side or vertex of the polygon.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
x : array_like
|
|
156
|
+
Array of x-coordinates of the vertices that define the polygon. These coordinates should
|
|
157
|
+
form a closed shape (i.e., the last point should be the same as the first point).
|
|
158
|
+
|
|
159
|
+
y : array_like
|
|
160
|
+
Array of y-coordinates of the vertices that define the polygon. These coordinates should
|
|
161
|
+
correspond to the x-coordinates, forming a closed shape.
|
|
162
|
+
|
|
163
|
+
Attributes
|
|
164
|
+
----------
|
|
165
|
+
x : :class:`numpy.ndarray`
|
|
166
|
+
Array of x-coordinates of the polygon vertices.
|
|
167
|
+
|
|
168
|
+
y : :class:`numpy.ndarray`
|
|
169
|
+
Array of y-coordinates of the polygon vertices.
|
|
126
170
|
"""
|
|
127
171
|
|
|
128
172
|
def __init__(self, x, y):
|
|
@@ -143,29 +187,34 @@ class Polygon:
|
|
|
143
187
|
self.y = self.y[::-1]
|
|
144
188
|
|
|
145
189
|
def is_inside(self, xpoint, ypoint, smalld=1e-12):
|
|
146
|
-
"""
|
|
190
|
+
"""
|
|
191
|
+
Check if a point or set of points are inside the polygon.
|
|
147
192
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
xpoint : :class:`float` or array_like
|
|
196
|
+
Array of x-coordinates of the points to be tested.
|
|
152
197
|
|
|
153
|
-
|
|
198
|
+
ypoint : :class:`float` or array_like
|
|
199
|
+
Array of y-coordinates of the points to be tested.
|
|
154
200
|
|
|
155
|
-
|
|
201
|
+
smalld : :class:`float`, optional
|
|
202
|
+
Tolerance used for floating point comparisons when checking if a point is exactly on a
|
|
203
|
+
polygon's edge. The default value is ``1e-12``.
|
|
156
204
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
:class:`float` or array_like
|
|
208
|
+
The distance from the point to the nearest point on the polygon. The values returned
|
|
209
|
+
have the following meanings:
|
|
210
|
+
- ``mindst < 0``: Point is outside the polygon.
|
|
211
|
+
- ``mindst = 0``: Point is on an edge of the polygon.
|
|
212
|
+
- ``mindst > 0``: Point is inside the polygon.
|
|
162
213
|
|
|
163
214
|
Notes
|
|
164
215
|
-----
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Software, Vol 7, No. 1, pp 45-47.
|
|
168
|
-
|
|
216
|
+
The method uses an improved algorithm based on Nordbeck and Rydstedt for determining
|
|
217
|
+
whether a point is inside a polygon :cite:`SLOAN198545`.
|
|
169
218
|
"""
|
|
170
219
|
xpoint = asarray(xpoint, dtype=float)
|
|
171
220
|
ypoint = asarray(ypoint, dtype=float)
|
|
@@ -252,142 +301,135 @@ class Polygon:
|
|
|
252
301
|
return mindst
|
|
253
302
|
|
|
254
303
|
|
|
255
|
-
|
|
256
|
-
|
|
304
|
+
@deprecated_alias({'gpos': 'pos'})
|
|
305
|
+
class Grid(ABCHasStrictTraits):
|
|
306
|
+
"""
|
|
307
|
+
Abstract base class for grid geometries.
|
|
257
308
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
be used directly as it contains no real functionality.
|
|
309
|
+
This class defines a common interface for all grid geometries and provides tools to query grid
|
|
310
|
+
properties and related data. It is intended to serve as a base class for specialized grid
|
|
311
|
+
implementations and should not be instantiated directly as it lacks concrete functionality.
|
|
262
312
|
"""
|
|
263
313
|
|
|
264
|
-
#:
|
|
265
|
-
#:
|
|
314
|
+
#: The total number of grid points. This property is automatically calculated based on other
|
|
315
|
+
#: defining attributes of the grid. (read-only)
|
|
266
316
|
size = Property(desc='overall number of grid points')
|
|
267
317
|
|
|
268
|
-
#:
|
|
269
|
-
#:
|
|
318
|
+
#: The shape of the grid, represented as a tuple. Primarily useful for Cartesian grids.
|
|
319
|
+
#: (read-only)
|
|
270
320
|
shape = Property(desc='grid shape as tuple')
|
|
271
321
|
|
|
272
|
-
#:
|
|
273
|
-
#:
|
|
274
|
-
|
|
322
|
+
#: The grid positions represented as a (3, :attr:`size`) array of :class:`floats<float>`.
|
|
323
|
+
#: (read-only)
|
|
324
|
+
pos = Property(desc='x, y, z positions of grid points')
|
|
275
325
|
|
|
276
|
-
|
|
326
|
+
#: A unique identifier for the grid, based on its properties. (read-only)
|
|
277
327
|
digest = Property
|
|
278
328
|
|
|
329
|
+
@abstractmethod
|
|
279
330
|
def _get_digest(self):
|
|
280
|
-
|
|
331
|
+
"""Generate a unique digest for the grid."""
|
|
281
332
|
|
|
282
|
-
# 'digest' is a placeholder for other properties in derived classes,
|
|
283
|
-
#
|
|
284
|
-
@property_depends_on('digest')
|
|
333
|
+
# 'digest' is a placeholder for other properties in derived classes, necessary to trigger the
|
|
334
|
+
# depends on mechanism
|
|
335
|
+
@property_depends_on(['digest'])
|
|
336
|
+
@abstractmethod
|
|
285
337
|
def _get_size(self):
|
|
286
|
-
|
|
338
|
+
"""Return the number of grid points."""
|
|
287
339
|
|
|
288
340
|
# 'digest' is a placeholder for other properties in derived classes
|
|
289
|
-
@property_depends_on('digest')
|
|
341
|
+
@property_depends_on(['digest'])
|
|
342
|
+
@abstractmethod
|
|
290
343
|
def _get_shape(self):
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
@property_depends_on('digest')
|
|
294
|
-
def _get_gpos(self):
|
|
295
|
-
return array([[0.0], [0.0], [0.0]])
|
|
296
|
-
|
|
297
|
-
def pos(self):
|
|
298
|
-
"""Calculates grid co-ordinates.
|
|
299
|
-
Deprecated; use :attr:`gpos` attribute instead.
|
|
300
|
-
The :meth:`pos` method will be removed in version 25.01.
|
|
301
|
-
|
|
302
|
-
Returns
|
|
303
|
-
-------
|
|
304
|
-
array of floats of shape (3, :attr:`size`)
|
|
305
|
-
The grid point x, y, z-coordinates in one array.
|
|
344
|
+
"""Return the shape of the grid as a Tuple."""
|
|
306
345
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
311
|
-
warn(msg, DeprecationWarning, stacklevel=2)
|
|
312
|
-
return self.gpos # array([[0.], [0.], [0.]])
|
|
346
|
+
@property_depends_on(['digest'])
|
|
347
|
+
@abstractmethod
|
|
348
|
+
def _get_pos(self):
|
|
349
|
+
"""Return the grid positions as array of floats, shape (3, :attr:`size`)."""
|
|
313
350
|
|
|
314
351
|
def subdomain(self, sector):
|
|
315
|
-
"""
|
|
352
|
+
"""
|
|
353
|
+
Return the indices for a subdomain in the grid.
|
|
316
354
|
|
|
317
|
-
Allows arbitrary subdomains of type :class:`Sector
|
|
355
|
+
Allows arbitrary subdomains of type :class:`Sector`.
|
|
318
356
|
|
|
319
357
|
Parameters
|
|
320
358
|
----------
|
|
321
|
-
sector : :class:`Sector`
|
|
359
|
+
sector : :class:`Sector` object
|
|
322
360
|
Sector describing the subdomain.
|
|
323
361
|
|
|
324
362
|
Returns
|
|
325
363
|
-------
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
364
|
+
:class:`tuple`
|
|
365
|
+
A 2-tuple of arrays of integers or :obj:`numpy.s_` objects that can be used to mask or
|
|
366
|
+
select the specified subdomain from a grid-shaped array.
|
|
329
367
|
|
|
368
|
+
Notes
|
|
369
|
+
-----
|
|
370
|
+
The :func:`numpy.where` method is used to determine the the indices.
|
|
330
371
|
"""
|
|
331
|
-
xpos = self.
|
|
372
|
+
xpos = self.pos
|
|
332
373
|
# construct grid-shaped array with "True" entries where sector is
|
|
333
374
|
xyi = sector.contains(xpos).reshape(self.shape)
|
|
334
375
|
# return indices of "True" entries
|
|
335
376
|
return where(xyi)
|
|
336
377
|
|
|
337
378
|
|
|
379
|
+
@deprecated_alias({'gpos': 'pos'}, read_only=True)
|
|
338
380
|
class RectGrid(Grid):
|
|
339
|
-
"""
|
|
381
|
+
"""
|
|
382
|
+
Provides a 2D Cartesian grid for beamforming results.
|
|
340
383
|
|
|
341
|
-
|
|
342
|
-
to the z-axis. It is defined by lower and upper x- and
|
|
343
|
-
z co-ordinate.
|
|
384
|
+
This grid is composed of square or nearly square cells and lies on a plane perpendicular
|
|
385
|
+
to the z-axis. It is defined by the lower and upper x- and y-limits and a constant z-coordinate.
|
|
344
386
|
"""
|
|
345
387
|
|
|
346
|
-
#: The lower x-limit that defines the grid
|
|
388
|
+
#: The lower x-limit that defines the grid. Default is ``-1``.
|
|
347
389
|
x_min = Float(-1.0, desc='minimum x-value')
|
|
348
390
|
|
|
349
|
-
#: The upper x-limit that defines the grid
|
|
391
|
+
#: The upper x-limit that defines the grid. Default is ``1``.
|
|
350
392
|
x_max = Float(1.0, desc='maximum x-value')
|
|
351
393
|
|
|
352
|
-
#: The lower y-limit that defines the grid
|
|
394
|
+
#: The lower y-limit that defines the grid. Default is ``-1``.
|
|
353
395
|
y_min = Float(-1.0, desc='minimum y-value')
|
|
354
396
|
|
|
355
|
-
#: The upper y-limit that defines the grid
|
|
397
|
+
#: The upper y-limit that defines the grid. Default is ``1``.
|
|
356
398
|
y_max = Float(1.0, desc='maximum y-value')
|
|
357
399
|
|
|
358
|
-
#: The z
|
|
400
|
+
#: The constant z-coordinate of the grid plane. Default is ``1.0``.
|
|
359
401
|
z = Float(1.0, desc='position on z-axis')
|
|
360
402
|
|
|
361
|
-
#: The
|
|
403
|
+
#: The side length of each cell. Default is ``0.1``.
|
|
362
404
|
increment = Float(0.1, desc='step size')
|
|
363
405
|
|
|
364
|
-
#: Number of grid points along x-axis
|
|
406
|
+
#: Number of grid points along x-axis. (read-only)
|
|
365
407
|
nxsteps = Property(desc='number of grid points along x-axis')
|
|
366
408
|
|
|
367
|
-
#: Number of grid points along y-axis
|
|
409
|
+
#: Number of grid points along y-axis. (read-only)
|
|
368
410
|
nysteps = Property(desc='number of grid points along y-axis')
|
|
369
411
|
|
|
370
|
-
|
|
412
|
+
#: A unique identifier for the grid, based on its properties. (read-only)
|
|
371
413
|
digest = Property(
|
|
372
414
|
depends_on=['x_min', 'x_max', 'y_min', 'y_max', 'z', 'increment'],
|
|
373
415
|
)
|
|
374
416
|
|
|
375
|
-
@property_depends_on('nxsteps, nysteps')
|
|
417
|
+
@property_depends_on(['nxsteps', 'nysteps'])
|
|
376
418
|
def _get_size(self):
|
|
377
419
|
return self.nxsteps * self.nysteps
|
|
378
420
|
|
|
379
|
-
@property_depends_on('nxsteps, nysteps')
|
|
421
|
+
@property_depends_on(['nxsteps', 'nysteps'])
|
|
380
422
|
def _get_shape(self):
|
|
381
423
|
return (self.nxsteps, self.nysteps)
|
|
382
424
|
|
|
383
|
-
@property_depends_on('x_min, x_max, increment')
|
|
425
|
+
@property_depends_on(['x_min', 'x_max', 'increment'])
|
|
384
426
|
def _get_nxsteps(self):
|
|
385
427
|
i = abs(self.increment)
|
|
386
428
|
if i != 0:
|
|
387
429
|
return int(round((abs(self.x_max - self.x_min) + i) / i))
|
|
388
430
|
return 1
|
|
389
431
|
|
|
390
|
-
@property_depends_on('y_min, y_max, increment')
|
|
432
|
+
@property_depends_on(['y_min', 'y_max', 'increment'])
|
|
391
433
|
def _get_nysteps(self):
|
|
392
434
|
i = abs(self.increment)
|
|
393
435
|
if i != 0:
|
|
@@ -398,16 +440,8 @@ class RectGrid(Grid):
|
|
|
398
440
|
def _get_digest(self):
|
|
399
441
|
return digest(self)
|
|
400
442
|
|
|
401
|
-
@property_depends_on('x_min, x_max, y_min, y_max, increment')
|
|
402
|
-
def
|
|
403
|
-
"""Calculates grid co-ordinates.
|
|
404
|
-
|
|
405
|
-
Returns
|
|
406
|
-
-------
|
|
407
|
-
array of floats of shape (3, :attr:`~Grid.size`)
|
|
408
|
-
The grid point x, y, z-coordinates in one array.
|
|
409
|
-
|
|
410
|
-
"""
|
|
443
|
+
@property_depends_on(['x_min', 'x_max', 'y_min', 'y_max', 'increment'])
|
|
444
|
+
def _get_pos(self):
|
|
411
445
|
bpos = mgrid[
|
|
412
446
|
self.x_min : self.x_max : self.nxsteps * 1j,
|
|
413
447
|
self.y_min : self.y_max : self.nysteps * 1j,
|
|
@@ -417,22 +451,25 @@ class RectGrid(Grid):
|
|
|
417
451
|
return bpos
|
|
418
452
|
|
|
419
453
|
def index(self, x, y):
|
|
420
|
-
"""
|
|
421
|
-
|
|
422
|
-
This can be used to query results or co-ordinates at/near a certain
|
|
423
|
-
co-ordinate.
|
|
454
|
+
"""
|
|
455
|
+
Find the indices of a grid point near a given coordinate.
|
|
424
456
|
|
|
425
457
|
Parameters
|
|
426
458
|
----------
|
|
427
|
-
x
|
|
428
|
-
The
|
|
459
|
+
x : :class:`float`
|
|
460
|
+
The x coordinate of interest.
|
|
461
|
+
y : :class:`float`
|
|
462
|
+
The y coordinate of interest.
|
|
429
463
|
|
|
430
464
|
Returns
|
|
431
465
|
-------
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
co-ordinates from an array with the same shape as the grid.
|
|
466
|
+
:class:`tuple` of :class:`ints<int>`
|
|
467
|
+
Indices corresponding to the nearest grid point.
|
|
435
468
|
|
|
469
|
+
Raises
|
|
470
|
+
------
|
|
471
|
+
ValueError
|
|
472
|
+
If the coordinates are outside the grid boundaries.
|
|
436
473
|
"""
|
|
437
474
|
if x < self.x_min or x > self.x_max:
|
|
438
475
|
msg = 'x-value out of range'
|
|
@@ -445,31 +482,27 @@ class RectGrid(Grid):
|
|
|
445
482
|
return xi, yi
|
|
446
483
|
|
|
447
484
|
def indices(self, *r):
|
|
448
|
-
"""
|
|
485
|
+
"""
|
|
486
|
+
Find the indices of a subdomain in the grid.
|
|
449
487
|
|
|
450
|
-
|
|
451
|
-
This can be used to mask or to query results from a certain
|
|
452
|
-
sector or subdomain.
|
|
488
|
+
Supports rectangular, circular, and polygonal subdomains.
|
|
453
489
|
|
|
454
490
|
Parameters
|
|
455
491
|
----------
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
polygonial shape with corners at (x_n, y_n).
|
|
492
|
+
r : :class:`tuple` of :class:`floats<float>`
|
|
493
|
+
Defines the subdomain shape and dimensions:
|
|
494
|
+
- If 3 values are provided: center ``(x1, y1)`` and radius ``r2`` define a circle.
|
|
495
|
+
- If 4 values are provided: corners ``(x1, y1)`` and ``(x2, y2)`` define a
|
|
496
|
+
rectangle.
|
|
497
|
+
- If more than 4 values are provided: vertices ``(xn, yn)`` define a polygon.
|
|
463
498
|
|
|
464
499
|
Returns
|
|
465
500
|
-------
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
an array with the same shape as the grid.
|
|
469
|
-
|
|
501
|
+
:class:`tuple`
|
|
502
|
+
A 2-tuple of indices or slices corresponding to the subdomain.
|
|
470
503
|
"""
|
|
471
504
|
if len(r) == 3: # only 3 values given -> use x,y,radius method
|
|
472
|
-
xpos = self.
|
|
505
|
+
xpos = self.pos
|
|
473
506
|
xis = []
|
|
474
507
|
yis = []
|
|
475
508
|
dr2 = (xpos[0, :] - r[0]) ** 2 + (xpos[1, :] - r[1]) ** 2
|
|
@@ -486,7 +519,7 @@ class RectGrid(Grid):
|
|
|
486
519
|
xi1, yi1 = self.index(min(r[0], r[2]), min(r[1], r[3]))
|
|
487
520
|
xi2, yi2 = self.index(max(r[0], r[2]), max(r[1], r[3]))
|
|
488
521
|
return s_[xi1 : xi2 + 1], s_[yi1 : yi2 + 1]
|
|
489
|
-
xpos = self.
|
|
522
|
+
xpos = self.pos
|
|
490
523
|
xis = []
|
|
491
524
|
yis = []
|
|
492
525
|
# replaced matplotlib Path by numpy
|
|
@@ -507,46 +540,62 @@ class RectGrid(Grid):
|
|
|
507
540
|
# return arange(self.size)[inds]
|
|
508
541
|
|
|
509
542
|
def extend(self):
|
|
510
|
-
"""
|
|
543
|
+
"""
|
|
544
|
+
Return the grid's extension in :obj:`matplotlib.pyplot.imshow` compatible form.
|
|
511
545
|
|
|
512
546
|
Returns
|
|
513
547
|
-------
|
|
514
|
-
|
|
515
|
-
|
|
548
|
+
:class:`tuple` of :class:`floats<float>`
|
|
549
|
+
(:attr:`x_min`, :attr:`x_max`, :attr:`y_min`, :attr:`y_max`) representing the grid's
|
|
550
|
+
extent.
|
|
516
551
|
|
|
552
|
+
Notes
|
|
553
|
+
-----
|
|
554
|
+
- ``pylab.imhow`` is the same as :obj:`matplotlib.pyplot.imshow`. It's only using a
|
|
555
|
+
different namespace.
|
|
556
|
+
- The return of the method is ment for the ``extent`` parameter of
|
|
557
|
+
:obj:`matplotlib.pyplot.imshow`.
|
|
558
|
+
|
|
559
|
+
Examples
|
|
560
|
+
--------
|
|
561
|
+
>>> from acoular import RectGrid
|
|
562
|
+
>>> grid = RectGrid()
|
|
563
|
+
>>> grid.y_min = -5
|
|
564
|
+
>>> grid.y_max = 5
|
|
565
|
+
>>> grid.extend()
|
|
566
|
+
(-1.0, 1.0, -5.0, 5.0)
|
|
517
567
|
"""
|
|
518
568
|
return (self.x_min, self.x_max, self.y_min, self.y_max)
|
|
519
569
|
|
|
520
570
|
|
|
521
571
|
class RectGrid3D(RectGrid):
|
|
522
|
-
"""
|
|
572
|
+
"""
|
|
573
|
+
Provide a cartesian 3D grid for the beamforming results.
|
|
523
574
|
|
|
524
|
-
The grid has cubic or nearly cubic cells. It is defined by lower and upper
|
|
525
|
-
x-, y- and z-limits.
|
|
575
|
+
The grid has cubic or nearly cubic cells. It is defined by lower and upper x-, y- and z-limits.
|
|
526
576
|
"""
|
|
527
577
|
|
|
528
|
-
#: The lower z-limit that defines the grid
|
|
578
|
+
#: The lower z-limit that defines the grid. Default is ``-1``.
|
|
529
579
|
z_min = Float(-1.0, desc='minimum z-value')
|
|
530
580
|
|
|
531
|
-
#: The upper z-limit that defines the grid
|
|
581
|
+
#: The upper z-limit that defines the grid. Default is ``1``.
|
|
532
582
|
z_max = Float(1.0, desc='maximum z-value')
|
|
533
583
|
|
|
534
|
-
#: Number of grid points along x-axis
|
|
584
|
+
#: Number of grid points along x-axis. (read-only)
|
|
535
585
|
nxsteps = Property(desc='number of grid points along x-axis')
|
|
536
586
|
|
|
537
|
-
#: Number of grid points along y-axis
|
|
587
|
+
#: Number of grid points along y-axis. (read-only)
|
|
538
588
|
nysteps = Property(desc='number of grid points along y-axis')
|
|
539
589
|
|
|
540
|
-
#: Number of grid points along
|
|
541
|
-
nzsteps = Property(desc='number of grid points along
|
|
590
|
+
#: Number of grid points along z-axis. (read-only)
|
|
591
|
+
nzsteps = Property(desc='number of grid points along z-axis')
|
|
542
592
|
|
|
543
593
|
# Private trait for increment handling
|
|
544
594
|
_increment = Union(Float(), CArray(shape=(3,), dtype=float), default_value=0.1, desc='step size')
|
|
545
595
|
|
|
546
|
-
#: The cell side length for the grid. This can either be a scalar (same
|
|
547
|
-
#:
|
|
548
|
-
#:
|
|
549
|
-
#: Defaults to 0.1.
|
|
596
|
+
#: The cell side length for the grid. This can either be a scalar (same increments in all 3
|
|
597
|
+
#: dimensions) or a (3,) array of :class:`tuple` of :class:`floats<float>` with respective
|
|
598
|
+
#: increments in x-, y-, and z-direction. Default is ``0.1``.
|
|
550
599
|
increment = Property(desc='step size')
|
|
551
600
|
|
|
552
601
|
def _get_increment(self):
|
|
@@ -563,53 +612,34 @@ class RectGrid3D(RectGrid):
|
|
|
563
612
|
else:
|
|
564
613
|
raise (TraitError(args=self, name='increment', info='Float or CArray(3,)', value=increment))
|
|
565
614
|
|
|
566
|
-
|
|
567
|
-
# Deprecated: Use :attr:`~RectGrid.increment` for this functionality
|
|
568
|
-
increment3D = Property(desc='3D step sizes') # noqa N815
|
|
569
|
-
|
|
570
|
-
def _get_increment3D(self): # noqa N802
|
|
571
|
-
msg = "Using 'increment3D' is deprecated and will be removed in version 25.01." "Use 'increment' instead."
|
|
572
|
-
warn(msg, DeprecationWarning, stacklevel=2)
|
|
573
|
-
if isscalar(self._increment):
|
|
574
|
-
return array([self._increment, self._increment, self._increment])
|
|
575
|
-
return self._increment
|
|
576
|
-
|
|
577
|
-
def _set_increment3D(self, inc): # noqa N802
|
|
578
|
-
msg = "Using 'increment3D' is deprecated and will be removed in version 25.01." "Use 'increment' instead."
|
|
579
|
-
warn(msg, DeprecationWarning, stacklevel=2)
|
|
580
|
-
if not isscalar(inc) and len(inc) == 3:
|
|
581
|
-
self._increment = array(inc, dtype=float)
|
|
582
|
-
else:
|
|
583
|
-
raise (TraitError(args=self, name='increment3D', info='CArray(3,)', value=inc))
|
|
584
|
-
|
|
585
|
-
# internal identifier
|
|
615
|
+
#: A unique identifier for the grid, based on its properties. (read-only)
|
|
586
616
|
digest = Property(
|
|
587
617
|
depends_on=['x_min', 'x_max', 'y_min', 'y_max', 'z_min', 'z_max', '_increment'],
|
|
588
618
|
)
|
|
589
619
|
|
|
590
|
-
@property_depends_on('nxsteps, nysteps, nzsteps')
|
|
620
|
+
@property_depends_on(['nxsteps', 'nysteps', 'nzsteps'])
|
|
591
621
|
def _get_size(self):
|
|
592
622
|
return self.nxsteps * self.nysteps * self.nzsteps
|
|
593
623
|
|
|
594
|
-
@property_depends_on('nxsteps, nysteps, nzsteps')
|
|
624
|
+
@property_depends_on(['nxsteps', 'nysteps', 'nzsteps'])
|
|
595
625
|
def _get_shape(self):
|
|
596
626
|
return (self.nxsteps, self.nysteps, self.nzsteps)
|
|
597
627
|
|
|
598
|
-
@property_depends_on('x_min, x_max, _increment')
|
|
628
|
+
@property_depends_on(['x_min', 'x_max', '_increment'])
|
|
599
629
|
def _get_nxsteps(self):
|
|
600
630
|
i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[0])
|
|
601
631
|
if i != 0:
|
|
602
632
|
return int(round((abs(self.x_max - self.x_min) + i) / i))
|
|
603
633
|
return 1
|
|
604
634
|
|
|
605
|
-
@property_depends_on('y_min, y_max, _increment')
|
|
635
|
+
@property_depends_on(['y_min', 'y_max', '_increment'])
|
|
606
636
|
def _get_nysteps(self):
|
|
607
637
|
i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[1])
|
|
608
638
|
if i != 0:
|
|
609
639
|
return int(round((abs(self.y_max - self.y_min) + i) / i))
|
|
610
640
|
return 1
|
|
611
641
|
|
|
612
|
-
@property_depends_on('z_min, z_max, _increment')
|
|
642
|
+
@property_depends_on(['z_min', 'z_max', '_increment'])
|
|
613
643
|
def _get_nzsteps(self):
|
|
614
644
|
i = abs(self.increment) if isscalar(self.increment) else abs(self.increment[2])
|
|
615
645
|
if i != 0:
|
|
@@ -617,15 +647,7 @@ class RectGrid3D(RectGrid):
|
|
|
617
647
|
return 1
|
|
618
648
|
|
|
619
649
|
@property_depends_on('digest')
|
|
620
|
-
def
|
|
621
|
-
"""Calculates grid co-ordinates.
|
|
622
|
-
|
|
623
|
-
Returns
|
|
624
|
-
-------
|
|
625
|
-
array of floats of shape (3, :attr:`~Grid.size`)
|
|
626
|
-
The grid point x, y, z-coordinates in one array.
|
|
627
|
-
|
|
628
|
-
"""
|
|
650
|
+
def _get_pos(self):
|
|
629
651
|
bpos = mgrid[
|
|
630
652
|
self.x_min : self.x_max : self.nxsteps * 1j,
|
|
631
653
|
self.y_min : self.y_max : self.nysteps * 1j,
|
|
@@ -639,22 +661,39 @@ class RectGrid3D(RectGrid):
|
|
|
639
661
|
return digest(self)
|
|
640
662
|
|
|
641
663
|
def index(self, x, y, z):
|
|
642
|
-
"""
|
|
664
|
+
"""
|
|
665
|
+
Return the indices for a grid point near a certain coordinate.
|
|
643
666
|
|
|
644
|
-
This can be used to query results or
|
|
645
|
-
|
|
667
|
+
This can be used to query results or coordinates at or near a certain coordinate. Raises an
|
|
668
|
+
exception if the given coordinate is outside the grid.
|
|
646
669
|
|
|
647
670
|
Parameters
|
|
648
671
|
----------
|
|
649
|
-
x, y, z : float
|
|
650
|
-
The
|
|
672
|
+
x, y, z : :class:`float`
|
|
673
|
+
The coordinates for which the indices is queried.
|
|
651
674
|
|
|
652
675
|
Returns
|
|
653
676
|
-------
|
|
654
|
-
3
|
|
655
|
-
The indices that give the grid point nearest to the given x, y, z
|
|
656
|
-
|
|
657
|
-
|
|
677
|
+
3-:class:`tuple` of :class:`ints<int>`
|
|
678
|
+
The indices that give the grid point nearest to the given x, y, z coordinates from an
|
|
679
|
+
array with the same shape as the grid.
|
|
680
|
+
|
|
681
|
+
Examples
|
|
682
|
+
--------
|
|
683
|
+
Check which of the points in a simple 8-point rectangular grid is closest to the point
|
|
684
|
+
``(0.5, 0.5, 1.0)``.
|
|
685
|
+
|
|
686
|
+
>>> import acoular as ac
|
|
687
|
+
>>>
|
|
688
|
+
>>> grid = ac.RectGrid3D()
|
|
689
|
+
>>> grid.increment = 2
|
|
690
|
+
>>> grid.pos
|
|
691
|
+
array([[-1., -1., -1., -1., 1., 1., 1., 1.],
|
|
692
|
+
[-1., -1., 1., 1., -1., -1., 1., 1.],
|
|
693
|
+
[-1., 1., -1., 1., -1., 1., -1., 1.]])
|
|
694
|
+
>>>
|
|
695
|
+
>>> grid.index(0.5, 0.5, 1.0)
|
|
696
|
+
(1, 1, 1)
|
|
658
697
|
"""
|
|
659
698
|
if x < self.x_min or x > self.x_max:
|
|
660
699
|
msg = f'x-value out of range {x:f} ({self.x_min:f}, {self.x_max:f})'
|
|
@@ -675,284 +714,510 @@ class RectGrid3D(RectGrid):
|
|
|
675
714
|
return xi, yi, zi
|
|
676
715
|
|
|
677
716
|
def indices(self, x1, y1, z1, x2, y2, z2):
|
|
678
|
-
"""
|
|
717
|
+
"""
|
|
718
|
+
Return the indices for a subdomain in the grid.
|
|
679
719
|
|
|
680
|
-
Allows box-shaped subdomains. This can be used to
|
|
681
|
-
|
|
720
|
+
Allows box-shaped subdomains. This can be used to mask or to query results from a certain
|
|
721
|
+
sector or subdomain.
|
|
682
722
|
|
|
683
723
|
Parameters
|
|
684
724
|
----------
|
|
685
|
-
x1, y1, z1, x2, y2, z2 : float
|
|
686
|
-
A box-shaped sector is assumed that is given by two corners
|
|
687
|
-
(
|
|
725
|
+
x1, y1, z1, x2, y2, z2 : :class:`float`
|
|
726
|
+
A box-shaped sector is assumed that is given by two corners ``(x1,y1,z1)`` and
|
|
727
|
+
``(x2,y2,z2)``.
|
|
688
728
|
|
|
689
729
|
Returns
|
|
690
730
|
-------
|
|
691
|
-
3
|
|
692
|
-
The indices that can be used to mask/select the grid subdomain from
|
|
693
|
-
|
|
694
|
-
|
|
731
|
+
3-:class:`tuple` of :obj:`numpy.s_` objects
|
|
732
|
+
The indices that can be used to mask/select the grid subdomain from an array with the
|
|
733
|
+
same shape as the grid.
|
|
734
|
+
|
|
735
|
+
Examples
|
|
736
|
+
--------
|
|
737
|
+
Get the indices of the grid points of a simple 27-point rectangular grid which are located
|
|
738
|
+
inside the first octant.
|
|
739
|
+
|
|
740
|
+
>>> import acoular as ac
|
|
741
|
+
>>>
|
|
742
|
+
>>> grid = ac.RectGrid3D()
|
|
743
|
+
>>> grid.increment = 1
|
|
744
|
+
>>> grid.pos
|
|
745
|
+
array([[-1., -1., -1., -1., -1., -1., -1., -1., -1., 0., 0., 0., 0.,
|
|
746
|
+
0., 0., 0., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1.,
|
|
747
|
+
1.],
|
|
748
|
+
[-1., -1., -1., 0., 0., 0., 1., 1., 1., -1., -1., -1., 0.,
|
|
749
|
+
0., 0., 1., 1., 1., -1., -1., -1., 0., 0., 0., 1., 1.,
|
|
750
|
+
1.],
|
|
751
|
+
[-1., 0., 1., -1., 0., 1., -1., 0., 1., -1., 0., 1., -1.,
|
|
752
|
+
0., 1., -1., 0., 1., -1., 0., 1., -1., 0., 1., -1., 0.,
|
|
753
|
+
1.]])
|
|
754
|
+
>>>
|
|
755
|
+
>>> grid.indices(0, 0, 0, 1, 1, 1)
|
|
756
|
+
(slice(1, 3, None), slice(1, 3, None), slice(1, 3, None))
|
|
695
757
|
"""
|
|
696
758
|
xi1, yi1, zi1 = self.index(min(x1, x2), min(y1, y2), min(z1, z2))
|
|
697
759
|
xi2, yi2, zi2 = self.index(max(x1, x2), max(y1, y2), max(z1, z2))
|
|
698
760
|
return s_[xi1 : xi2 + 1], s_[yi1 : yi2 + 1], s_[zi1 : zi2 + 1]
|
|
699
761
|
|
|
700
762
|
|
|
763
|
+
@deprecated_alias({'from_file': 'file', 'gpos_file': 'pos'})
|
|
701
764
|
class ImportGrid(Grid):
|
|
702
|
-
"""
|
|
765
|
+
"""
|
|
766
|
+
Load a 3D grid from an XML file.
|
|
703
767
|
|
|
704
|
-
|
|
705
|
-
|
|
768
|
+
This class is used to import a 3D grid defined in an XML file. The grid's
|
|
769
|
+
positions and subgrid names are parsed and stored for further processing.
|
|
770
|
+
"""
|
|
706
771
|
|
|
707
|
-
|
|
772
|
+
#: Name of the .xml-file from which to read the data.
|
|
773
|
+
file = File(filter=['*.xml'], exists=True, desc='name of the xml file to import')
|
|
708
774
|
|
|
709
|
-
|
|
710
|
-
basename = Property(depends_on='from_file', desc='basename of xml file')
|
|
775
|
+
_gpos = CArray(dtype=float, desc='x, y, z position of all Grid Points')
|
|
711
776
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
)
|
|
777
|
+
#: Names of subgrids for each point.
|
|
778
|
+
#: This is an optional property, typically used when grids are divided into named subregions.
|
|
779
|
+
subgrids = CArray(desc='names of subgrids for each point')
|
|
716
780
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
return path.splitext(path.basename(self.from_file))[0]
|
|
781
|
+
#: A unique identifier for the grid, based on its properties. (read-only)
|
|
782
|
+
digest = Property(depends_on=['_gpos'])
|
|
720
783
|
|
|
721
784
|
@cached_property
|
|
722
785
|
def _get_digest(self):
|
|
723
786
|
return digest(self)
|
|
724
787
|
|
|
725
|
-
|
|
726
|
-
# necessary to trigger the depends on mechanism
|
|
727
|
-
@property_depends_on('basename')
|
|
788
|
+
@property_depends_on(['_gpos'])
|
|
728
789
|
def _get_size(self):
|
|
729
|
-
return self.
|
|
790
|
+
return self.pos.shape[-1]
|
|
730
791
|
|
|
731
|
-
|
|
732
|
-
@property_depends_on('basename')
|
|
792
|
+
@property_depends_on(['_gpos'])
|
|
733
793
|
def _get_shape(self):
|
|
734
|
-
return (self.
|
|
794
|
+
return (self.pos.shape[-1],)
|
|
735
795
|
|
|
736
|
-
@property_depends_on('
|
|
737
|
-
def
|
|
738
|
-
return self.
|
|
796
|
+
@property_depends_on(['_gpos'])
|
|
797
|
+
def _get_pos(self):
|
|
798
|
+
return self._gpos
|
|
739
799
|
|
|
740
|
-
|
|
800
|
+
def _set_pos(self, pos):
|
|
801
|
+
self._gpos = pos
|
|
741
802
|
|
|
742
|
-
@on_trait_change('
|
|
803
|
+
@on_trait_change('file')
|
|
743
804
|
def import_gpos(self):
|
|
744
|
-
"""Import the the grid point locations from .xml file.
|
|
745
|
-
Called when :attr:`basename` changes.
|
|
746
805
|
"""
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
return
|
|
751
|
-
import xml.dom.minidom
|
|
806
|
+
Import the grid point locations and subgrid names from an XML file.
|
|
807
|
+
|
|
808
|
+
This method is automatically called whenever the :attr:`file` attribute changes.
|
|
752
809
|
|
|
753
|
-
|
|
810
|
+
Notes
|
|
811
|
+
-----
|
|
812
|
+
The XML file should have elements with tag ``<pos>``, where each ``<pos>`` element
|
|
813
|
+
contains attributes for ``x``, ``y``, ``z``, and optionally ``subgrid``.
|
|
814
|
+
|
|
815
|
+
Examples
|
|
816
|
+
--------
|
|
817
|
+
Generate the positional data of two microphone grids:
|
|
818
|
+
|
|
819
|
+
>>> import numpy as np
|
|
820
|
+
>>>
|
|
821
|
+
>>> # Grid 1: ten points aranged in a circle in the x-y plane at z=0
|
|
822
|
+
>>> args = 2 * np.pi * np.arange(10) / 10
|
|
823
|
+
>>> x1 = np.cos(args)
|
|
824
|
+
>>> y1 = np.sin(args)
|
|
825
|
+
>>> z1 = np.zeros_like(x1)
|
|
826
|
+
>>> grid1 = np.vstack([x1, y1, z1]).T
|
|
827
|
+
>>>
|
|
828
|
+
>>> # Grid 2: nine points aranged in a mesh grid the the x-y plane at z=1
|
|
829
|
+
>>> a = np.linspace(-1, 1, 3)
|
|
830
|
+
>>> x2, y2 = np.meshgrid(a, a)
|
|
831
|
+
>>> z2 = np.ones_like(x2)
|
|
832
|
+
>>> grid2 = np.vstack([x2, y2, z2])
|
|
833
|
+
|
|
834
|
+
Save the generated data in an XML file:
|
|
835
|
+
|
|
836
|
+
>>> import xml.etree.cElementTree as ET
|
|
837
|
+
>>>
|
|
838
|
+
>>> # Create the root element for the XML document
|
|
839
|
+
>>> root = ET.Element('root')
|
|
840
|
+
>>>
|
|
841
|
+
>>> # Loop over both grid 1 and grid 2, and create XML elements for each of their points
|
|
842
|
+
>>> for num, grid in enumerate([grid1, grid2]): # doctest: +SKIP
|
|
843
|
+
... for x, y, z in grid:
|
|
844
|
+
... # For each point in the grid, create a 'pos' XML element
|
|
845
|
+
... ET.SubElement(root, 'pos', subgrid=str(num), x=str(x), y=str(y), z=str(z))
|
|
846
|
+
>>>
|
|
847
|
+
>>> # Create the final XML tree
|
|
848
|
+
>>> tree = ET.ElementTree(root)
|
|
849
|
+
>>>
|
|
850
|
+
>>> # Write the XML tree to a file named 'two_grids.xml'
|
|
851
|
+
>>> tree.write('two_grids.xml') # doctest: +SKIP
|
|
852
|
+
|
|
853
|
+
The ``two_grids.xml`` file will look like this:
|
|
854
|
+
|
|
855
|
+
.. code-block:: xml
|
|
856
|
+
|
|
857
|
+
<root>
|
|
858
|
+
<pos subgrid="0" x="1.0" y="0.0" z="0.0" />
|
|
859
|
+
<pos subgrid="0" x="0.8090169943749475" y="0.5877852522924731" z="0.0" />
|
|
860
|
+
<pos subgrid="0" x="0.30901699437494745" y="0.9510565162951535" z="0.0" />
|
|
861
|
+
<pos subgrid="0" x="-0.30901699437494734" y="0.9510565162951536" z="0.0" />
|
|
862
|
+
<pos subgrid="0" x="-0.8090169943749473" y="0.5877852522924732" z="0.0" />
|
|
863
|
+
<pos subgrid="0" x="-1.0" y="1.2246467991473532e-16" z="0.0" />
|
|
864
|
+
<pos subgrid="0" x="-0.8090169943749475" y="-0.587785252292473" z="0.0" />
|
|
865
|
+
<pos subgrid="0" x="-0.30901699437494756" y="-0.9510565162951535" z="0.0" />
|
|
866
|
+
<pos subgrid="0" x="0.30901699437494723" y="-0.9510565162951536" z="0.0" />
|
|
867
|
+
<pos subgrid="0" x="0.8090169943749473" y="-0.5877852522924732" z="0.0" />
|
|
868
|
+
<pos subgrid="1" x="-1.0" y="0.0" z="1.0" />
|
|
869
|
+
<pos subgrid="1" x="-1.0" y="0.0" z="1.0" />
|
|
870
|
+
<pos subgrid="1" x="-1.0" y="0.0" z="1.0" />
|
|
871
|
+
<pos subgrid="1" x="-1.0" y="-1.0" z="-1.0" />
|
|
872
|
+
<pos subgrid="1" x="0.0" y="0.0" z="0.0" />
|
|
873
|
+
<pos subgrid="1" x="1.0" y="1.0" z="1.0" />
|
|
874
|
+
<pos subgrid="1" x="1.0" y="1.0" z="1.0" />
|
|
875
|
+
<pos subgrid="1" x="1.0" y="1.0" z="1.0" />
|
|
876
|
+
<pos subgrid="1" x="1.0" y="1.0" z="1.0" />
|
|
877
|
+
</root>
|
|
878
|
+
|
|
879
|
+
Importing the ``two_grids.xml`` file:
|
|
880
|
+
|
|
881
|
+
>>> import acoular as ac
|
|
882
|
+
>>>
|
|
883
|
+
>>> grids = ac.ImportGrid(file='two_grids.xml') # doctest: +SKIP
|
|
884
|
+
>>> grids.size # doctest: +SKIP
|
|
885
|
+
19
|
|
886
|
+
>>> grids.subgrids # doctest: +SKIP
|
|
887
|
+
array(['0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1',
|
|
888
|
+
'1', '1', '1', '1', '1', '1'], dtype='<U1')
|
|
889
|
+
>>> grids.gpos.T # doctest: +SKIP
|
|
890
|
+
array([[ 1.00000000e+00, 0.00000000e+00, 0.00000000e+00],
|
|
891
|
+
[ 8.09016994e-01, 5.87785252e-01, 0.00000000e+00],
|
|
892
|
+
[ 3.09016994e-01, 9.51056516e-01, 0.00000000e+00],
|
|
893
|
+
[-3.09016994e-01, 9.51056516e-01, 0.00000000e+00],
|
|
894
|
+
[-8.09016994e-01, 5.87785252e-01, 0.00000000e+00],
|
|
895
|
+
[-1.00000000e+00, 1.22464680e-16, 0.00000000e+00],
|
|
896
|
+
[-8.09016994e-01, -5.87785252e-01, 0.00000000e+00],
|
|
897
|
+
[-3.09016994e-01, -9.51056516e-01, 0.00000000e+00],
|
|
898
|
+
[ 3.09016994e-01, -9.51056516e-01, 0.00000000e+00],
|
|
899
|
+
[ 8.09016994e-01, -5.87785252e-01, 0.00000000e+00],
|
|
900
|
+
[-1.00000000e+00, 0.00000000e+00, 1.00000000e+00],
|
|
901
|
+
[-1.00000000e+00, 0.00000000e+00, 1.00000000e+00],
|
|
902
|
+
[-1.00000000e+00, 0.00000000e+00, 1.00000000e+00],
|
|
903
|
+
[-1.00000000e+00, -1.00000000e+00, -1.00000000e+00],
|
|
904
|
+
[ 0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
|
|
905
|
+
[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
|
|
906
|
+
[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
|
|
907
|
+
[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
|
|
908
|
+
[ 1.00000000e+00, 1.00000000e+00, 1.00000000e+00]])
|
|
909
|
+
|
|
910
|
+
Consider two XML files, ``grid1.xml`` and ``grid2.xml`` containing different grids.
|
|
911
|
+
|
|
912
|
+
>>> import acoular as ac
|
|
913
|
+
>>>
|
|
914
|
+
>>> grid = ac.ImportGrid(file='grid1.xml') # doctest: +SKIP
|
|
915
|
+
>>> grid.size # doctest: +SKIP
|
|
916
|
+
8
|
|
917
|
+
>>> grid.file = 'grid2.xml' # doctest: +SKIP
|
|
918
|
+
>>> grid.size # doctest: +SKIP
|
|
919
|
+
12
|
|
920
|
+
"""
|
|
921
|
+
doc = xml.dom.minidom.parse(self.file)
|
|
754
922
|
names = []
|
|
755
923
|
xyz = []
|
|
756
924
|
for el in doc.getElementsByTagName('pos'):
|
|
757
925
|
names.append(el.getAttribute('subgrid'))
|
|
758
926
|
xyz.append([float(el.getAttribute(a)) for a in 'xyz'])
|
|
759
|
-
self.
|
|
927
|
+
self._gpos = array(xyz, 'd').swapaxes(0, 1)
|
|
760
928
|
self.subgrids = array(names)
|
|
761
929
|
|
|
762
930
|
|
|
931
|
+
@deprecated_alias({'gpos': 'pos', 'numpoints': 'num_points'}, read_only=['gpos'])
|
|
763
932
|
class LineGrid(Grid):
|
|
764
|
-
"""
|
|
933
|
+
"""
|
|
934
|
+
Define a 3D grid for a line geometry.
|
|
935
|
+
|
|
936
|
+
The :class:`LineGrid` class represents a grid where points are arranged linearly in 3D space.
|
|
937
|
+
The grid is defined by a starting location (:attr:`loc`), a direction vector
|
|
938
|
+
(:attr:`direction`), a total length (:attr:`length`), and the number of points
|
|
939
|
+
(:attr:`num_points`) along the line.
|
|
765
940
|
|
|
766
|
-
|
|
941
|
+
Notes
|
|
942
|
+
-----
|
|
943
|
+
- The distance between points is :attr:`length` ``/ (`` :attr:`num_points` ``- 1)``.
|
|
944
|
+
- The direction vector is normalized to ensure consistency.
|
|
945
|
+
|
|
946
|
+
Examples
|
|
947
|
+
--------
|
|
948
|
+
Create a line grid with 5 points along the x-axis, starting at (0, 0, 0), with a length of 4
|
|
949
|
+
meters:
|
|
950
|
+
|
|
951
|
+
>>> import acoular as ac
|
|
952
|
+
>>> grid = ac.LineGrid()
|
|
953
|
+
>>> grid.loc = (0.0, 0.0, 0.0)
|
|
954
|
+
>>> grid.direction = (1.0, 0.0, 0.0)
|
|
955
|
+
>>> grid.length = 4
|
|
956
|
+
>>> grid.num_points = 5
|
|
957
|
+
>>> grid.pos
|
|
958
|
+
array([[0., 1., 2., 3., 4.],
|
|
959
|
+
[0., 0., 0., 0., 0.],
|
|
960
|
+
[0., 0., 0., 0., 0.]])
|
|
961
|
+
"""
|
|
962
|
+
|
|
963
|
+
#: Starting point of the grid in 3D space. Default is ``(0.0, 0.0, 0.0)``.
|
|
767
964
|
loc = Tuple((0.0, 0.0, 0.0))
|
|
768
965
|
|
|
769
|
-
#:
|
|
966
|
+
#: A vector defining the orientation of the line in 3D space. Default is ``(1.0, 0.0, 0.0)``.
|
|
770
967
|
direction = Tuple((1.0, 0.0, 0.0), desc='Line orientation ')
|
|
771
968
|
|
|
772
|
-
#:
|
|
969
|
+
#: Total length of the line. Default is ``1.0``.
|
|
773
970
|
length = Float(1, desc='length of the line source')
|
|
774
971
|
|
|
775
|
-
#:
|
|
776
|
-
|
|
972
|
+
#: Number of grid points along the line. Default is ``1``.
|
|
973
|
+
num_points = Int(1, desc='length of the line source')
|
|
777
974
|
|
|
778
|
-
#:
|
|
779
|
-
#:
|
|
975
|
+
#: The total number of grid points. Automatically updated when other grid-defining attributes
|
|
976
|
+
#: are set. (read-only)
|
|
780
977
|
size = Property(desc='overall number of grid points')
|
|
781
978
|
|
|
782
|
-
#:
|
|
783
|
-
#:
|
|
784
|
-
|
|
979
|
+
#: A (3, :attr:`size`) array containing the x, y, and z positions
|
|
980
|
+
#: of the grid points. (read-only)
|
|
981
|
+
pos = Property(desc='x, y, z positions of grid points')
|
|
785
982
|
|
|
983
|
+
#: A unique identifier for the grid, based on its properties. (read-only)
|
|
786
984
|
digest = Property(
|
|
787
|
-
depends_on=['loc', 'direction', 'length', '
|
|
985
|
+
depends_on=['loc', 'direction', 'length', 'num_points', 'size'],
|
|
788
986
|
)
|
|
789
987
|
|
|
790
988
|
@cached_property
|
|
791
989
|
def _get_digest(self):
|
|
792
990
|
return digest(self)
|
|
793
991
|
|
|
794
|
-
|
|
795
|
-
# necessary to trigger the depends on mechanism
|
|
796
|
-
@property_depends_on('numpoints')
|
|
992
|
+
@property_depends_on(['num_points'])
|
|
797
993
|
def _get_size(self):
|
|
798
|
-
return self.
|
|
994
|
+
return self.pos.shape[-1]
|
|
799
995
|
|
|
800
|
-
|
|
801
|
-
@property_depends_on('numpoints')
|
|
996
|
+
@property_depends_on(['num_points'])
|
|
802
997
|
def _get_shape(self):
|
|
803
|
-
return self.
|
|
998
|
+
return self.pos.shape[-1]
|
|
804
999
|
|
|
805
|
-
@property_depends_on('
|
|
806
|
-
def
|
|
807
|
-
dist = self.length / (self.
|
|
1000
|
+
@property_depends_on(['num_points', 'length', 'direction', 'loc'])
|
|
1001
|
+
def _get_pos(self):
|
|
1002
|
+
dist = self.length / (self.num_points - 1)
|
|
808
1003
|
loc = array(self.loc, dtype=float).reshape((3, 1))
|
|
809
1004
|
direc_n = array(self.direction) / norm(self.direction)
|
|
810
|
-
pos = zeros((self.
|
|
811
|
-
for s in range(self.
|
|
1005
|
+
pos = zeros((self.num_points, 3))
|
|
1006
|
+
for s in range(self.num_points):
|
|
812
1007
|
pos[s] = loc.T + direc_n * dist * s
|
|
813
1008
|
return pos.T
|
|
814
1009
|
|
|
815
1010
|
|
|
1011
|
+
@deprecated_alias({'gpos': 'pos'}, read_only=True)
|
|
816
1012
|
class MergeGrid(Grid):
|
|
817
|
-
"""
|
|
1013
|
+
"""
|
|
1014
|
+
Base class for merging multiple grid geometries.
|
|
1015
|
+
|
|
1016
|
+
The `MergeGrid` class allows the combination of multiple grid geometries into a single unified
|
|
1017
|
+
grid. Each input grid is assigned a subdomain in the resulting grid, and all properties, such as
|
|
1018
|
+
positions and identifiers, are appropriately merged.
|
|
818
1019
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1020
|
+
Notes
|
|
1021
|
+
-----
|
|
1022
|
+
- The merged grid eliminates duplicate points based on their positions.
|
|
1023
|
+
- Each subgrid retains its original grid properties, such as digest and size.
|
|
1024
|
+
|
|
1025
|
+
Examples
|
|
1026
|
+
--------
|
|
1027
|
+
Merging two simple grids:
|
|
1028
|
+
|
|
1029
|
+
>>> import acoular as ac
|
|
1030
|
+
>>> grid1 = ac.LineGrid(loc=(0, 0, 0), direction=(1, 0, 0), length=1, num_points=3)
|
|
1031
|
+
>>> grid2 = ac.LineGrid(loc=(0, 0, 0), direction=(0, 1, 0), length=1, num_points=3)
|
|
1032
|
+
>>> merged_grid = ac.MergeGrid()
|
|
1033
|
+
>>> merged_grid.grids = [grid1, grid2]
|
|
1034
|
+
>>> merged_grid.size
|
|
1035
|
+
5
|
|
1036
|
+
>>> merged_grid.pos
|
|
1037
|
+
array([[0. , 0. , 0. , 0.5, 1. ],
|
|
1038
|
+
[0. , 0.5, 1. , 0. , 0. ],
|
|
1039
|
+
[0. , 0. , 0. , 0. , 0. ]])
|
|
1040
|
+
"""
|
|
1041
|
+
|
|
1042
|
+
#: A list of :class:`Grid` objects to be merged. Each grid is treated as a subdomain in the
|
|
1043
|
+
#: resulting merged grid.
|
|
822
1044
|
grids = List(desc='list of grids')
|
|
823
1045
|
|
|
824
|
-
|
|
1046
|
+
#: A list of unique digests for each grid being merged. (read-only)
|
|
1047
|
+
grid_digest = Str(desc='digest of the merged grids')
|
|
825
1048
|
|
|
1049
|
+
#: Names of subgrids corresponding to each point in the merged grid. (read-only)
|
|
826
1050
|
subgrids = Property(desc='names of subgrids for each point')
|
|
827
1051
|
|
|
828
|
-
|
|
829
|
-
digest = Property(
|
|
830
|
-
depends_on=['grids', 'grid_digest'],
|
|
831
|
-
)
|
|
1052
|
+
#: A unique identifier for the grid, based on its properties. (read-only)
|
|
1053
|
+
digest = Property(depends_on=['grids', 'grid_digest'])
|
|
832
1054
|
|
|
833
1055
|
@cached_property
|
|
834
1056
|
def _get_digest(self):
|
|
835
1057
|
return digest(self)
|
|
836
1058
|
|
|
837
|
-
@
|
|
838
|
-
def
|
|
839
|
-
|
|
840
|
-
for grid in self.grids:
|
|
841
|
-
griddigest.append(grid.digest)
|
|
842
|
-
return griddigest
|
|
1059
|
+
@observe('grids.items.digest')
|
|
1060
|
+
def _set_sourcesdigest(self, event): # noqa ARG002
|
|
1061
|
+
self.grid_digest = ldigest(self.grids)
|
|
843
1062
|
|
|
844
|
-
|
|
845
|
-
# necessary to trigger the depends on mechanism
|
|
846
|
-
@property_depends_on('digest')
|
|
1063
|
+
@property_depends_on(['digest'])
|
|
847
1064
|
def _get_size(self):
|
|
848
|
-
return self.
|
|
1065
|
+
return self.pos.shape[-1]
|
|
849
1066
|
|
|
850
|
-
|
|
851
|
-
@property_depends_on('digest')
|
|
1067
|
+
@property_depends_on(['digest'])
|
|
852
1068
|
def _get_shape(self):
|
|
853
|
-
return self.
|
|
1069
|
+
return self.pos.shape[-1]
|
|
854
1070
|
|
|
855
|
-
@property_depends_on('digest')
|
|
1071
|
+
@property_depends_on(['digest'])
|
|
856
1072
|
def _get_subgrids(self):
|
|
857
1073
|
subgrids = zeros((1, 0), dtype=str)
|
|
858
1074
|
for grid in self.grids:
|
|
859
1075
|
subgrids = append(subgrids, tile(grid.__class__.__name__ + grid.digest, grid.size))
|
|
860
1076
|
return subgrids[:, newaxis].T
|
|
861
1077
|
|
|
862
|
-
@property_depends_on('digest')
|
|
863
|
-
def
|
|
1078
|
+
@property_depends_on(['digest'])
|
|
1079
|
+
def _get_pos(self):
|
|
864
1080
|
bpos = zeros((3, 0))
|
|
865
|
-
# subgrids = zeros((1,0))
|
|
866
1081
|
for grid in self.grids:
|
|
867
|
-
bpos = append(bpos, grid.
|
|
868
|
-
# subgrids = append(subgrids,str(grid))
|
|
1082
|
+
bpos = append(bpos, grid.pos, axis=1)
|
|
869
1083
|
return unique(bpos, axis=1)
|
|
870
1084
|
|
|
871
1085
|
|
|
872
|
-
class Sector(
|
|
873
|
-
"""
|
|
1086
|
+
class Sector(ABCHasStrictTraits):
|
|
1087
|
+
"""
|
|
1088
|
+
Abstract base class for all sector types.
|
|
1089
|
+
|
|
1090
|
+
The :class:`Sector` class defines the common interface for all sector implementations. It serves
|
|
1091
|
+
as the base class for creating diverse sector geometries, each capable of determining whether
|
|
1092
|
+
specific grid points fall within its bounds.
|
|
1093
|
+
|
|
1094
|
+
When used directly, this class represents a sector encompassing the entire grid, meaning all
|
|
1095
|
+
positions are considered valid.
|
|
1096
|
+
|
|
1097
|
+
Notes
|
|
1098
|
+
-----
|
|
1099
|
+
This class is designed to be subclassed. Derived classes should override the :meth:`contains`
|
|
1100
|
+
method to implement specific sector geometries (e.g., circular, rectangular, or polygon shapes).
|
|
874
1101
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1102
|
+
Examples
|
|
1103
|
+
--------
|
|
1104
|
+
Load example data and set different Sectors for integration in the
|
|
1105
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
878
1106
|
"""
|
|
879
1107
|
|
|
880
1108
|
def contains(self, pos):
|
|
881
|
-
"""
|
|
882
|
-
|
|
883
|
-
|
|
1109
|
+
"""
|
|
1110
|
+
Check whether the given coordinates lie within the sector's bounds.
|
|
1111
|
+
|
|
1112
|
+
This method determines if each column of the input array :attr:`pos` corresponds to a point
|
|
1113
|
+
that falls within the sector. For this base class, all points are considered within the
|
|
1114
|
+
sector.
|
|
884
1115
|
|
|
885
1116
|
Parameters
|
|
886
1117
|
----------
|
|
887
|
-
pos :
|
|
888
|
-
|
|
889
|
-
grid
|
|
1118
|
+
pos : :class:`numpy.ndarray`
|
|
1119
|
+
A 2D array with shape `(3, N)`, where `N` is the number of grid points. Each column
|
|
1120
|
+
represents the ``x, y, z`` coordinates of a grid point.
|
|
890
1121
|
|
|
891
1122
|
Returns
|
|
892
1123
|
-------
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1124
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
1125
|
+
A 1D array of length `N`, where each entry indicates whether the corresponding
|
|
1126
|
+
column in :attr:`pos` lies within the sector's bounds.
|
|
1127
|
+
|
|
1128
|
+
Examples
|
|
1129
|
+
--------
|
|
1130
|
+
>>> import numpy as np
|
|
1131
|
+
>>> import acoular as ac
|
|
1132
|
+
>>> sector = ac.Sector()
|
|
1133
|
+
>>> positions = np.array([[0, 1], [0, 0], [0, 0]]) # Two grid points
|
|
1134
|
+
>>> sector.contains(positions)
|
|
1135
|
+
array([ True, True])
|
|
897
1136
|
"""
|
|
898
1137
|
return ones(pos.shape[1], dtype=bool)
|
|
899
1138
|
|
|
900
1139
|
|
|
901
1140
|
class SingleSector(Sector):
|
|
902
|
-
"""
|
|
1141
|
+
"""
|
|
1142
|
+
Base class for single sector types.
|
|
1143
|
+
|
|
1144
|
+
Defines the common interface for all single sector classes. This class can serve as a base for
|
|
1145
|
+
various single sector implementations. When used directly, it defines a sector that encompasses
|
|
1146
|
+
the whole grid. It includes attributes for handling border inclusion, tolerance for sector
|
|
1147
|
+
borders, and default behavior when no points are inside the sector.
|
|
903
1148
|
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1149
|
+
Examples
|
|
1150
|
+
--------
|
|
1151
|
+
Load example data and set diffrent Sectors for intergration in the
|
|
1152
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
907
1153
|
"""
|
|
908
1154
|
|
|
909
|
-
#:
|
|
1155
|
+
#: If ``True``, grid points lying on the sector border are included in the sector. Default is
|
|
1156
|
+
#: ``True``.
|
|
910
1157
|
include_border = Bool(True, desc='include points on the border')
|
|
911
1158
|
|
|
912
|
-
#:
|
|
1159
|
+
#: The absolute tolerance to apply when determining if a grid point lies on the sector border.
|
|
1160
|
+
#: Default is ``1e-12``.
|
|
913
1161
|
abs_tol = Float(1e-12, desc='absolute tolerance for sector border')
|
|
914
1162
|
|
|
915
|
-
#:
|
|
916
|
-
|
|
1163
|
+
#: If ``True``, the ``contains`` method (as in :meth:`RectSector.contains`,
|
|
1164
|
+
#: :meth:`RectSector3D.contains`, :meth:`CircSector.contains`, and :meth:`PolySector.contains`)
|
|
1165
|
+
#: returns the nearest grid point if no grid points are inside the sector. Default is ``True``.
|
|
1166
|
+
default_nearest = Bool(True, desc='``contains`` method return nearest grid point to center if none inside sector')
|
|
917
1167
|
|
|
918
1168
|
|
|
919
1169
|
class RectSector(SingleSector):
|
|
920
|
-
"""
|
|
1170
|
+
"""
|
|
1171
|
+
Class for defining a rectangular sector.
|
|
1172
|
+
|
|
1173
|
+
Defines a rectangular sector either for 2D grids (rectangle in the XY-plane) or for 3D grids
|
|
1174
|
+
(rectangular cylindrical sector parallel to the z-axis). The sector is bounded by the
|
|
1175
|
+
specified :attr:`x_min`, :attr:`x_max`, :attr:`y_min`, and :attr:`y_max` positions, defining the
|
|
1176
|
+
lower and upper bounds of the rectangle along the x and y axes.
|
|
921
1177
|
|
|
922
|
-
|
|
923
|
-
|
|
1178
|
+
Examples
|
|
1179
|
+
--------
|
|
1180
|
+
Load example data and set diffrent Sectors for intergration in the
|
|
1181
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
924
1182
|
"""
|
|
925
1183
|
|
|
926
|
-
#: The
|
|
1184
|
+
#: The minimum x position of the rectangle. Default is ``-1.0``.
|
|
927
1185
|
x_min = Float(-1.0, desc='minimum x position of the rectangle')
|
|
928
1186
|
|
|
929
|
-
#: The
|
|
1187
|
+
#: The maximum x position of the rectangle. Default is ``1.0``.
|
|
930
1188
|
x_max = Float(1.0, desc='maximum x position of the rectangle')
|
|
931
1189
|
|
|
932
|
-
#: The
|
|
1190
|
+
#: The minimum y position of the rectangle. Default is ``-1.0``.
|
|
933
1191
|
y_min = Float(-1.0, desc='minimum y position of the rectangle')
|
|
934
1192
|
|
|
935
|
-
#: The
|
|
1193
|
+
#: The maximum y position of the rectangle. Default is ``1.0``.
|
|
936
1194
|
y_max = Float(1.0, desc='maximum y position of the rectangle')
|
|
937
1195
|
|
|
938
1196
|
def contains(self, pos):
|
|
939
|
-
"""
|
|
940
|
-
rectangular sector.
|
|
941
|
-
|
|
942
|
-
is
|
|
1197
|
+
"""
|
|
1198
|
+
Check if the coordinates in a given array lie within the rectangular sector.
|
|
1199
|
+
|
|
1200
|
+
If no coordinate is inside, the nearest one to the rectangle center is returned if
|
|
1201
|
+
:attr:`~SingleSector.default_nearest` is ``True``.
|
|
943
1202
|
|
|
944
1203
|
Parameters
|
|
945
1204
|
----------
|
|
946
|
-
pos : array of floats
|
|
947
|
-
|
|
948
|
-
grid positions
|
|
1205
|
+
pos : array of :class:`floats<float>`
|
|
1206
|
+
A `(3, N)` array containing the positions of `N` grid points.
|
|
949
1207
|
|
|
950
1208
|
Returns
|
|
951
1209
|
-------
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
given sector
|
|
955
|
-
|
|
1210
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
1211
|
+
An array of shape (N,) indicating which of the given positions
|
|
1212
|
+
lie within the given sector.
|
|
1213
|
+
|
|
1214
|
+
Examples
|
|
1215
|
+
--------
|
|
1216
|
+
>>> import acoular as ac
|
|
1217
|
+
>>> grid = ac.RectGrid(increment=2)
|
|
1218
|
+
>>> sec = ac.RectSector(x_min=0, y_min=0)
|
|
1219
|
+
>>> sec.contains(grid.pos)
|
|
1220
|
+
array([False, False, False, True])
|
|
956
1221
|
"""
|
|
957
1222
|
# make sure xmin is minimum etc
|
|
958
1223
|
xmin = min(self.x_min, self.x_max)
|
|
@@ -988,35 +1253,53 @@ class RectSector(SingleSector):
|
|
|
988
1253
|
|
|
989
1254
|
|
|
990
1255
|
class RectSector3D(RectSector):
|
|
991
|
-
"""
|
|
1256
|
+
"""
|
|
1257
|
+
Class for defining a cuboid sector.
|
|
1258
|
+
|
|
1259
|
+
This class extends the :class:`RectSector` class to define a cuboid sector, which can be used
|
|
1260
|
+
for 3D grids. The cuboid sector is defined by its bounds along the x, y, and z axes.
|
|
992
1261
|
|
|
993
|
-
|
|
1262
|
+
Examples
|
|
1263
|
+
--------
|
|
1264
|
+
Load example data and set diffrent Sectors for intergration in the
|
|
1265
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
994
1266
|
"""
|
|
995
1267
|
|
|
996
|
-
#: The lower z position of the cuboid
|
|
1268
|
+
#: The lower z position of the cuboid. Default is ``-1.0``.
|
|
997
1269
|
z_min = Float(-1.0, desc='minimum z position of the cuboid')
|
|
998
1270
|
|
|
999
|
-
#: The upper z position of the cuboid
|
|
1271
|
+
#: The upper z position of the cuboid. Default is ``1.0``.
|
|
1000
1272
|
z_max = Float(1.0, desc='maximum z position of the cuboid')
|
|
1001
1273
|
|
|
1002
1274
|
def contains(self, pos):
|
|
1003
|
-
"""
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1275
|
+
"""
|
|
1276
|
+
Check if the coordinates in a given array lie within the cuboid sector.
|
|
1277
|
+
|
|
1278
|
+
The method checks if the points in the provided position array are within the cuboid
|
|
1279
|
+
defined by the bounds along the x, y, and z axes. If no point is inside the sector, and if
|
|
1280
|
+
:attr:`~SingleSector.default_nearest` is ``True``, the nearest point to the center of the
|
|
1281
|
+
cuboid is returned.
|
|
1007
1282
|
|
|
1008
1283
|
Parameters
|
|
1009
1284
|
----------
|
|
1010
|
-
pos : array of floats
|
|
1011
|
-
|
|
1012
|
-
|
|
1285
|
+
pos : array of :class:`floats<float>`
|
|
1286
|
+
A (3, N) array containing the positions of N grid points, where each point is
|
|
1287
|
+
represented by its x, y, and z coordinates.
|
|
1013
1288
|
|
|
1014
1289
|
Returns
|
|
1015
1290
|
-------
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1291
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
1292
|
+
A boolean array of shape shape ``(N,)`` indicating which of the given positions lie
|
|
1293
|
+
within the cuboid sector. ``True`` if the grid point is inside the cuboid,
|
|
1294
|
+
otherwise ``False``.
|
|
1295
|
+
|
|
1296
|
+
Examples
|
|
1297
|
+
--------
|
|
1298
|
+
>>> import acoular as ac
|
|
1299
|
+
>>> grid = ac.RectGrid3D(increment=2)
|
|
1300
|
+
>>> sec = ac.RectSector3D(x_min=0, y_min=0, z_min=0)
|
|
1301
|
+
>>> sec.contains(grid.pos)
|
|
1302
|
+
array([False, False, False, False, False, False, False, True])
|
|
1020
1303
|
"""
|
|
1021
1304
|
# make sure xmin is minimum etc
|
|
1022
1305
|
xmin = min(self.x_min, self.x_max)
|
|
@@ -1058,45 +1341,67 @@ class RectSector3D(RectSector):
|
|
|
1058
1341
|
|
|
1059
1342
|
|
|
1060
1343
|
class CircSector(SingleSector):
|
|
1061
|
-
"""
|
|
1344
|
+
"""
|
|
1345
|
+
Class for defining a circular sector.
|
|
1346
|
+
|
|
1347
|
+
Defines a circular sector, which can be used for both 2D grids (as a circle in the XY-plane) or
|
|
1348
|
+
for 3D grids (as a cylindrical sector parallel to the z-axis). The sector is defined by its
|
|
1349
|
+
center position (:attr:`x`, :attr:`y`) and its radius :attr:`r`.
|
|
1062
1350
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1351
|
+
Examples
|
|
1352
|
+
--------
|
|
1353
|
+
Load example data and set diffrent Sectors for intergration in the
|
|
1354
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
1065
1355
|
"""
|
|
1066
1356
|
|
|
1067
|
-
#: x position of the circle center
|
|
1357
|
+
#: The x position of the circle center. Default is ``0.0``.
|
|
1068
1358
|
x = Float(0.0, desc='x position of the circle center')
|
|
1069
1359
|
|
|
1070
|
-
#: y position of the circle center
|
|
1360
|
+
#: The y position of the circle center. Default is ``0.0``.
|
|
1071
1361
|
y = Float(0.0, desc='y position of the circle center')
|
|
1072
1362
|
|
|
1073
|
-
#:
|
|
1363
|
+
#: Radius of the circle. Default is ``1.0``.
|
|
1074
1364
|
r = Float(1.0, desc='radius of the circle')
|
|
1075
1365
|
|
|
1076
1366
|
def contains(self, pos):
|
|
1077
|
-
"""
|
|
1078
|
-
circular sector.
|
|
1079
|
-
|
|
1080
|
-
|
|
1367
|
+
"""
|
|
1368
|
+
Check if the coordinates in a given array lie within the circular sector.
|
|
1369
|
+
|
|
1370
|
+
The method calculates the squared distance of each point from the center of the circle and
|
|
1371
|
+
checks if it lies within the sector, considering the sector's radius :attr:`r`. If no point
|
|
1372
|
+
is inside and :attr:`~SingleSector.default_nearest` is ``True``, the nearest point outside
|
|
1373
|
+
the sector will be returned.
|
|
1081
1374
|
|
|
1082
1375
|
Parameters
|
|
1083
1376
|
----------
|
|
1084
|
-
pos : array of floats
|
|
1085
|
-
|
|
1086
|
-
|
|
1377
|
+
pos : array of :class:`floats<float>`
|
|
1378
|
+
A (3, N) array containing the positions of N grid points, where each point is
|
|
1379
|
+
represented by its x, y, and z coordinates.
|
|
1087
1380
|
|
|
1088
1381
|
Returns
|
|
1089
1382
|
-------
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1383
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
1384
|
+
A boolean array of shape shape (N,) indicating which of the given positions lie within
|
|
1385
|
+
the circular sector. ``True`` if the grid point is inside the circular sector,
|
|
1386
|
+
otherwise ``False``.
|
|
1387
|
+
|
|
1388
|
+
Examples
|
|
1389
|
+
--------
|
|
1390
|
+
>>> import acoular as ac
|
|
1391
|
+
>>> grid = ac.RectGrid(increment=1)
|
|
1392
|
+
>>> grid.pos
|
|
1393
|
+
array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
|
|
1394
|
+
[-1., 0., 1., -1., 0., 1., -1., 0., 1.],
|
|
1395
|
+
[ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
|
|
1396
|
+
>>> sec = ac.CircSector(x=1, y=1, r=0.5)
|
|
1397
|
+
>>> sec.contains(grid.pos)
|
|
1398
|
+
array([False, False, False, False, False, False, False, False, True])
|
|
1094
1399
|
"""
|
|
1095
1400
|
dr2 = (pos[0, :] - self.x) ** 2 + (pos[1, :] - self.y) ** 2
|
|
1096
1401
|
# which points are in the circle?
|
|
1097
1402
|
inds = dr2 - self.r**2 < self.abs_tol if self.include_border else dr2 - self.r**2 < -self.abs_tol
|
|
1098
1403
|
|
|
1099
|
-
# if there's no
|
|
1404
|
+
# if there's no point inside
|
|
1100
1405
|
if ~inds.any() and self.default_nearest:
|
|
1101
1406
|
inds[argmin(dr2)] = True
|
|
1102
1407
|
|
|
@@ -1104,32 +1409,56 @@ class CircSector(SingleSector):
|
|
|
1104
1409
|
|
|
1105
1410
|
|
|
1106
1411
|
class PolySector(SingleSector):
|
|
1107
|
-
"""
|
|
1412
|
+
"""
|
|
1413
|
+
Class for defining a polygon sector.
|
|
1414
|
+
|
|
1415
|
+
Inherits from :class:`SingleSector` and provides functionality to define a polygonal sector on a
|
|
1416
|
+
2D grid.
|
|
1417
|
+
|
|
1418
|
+
Notes
|
|
1419
|
+
-----
|
|
1420
|
+
The polygon is specified by the :class:`Polygon` class.
|
|
1108
1421
|
|
|
1109
|
-
|
|
1422
|
+
Examples
|
|
1423
|
+
--------
|
|
1424
|
+
Load example data and set diffrent Sectors for intergration in the
|
|
1425
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
1110
1426
|
"""
|
|
1111
1427
|
|
|
1112
|
-
|
|
1428
|
+
#: List of coordinates representing the polygon's vertices. The coordinates must define a closed
|
|
1429
|
+
#: polygon like ``x1, y1, x2, y2, ... xn, yn``.
|
|
1113
1430
|
edges = List(Float)
|
|
1114
1431
|
|
|
1115
1432
|
def contains(self, pos):
|
|
1116
|
-
"""
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
is
|
|
1433
|
+
"""
|
|
1434
|
+
Check if the coordinates in a given array lie within the polygon sector.
|
|
1435
|
+
|
|
1436
|
+
If no coordinate is inside, the nearest one to the rectangle center is returned if
|
|
1437
|
+
:attr:`~SingleSector.default_nearest` is ``True``.
|
|
1120
1438
|
|
|
1121
1439
|
Parameters
|
|
1122
1440
|
----------
|
|
1123
|
-
pos : array of floats
|
|
1124
|
-
|
|
1125
|
-
|
|
1441
|
+
pos : array of :class:`floats<float>`
|
|
1442
|
+
A (3, N) array containing the positions of N grid points where each point is represented
|
|
1443
|
+
by its x, y, and z coordinates.
|
|
1126
1444
|
|
|
1127
1445
|
Returns
|
|
1128
1446
|
-------
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1447
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
1448
|
+
A boolean array of shape `(N,)` indicating which of the given positions lie within the
|
|
1449
|
+
polygon sector. ``True`` if the grid point is inside the polygon, otherwise ``False``.
|
|
1450
|
+
|
|
1451
|
+
Examples
|
|
1452
|
+
--------
|
|
1453
|
+
>>> import acoular as ac
|
|
1454
|
+
>>> grid = ac.RectGrid(increment=1)
|
|
1455
|
+
>>> grid.pos
|
|
1456
|
+
array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
|
|
1457
|
+
[-1., 0., 1., -1., 0., 1., -1., 0., 1.],
|
|
1458
|
+
[ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
|
|
1459
|
+
>>> sec = ac.PolySector(edges=[0, 0, 1, 0, 1, 1, 0, 1])
|
|
1460
|
+
>>> sec.contains(grid.pos)
|
|
1461
|
+
array([False, False, False, False, True, True, False, True, True])
|
|
1133
1462
|
"""
|
|
1134
1463
|
poly = Polygon(array(self.edges).reshape(-1, 2)[:, 0], array(self.edges).reshape(-1, 2)[:, 1])
|
|
1135
1464
|
dists = poly.is_inside(pos[0, :], pos[1, :])
|
|
@@ -1144,71 +1473,111 @@ class PolySector(SingleSector):
|
|
|
1144
1473
|
|
|
1145
1474
|
|
|
1146
1475
|
class ConvexSector(SingleSector):
|
|
1147
|
-
"""
|
|
1476
|
+
"""
|
|
1477
|
+
Class for defining a convex hull sector.
|
|
1148
1478
|
|
|
1149
|
-
|
|
1479
|
+
This class defines a convex hull sector for 2D grids. The sector is created using a list of edge
|
|
1480
|
+
coordinates :attr:`edges` which represent the vertices of a polygon. The convex hull is the
|
|
1481
|
+
smallest convex shape that contains all the given vertices.
|
|
1482
|
+
|
|
1483
|
+
Examples
|
|
1484
|
+
--------
|
|
1485
|
+
Load example data and set diffrent Sectors for intergration in the
|
|
1486
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
1150
1487
|
"""
|
|
1151
1488
|
|
|
1152
|
-
|
|
1489
|
+
#: List of edge coordinates that define the convex hull. The coordinates must define a closed
|
|
1490
|
+
#: polygon that forms the convex hull like `x1, y1, x2, y2, ... xn, yn`.
|
|
1153
1491
|
edges = List(Float)
|
|
1154
1492
|
|
|
1155
1493
|
def contains(self, pos):
|
|
1156
|
-
"""
|
|
1157
|
-
convex sector.
|
|
1158
|
-
|
|
1159
|
-
is
|
|
1494
|
+
"""
|
|
1495
|
+
Check if the coordinates in a given array lie within the convex sector.
|
|
1496
|
+
|
|
1497
|
+
If no coordinate is inside, the nearest one to the rectangle center is returned if
|
|
1498
|
+
:attr:`~SingleSector.default_nearest` is ``True``.
|
|
1160
1499
|
|
|
1161
1500
|
Parameters
|
|
1162
1501
|
----------
|
|
1163
|
-
pos : array of floats
|
|
1164
|
-
Array
|
|
1165
|
-
grid positions
|
|
1502
|
+
pos : array of :class:`floats<float>`
|
|
1503
|
+
Array containing the positions of N grid points, shape `(3, N)`.
|
|
1166
1504
|
|
|
1167
1505
|
Returns
|
|
1168
1506
|
-------
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
given sector
|
|
1172
|
-
|
|
1507
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
1508
|
+
An array of shape `(N,)` indicating which of the given positions
|
|
1509
|
+
lie within the given sector.
|
|
1510
|
+
|
|
1511
|
+
Examples
|
|
1512
|
+
--------
|
|
1513
|
+
>>> import acoular as ac
|
|
1514
|
+
>>> grid = ac.RectGrid(increment=1)
|
|
1515
|
+
>>> grid.pos
|
|
1516
|
+
array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
|
|
1517
|
+
[-1., 0., 1., -1., 0., 1., -1., 0., 1.],
|
|
1518
|
+
[ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
|
|
1519
|
+
>>> sec = ac.ConvexSector(edges=[0, 0, 1, 0, 1, 1, 0, 1])
|
|
1520
|
+
>>> sec.contains(grid.pos)
|
|
1521
|
+
array([False, False, False, False, True, True, False, True, True])
|
|
1173
1522
|
"""
|
|
1174
1523
|
inds = in_hull(pos[:2, :].T, array(self.edges).reshape(-1, 2), border=self.include_border, tol=self.abs_tol)
|
|
1175
1524
|
|
|
1176
1525
|
# if none inside, take nearest
|
|
1177
1526
|
if ~inds.any() and self.default_nearest:
|
|
1178
|
-
dr2 = array(self.edges).reshape(-1, 2).mean(0)
|
|
1527
|
+
dr2 = array(self.edges).reshape(-1, 2).mean(0) # Use the centroid of the polygon as the "center"
|
|
1179
1528
|
inds[argmin(dr2)] = True
|
|
1180
1529
|
|
|
1181
1530
|
return inds
|
|
1182
1531
|
|
|
1183
1532
|
|
|
1184
1533
|
class MultiSector(Sector):
|
|
1185
|
-
"""
|
|
1534
|
+
"""
|
|
1535
|
+
Class for defining a sector consisting of multiple sectors.
|
|
1186
1536
|
|
|
1187
|
-
|
|
1188
|
-
and returns the points contained in each sector.
|
|
1537
|
+
This class allows the combination of several individual sectors into one.
|
|
1189
1538
|
|
|
1539
|
+
Examples
|
|
1540
|
+
--------
|
|
1541
|
+
Load example data and set diffrent Sectors for intergration in the
|
|
1542
|
+
:ref:`sector integration example<sector_integration_example>`.
|
|
1190
1543
|
"""
|
|
1191
1544
|
|
|
1192
|
-
#: List of :class:`
|
|
1193
|
-
#: to be mixed.
|
|
1545
|
+
#: List of :class:`Sector` objects to be mixed, each defining a different sector.
|
|
1194
1546
|
sectors = List(Instance(Sector))
|
|
1195
1547
|
|
|
1196
1548
|
def contains(self, pos):
|
|
1197
|
-
"""
|
|
1198
|
-
of the sub-sectors.
|
|
1549
|
+
"""
|
|
1550
|
+
Check if the coordinates in a given array lie within any of the sub-sectors.
|
|
1551
|
+
|
|
1552
|
+
This method iterates over the list of sectors, checking if each point in the given position
|
|
1553
|
+
array lies within any of the defined sectors.
|
|
1199
1554
|
|
|
1200
1555
|
Parameters
|
|
1201
1556
|
----------
|
|
1202
|
-
pos : array of floats
|
|
1203
|
-
|
|
1204
|
-
|
|
1557
|
+
pos : array of :class:`floats<float>`
|
|
1558
|
+
A (3, N) array containing the positions of N grid points, where each point is
|
|
1559
|
+
represented by its x, y, and z coordinates.
|
|
1205
1560
|
|
|
1206
1561
|
Returns
|
|
1207
1562
|
-------
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
sectors
|
|
1211
|
-
|
|
1563
|
+
:class:`numpy.ndarray` of :class:`bools<bool>`
|
|
1564
|
+
A boolean array of shape `(N,)` indicating which of the given positions lie within any
|
|
1565
|
+
of the defined sectors. ``True`` if the grid point is inside the circular sector,
|
|
1566
|
+
``False`` if otherwise.
|
|
1567
|
+
|
|
1568
|
+
Examples
|
|
1569
|
+
--------
|
|
1570
|
+
>>> import acoular as ac
|
|
1571
|
+
>>> grid = ac.RectGrid(increment=1)
|
|
1572
|
+
>>> grid.pos
|
|
1573
|
+
array([[-1., -1., -1., 0., 0., 0., 1., 1., 1.],
|
|
1574
|
+
[-1., 0., 1., -1., 0., 1., -1., 0., 1.],
|
|
1575
|
+
[ 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
|
|
1576
|
+
>>> sec1 = ac.RectSector(x_min=0, y_min=0)
|
|
1577
|
+
>>> sec2 = ac.CircSector(x=1, y=1, r=0.5)
|
|
1578
|
+
>>> multi_sec = ac.MultiSector(sectors=[sec1, sec2])
|
|
1579
|
+
>>> multi_sec.contains(grid.pos)
|
|
1580
|
+
array([False, False, False, False, True, True, False, True, True])
|
|
1212
1581
|
"""
|
|
1213
1582
|
# initialize with only "False" entries
|
|
1214
1583
|
inds = zeros(pos.shape[1], dtype=bool)
|