You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

math.py 3.8 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. #!/usr/bin/env python3
  2. # Miscellaenous useful mathematical functions
  3. from numpy import *
  4. import scipy
  5. def numpy_raise_errors(func):
  6. def wrap(*args, **kwargs):
  7. old = seterr('raise')
  8. try:
  9. return func(*args, **kwargs)
  10. finally:
  11. seterr(**old)
  12. return wrap
  13. @numpy_raise_errors
  14. def sfit4(data, fs):
  15. """(A, f0, phi, C) = sfit4(data, fs)
  16. Compute 4-parameter (unknown-frequency) least-squares fit to
  17. sine-wave data, according to IEEE Std 1241-2010 Annex B
  18. Input:
  19. data vector of input samples
  20. fs sampling rate (Hz)
  21. Output:
  22. Parameters [A, f0, phi, C] to fit the equation
  23. x[n] = A * sin(f0/fs * 2 * pi * n + phi) + C
  24. where n is sample number. Or, as a function of time:
  25. x(t) = A * sin(f0 * 2 * pi * t + phi) + C
  26. by Jim Paris
  27. (Verified to match sfit4.m)
  28. """
  29. N = len(data)
  30. if N < 2:
  31. raise ValueError("bad data")
  32. t = linspace(0, (N-1) / float(fs), N)
  33. #
  34. # Estimate frequency using FFT (step b)
  35. #
  36. Fc = scipy.fft.fft(data)
  37. F = abs(Fc)
  38. F[0] = 0 # eliminate DC
  39. # Find pair of spectral lines with largest amplitude:
  40. # resulting values are in F(i) and F(i+1)
  41. i = argmax(F[0:int(N/2)] + F[1:int(N/2+1)])
  42. # Interpolate FFT to get a better result (from Markus [B37])
  43. try:
  44. U1 = real(Fc[i])
  45. U2 = real(Fc[i+1])
  46. V1 = imag(Fc[i])
  47. V2 = imag(Fc[i+1])
  48. n = 2 * pi / N
  49. ni1 = n * i
  50. ni2 = n * (i+1)
  51. K = ((V2-V1)*sin(ni1) + (U2-U1)*cos(ni1)) / (U2-U1)
  52. Z1 = V1 * (K - cos(ni1)) / sin(ni1) + U1
  53. Z2 = V2 * (K - cos(ni2)) / sin(ni2) + U2
  54. i = arccos((Z2*cos(ni2) - Z1*cos(ni1)) / (Z2-Z1)) / n
  55. except Exception:
  56. # Just go with the biggest FFT peak
  57. i = argmax(F[0:int(N/2)])
  58. # Convert to Hz
  59. f0 = i * float(fs) / N
  60. # Fit it. We'll catch exceptions here and just returns zeros
  61. # if something fails with the least squares fit, etc.
  62. try:
  63. # first guess for A0, B0 using 3-parameter fit (step c)
  64. s = zeros(3)
  65. w = 2*pi*f0
  66. # Now iterate 7 times (step b, plus 6 iterations of step i)
  67. for idx in range(7):
  68. D = c_[cos(w*t), sin(w*t), ones(N),
  69. -s[0] * t * sin(w*t) + s[1] * t * cos(w*t)] # eqn B.16
  70. s = linalg.lstsq(D, data, rcond=None)[0] # eqn B.18
  71. w = w + s[3] # update frequency estimate
  72. #
  73. # Extract results
  74. #
  75. A = sqrt(s[0]*s[0] + s[1]*s[1]) # eqn B.21
  76. f0 = w / (2*pi)
  77. phi = arctan2(s[0], s[1]) # eqn B.22 (flipped for sin instead of cos)
  78. C = s[2]
  79. return (A, f0, phi, C)
  80. except Exception: # pragma: no cover (not sure if we can hit this?)
  81. # something broke down; just return zeros
  82. return (0, 0, 0, 0)
  83. def peak_detect(data, delta=0.1):
  84. """Simple min/max peak detection algorithm, taken from my code
  85. in the disagg.m from the 10-8-5 paper.
  86. Returns an array of peaks: each peak is a tuple
  87. (n, p, is_max)
  88. where n is the row number in 'data', and p is 'data[n]',
  89. and is_max is True if this is a maximum, False if it's a minimum,
  90. """
  91. peaks = []
  92. cur_min = (None, inf)
  93. cur_max = (None, -inf)
  94. lookformax = False
  95. for (n, p) in enumerate(data):
  96. if p > cur_max[1]:
  97. cur_max = (n, p)
  98. if p < cur_min[1]:
  99. cur_min = (n, p)
  100. if lookformax:
  101. if p < (cur_max[1] - delta):
  102. peaks.append((cur_max[0], cur_max[1], True))
  103. cur_min = (n, p)
  104. lookformax = False
  105. else:
  106. if p > (cur_min[1] + delta):
  107. peaks.append((cur_min[0], cur_min[1], False))
  108. cur_max = (n, p)
  109. lookformax = True
  110. return peaks