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
acoular/fbeamform.py CHANGED
@@ -43,12 +43,19 @@ invert, dot, newaxis, zeros, linalg, \
43
43
  searchsorted, pi, sign, diag, arange, sqrt, log10, \
44
44
  reshape, hstack, vstack, eye, tril, size, clip, tile, round, delete, \
45
45
  absolute, argsort, sum, hsplit, fill_diagonal, zeros_like, \
46
- einsum, ndarray, isscalar, inf, real, unique
46
+ einsum, ndarray, isscalar, inf, real, unique, atleast_2d, einsum_path,trace
47
47
 
48
48
  from numpy.linalg import norm
49
49
 
50
50
  from sklearn.linear_model import LassoLars, LassoLarsCV, LassoLarsIC,\
51
- OrthogonalMatchingPursuitCV
51
+ OrthogonalMatchingPursuitCV, LinearRegression
52
+
53
+ #check for sklearn version to account for incompatible behavior
54
+ import sklearn
55
+ from packaging.version import parse
56
+ sklearn_ndict = {}
57
+ if parse(sklearn.__version__)<parse('1.4'):
58
+ sklearn_ndict['normalize'] = False
52
59
 
53
60
  from scipy.optimize import nnls, linprog, fmin_l_bfgs_b, shgo
54
61
  from scipy.linalg import inv, eigh, eigvals, fractional_matrix_power
@@ -170,7 +177,7 @@ class SteeringVector( HasPrivateTraits ):
170
177
 
171
178
  @property_depends_on('grid.digest, mics.digest, env.digest')
172
179
  def _get_rm ( self ):
173
- return self.env._r(self.grid.pos(), self.mics.mpos)
180
+ return atleast_2d(self.env._r(self.grid.pos(), self.mics.mpos))
174
181
 
175
182
  @cached_property
176
183
  def _get_digest( self ):
@@ -681,8 +688,14 @@ class BeamformerBase( HasPrivateTraits ):
681
688
  # return h.reshape(h.shape[0], prod(h.shape[1:])).sum(axis=1)
682
689
  if isinstance(sector, Sector):
683
690
  ind = self.steer.grid.subdomain(sector)
684
- else:
691
+ elif hasattr(self.steer.grid, 'indices'):
685
692
  ind = self.steer.grid.indices(*sector)
693
+ else:
694
+ raise NotImplementedError(
695
+ f'Grid of type {self.steer.grid.__class__.__name__} does not have an indices method! '
696
+ f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
697
+ 'instead of type numpy.array.'
698
+ )
686
699
  gshape = self.steer.grid.shape
687
700
  r = self.result
688
701
  h = zeros(r.shape[0])
@@ -1415,51 +1428,56 @@ class BeamformerDamasPlus (BeamformerDamas):
1415
1428
  p.freq = f[i]
1416
1429
  psf = p.psf[:]
1417
1430
 
1418
- if self.method == 'NNLS':
1419
- resopt = nnls(psf,y)[0]
1420
- elif self.method == 'LP': # linear programming (Dougherty)
1431
+ if self.method == "NNLS":
1432
+ ac[i] = nnls(psf, y)[0] / unit
1433
+ elif self.method == "LP": # linear programming (Dougherty)
1421
1434
  if self.r_diag:
