acoular 23.6__py3-none-any.whl → 24.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.
Files changed (64) hide show
  1. acoular/__init__.py +2 -2
  2. acoular/configuration.py +37 -1
  3. acoular/environments.py +15 -9
  4. acoular/fastFuncs.py +199 -472
  5. acoular/fbeamform.py +168 -109
  6. acoular/grids.py +33 -114
  7. acoular/sources.py +77 -3
  8. acoular/spectra.py +2 -2
  9. acoular/tbeamform.py +15 -8
  10. acoular/tests/reference_data/BeamformerBaseFalse1.npy +0 -0
  11. acoular/tests/reference_data/BeamformerBaseFalse2.npy +0 -0
  12. acoular/tests/reference_data/BeamformerBaseFalse3.npy +0 -0
  13. acoular/tests/reference_data/BeamformerBaseFalse4.npy +0 -0
  14. acoular/tests/reference_data/BeamformerBaseTrue1.npy +0 -0
  15. acoular/tests/reference_data/BeamformerBaseTrue2.npy +0 -0
  16. acoular/tests/reference_data/BeamformerBaseTrue3.npy +0 -0
  17. acoular/tests/reference_data/BeamformerBaseTrue4.npy +0 -0
  18. acoular/tests/reference_data/BeamformerCMFLassoLarsBIC.npy +0 -0
  19. acoular/tests/reference_data/BeamformerCMFNNLS.npy +0 -0
  20. acoular/tests/reference_data/BeamformerCleantSqTraj.npy +0 -0
  21. acoular/tests/reference_data/BeamformerCleantTraj.npy +0 -0
  22. acoular/tests/reference_data/BeamformerEigFalse1.npy +0 -0
  23. acoular/tests/reference_data/BeamformerEigFalse2.npy +0 -0
  24. acoular/tests/reference_data/BeamformerEigFalse3.npy +0 -0
  25. acoular/tests/reference_data/BeamformerEigFalse4.npy +0 -0
  26. acoular/tests/reference_data/BeamformerEigTrue1.npy +0 -0
  27. acoular/tests/reference_data/BeamformerEigTrue2.npy +0 -0
  28. acoular/tests/reference_data/BeamformerEigTrue3.npy +0 -0
  29. acoular/tests/reference_data/BeamformerEigTrue4.npy +0 -0
  30. acoular/tests/reference_data/BeamformerGIB.npy +0 -0
  31. acoular/tests/reference_data/BeamformerSODIX.npy +0 -0
  32. acoular/tests/reference_data/FiltFiltOctave__.npy +0 -0
  33. acoular/tests/reference_data/FiltFiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
  34. acoular/tests/reference_data/FiltFreqWeight_weight_A_.npy +0 -0
  35. acoular/tests/reference_data/FiltFreqWeight_weight_C_.npy +0 -0
  36. acoular/tests/reference_data/FiltFreqWeight_weight_Z_.npy +0 -0
  37. acoular/tests/reference_data/FiltOctave__.npy +0 -0
  38. acoular/tests/reference_data/FiltOctave_band_100_0_fraction_Thirdoctave_.npy +0 -0
  39. acoular/tests/reference_data/Filter__.npy +0 -0
  40. acoular/tests/reference_data/OctaveFilterBank__.npy +0 -0
  41. acoular/tests/reference_data/TimeAverage__.npy +0 -0
  42. acoular/tests/reference_data/TimeCumAverage__.npy +0 -0
  43. acoular/tests/reference_data/TimeExpAverage_weight_F_.npy +0 -0
  44. acoular/tests/reference_data/TimeExpAverage_weight_I_.npy +0 -0
  45. acoular/tests/reference_data/TimeExpAverage_weight_S_.npy +0 -0
  46. acoular/tests/reference_data/TimeInOut__.npy +0 -0
  47. acoular/tests/reference_data/TimePower__.npy +0 -0
  48. acoular/tests/reference_data/TimeReverse__.npy +0 -0
  49. acoular/tests/test_beamformer_results.py +39 -8
  50. acoular/tests/test_grid.py +92 -0
  51. acoular/tests/test_integrate.py +102 -0
  52. acoular/tests/test_tprocess.py +52 -0
  53. acoular/tests/test_traj_beamformer_results.py +2 -2
  54. acoular/tfastfuncs.py +24 -25
  55. acoular/tools.py +144 -2
  56. acoular/tprocess.py +91 -102
  57. acoular/version.py +2 -2
  58. acoular-24.3.dist-info/METADATA +181 -0
  59. {acoular-23.6.dist-info → acoular-24.3.dist-info}/RECORD +62 -25
  60. {acoular-23.6.dist-info → acoular-24.3.dist-info}/WHEEL +1 -1
  61. {acoular-23.6.dist-info → acoular-24.3.dist-info}/licenses/LICENSE +1 -1
  62. acoular/tests/reference_data/BeamformerCMF.npy +0 -0
  63. acoular-23.6.dist-info/METADATA +0 -82
  64. {acoular-23.6.dist-info → acoular-24.3.dist-info}/licenses/AUTHORS.rst +0 -0
