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/tprocess.py CHANGED
@@ -40,7 +40,7 @@
40
40
  from numpy import array, empty, empty_like, pi, sin, sqrt, zeros, newaxis, unique, \
41
41
  int16, nan, concatenate, sum, float64, identity, argsort, interp, arange, append, \
42
42
  linspace, flatnonzero, argmin, argmax, delete, mean, inf, asarray, stack, sinc, exp, \
43
- polymul, arange, cumsum, ceil, split
43
+ polymul, arange, cumsum, ceil, split, array_equal
44
44
 
45
45
  from numpy.linalg import norm
46
46
  from numpy.matlib import repmat
@@ -59,7 +59,7 @@ import numba as nb
59
59
  from datetime import datetime
60
60
  from os import path
61
61
  import wave
62
- from scipy.signal import butter, lfilter, filtfilt, bilinear
62
+ from scipy.signal import butter, filtfilt, bilinear, tf2sos, sosfilt, sosfiltfilt
63
63
  from warnings import warn
64
64
  from collections import deque
65
65
  from inspect import currentframe
@@ -957,7 +957,7 @@ class SpatialInterpolator(TimeInOut):
957
957
  # Interpolation for 1D Arrays
958
958
  if self.array_dimension =='1D' or self.array_dimension =='ring':
959
959
  #for rotation add phidelay
960
- if not phiDelay == []:
960
+ if not array_equal(phiDelay,[]):
961
961
  xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
962
962
  xInterp = ((xInterpHelp + pi ) % (2 * pi)) - pi # shifting phi cootrdinate into feasible area [-pi, pi]
963
963
  #if no rotation given
@@ -1001,7 +1001,7 @@ class SpatialInterpolator(TimeInOut):
1001
1001
  # Interpolation for arbitrary 2D Arrays
1002
1002
  elif self.array_dimension =='2D':
1003
1003
  #check rotation
1004
- if not phiDelay == []:
1004
+ if not array_equal(phiDelay,[]):
1005
1005
  xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
1006
1006
  xInterp = ((xInterpHelp + pi ) % (2 * pi)) - pi #shifting phi cootrdinate into feasible area [-pi, pi]
1007
1007
  else:
@@ -1081,7 +1081,7 @@ class SpatialInterpolator(TimeInOut):
1081
1081
  # Interpolation for arbitrary 3D Arrays
1082
1082
  elif self.array_dimension =='3D':
1083
1083
  #check rotation
1084
- if not phiDelay == []:
1084
+ if not array_equal(phiDelay,[]):
1085
1085
  xInterpHelp = repmat(virtNewCoord[0, :], nTime, 1) + repmat(phiDelay, virtNewCoord.shape[1], 1).T
1086
1086
  xInterp = ((xInterpHelp + pi ) % (2 * pi)) - pi #shifting phi cootrdinate into feasible area [-pi, pi]
1087
1087
  else:
@@ -1294,7 +1294,10 @@ class Mixer( TimeInOut ):
1294
1294
  for temp in self.source.result(num):
1295
1295
  sh = temp.shape[0]
1296
1296
  for g in gens:
1297
- temp1 = next(g)
1297
+ try:
1298
+ temp1 = next(g)
1299
+ except StopIteration:
1300
+ return
1298
1301
  if temp.shape[0] > temp1.shape[0]:
1299
1302
  temp = temp[:temp1.shape[0]]
1300
1303
  temp += temp1[:temp.shape[0]]
@@ -1455,10 +1458,10 @@ class Filter(TimeInOut):
1455
1458
  Should not be instanciated by itself
1456
1459
  """
1457
1460
  #: Filter coefficients
1458
- ba = Property()
1461
+ sos = Property()
1459
1462
 
1460
- def _get_ba( self ):
1461
- return [1],[1]
1463
+ def _get_sos( self ):
1464
+ return tf2sos([1],[1])
1462
1465
 
1463
1466
  def result(self, num):
1464
1467
  """
@@ -1477,20 +1480,17 @@ class Filter(TimeInOut):
1477
1480
  Delivers the bandpass filtered output of source.
1478
1481
  The last block may be shorter than num.