1422
- warn('Linear programming solver may fail when CSM main '
1423
- 'diagonal is removed for delay-and-sum beamforming.',
1424
- Warning, stacklevel = 5)
1425
- cT = -1*psf.sum(1) # turn the minimization into a maximization
1426
- resopt = linprog(c=cT, A_ub=psf, b_ub=y).x # defaults to simplex method and non-negative x
1427
- elif self.method == 'LassoLars':
1428
- model = LassoLars(alpha = self.alpha * unit,
1429
- max_iter = self.max_iter)
1430
- else: # self.method == 'OMPCV':
1431
- model = OrthogonalMatchingPursuitCV()
1432
-
1433
-
1434
- if self.method in ('NNLS','LP'):
1435
- ac[i] = resopt / unit
1436
- else: # sklearn models
1437
- model.fit(psf,y)
1438
- ac[i] = model.coef_[:] / unit
1435
+ warn(
1436
+ "Linear programming solver may fail when CSM main "
1437
+ "diagonal is removed for delay-and-sum beamforming.",
1438
+ Warning,
1439
+ stacklevel=5,
1440
+ )
1441
+ cT = -1 * psf.sum(1) # turn the minimization into a maximization
1442
+ ac[i] = (
1443
+ linprog(c=cT, A_ub=psf, b_ub=y).x / unit
1444
+ ) # defaults to simplex method and non-negative x
1445
+ else:
1446
+ if self.method == "LassoLars":
1447
+ model = LassoLars(
1448
+ alpha=self.alpha * unit, max_iter=self.max_iter
1449
+ )
1450
+ elif self.method == "OMPCV":
1451
+ model = OrthogonalMatchingPursuitCV()
1452
+ else:
1453
+ raise NotImplementedError(f"%model solver not implemented")
1454
+ model.normalize = False
1455
+ # from sklearn 1.2, normalize=True does not work the same way anymore and the pipeline approach
1456
+ # with StandardScaler does scale in a different way, thus we monkeypatch the code and normalize
1457
+ # ourselves to make results the same over different sklearn versions
1458
+ norms = norm(psf, axis=0)
1459
+ # get rid of annoying sklearn warnings that appear
1460
+ # for sklearn<1.2 despite any settings
1461
+ with warnings.catch_warnings():
1462
+ warnings.simplefilter("ignore", category=FutureWarning)
1463
+ # normalized psf
1464
+ model.fit(psf / norms, y)
1465
+ # recover normalization in the coef's
1466
+ ac[i] = model.coef_[:] / norms / unit
1439
1467
 
1440
1468
  fr[i] = 1
1441
1469
 
1442
- class BeamformerOrth (BeamformerBase):
1470
+
1471
+ class BeamformerOrth( BeamformerBase ):
1443
1472
  """
1444
- Orthogonal beamforming, see :ref:`Sarradj, 2010<Sarradj2010>`.
1445
- Needs a-priori beamforming with eigenvalue decomposition (:class:`BeamformerEig`).
1473
+ Orthogonal deconvolution, see :ref:`Sarradj, 2010<Sarradj2010>`.
1474
+ New faster implementation without explicit (:class:`BeamformerEig`).
1446
1475
  """
1447
-
1448
- #: :class:`BeamformerEig` object that provides data for deconvolution.
1476
+ #: (only for backward compatibility) :class:`BeamformerEig` object
1477
+ #: if set, provides :attr:`freq_data`, :attr:`steer`, :attr:`r_diag`
1478
+ #: if not set, these have to be set explicitly
1449
1479
  beamformer = Trait(BeamformerEig)
1450
1480
 
1451
- #: :class:`~acoular.spectra.PowerSpectra` object that provides the cross spectral matrix
1452
- #: and eigenvalues, is set automatically.
1453
- freq_data = Delegate('beamformer')
1454
-
1455
- #: Flag, if 'True' (default), the main diagonal is removed before beamforming,
1456
- #: is set automatically.
1457
- r_diag = Delegate('beamformer')
1458
-
1459
- #: instance of :class:`~acoular.fbeamform.SteeringVector` or its derived classes,
1460
- #: that contains information about the steering vector. Is set automatically.
1461
- steer = Delegate('beamformer')
1462
-
1463
1481
  #: List of components to consider, use this to directly set the eigenvalues
1464
1482
  #: used in the beamformer. Alternatively, set :attr:`n`.