@@ -1,4 +1,6 @@
1
1
  import unittest
2
+ from os.path import join
3
+ import re
2
4
  import numpy as np
3
5
  from acoular import (
4
6
  config,
@@ -6,11 +8,46 @@ from acoular import (
6
8
  WNoiseGenerator,
7
9
  PointSource,
8
10
  MicGeom,
11
+ MaskedTimeSamples,
9
12
  tools
10
13
  )
14
+ from acoular.tprocess import *
15
+
16
+ WRITE_NEW_REFERENCE_DATA = False
11
17
 
12
18
  config.global_caching = "none"
19
+ datafile = join('..','..','examples','example_data.h5')
20
+ t1 = MaskedTimeSamples(name=datafile)
21
+ t1.start = 0 # first sample, default
22
+ t1.stop = 500 # last valid sample = 15999
23
+ invalid = list(range(4,64)) # list of invalid channels
24
+ t1.invalid_channels = invalid # use four channels
25
+
26
+ # these are tested
27
+ test_list = (
28
+ "TimeInOut()",
29
+ "TimePower()",
30
+ "TimeAverage()",
31
+ "TimeCumAverage()",
32
+ "TimeReverse()",
33
+ "Filter()",
34
+ "FiltFiltOctave(band = 100.0, fraction = 'Third octave')",
35
+ "FiltFiltOctave()",
36
+ "FiltOctave(band = 100.0, fraction = 'Third octave')",
37
+ "FiltOctave()",
38
+ "TimeExpAverage(weight = 'F')",
39
+ "TimeExpAverage(weight = 'S')",
40
+ "TimeExpAverage(weight = 'I')",
41
+ "FiltFreqWeight(weight = 'A')",
42
+ "FiltFreqWeight(weight = 'C')",
43
+ "FiltFreqWeight(weight = 'Z')",
44
+ "OctaveFilterBank()"
45
+ )
13
46
 
47
+ def fname(s):
48
+ """converts string to file name removing unsafe characters"""
49
+ s1 = re.sub(r'[,.()=]', '_', s)
50
+ return re.sub(r'[/\\:*?"<>|\' ]', '', s1)
14
51
 
15
52
  class TprocessTest(unittest.TestCase):
16
53
  """
@@ -34,5 +71,20 @@ class TprocessTest(unittest.TestCase):
34
71
  REF = np.convolve(np.squeeze(KERNEL), np.squeeze(SIG[:,i]))
35
72
  np.testing.assert_allclose(np.squeeze(RES[:,i]), REF, rtol=1e-5, atol=1e-8)
36
73
 
74
+ #@unittest.skip
75
+ def test_tprocess_results(self):
76
+ """compare results with reference results"""
77
+ for s in test_list:
78
+ b = eval(s)
79
+ with self.subTest(s):
80
+ name = join('reference_data',f'{fname(s)}.npy')
81
+ b.source = t1
82
+ # compute with block size 64 and add some extra
83
+ actual_data = tools.return_result(b, nmax=70, num=64)
84
+ if WRITE_NEW_REFERENCE_DATA:
85
+ np.save(name,actual_data)
86
+ ref_data = np.load(name)
87
+ np.testing.assert_allclose(actual_data, ref_data, rtol=1e-5, atol=1e-8)
88
+
37
89
  if __name__ == "__main__":
38
90
  unittest.main()
@@ -145,7 +145,7 @@ class BeamformerTimeTest(unittest.TestCase):
145
145
  if WRITE_NEW_REFERENCE_DATA:
146
146
  np.save(name,actual_data)
147
147
  ref_data = np.load(name)
148
- np.testing.assert_allclose(actual_data, ref_data, rtol=1e-5, atol=1e-8)
148
+ np.testing.assert_allclose(actual_data, ref_data, rtol=5e-5, atol=5e-6)
149
149
 
150
150
  def test_beamformer_time_result(self):
151
151
  """compare results of time beamformers with fixed focus against previous
@@ -157,7 +157,7 @@ class BeamformerTimeTest(unittest.TestCase):
157
157
  if WRITE_NEW_REFERENCE_DATA:
158
158
  np.save(name,actual_data)
159
159
  ref_data = np.load(name)
160
- np.testing.assert_allclose(actual_data, ref_data, rtol=1e-5, atol=1e-8)
160
+ np.testing.assert_allclose(actual_data, ref_data, rtol=5e-5, atol=5e-6)
161
161
 
162
162
 
163
163
  if __name__ == '__main__':
acoular/tfastfuncs.py CHANGED
@@ -13,14 +13,11 @@ import numpy as np
13
13
  cachedOption = True # if True: saves the numba func as compiled func in sub directory
14
14
  fastOption = True # fastmath options
15
15
 
16
- @nb.njit([(nb.float64[:,:], nb.int64[:,:], nb.float64[:,:], nb.float64[:,:], nb.float64[:,:], nb.float64[:,:])],
16
+ @nb.njit([(nb.float64[:,::1], nb.int64[:,::1], nb.float64[:,::1], nb.float64[:,::1], nb.float64[:,::1], nb.float64[:,::1])],
17
17
  cache=True, parallel=True, fastmath=True)
18
18
  def _delayandsum4(data, offsets, ifactor2, steeramp, out, autopower):
19
19
  """ Performs one time step of delay and sum with output and additional autopower removal
20
-
21
- **Note**: parallel could be set to true, but unless the number of gridpoints gets huge, it
22
- will be _slower_ in parallel mode
23
-
20
+
24
21
  Parameters
25
22
  ----------
26
23
  data : float64[nSamples, nMics]
@@ -38,26 +35,25 @@ def _delayandsum4(data, offsets, ifactor2, steeramp, out, autopower):
38
35
  """
39
36
  gridsize, numchannels = offsets.shape
40
37
  num = out.shape[0]
38
+ ZERO = data.dtype.type(0.)
39
+ ONE = data.dtype.type(1.)
41
40
  for n in nb.prange(num):
42
41
  for gi in nb.prange(gridsize):
43
- out[n,gi] = 0
44
- autopower[n,gi] = 0
42
+ out[n,gi] = ZERO
43
+ autopower[n,gi] = ZERO
45
44
  for mi in range(numchannels):
46
- ind = offsets[gi,mi]
47
- r = (data[ind+n,mi] * (1-ifactor2[gi,mi]) \
48
- + data[ind+n+1,mi] * ifactor2[gi,mi]) * steeramp[gi,mi]
45
+ ind = (gi,mi)
46
+ r = (data[offsets[ind]+n,mi] * (1.-ifactor2[ind]) \
47
+ + data[offsets[ind]+n+1,mi] * ifactor2[ind]) * steeramp[ind]
49
48
  out[n,gi] += r
50
49
  autopower[n,gi] += r*r
51
50
 
52
- @nb.njit([(nb.float32[:,:], nb.int32[:,:,:], nb.float32[:,:,:], nb.float32[:,:,:], nb.float32[:,:], nb.float32[:,:]),
53
- (nb.float64[:,:], nb.int64[:,:,:], nb.float64[:,:,:], nb.float64[:,:,:], nb.float64[:,:], nb.float64[:,:])],
51
+ @nb.njit([(nb.float32[:,::1], nb.int32[:,:,::1], nb.float32[:,:,::1], nb.float32[:,:,::1], nb.float32[:,::1], nb.float32[:,::1]),
52
+ (nb.float64[:,::1], nb.int64[:,:,::1], nb.float64[:,:,::1], nb.float64[:,:,::1], nb.float64[:,::1], nb.float64[:,::1])],
54
53
  cache=True, parallel=True, fastmath=True)
55
54
  def _delayandsum5(data, offsets, ifactor2, steeramp, out, autopower):
56
55
  """ Performs one time step of delay and sum with output and additional autopower removal
57
-
58
- **Note**: parallel could be set to true, but unless the number of gridpoints gets huge, it
59
- will be _slower_ in parallel mode
60
-
56
+
61
57
  Parameters
62
58
  ----------
63
59
  data : float64[nSamples, nMics]
@@ -75,13 +71,15 @@ def _delayandsum5(data, offsets, ifactor2, steeramp, out, autopower):
75
71
  """
76
72
  num, gridsize, numchannels = offsets.shape
77
73
  num = out.shape[0]
74
+ #ZERO = data.dtype.type(0.)
75
+ ONE = data.dtype.type(1.)
78
76
  for n in nb.prange(num):
79
77
  for gi in nb.prange(gridsize):
80
78
  out[n,gi] = 0
81
79
  autopower[n,gi] = 0
82
80
  for mi in range(numchannels):
83
- ind = offsets[n,gi,mi] + n
84
- r = (data[ind,mi] * (1-ifactor2[n,gi,mi]) \
81
+ ind = offsets[n,gi,mi]+n
82
+ r = (data[ind,mi] * (ONE - ifactor2[n,gi,mi]) \
85
83
  + data[ind+1,mi] * ifactor2[n,gi,mi]) * steeramp[n,gi,mi]
86
84
  out[n,gi] += r
87
85
  autopower[n,gi] += r*r
@@ -145,18 +143,19 @@ def _steer_IV(rm, r0, amp):
145
143
  for mi in nb.prange(numchannels):
146
144
  amp[n,gi,mi] = np.divide(Nr,rm[n,gi,mi]*rm2)
147
145
 
148
- @nb.njit([(nb.float32[:,:,:], nb.float32[:,:,:], nb.float32, nb.float32[:,:,:], nb.int32[:,:,:]),
149
- (nb.float64[:,:,:], nb.float64[:,:,:], nb.float64, nb.float64[:,:,:], nb.int64[:,:,:])],
146
+ @nb.njit([(nb.float32[:,:,::1], nb.float32, nb.float32[:,:,::1], nb.int32[:,:,::1]),
147
+ (nb.float64[:,:,::1], nb.float64, nb.float64[:,:,::1], nb.int64[:,:,::1])],
150
148
  cache=True, parallel=True, fastmath=True)
151
- def _delays(rm, delays, c, interp2, index):
149
+ def _delays(rm, c, interp2, index):
152
150
  num, gridsize, numchannels = rm.shape
151
+ invc = 1/c
152
+ intt = index.dtype.type
153
153
  for n in nb.prange(num):
154
154
  for gi in nb.prange(gridsize):
155
155
  for mi in nb.prange(numchannels):
156
- delays[n,gi,mi] = np.divide(rm[n,gi,mi],c)
157
- index[n,gi,mi] = int(delays[n,gi,mi])
158
- interp2[n,gi,mi] = delays[n,gi,mi] - index[n,gi,mi]
159
-
156
+ delays = invc * rm[n,gi,mi]
157
+ index[n,gi,mi] = intt(delays)
158
+ interp2[n,gi,mi] = delays - nb.int64(delays)
160
159
 
161
160
  @nb.njit([(nb.float32[:,:,:], nb.float32[:,:,:], nb.int32[:,:,:]),
162
161
  (nb.float64[:,:,:], nb.float64[:,:,:], nb.int64[:,:,:])],
acoular/tools.py CHANGED
@@ -9,6 +9,7 @@ Implements tools for Acoular.
9
9
  .. autosummary::
10
10
  :toctree: generated/
11
11
 
12
+ MetricEvaluator
12
13
  return_result
13
14
  spherical_hn1
14
15
  get_radiation_angles
@@ -17,13 +18,154 @@ Implements tools for Acoular.
17
18
  bardata
18
19
  """
19
20
 
20
- from traits.api import HasStrictTraits
21
- from numpy import array, concatenate, newaxis, where,arctan2,sqrt,pi,mod,zeros,complex128
21
+ import acoular as ac
22
+ from numpy import array, concatenate, newaxis, where,arctan2,sqrt,pi,mod,zeros,\
23
+ complex128, ones, inf, minimum, empty
22
24
  from numpy.linalg import norm
23
25
  from numpy.ma import masked_where
24
26
  from .spectra import synthetic
27
+ from traits.api import Bool, CArray, HasPrivateTraits, Instance, Property, Any
25
28
 
26
29
  from scipy.special import spherical_yn, spherical_jn, sph_harm
30
+ from scipy.spatial.distance import cdist
31
+ from copy import copy
32
+
33
+ class MetricEvaluator(HasPrivateTraits):
34
+ """Evaluate the reconstruction performance of source mapping methods.
35
+
36
+ This class can be used to calculate the following performance metrics
37
+ according to Herold and Sarradj (2017):
38
+ * Specific level error
39
+ * Overall level error
40
+ * Inverse level error
41
+ """
42
+
43
+ #: an array of shape=(nf,ng) containing the squared sound pressure data of the
44
+ #: source mapping. (nf: number of frequencies, ng: number of grid points)
45
+ data = CArray(shape=(None,None),
46
+ desc="Contains the calculated squared sound pressure values in Pa**2.")
47
+
48
+ #: an array of shape=(nf,ns) containing the squared sound pressure data of the
49
+ #: ground-truth sources. (nf: number of frequencies, ns: number of sources)
50
+ target_data = CArray(shape=(None,None),
51
+ desc="Contains the ground-truth squared sound pressure values in Pa**2.")
52
+
53
+ #: :class:`~acoular.grids.Grid`-derived object that provides the grid locations
54
+ #: for the calculated source mapping data.
55
+ grid = Instance(ac.Grid,
56
+ desc="Grid instance that belongs to the calculated data")
57
+
58
+ #: :class:`~acoular.grids.Grid`-derived object that provides the grid locations
59
+ #: for the ground-truth data.
60
+ target_grid = Instance(ac.Grid,
61
+ desc="Grid instance that belongs to the ground-truth data")
62
+
63
+ #: sector type. Currently only circular sectors are supported.
64
+ sector = Instance(ac.CircSector, default=ac.CircSector(),)
65
+
66
+ #: if set True: use shrink integration area if two sources are closer
67
+ #: than 2*r. The radius of the integration area is then set to half the
68
+ #: distance between the two sources.
69
+ adaptive_sector_size = Bool(True,
70
+ desc="adaptive integration area")
71
+
72
+ #: if set `True`, the same amplitude can be assigned to multiple targets if
73
+ #: the integration area overlaps. If set `False`, the amplitude is assigned
74
+ #: to the first target and the other targets are ignored.
75
+ multi_assignment = Bool(True,
76
+ desc="if set True, the same amplitude can be assigned to multiple targets if the integration area overlaps")
77
+
78
+ #: returns the determined sector sizes for each ground-truth source position
79
+ sectors = Property()
80
+
81
+ def _validate_shapes(self):
82
+ if self.data.shape[0] != self.target_data.shape[0]:
83
+ raise ValueError("data and target_data must have the same number of frequencies!")
84
+ if self.data.shape[1] != self.grid.size:
85
+ raise ValueError("data and grid must have the same number of grid points!")
86
+ if self.target_data.shape[1] != self.target_grid.size:
87
+ raise ValueError("target_data and target_grid must have the same number of grid points!")
88
+
89
+ def _get_sector_radii(self):
90
+ ns = self.target_data.shape[1]
91
+ radii = ones(ns)*self.sector.r
92
+ if self.adaptive_sector_size:
93
+ locs = self.target_grid.gpos.T
94
+ intersrcdist = cdist(locs, locs)
95
+ intersrcdist[intersrcdist == 0] = inf
96
+ intersrcdist = intersrcdist.min(0)/2
97
+ radii = minimum(radii,intersrcdist)
98
+ return radii
99
+
100
+ def _get_sectors(self):
101
+ """Returns a list of CircSector objects for each target location."""
102
+ r = self._get_sector_radii()
103
+ ns = self.target_data.shape[1]
104
+ sectors = []
105
+ for i in range(ns):
106
+ loc = self.target_grid.gpos[:,i]
107
+ sector = copy(self.sector)
108
+ sector.r = r[i]
109
+ sector.x = loc[0]
110
+ sector.y = loc[1]
111
+ sectors.append(sector)
112
+ return sectors
113
+
114
+ def _integrate_sectors(self):
115
+ """Integrates over target sectors.
116
+
117
+ Returns
118
+ -------
119
+ array (num_freqs,num_sources)
120
+ returns the integrated Pa**2 values for each sector
121
+ """
122
+ sectors = self.sectors
123
+ results = empty(shape=self.target_data.shape)
124
+ for f in range(self.target_data.shape[0]):
125
+ data = self.data[f]
126
+ for i in range(self.target_data.shape[1]):
127
+ sector = sectors[i]
128
+ results[f,i] = ac.integrate(data,self.grid,sector)
129
+ if not self.multi_assignment:
130
+ indices = self.grid.subdomain(sector)
131
+ data[indices] = 0 # set values to zero (can not be assigned again)
132
+ return results
133
+
134
+ def get_overall_level_error(self):
135
+ """Returns the overall level error (Herold and Sarradj, 2017).
136
+
137
+ Returns
138
+ -------
139
+ numpy.array
140
+ overall level error of shape=(nf,)
141
+ """
142
+ self._validate_shapes()
143
+ return ac.L_p(self.data.sum(axis=1)) - ac.L_p(self.target_data.sum(axis=1))
144
+
145
+ def get_specific_level_error(self):
146
+ """Returns the specific level error (Herold and Sarradj, 2017).
147
+
148
+ Returns
149
+ -------
150
+ numpy.array
151
+ specific level error of shape=(nf,ns). nf: number of frequencies, ns: number of sources
152
+ """
153
+ self._validate_shapes()
154
+ sector_result = self._integrate_sectors()
155
+ return ac.L_p(sector_result) - ac.L_p(self.target_data)
156
+
157
+ def get_inverse_level_error(self):
158
+ """Returns the inverse level error (Herold and Sarradj, 2017).
159
+
160
+ Returns
161
+ -------
162
+ numpy.array
163
+ inverse level error of shape=(nf,1)
164
+ """
165
+ self._validate_shapes()
166
+ sector_result = self._integrate_sectors()
167
+ return ac.L_p(sector_result.sum(axis=1)) - ac.L_p(self.data.sum(axis=1))
168
+
27
169
 
28
170
 
29
171
  def return_result(source, nmax=-1, num=128):