1479
1482
  """
1480
- b, a = self.ba
1481
- zi = zeros((max(len(a), len(b))-1, self.source.numchannels))
1483
+ sos = self.sos
1484
+ zi = zeros((sos.shape[0], 2, self.source.numchannels))
1482
1485
  for block in self.source.result(num):
1483
- b, a = self.ba # this line is useful in case of changes
1484
- # to self.ba during generator lifetime
1485
- block, zi = lfilter(b, a, block, axis=0, zi=zi)
1486
+ sos = self.sos # this line is useful in case of changes
1487
+ # to self.sos during generator lifetime
1488
+ block, zi = sosfilt(sos, block, axis=0, zi=zi)
1486
1489
  yield block
1487
1490
 
1488
- class FiltFiltOctave( TimeInOut ):
1491
+ class FiltOctave( Filter ):
1489
1492
  """
1490
- Octave or third-octave filter with zero phase delay.
1491
-
1492
- This filter can be applied on time signals.
1493
- It requires large amounts of memory!
1493
+ Octave or third-octave filter (causal, non-zero phase delay).
1494
1494
  """
1495
1495
  #: Band center frequency; defaults to 1000.
1496
1496
  band = Float(1000.0,
@@ -1499,33 +1499,28 @@ class FiltFiltOctave( TimeInOut ):
1499
1499
  #: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1500
1500
  fraction = Trait('Octave', {'Octave':1, 'Third octave':3},
1501
1501
  desc = "fraction of octave")
1502
+
1503
+ #: Filter order
1504
+ order = Int(3, desc = "IIR filter order")
1502
1505
 
1506
+ sos = Property( depends_on = ['band', 'fraction', 'source.digest', 'order'])
1507
+
1503
1508
  # internal identifier
1504
1509
  digest = Property( depends_on = ['source.digest', '__class__', \
1505
- 'band', 'fraction'])
1510
+ 'band', 'fraction','order'])
1506
1511
 
1507
1512
  @cached_property
1508
1513
  def _get_digest( self ):
1509
1514
  return digest(self)
1510
1515
 
1511
- def ba(self, order):
1512
- """
1513
- Internal Butterworth filter design routine.
1514
-
1515
- Parameters
1516
- ----------
1517
- order : integer
1518
- The order of the filter.
1519
-
1520
- Returns
1521
- -------
1522
- b, a : ndarray, ndarray
1523
- Filter coefficients.
1524
- """
1516
+ @cached_property
1517
+ def _get_sos( self ):
1525
1518
  # filter design
1526
1519
  fs = self.sample_freq
1527
- # adjust filter edge frequencies
1528
- beta = pi/(4*order)
1520
+ # adjust filter edge frequencies for correct power bandwidth (see ANSI 1.11 1987
1521
+ # and Kalb,J.T.: "A thirty channel real time audio analyzer and its applications",
1522
+ # PhD Thesis: Georgia Inst. of Techn., 1975
1523
+ beta = pi/(2*self.order)
1529
1524
  alpha = pow(2.0, 1.0/(2.0*self.fraction_))
1530
1525
  beta = 2 * beta / sin(beta) / (alpha-1/alpha)
1531
1526
  alpha = (1+sqrt(1+beta*beta))/beta
@@ -1534,9 +1529,44 @@ class FiltFiltOctave( TimeInOut ):
1534
1529
  raise ValueError("band frequency too high:%f,%f" % (self.band, fs))
1535
1530
  om1 = fr/alpha
1536
1531
  om2 = fr*alpha
1537
- # print om1, om2
1538
- return butter(order, [om1, om2], 'bandpass')
1539
-
1532
+ return butter(self.order, [om1, om2], 'bandpass', output = 'sos')
1533
+
1534
+ class FiltFiltOctave( FiltOctave ):
1535
+ """
1536
+ Octave or third-octave filter with zero phase delay.
1537
+
1538
+ This filter can be applied on time signals.
1539
+ It requires large amounts of memory!
1540
+ """
1541
+ #: Filter order (applied for forward filter and backward filter)
1542
+ order = Int(2, desc = "IIR filter half order")
1543
+
1544
+ # internal identifier
1545
+ digest = Property( depends_on = ['source.digest', '__class__', \
1546
+ 'band', 'fraction','order'])
1547
+
1548
+ @cached_property
1549
+ def _get_digest( self ):
1550
+ return digest(self)
1551
+
1552
+ @cached_property
1553
+ def _get_sos( self ):
1554
+ # filter design
1555
+ fs = self.sample_freq
1556
+ # adjust filter edge frequencies for correct power bandwidth (see FiltOctave)
1557
+ beta = pi/(2*self.order)
1558
+ alpha = pow(2.0, 1.0/(2.0*self.fraction_))
1559
+ beta = 2 * beta / sin(beta) / (alpha-1/alpha)
1560
+ alpha = (1+sqrt(1+beta*beta))/beta
1561
+ # additional bandwidth correction for double-pass
1562
+ alpha = alpha * {6:1.01,5:1.012,4:1.016,3:1.022,2:1.036,1:1.083}.get(self.order,1.0)**(3/self.fraction_)
1563
+ fr = 2*self.band/fs
1564
+ if fr > 1/sqrt(2):
1565
+ raise ValueError("band frequency too high:%f,%f" % (self.band, fs))
1566
+ om1 = fr/alpha
1567
+ om2 = fr*alpha
1568
+ return butter(self.order, [om1, om2], 'bandpass', output = 'sos')
1569
+
1540
1570
  def result(self, num):
1541
1571
  """