1465
1483
  eva_list = CArray(dtype=int,
@@ -1473,14 +1491,10 @@ class BeamformerOrth (BeamformerBase):
1473
1491
 
1474
1492
  # internal identifier
1475
1493
  digest = Property(
1476
- depends_on = ['beamformer.digest', 'eva_list'],
1477
- )
1478
-
1479
- # internal identifier
1480
- ext_digest = Property(
1481
- depends_on = ['digest', 'beamformer.ext_digest'],
1494
+ depends_on = ['freq_data.digest', '_steer_obj.digest', 'r_diag',
1495
+ 'eva_list'],
1482
1496
  )
1483
-
1497
+
1484
1498
  @cached_property
1485
1499
  def _get_digest( self ):
1486
1500
  return digest( self )
@@ -1488,7 +1502,13 @@ class BeamformerOrth (BeamformerBase):
1488
1502
  @cached_property
1489
1503
  def _get_ext_digest( self ):
1490
1504
  return digest( self, 'ext_digest' )
1491
-
1505
+
1506
+ @on_trait_change('beamformer.digest')
1507
+ def delegate_beamformer_traits(self):
1508
+ self.freq_data = self.beamformer.freq_data
1509
+ self.r_diag = self.beamformer.r_diag
1510
+ self.steer = self.beamformer.steer
1511
+
1492
1512
  @on_trait_change('n')
1493
1513
  def set_eva_list(self):
1494
1514
  """ sets the list of eigenvalues to consider """
@@ -1521,19 +1541,23 @@ class BeamformerOrth (BeamformerBase):
1521
1541
  This method only returns values through the *ac* and *fr* parameters
1522
1542
  """
1523
1543
  # prepare calculation
1524
- ii = []
1544
+ f = self.freq_data.fftfreq()
1545
+ numchannels = self.freq_data.numchannels
1546
+ normFactor = self.sig_loss_norm()
1547
+ param_steer_type, steer_vector = self._beamformer_params()
1525
1548
  for i in self.freq_data.indices:
1526
1549
  if not fr[i]:
1527
- ii.append(i)
1528
- numchannels = self.freq_data.numchannels
1529
- e = self.beamformer
1530
- for n in self.eva_list:
1531
- e.n = n
1532
- for i in ii:
1533
- ac[i, e.result[i].argmax()]+=e.freq_data.eva[i, n]/numchannels
1534
- for i in ii:
1535
- fr[i] = 1
1536
-
1550
+ eva = array(self.freq_data.eva[i], dtype='float64')
1551
+ eve = array(self.freq_data.eve[i], dtype='complex128')
1552
+ for n in self.eva_list:
1553
+ beamformerOutput = beamformerFreq(param_steer_type,
1554
+ self.r_diag,
1555
+ normFactor,
1556
+ steer_vector(f[i]),
1557
+ (ones(1), eve[:, n].reshape((-1,1))))[0]
1558
+ ac[i, beamformerOutput.argmax()]+=eva[n]/numchannels
1559
+ fr[i] = 1
1560
+
1537
1561
  class BeamformerCleansc( BeamformerBase ):
1538
1562
  """
1539
1563
  CLEAN-SC deconvolution, see :ref:`Sijtsma, 2007<Sijtsma2007>`.
@@ -1865,13 +1889,7 @@ class BeamformerCMF ( BeamformerBase ):
1865
1889
  else:
1866
1890
  # take all real parts -- also main diagonal
1867
1891
  ind_reim = hstack([ones(size(ind_im0),)>0,ind_im0])
1868
- ind_reim[0]=True # TODO: warum hier extra definiert??
1869
- # if sigma2:
1870
- # # identity matrix, needed when noise term sigma is used
1871
- # I = eye(nc).reshape(nc*nc,1)
1872
- # A = realify( hstack([Ac, I])[ind,:] )[ind_reim,:]
1873
- # # ... ac[i] = model.coef_[:-1]
1874
- # else:
1892
+ ind_reim[0]=True # why this ?
1875
1893
 
1876
1894
  A = realify( Ac [ind,:] )[ind_reim,:]
1877
1895
  # use csm.T for column stacking reshape!
@@ -1879,18 +1897,18 @@ class BeamformerCMF ( BeamformerBase ):
1879
1897
  # choose method
1880
1898
  if self.method == 'LassoLars':
1881
1899
  model = LassoLars(alpha = self.alpha * unit,
1882
- max_iter = self.max_iter)
1900
+ max_iter = self.max_iter,
1901
+ **sklearn_ndict)
1883
1902
  elif self.method == 'LassoLarsBIC':
1884
1903
  model = LassoLarsIC(criterion = 'bic',
1885
- max_iter = self.max_iter)
1904
+ max_iter = self.max_iter,
1905
+ **sklearn_ndict)
1886
1906
  elif self.method == 'OMPCV':
1887
- model = OrthogonalMatchingPursuitCV()
1907
+ model = OrthogonalMatchingPursuitCV(**sklearn_ndict)
1908
+ elif self.method == 'NNLS':
1909
+ model = LinearRegression(positive=True)
1888
1910
 
1889
- # nnls is not in sklearn
1890
- if self.method == 'NNLS':
1891
- ac[i] , x = nnls(A,R.flat)
1892
- ac[i] /= unit
1893
- elif self.method == 'Split_Bregman' and PYLOPS_TRUE:
1911
+ if self.method == 'Split_Bregman' and PYLOPS_TRUE:
1894
1912
  Oop = MatrixMult(A) #tranfer operator
1895
1913
  Iop = self.alpha*Identity(numpoints) # regularisation
1896
1914
  ac[i],iterations = SplitBregman(Oop, [Iop] , R[:,0],
@@ -1932,11 +1950,17 @@ class BeamformerCMF ( BeamformerBase ):
1932
1950
 
1933
1951
  ac[i] /= unit
1934
1952
  else:
1935
- # get rid of annoying sklearn warnings that appear despite any settings
1953
+ # from sklearn 1.2, normalize=True does not work the same way anymore and the pipeline
1954
+ # approach with StandardScaler does scale in a different way, thus we monkeypatch the
1955
+ # code and normalize ourselves to make results the same over different sklearn versions
1956
+ norms = norm(A, axis=0)
1957
+ # get rid of annoying sklearn warnings that appear for sklearn<1.2 despite any settings
1936
1958
  with warnings.catch_warnings():
1937
1959
  warnings.simplefilter("ignore", category=FutureWarning)
1938
- model.fit(A,R[:,0])
1939
- ac[i] = model.coef_[:] / unit
1960
+ # normalized A
1961
+ model.fit(A/norms,R[:,0])
1962
+ # recover normalization in the coef's
1963
+ ac[i] = model.coef_[:] / norms / unit
1940
1964
  fr[i] = 1
1941
1965
 
1942
1966
 
@@ -2024,7 +2048,8 @@ class BeamformerSODIX( BeamformerBase ):
2024
2048
  group)
2025
2049
  ac = self.h5f.get_data_by_reference('result','/'+nodename)
2026
2050
  fr = self.h5f.get_data_by_reference('freqs','/'+nodename)
2027
- return (ac,fr)
2051
+ gpos = None
2052
+ return (ac,fr,gpos)
2028
2053
 
2029
2054
  @property_depends_on('ext_digest')
2030
2055
  def _get_sodix_result ( self ):
@@ -2042,7 +2067,7 @@ class BeamformerSODIX( BeamformerBase ):
2042
2067
  config.global_caching == 'none' or
2043
2068
  (config.global_caching == 'individual' and self.cached == False)
2044
2069
  ):
2045
- (ac,fr) = self._get_filecache()
2070
+ (ac,fr,gpos) = self._get_filecache()
2046
2071
  if ac and fr:
2047
2072
  if not fr[f.ind_low:f.ind_high].all():
2048
2073
  if config.global_caching == 'readonly':
@@ -2199,21 +2224,28 @@ class BeamformerSODIX( BeamformerBase ):
2199
2224
 