1542
1572
  Python generator that yields the output block-wise.
@@ -1554,63 +1584,22 @@ class FiltFiltOctave( TimeInOut ):
1554
1584
  Delivers the zero-phase bandpass filtered output of source.
1555
1585
  The last block may be shorter than num.
1556
1586
  """
1557
- b, a = self.ba(3) # filter order = 3
1587
+ sos = self.sos
1558
1588
  data = empty((self.source.numsamples, self.source.numchannels))
1559
1589
  j = 0
1560
1590
  for block in self.source.result(num):
1561
1591
  ns, nc = block.shape
1562
1592
  data[j:j+ns] = block
1563
1593
  j += ns
1594
+ # filter one channel at a time to save memory
1564
1595
  for j in range(self.source.numchannels):
1565
- data[:, j] = filtfilt(b, a, data[:, j])
1596
+ data[:, j] = sosfiltfilt(sos, data[:, j])
1566
1597
  j = 0
1567
1598
  ns = data.shape[0]
1568
1599
  while j < ns:
1569
1600
  yield data[j:j+num]
1570
1601
  j += num
1571
1602
 
1572
-
1573
- class FiltOctave( Filter ):
1574
- """
1575
- Octave or third-octave filter (causal, non-zero phase delay).
1576
- """
1577
- #: Band center frequency; defaults to 1000.
1578
- band = Float(1000.0,
1579
- desc = "band center frequency")
1580
-
1581
- #: Octave fraction: 'Octave' or 'Third octave'; defaults to 'Octave'.
1582
- fraction = Trait('Octave', {'Octave':1, 'Third octave':3},
1583
- desc = "fraction of octave")
1584
-
1585
- #: Filter order
1586
- order = Int(3, desc = "IIR filter order")
1587
-
1588
- ba = Property( depends_on = ['band', 'fraction', 'source.digest', 'order'])
1589
-
1590
- # internal identifier
1591
- digest = Property( depends_on = ['source.digest', '__class__', \
1592
- 'band', 'fraction','order'])
1593
-
1594
- @cached_property
1595
- def _get_digest( self ):
1596
- return digest(self)
1597
-
1598
- @cached_property
1599
- def _get_ba( self ):
1600
- # filter design
1601
- fs = self.sample_freq
1602
- # adjust filter edge frequencies
1603
- beta = pi/(4*self.order)
1604
- alpha = pow(2.0, 1.0/(2.0*self.fraction_))
1605
- beta = 2 * beta / sin(beta) / (alpha-1/alpha)
1606
- alpha = (1+sqrt(1+beta*beta))/beta
1607
- fr = 2*self.band/fs
1608
- if fr > 1/sqrt(2):
1609
- raise ValueError("band frequency too high:%f,%f" % (self.band, fs))
1610
- om1 = fr/alpha
1611
- om2 = fr*alpha
1612
- return butter(self.order, [om1, om2], 'bandpass')
1613
-
1614
1603
  class TimeExpAverage(Filter):
1615
1604
  """