2200
2225
  '''
2201
2226
  #### the sodix function ####
2202
- Djm = D.reshape([numpoints,num_mics])
2203
- csmmod = einsum('jm,jm,jn,jn->mn',h.T,Djm,Djm,h.T.conj() )
2204
- func = sum(absolute((csm - csmmod)))**2 + self.alpha*norm(Djm,self.pnorm)
2205
- ####the sodix derivitaive ####
2206
- inner = csm - einsum('jl,jl,jm,jm->lm',h.T,Djm,Djm,h.T.conj() )
2207
- derdrl = -4 * Djm * real(einsum('rm,rl,lm->rl',h.T,h.T.conj(),inner)) + \
2208
- self.alpha * (abs(Djm)/norm(Djm,self.pnorm))**(1-self.pnorm)*sign(Djm)
2209
-
2210
- return func, derdrl[:].flatten() #func[0]
2211
-
2227
+ Djm = D.reshape([numpoints,num_mics])
2228
+ p = h.T * Djm
2229
+ csm_mod = dot(p.T, p.conj())
2230
+ Q = csm - csm_mod
2231
+ func = sum((absolute(Q))**2)
2232
+
2233
+ # subscripts and operands for numpy einsum and einsum_path
2234
+ subscripts = 'rl,rm,ml->rl'
2235
+ operands = (h.T,h.T.conj()*Djm,Q)
2236
+ es_path = einsum_path(subscripts, *operands, optimize='greedy')[0]
2237
+
2238
+ #### the sodix derivative ####
2239
+ derdrl = einsum(subscripts, *operands, optimize=es_path)
2240
+ derdrl = -4 * real(derdrl)
2241
+ return func, derdrl.ravel()
2242
+
2212
2243
  ##### initial guess ####
2213
2244
  if all(ac[(i-1)]==0):
2214
2245
  D0 = ones([numpoints,num_mics])
2215
2246
  else:
2216
- D0 = ac[(i-1)]
2247
+ D0 = sqrt(ac[(i-1)]*
2248
+ real((trace(csm)/trace(array(self.freq_data.csm[i-1], dtype='complex128',copy=1)))))
2217
2249
 
2218
2250
  #boundarys - set to non negative [2*(numpoints*num_mics)]
2219
2251
  boundarys = tile((0, +inf), (numpoints*num_mics,1))
@@ -2222,11 +2254,11 @@ class BeamformerSODIX( BeamformerBase ):
2222
2254
  # see https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin_l_bfgs_b.html
2223
2255
 
2224
2256
  qi = ones([numpoints,num_mics])
2225
- qi, yval, dicts = fmin_l_bfgs_b(function, D0, fprime=None, args=(), #None
2226
- approx_grad=0, bounds=boundarys, #approx_grad 0 or True
2227
- factr=100.0, pgtol=1e-09, epsilon=1e-08,
2228
- iprint=0, maxfun=1500000, maxiter=self.max_iter,
2229
- disp=None, callback=None, maxls=20)
2257
+ qi, yval, dicts = fmin_l_bfgs_b(function, D0, fprime=None, args=(),
2258
+ approx_grad=0, bounds=boundarys,
2259
+ factr=100.0, pgtol=1e-12, epsilon=1e-08,
2260
+ iprint=-1, maxfun=1500000, maxiter=self.max_iter,
2261
+ disp=-1, callback=None, maxls=20)
2230
2262
  #squared pressure
2231
2263
  ac[i]=qi**2
2232
2264
  else:
@@ -2411,21 +2443,34 @@ class BeamformerGIB(BeamformerEig): #BeamformerEig #BeamformerBase
2411
2443
  AB = vstack([hstack([A.real,-A.imag]),hstack([A.imag,A.real])])
2412
2444
  R = hstack([emode.real.T,emode.imag.T]) * unit
2413
2445
  if self.method == 'LassoLars':
2414
- model = LassoLars(alpha=self.alpha * unit,max_iter=self.max_iter)
2446
+ model = LassoLars(alpha=self.alpha * unit,
2447
+ max_iter=self.max_iter)
2415
2448
  elif self.method == 'LassoLarsBIC':
2416
- model = LassoLarsIC(criterion='bic',max_iter=self.max_iter)
2449
+ model = LassoLarsIC(criterion='bic',
2450
+ max_iter=self.max_iter)
2417
2451
  elif self.method == 'OMPCV':
2418
2452
  model = OrthogonalMatchingPursuitCV()
2419
2453
  elif self.method == 'LassoLarsCV':
2420
- model = LassoLarsCV()
2421
- if self.method == 'NNLS':
2422
- x , zz = nnls(AB,R)
2423
- qi_real,qi_imag = hsplit(x/unit, 2)
2424
- else:
2425
- with warnings.catch_warnings():
2426
- warnings.simplefilter("ignore", category=FutureWarning)
2427
- model.fit(AB,R)
2428
- qi_real,qi_imag = hsplit(model.coef_[:]/unit, 2)
2454
+ model = LassoLarsCV()
2455
+ elif self.method == 'NNLS':
2456
+ model = LinearRegression(positive=True)
2457
+ model.normalize = False
2458
+ # from sklearn 1.2, normalize=True does not work
2459
+ # the same way anymore and the pipeline approach
2460
+ # with StandardScaler does scale in a different
2461
+ # way, thus we monkeypatch the code and normalize
2462
+ # ourselves to make results the same over different
2463
+ # sklearn versions
2464
+ norms = norm(AB, axis=0)
2465
+ # get rid of annoying sklearn warnings that appear
2466
+ # for sklearn<1.2 despite any settings
2467
+ with warnings.catch_warnings():
2468
+ warnings.simplefilter("ignore",
2469
+ category=FutureWarning)
2470
+ # normalized A
2471
+ model.fit(AB/norms,R)
2472
+ # recover normalization in the coef's
2473
+ qi_real,qi_imag = hsplit(model.coef_[:]/norms/unit, 2)
2429
2474
  #print(s,qi.size)
2430
2475
  qi[s,locpoints] = qi_real+qi_imag*1j
2431
2476
  else:
@@ -2464,6 +2509,12 @@ class BeamformerAdaptiveGrid(BeamformerBase,Grid):
2464
2509
  array of floats
2465
2510
  The spectrum (all calculated frequency bands) for the integrated sector.
2466
2511
  """
2512
+ if not isinstance(sector, Sector):
2513
+ raise NotImplementedError(
2514
+ f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
2515
+ f'instead of type {type(sector)}.'
2516
+ )
2517
+
2467
2518
  ind = self.subdomain(sector)
2468
2519
  r = self.result
2469
2520
  h = zeros(r.shape[0])
@@ -2648,13 +2699,15 @@ def integrate(data, grid, sector):
2648
2699
  grid: Grid object
2649
2700
  Object of a :class:`~acoular.grids.Grid`-derived class
2650
2701
  that provides the grid locations.
2651
- sector: array of floats
2702
+ sector: array of floats or :class:`~acoular.grids.Sector`-derived object
2652
2703
  Tuple with arguments for the `indices` method
2653
2704
  of a :class:`~acoular.grids.Grid`-derived class
2654
2705
  (e.g. :meth:`RectGrid.indices<acoular.grids.RectGrid.indices>`
2655
2706
  or :meth:`RectGrid3D.indices<acoular.grids.RectGrid3D.indices>`).
2656
2707
  Possible sectors would be `array([xmin, ymin, xmax, ymax])`
2657
2708
  or `array([x, y, radius])`.
2709
+ Alternatively, a :class:`~acoular.grids.Sector`-derived object
2710
+ can be used.
2658
2711
 
2659
2712
  Returns
2660
2713
  -------
@@ -2664,8 +2717,14 @@ def integrate(data, grid, sector):
2664
2717
 
2665
2718
  if isinstance(sector, Sector):
2666
2719
  ind = grid.subdomain(sector)
2667
- else:
2720
+ elif hasattr(grid, 'indices'):
2668
2721
  ind = grid.indices(*sector)
2722
+ else:
2723
+ raise NotImplementedError(
2724
+ f'Grid of type {grid.__class__.__name__} does not have an indices method! '
2725
+ f'Please use a sector derived instance of type :class:`~acoular.grids.Sector` '
2726
+ 'instead of type numpy.array.'
2727
+ )
2669
2728
 
2670
2729
  gshape = grid.shape
2671
2730
  gsize = grid.size
acoular/grids.py CHANGED
@@ -15,6 +15,7 @@
15
15
  LineGrid
16
16
  MergeGrid
17
17
  Sector
18
+ SingleSector
18
19
  RectSector
19
20
  RectSector3D
20
21
  CircSector
@@ -27,7 +28,7 @@
27
28
  # imports from other packages
28
29
  from numpy import mgrid, s_, array, arange, isscalar, absolute, ones, argmin,\
29
30
  zeros, where, asfarray,concatenate,sum,ma,ones_like,inf,copysign,fabs ,append,\
30
- tile,newaxis
31
+ tile,newaxis, unique
31
32
  from numpy.linalg import norm
32
33
  from traits.api import HasPrivateTraits, Float, Property, Any, \
33
34
  property_depends_on, cached_property, Bool, List, Instance, File ,on_trait_change,\
@@ -733,7 +734,7 @@ class ImportGrid( Grid ):
733
734
  @on_trait_change('basename')
734
735
  def import_gpos( self ):
735
736
  """
736
- Import the microphone positions from .xml file.
737
+ Import the the grid point locations from .xml file.
737
738
  Called when :attr:`basename` changes.
738
739
  """
739
740
  if not path.isfile(self.from_file):
@@ -749,98 +750,6 @@ class ImportGrid( Grid ):
749
750
  xyz.append(list(map(lambda a : float(el.getAttribute(a)), 'xyz')))
750
751
  self.gpos_file = array(xyz, 'd').swapaxes(0, 1)
751
752
  self.subgrids = array(names)
752
-
753
- def index ( self, x, y ):
754
- """
755
- Queries the indices for a grid point near a certain co-ordinate.
756
-
757
- This can be used to query results or co-ordinates at/near a certain
758
- co-ordinate.
759
-
760
- Parameters
761
- ----------
762
- x, y : float
763
- The co-ordinates for which the indices are queried.
764
-
765
- Returns
766
- -------
767
- 2-tuple of integers
768
- The indices that give the grid point nearest to the given x, y
769
- co-ordinates from an array with the same shape as the grid.
770
- """
771
- if x < self.x_min or x > self.x_max:
772
- raise ValueError("x-value out of range")
773
- if y < self.y_min or y > self.y_max:
774
- raise ValueError("y-value out of range")
775
- xi = int((x-self.x_min)/self.increment+0.5)
776
- yi = int((y-self.y_min)/self.increment+0.5)
777
- return xi, yi
778
-
779
- def indices ( self, *r):
780
- """
781
- Queries the indices for a subdomain in the grid.
782
-
783
- Allows either rectangular, circular or polygonial subdomains.
784
- This can be used to mask or to query results from a certain
785
- sector or subdomain.
786
-
787
- Parameters
788
- ----------
789
- x1, y1, x2, y2, ... : float
790
- If three parameters are given, then a circular sector is assumed
791
- that is given by its center (x1, y1) and the radius x2.
792
- If four paramters are given, then a rectangular sector is
793
- assumed that is given by two corners (x1, y1) and (x2, y2).
794
- If more parameters are given, the subdomain is assumed to have
795
- polygonial shape with corners at (x_n, y_n).
796
-
797
- Returns
798
- -------
799
- 2-tuple of arrays of integers or of numpy slice objects
800
- The indices that can be used to mask/select the grid subdomain from
801
- an array with the same shape as the grid.
802
- """
803
-
804
- if len(r) == 3: # only 3 values given -> use x,y,radius method
805
- xpos = self.gpos
806
- xis = []
807
- yis = []
808
- dr2 = (xpos[0, :]-r[0])**2 + (xpos[1, :]-r[1])**2
809
- # array with true/false entries
810
- inds = dr2 <= r[2]**2
811
- for np in arange(self.size)[inds]: # np -- points in x2-circle
812
- xi, yi = self.index(xpos[0, np], xpos[1, np])
813
- xis += [xi]
814
- yis += [yi]
815
- if not (xis and yis): # if no points in circle, take nearest one
816
- return self.index(r[0], r[1])
817
- else:
818
- return array(xis), array(yis)
819
- elif len(r) == 4: # rectangular subdomain - old functionality
820
- xi1, yi1 = self.index(min(r[0], r[2]), min(r[1], r[3]))
821
- xi2, yi2 = self.index(max(r[0], r[2]), max(r[1], r[3]))
822
- return s_[xi1:xi2+1], s_[yi1:yi2+1]
823
- else: # use enveloping polygon
824
- xpos = self.gpos
825
- xis = []
826
- yis = []
827
- #replaced matplotlib Path by numpy
828
- #p = Path(array(r).reshape(-1,2))
829
- #inds = p.contains_points()
830
- #inds = in_poly(xpos[:2,:].T,array(r).reshape(-1,2))
831
- poly = Polygon(array(r).reshape(-1,2)[:,0],array(r).reshape(-1,2)[:,1])
832
- dists = poly.is_inside(xpos[0,:],xpos[1,:])
833
- inds = dists >= 0
834
- for np in arange(self.size)[inds]: # np -- points in x2-circle
835
- xi, yi = self.index(xpos[0, np], xpos[1, np])
836
- xis += [xi]
837
- yis += [yi]
838
- if not (xis and yis): # if no points inside, take nearest to center
839
- center = array(r).reshape(-1,2).mean(0)
840
- return self.index(center[0], center[1])
841
- else:
842
- return array(xis), array(yis)
843
- #return arange(self.size)[inds]
844
753
 
845
754
  class LineGrid( Grid ):
846
755
  """
@@ -948,7 +857,6 @@ class MergeGrid( Grid ):
948
857
  subgrids = append(subgrids,tile(grid.__class__.__name__+grid.digest,grid.size))
949
858
  return subgrids[:,newaxis].T
950
859
 
951
-
952
860
  @property_depends_on('digest')
953
861
  def _get_gpos( self ):
954
862
  bpos = zeros((3,0))
@@ -956,29 +864,18 @@ class MergeGrid( Grid ):
956
864
  for grid in self.grids:
957
865
  bpos = append(bpos,grid.gpos, axis = 1)
958
866
  #subgrids = append(subgrids,str(grid))
867
+ bpos = unique(bpos,axis=1)
959
868
  return bpos
960
869
 
961
870
  class Sector( HasPrivateTraits ):
962
871
  """
963
- Base class for sector types.
872
+ Base class for all sector types.
964
873
 
965
- Defines the common interface for all sector classes. This class
874
+ Defines the common interface for all tbdsector classes. This class
966
875
  may be used as a base for diverse sector implementaions. If used
967
876
  directly, it implements a sector encompassing the whole grid.
968
877
  """
969
878
 
970
- #: Boolean flag, if 'True' (default), grid points lying on the sector border are included.
971
- include_border = Bool(True,
972
- desc="include points on the border")
973
-
974
- #: Absolute tolerance for sector border
975
- abs_tol = Float(1e-12,
976
- desc="absolute tolerance for sector border")
977
-
978
- #: Boolean flag, if 'True' (default), the nearest grid point is returned if none is inside the sector.
979
- default_nearest = Bool(True,
980
- desc="return nearest grid point to center of none inside sector")
981
-
982
879
  def contains ( self, pos ):
983
880
  """
984
881
  Queries whether the coordinates in a given array lie within the
@@ -1000,7 +897,30 @@ class Sector( HasPrivateTraits ):
1000
897
  return ones(pos.shape[1], dtype=bool)
1001
898
 
1002
899
 
1003
- class RectSector( Sector ):
900
+ class SingleSector( Sector ):
901
+ """
902
+ Base class for single sector types.
903
+
904
+ Defines the common interface for all single sector classes. This class
905
+ may be used as a base for diverse single sector implementaions. If used
906
+ directly, it implements a sector encompassing the whole grid.
907
+ """
908
+
909
+ #: Boolean flag, if 'True' (default), grid points lying on the sector border are included.
910
+ include_border = Bool(True,
911
+ desc="include points on the border")
912
+
913
+ #: Absolute tolerance for sector border
914
+ abs_tol = Float(1e-12,
915
+ desc="absolute tolerance for sector border")
916
+
917
+ #: Boolean flag, if 'True' (default), the nearest grid point is returned if none is inside the sector.
918
+ default_nearest = Bool(True,
919
+ desc="return nearest grid point to center of none inside sector")
920
+
921
+
922
+
923
+ class RectSector( SingleSector ):
1004
924
  """
1005
925
  Class for defining a rectangular sector.
1006
926
 
@@ -1142,7 +1062,7 @@ class RectSector3D( RectSector ):
1142
1062
  return inds.astype(bool)
1143
1063
 
1144
1064
 
1145
- class CircSector( Sector ):
1065
+ class CircSector( SingleSector ):
1146
1066
  """
1147
1067
  Class for defining a circular sector.
1148
1068
 
@@ -1197,7 +1117,7 @@ class CircSector( Sector ):
1197
1117
  return inds
1198
1118
 
1199
1119
 
1200
- class PolySector( Sector ):
1120
+ class PolySector( SingleSector ):
1201
1121
  """
1202
1122
  Class for defining a polygon sector.
1203
1123
 
@@ -1242,7 +1162,7 @@ class PolySector( Sector ):
1242
1162
 
1243
1163
  return inds
1244
1164
 
1245
- class ConvexSector( Sector ):
1165
+ class ConvexSector( SingleSector ):
1246
1166
  """
1247
1167
  Class for defining a convex hull sector.
1248
1168
 
@@ -1297,7 +1217,6 @@ class MultiSector(Sector):
1297
1217
  #: to be mixed.
1298
1218
  sectors = List(Instance(Sector))
1299
1219
 
1300
-
1301
1220
  def contains ( self, pos ):
1302
1221
  """
1303
1222
  Queries whether the coordinates in a given array lie within any