1616
1605
  Computes exponential averaging according to IEC 61672-1
@@ -1622,7 +1611,7 @@ class TimeExpAverage(Filter):
1622
1611
  weight = Trait('F', {'F':0.125, 'S':1.0, 'I':0.035},
1623
1612
  desc = "time weighting")
1624
1613
 
1625
- ba = Property( depends_on = ['weight', 'source.digest'])
1614
+ sos = Property( depends_on = ['weight', 'source.digest'])
1626
1615
 
1627
1616
  # internal identifier
1628
1617
  digest = Property( depends_on = ['source.digest', '__class__', \
@@ -1633,11 +1622,11 @@ class TimeExpAverage(Filter):
1633
1622
  return digest(self)
1634
1623
 
1635
1624
  @cached_property
1636
- def _get_ba( self ):
1625
+ def _get_sos( self ):
1637
1626
  alpha = 1-exp(-1/self.weight_/self.sample_freq)
1638
1627
  a = [1, alpha-1]
1639
1628
  b = [alpha]
1640
- return b,a
1629
+ return tf2sos(b,a)
1641
1630
 
1642
1631
  class FiltFreqWeight( Filter ):
1643
1632
  """
@@ -1646,17 +1635,18 @@ class FiltFreqWeight( Filter ):
1646
1635
  #: weighting characteristics
1647
1636
  weight = Trait('A',('A','C','Z'), desc="frequency weighting")
1648
1637
 
1649
- ba = Property( depends_on = ['weight', 'source.digest'])
1638
+ sos = Property( depends_on = ['weight', 'source.digest'])
1650
1639
 
1651
1640
  # internal identifier
1652
- digest = Property( depends_on = ['source.digest', '__class__'])
1641
+ digest = Property( depends_on = ['source.digest', '__class__', \
1642
+ 'weight'])
1653
1643
 
1654
1644
  @cached_property
1655
1645
  def _get_digest( self ):
1656
1646
  return digest(self)
1657
1647
 
1658
1648
  @cached_property
1659
- def _get_ba( self ):
1649
+ def _get_sos( self ):
1660
1650
  # s domain coefficients
1661
1651
  f1 = 20.598997
1662
1652
  f2 = 107.65265
@@ -1677,7 +1667,7 @@ class FiltFreqWeight( Filter ):
1677
1667
  b = zeros(7)
1678
1668
  b[0] = 1.0
1679
1669
  a = b # 6th order flat response
1680
- return b,a
1670
+ return tf2sos(b,a)
1681
1671
 
1682
1672
  class FilterBank(TimeInOut):
1683
1673
  """
@@ -1688,7 +1678,7 @@ class FilterBank(TimeInOut):
1688
1678
  """
1689
1679
 
1690
1680
  #: List of filter coefficients for all filters
1691
- ba = Property()
1681
+ sos = Property()
1692
1682
 
1693
1683
  #: List of labels for bands
1694
1684
  bands = Property()
@@ -1699,8 +1689,8 @@ class FilterBank(TimeInOut):
1699
1689
  #: Number of bands
1700
1690
  numchannels = Property()
1701
1691
 
1702
- def _get_ba( self ):
1703
- return [[1]],[[1]]
1692
+ def _get_sos( self ):
1693
+ return [tf2sos([1],[1])]
1704
1694
 
1705
1695
  def _get_bands( self ):
1706
1696
  return ['']
@@ -1729,13 +1719,13 @@ class FilterBank(TimeInOut):
1729
1719
  """
1730
1720
  numbands = self.numbands
1731
1721
  snumch = self.source.numchannels
1732
- b, a = self.ba
1733
- zi = [zeros( (max(len(a[0]), len(b[0]))-1, snumch)) for _ in range(numbands)]
1722
+ sos = self.sos
1723
+ zi = [zeros( (sos[0].shape[0],2, snumch)) for _ in range(numbands)]
1734
1724
  res = zeros((num,self.numchannels),dtype='float')
1735
1725
  for block in self.source.result(num):
1736
1726
  bl = block.shape[0]
1737
1727
  for i in range(numbands):
1738
- res[:,i*snumch:(i+1)*snumch], zi[i] = lfilter(b[i], a[i], block, axis=0, zi=zi[i])
1728
+ res[:,i*snumch:(i+1)*snumch], zi[i] = sosfilt(sos[i], block, axis=0, zi=zi[i])
1739
1729
  yield res
1740
1730
 
1741
1731
  class OctaveFilterBank(FilterBank):
@@ -1780,15 +1770,14 @@ class OctaveFilterBank(FilterBank):
1780
1770
  return len(self.bands)
1781
1771
 
1782
1772
  @cached_property
1783
- def _get_ba( self ):
1773
+ def _get_sos( self ):
1784
1774
  of = FiltOctave(source=self.source, fraction=self.fraction)
1785
- b, a = [], []
1775
+ sos = []
1786
1776
  for i in range(self.lband,self.hband,4-self.fraction_):
1787
1777
  of.band = 10**(i/10)
1788
- b_,a_ = of.ba
1789
- b.append(b_)
1790
- a.append(a_)
1791
- return b, a
1778
+ sos_ = of.sos
1779
+ sos.append(sos_)
1780
+ return sos
1792
1781
 
1793
1782
  class TimeCache( TimeInOut ):
1794
1783
  """
acoular/version.py CHANGED
@@ -5,5 +5,5 @@
5
5
 
6
6
  # separate file to find out about version without importing the acoular lib
7
7
  __author__ = "Acoular Development Team"
8
- __date__ = "26 June 2023"
9
- __version__ = "23.6"
8
+ __date__ = "11 March 2024"
9
+ __version__ = "24.03"
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.1
2
+ Name: acoular
3
+ Version: 24.3
4
+ Summary: Python library for acoustic beamforming
5
+ Project-URL: homepage, https://acoular.org
6
+ Project-URL: documentation, https://acoular.org
7
+ Project-URL: repository, https://github.com/acoular/acoular
8
+ Author-email: Acoular Development Team <info@acoular.org>
9
+ Maintainer-email: Adam Kujawski <adam.kujawski@tu-berlin.de>, Art Pelling <a.pelling@tu-berlin.de>, Ennes Sarradj <ennes.sarradj@tu-berlin.de>, Gert Herold <gert.herold@tu-berlin.de>, Mikolaj Czuchaj <mikolaj.czuchaj@tu-berlin.de>, Simon Jekosch <s.jekosch@tu-berlin.de>
10
+ License: Copyright (c) Acoular Development Team.
11
+ All rights reserved.
12
+
13
+
14
+ Redistribution and use in source and binary forms, with or without
15
+ modification, are permitted provided that the following conditions are met:
16
+
17
+ a. Redistributions of source code must retain the above copyright notice,
18
+ this list of conditions and the following disclaimer.
19
+ b. Redistributions in binary form must reproduce the above copyright
20
+ notice, this list of conditions and the following disclaimer in the
21
+ documentation and/or other materials provided with the distribution.
22
+ c. Neither the name of the acoular developers nor the names of
23
+ its contributors may be used to endorse or promote products
24
+ derived from this software without specific prior written
25
+ permission.
26
+
27
+
28
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
32
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
38
+ DAMAGE.
39
+ License-File: AUTHORS.rst
40
+ License-File: LICENSE
41
+ Keywords: acoustics,beamforming,microphone array
42
+ Classifier: Development Status :: 5 - Production/Stable
43
+ Classifier: Intended Audience :: Education
44
+ Classifier: Intended Audience :: Science/Research
45
+ Classifier: License :: OSI Approved :: BSD License
46
+ Classifier: Programming Language :: Python :: 3.7
47
+ Classifier: Programming Language :: Python :: 3.8
48
+ Classifier: Programming Language :: Python :: 3.9
49
+ Classifier: Programming Language :: Python :: 3.10
50
+ Classifier: Programming Language :: Python :: 3.11
51
+ Classifier: Topic :: Scientific/Engineering :: Physics
52
+ Requires-Python: <=11,>=3.7
53
+ Requires-Dist: numba
54
+ Requires-Dist: numpy
55
+ Requires-Dist: scikit-learn
56
+ Requires-Dist: scipy>=1.1.0
57
+ Requires-Dist: tables>=3.4.4
58
+ Requires-Dist: traits>=6.0
59
+ Provides-Extra: dev
60
+ Requires-Dist: graphviz; extra == 'dev'
61
+ Requires-Dist: ipython; extra == 'dev'
62
+ Requires-Dist: matplotlib; extra == 'dev'
63
+ Requires-Dist: nox; extra == 'dev'
64
+ Requires-Dist: numpydoc; extra == 'dev'
65
+ Requires-Dist: pickleshare; extra == 'dev'
66
+ Requires-Dist: sounddevice; extra == 'dev'
67
+ Requires-Dist: sphinx; extra == 'dev'
68
+ Provides-Extra: full
69
+ Requires-Dist: matplotlib; extra == 'full'
70
+ Requires-Dist: pylops; extra == 'full'
71
+ Requires-Dist: sounddevice; extra == 'full'
72
+ Description-Content-Type: text/markdown
73
+
74
+ ![Acoular Logo](./docs/source/_static/Acoular_logo.png)
75
+
76
+ [![PyPI](https://img.shields.io/pypi/pyversions/acoular.svg)](https://pypi.org/project/acoular)
77
+ [![PyPI](https://img.shields.io/pypi/v/acoular.svg)](https://pypi.org/project/acoular)
78
+ [![Github](https://github.com/acoular/acoular/actions/workflows/python-package.yml/badge.svg)](https://github.com/acoular/acoular/actions/workflows/python-package.yml)
79
+
80
+ # Acoular
81
+ Acoular is a Python module for acoustic beamforming that is distributed under the new BSD license.
82
+
83
+ It is aimed at applications in acoustic testing. Multichannel data recorded by a microphone array can be processed and analyzed in order to generate mappings of sound source distributions. The maps (acoustic photographs) can then be used to locate sources of interest and to characterize them using their spectra.
84
+
85
+ # Features
86
+ - frequency domain beamforming algorithms: delay & sum, Capon (adaptive), MUSIC, functional beamforming, eigenvalue beamforming
87
+ - frequency domain deconvolution algorithms: DAMAS, DAMAS+, Clean, CleanSC, orthogonal deconvolution
88
+ - frequency domain inverse methods: CMF (covariance matrix fitting), general inverse beamforming, SODIX
89
+ - time domain methods: delay & sum beamforming, CleanT deconvolution
90
+ - time domain methods applicable for moving source with arbitrary trajectory (linear, circular, arbitrarily 3D curved),
91
+ - frequency domain methods for rotating sources via virtual array rotation for arbitrary arrays and with different interpolation techniques
92
+ - 1D, 2D and 3D mapping grids for all methods
93
+ - gridless option for orthogonal deconvolution
94
+ - four different built-in steering vector formulations
95
+ - arbitrary stationary background flow can be considered for all methods
96
+ - efficient cross spectral matrix computation
97
+ - flexible modular time domain processing: n-th octave band filters, fast, slow, and impulse weighting, A-, C-, and Z-weighting, filter bank, zero delay filters
98
+ - time domain simulation of array microphone signals from fixed and arbitrarily moving sources in arbitrary flow
99
+ - fully object-oriented interface
100
+ - lazy evaluation: while processing blocks are set up at any time, (expensive) computations are only performed when needed
101
+ - intelligent and transparent caching: computed results are automatically saved and loaded on the next run to avoid unnecessary re-computation
102
+ - parallel (multithreaded) implementation with Numba for most algorithms
103
+ - easily extendable with new algorithms
104
+
105
+ # License
106
+ Acoular is licensed under the BSD 3-clause. See [LICENSE](LICENSE)
107
+
108
+ # Citing
109
+
110
+ If you use Acoular for academic work, please consider citing our
111
+ [publication](https://doi.org/10.1016/j.apacoust.2016.09.015):
112
+
113
+ Ennes Sarradj, Gert Herold,
114
+ A Python framework for microphone array data processing,
115
+ Applied Acoustics, Volume 116, 2017, Pages 50-58
116
+
117
+ # Dependencies
118
+ Acoular runs under Linux, Windows and MacOS and needs Numpy, Scipy, Traits, scikit-learn, pytables, Numba packages available.
119
+ Matplotlib is needed for some of the examples.
120
+
121
+ If you want to use input from a soundcard hardware, you will also need to install the [sounddevice](https://python-sounddevice.readthedocs.io/en/0.3.12/installation.html) package. Some solvers for the CMF method need [Pylops](https://pylops.readthedocs.io/en/stable/installation.html).
122
+
123
+ # Installation
124
+
125
+ Acoular can be installed via [conda](https://docs.conda.io/en/latest/), which is also part of the [Anaconda Python distribution](https://www.anaconda.com/). It is recommended to install into a dedicated [conda environment](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html). After activating this environment, run
126
+
127
+ conda install -c acoular acoular
128
+
129
+ This will install Acoular in your Anaconda Python enviroment and make the Acoular library available from Python. In addition, this will install all dependencies (those other packages mentioned above) if they are not already present on your system.
130
+
131
+ A second option is to install Acoular via [pip](https://pip.pypa.io/en/stable/). It is recommended to use a dedicated [virtual environment](https://virtualenv.pypa.io/en/latest/) and then run
132
+
133
+ pip install acoular
134
+
135
+ For more detailed install instructions see the [documentation](http://acoular.org/install/index.html).
136
+
137
+ # Documentation and help
138
+ Documentation is available [here](http://acoular.org) with a
139
+ [getting started](http://acoular.org/get_started/index.html) section and
140
+ [examples](http://acoular.org/examples/index.html).
141
+
142
+ The Acoular [blog](https://acoular.github.io/blog/) contains some tutorials.
143
+
144
+ Problems, suggestions and success using Acoular may be reported via the [acoular-users](https://groups.google.com/forum/#!forum/acoular-users) discussion forum.
145
+
146
+ # Example
147
+ This reads data from 64 microphone channels and computes a beamforming map for the 8kHz third octave band:
148
+
149
+ ```python
150
+ from os import path
151
+ import acoular
152
+ from matplotlib.pylab import figure, plot, axis, imshow, colorbar, show
153
+
154
+ # this file contains the microphone coordinates
155
+ micgeofile = path.join(path.split(acoular.__file__)[0],'xml','array_64.xml')
156
+ # set up object managing the microphone coordinates
157
+ mg = acoular.MicGeom( from_file=micgeofile )
158
+ # set up object managing the microphone array data (usually from measurement)
159
+ ts = acoular.TimeSamples( name='three_sources.h5' )
160
+ # set up object managing the cross spectral matrix computation
161
+ ps = acoular.PowerSpectra( time_data=ts, block_size=128, window='Hanning' )
162
+ # set up object managing the mapping grid
163
+ rg = acoular.RectGrid( x_min=-0.2, x_max=0.2, y_min=-0.2, y_max=0.2, z=0.3, \
164
+ increment=0.01 )
165
+ # set up steering vector, implicitely contains also the standard quiescent
166
+ # environment with standard speed of sound
167
+ st = acoular.SteeringVector( grid = rg, mics=mg )
168
+ # set up the object managing the delay & sum beamformer
169
+ bb = acoular.BeamformerBase( freq_data=ps, steer=st )
170
+ # request the result in the 8kHz third octave band from approriate FFT-Lines
171
+ # this starts the actual computation (data intake, FFT, Welch CSM, beamforming)
172
+ pm = bb.synthetic( 8000, 3 )
173
+ # compute the sound pressure level
174
+ Lm = acoular.L_p( pm )
175
+ # plot the map
176
+ imshow( Lm.T, origin='lower', vmin=Lm.max()-10, extent=rg.extend(), \
177
+ interpolation='bicubic')
178
+ colorbar()
179
+ ```
180
+
181
+ ![result](./docs/source/get_started/three_source_py3_colormap.png)