test_savitzky_golay.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. from __future__ import division, print_function, absolute_import
  2. import numpy as np
  3. from numpy.testing import (assert_allclose, assert_equal,
  4. assert_almost_equal, assert_array_equal,
  5. assert_array_almost_equal)
  6. from scipy.ndimage import convolve1d
  7. from scipy.signal import savgol_coeffs, savgol_filter
  8. from scipy.signal._savitzky_golay import _polyder
  9. def check_polyder(p, m, expected):
  10. dp = _polyder(p, m)
  11. assert_array_equal(dp, expected)
  12. def test_polyder():
  13. cases = [
  14. ([5], 0, [5]),
  15. ([5], 1, [0]),
  16. ([3, 2, 1], 0, [3, 2, 1]),
  17. ([3, 2, 1], 1, [6, 2]),
  18. ([3, 2, 1], 2, [6]),
  19. ([3, 2, 1], 3, [0]),
  20. ([[3, 2, 1], [5, 6, 7]], 0, [[3, 2, 1], [5, 6, 7]]),
  21. ([[3, 2, 1], [5, 6, 7]], 1, [[6, 2], [10, 6]]),
  22. ([[3, 2, 1], [5, 6, 7]], 2, [[6], [10]]),
  23. ([[3, 2, 1], [5, 6, 7]], 3, [[0], [0]]),
  24. ]
  25. for p, m, expected in cases:
  26. check_polyder(np.array(p).T, m, np.array(expected).T)
  27. #--------------------------------------------------------------------
  28. # savgol_coeffs tests
  29. #--------------------------------------------------------------------
  30. def alt_sg_coeffs(window_length, polyorder, pos):
  31. """This is an alternative implementation of the SG coefficients.
  32. It uses numpy.polyfit and numpy.polyval. The results should be
  33. equivalent to those of savgol_coeffs(), but this implementation
  34. is slower.
  35. window_length should be odd.
  36. """
  37. if pos is None:
  38. pos = window_length // 2
  39. t = np.arange(window_length)
  40. unit = (t == pos).astype(int)
  41. h = np.polyval(np.polyfit(t, unit, polyorder), t)
  42. return h
  43. def test_sg_coeffs_trivial():
  44. # Test a trivial case of savgol_coeffs: polyorder = window_length - 1
  45. h = savgol_coeffs(1, 0)
  46. assert_allclose(h, [1])
  47. h = savgol_coeffs(3, 2)
  48. assert_allclose(h, [0, 1, 0], atol=1e-10)
  49. h = savgol_coeffs(5, 4)
  50. assert_allclose(h, [0, 0, 1, 0, 0], atol=1e-10)
  51. h = savgol_coeffs(5, 4, pos=1)
  52. assert_allclose(h, [0, 0, 0, 1, 0], atol=1e-10)
  53. h = savgol_coeffs(5, 4, pos=1, use='dot')
  54. assert_allclose(h, [0, 1, 0, 0, 0], atol=1e-10)
  55. def compare_coeffs_to_alt(window_length, order):
  56. # For the given window_length and order, compare the results
  57. # of savgol_coeffs and alt_sg_coeffs for pos from 0 to window_length - 1.
  58. # Also include pos=None.
  59. for pos in [None] + list(range(window_length)):
  60. h1 = savgol_coeffs(window_length, order, pos=pos, use='dot')
  61. h2 = alt_sg_coeffs(window_length, order, pos=pos)
  62. assert_allclose(h1, h2, atol=1e-10,
  63. err_msg=("window_length = %d, order = %d, pos = %s" %
  64. (window_length, order, pos)))
  65. def test_sg_coeffs_compare():
  66. # Compare savgol_coeffs() to alt_sg_coeffs().
  67. for window_length in range(1, 8, 2):
  68. for order in range(window_length):
  69. compare_coeffs_to_alt(window_length, order)
  70. def test_sg_coeffs_exact():
  71. polyorder = 4
  72. window_length = 9
  73. halflen = window_length // 2
  74. x = np.linspace(0, 21, 43)
  75. delta = x[1] - x[0]
  76. # The data is a cubic polynomial. We'll use an order 4
  77. # SG filter, so the filtered values should equal the input data
  78. # (except within half window_length of the edges).
  79. y = 0.5 * x ** 3 - x
  80. h = savgol_coeffs(window_length, polyorder)
  81. y0 = convolve1d(y, h)
  82. assert_allclose(y0[halflen:-halflen], y[halflen:-halflen])
  83. # Check the same input, but use deriv=1. dy is the exact result.
  84. dy = 1.5 * x ** 2 - 1
  85. h = savgol_coeffs(window_length, polyorder, deriv=1, delta=delta)
  86. y1 = convolve1d(y, h)
  87. assert_allclose(y1[halflen:-halflen], dy[halflen:-halflen])
  88. # Check the same input, but use deriv=2. d2y is the exact result.
  89. d2y = 3.0 * x
  90. h = savgol_coeffs(window_length, polyorder, deriv=2, delta=delta)
  91. y2 = convolve1d(y, h)
  92. assert_allclose(y2[halflen:-halflen], d2y[halflen:-halflen])
  93. def test_sg_coeffs_deriv():
  94. # The data in `x` is a sampled parabola, so using savgol_coeffs with an
  95. # order 2 or higher polynomial should give exact results.
  96. i = np.array([-2.0, 0.0, 2.0, 4.0, 6.0])
  97. x = i ** 2 / 4
  98. dx = i / 2
  99. d2x = 0.5 * np.ones_like(i)
  100. for pos in range(x.size):
  101. coeffs0 = savgol_coeffs(5, 3, pos=pos, delta=2.0, use='dot')
  102. assert_allclose(coeffs0.dot(x), x[pos], atol=1e-10)
  103. coeffs1 = savgol_coeffs(5, 3, pos=pos, delta=2.0, use='dot', deriv=1)
  104. assert_allclose(coeffs1.dot(x), dx[pos], atol=1e-10)
  105. coeffs2 = savgol_coeffs(5, 3, pos=pos, delta=2.0, use='dot', deriv=2)
  106. assert_allclose(coeffs2.dot(x), d2x[pos], atol=1e-10)
  107. def test_sg_coeffs_large():
  108. # Test that for large values of window_length and polyorder the array of
  109. # coefficients returned is symmetric. The aim is to ensure that
  110. # no potential numeric overflow occurs.
  111. coeffs0 = savgol_coeffs(31, 9)
  112. assert_array_almost_equal(coeffs0, coeffs0[::-1])
  113. coeffs1 = savgol_coeffs(31, 9, deriv=1)
  114. assert_array_almost_equal(coeffs1, -coeffs1[::-1])
  115. #--------------------------------------------------------------------
  116. # savgol_filter tests
  117. #--------------------------------------------------------------------
  118. def test_sg_filter_trivial():
  119. """ Test some trivial edge cases for savgol_filter()."""
  120. x = np.array([1.0])
  121. y = savgol_filter(x, 1, 0)
  122. assert_equal(y, [1.0])
  123. # Input is a single value. With a window length of 3 and polyorder 1,
  124. # the value in y is from the straight-line fit of (-1,0), (0,3) and
  125. # (1, 0) at 0. This is just the average of the three values, hence 1.0.
  126. x = np.array([3.0])
  127. y = savgol_filter(x, 3, 1, mode='constant')
  128. assert_almost_equal(y, [1.0], decimal=15)
  129. x = np.array([3.0])
  130. y = savgol_filter(x, 3, 1, mode='nearest')
  131. assert_almost_equal(y, [3.0], decimal=15)
  132. x = np.array([1.0] * 3)
  133. y = savgol_filter(x, 3, 1, mode='wrap')
  134. assert_almost_equal(y, [1.0, 1.0, 1.0], decimal=15)
  135. def test_sg_filter_basic():
  136. # Some basic test cases for savgol_filter().
  137. x = np.array([1.0, 2.0, 1.0])
  138. y = savgol_filter(x, 3, 1, mode='constant')
  139. assert_allclose(y, [1.0, 4.0 / 3, 1.0])
  140. y = savgol_filter(x, 3, 1, mode='mirror')
  141. assert_allclose(y, [5.0 / 3, 4.0 / 3, 5.0 / 3])
  142. y = savgol_filter(x, 3, 1, mode='wrap')
  143. assert_allclose(y, [4.0 / 3, 4.0 / 3, 4.0 / 3])
  144. def test_sg_filter_2d():
  145. x = np.array([[1.0, 2.0, 1.0],
  146. [2.0, 4.0, 2.0]])
  147. expected = np.array([[1.0, 4.0 / 3, 1.0],
  148. [2.0, 8.0 / 3, 2.0]])
  149. y = savgol_filter(x, 3, 1, mode='constant')
  150. assert_allclose(y, expected)
  151. y = savgol_filter(x.T, 3, 1, mode='constant', axis=0)
  152. assert_allclose(y, expected.T)
  153. def test_sg_filter_interp_edges():
  154. # Another test with low degree polynomial data, for which we can easily
  155. # give the exact results. In this test, we use mode='interp', so
  156. # savgol_filter should match the exact solution for the entire data set,
  157. # including the edges.
  158. t = np.linspace(-5, 5, 21)
  159. delta = t[1] - t[0]
  160. # Polynomial test data.
  161. x = np.array([t,
  162. 3 * t ** 2,
  163. t ** 3 - t])
  164. dx = np.array([np.ones_like(t),
  165. 6 * t,
  166. 3 * t ** 2 - 1.0])
  167. d2x = np.array([np.zeros_like(t),
  168. 6 * np.ones_like(t),
  169. 6 * t])
  170. window_length = 7
  171. y = savgol_filter(x, window_length, 3, axis=-1, mode='interp')
  172. assert_allclose(y, x, atol=1e-12)
  173. y1 = savgol_filter(x, window_length, 3, axis=-1, mode='interp',
  174. deriv=1, delta=delta)
  175. assert_allclose(y1, dx, atol=1e-12)
  176. y2 = savgol_filter(x, window_length, 3, axis=-1, mode='interp',
  177. deriv=2, delta=delta)
  178. assert_allclose(y2, d2x, atol=1e-12)
  179. # Transpose everything, and test again with axis=0.
  180. x = x.T
  181. dx = dx.T
  182. d2x = d2x.T
  183. y = savgol_filter(x, window_length, 3, axis=0, mode='interp')
  184. assert_allclose(y, x, atol=1e-12)
  185. y1 = savgol_filter(x, window_length, 3, axis=0, mode='interp',
  186. deriv=1, delta=delta)
  187. assert_allclose(y1, dx, atol=1e-12)
  188. y2 = savgol_filter(x, window_length, 3, axis=0, mode='interp',
  189. deriv=2, delta=delta)
  190. assert_allclose(y2, d2x, atol=1e-12)
  191. def test_sg_filter_interp_edges_3d():
  192. # Test mode='interp' with a 3-D array.
  193. t = np.linspace(-5, 5, 21)
  194. delta = t[1] - t[0]
  195. x1 = np.array([t, -t])
  196. x2 = np.array([t ** 2, 3 * t ** 2 + 5])
  197. x3 = np.array([t ** 3, 2 * t ** 3 + t ** 2 - 0.5 * t])
  198. dx1 = np.array([np.ones_like(t), -np.ones_like(t)])
  199. dx2 = np.array([2 * t, 6 * t])
  200. dx3 = np.array([3 * t ** 2, 6 * t ** 2 + 2 * t - 0.5])
  201. # z has shape (3, 2, 21)
  202. z = np.array([x1, x2, x3])
  203. dz = np.array([dx1, dx2, dx3])
  204. y = savgol_filter(z, 7, 3, axis=-1, mode='interp', delta=delta)
  205. assert_allclose(y, z, atol=1e-10)
  206. dy = savgol_filter(z, 7, 3, axis=-1, mode='interp', deriv=1, delta=delta)
  207. assert_allclose(dy, dz, atol=1e-10)
  208. # z has shape (3, 21, 2)
  209. z = np.array([x1.T, x2.T, x3.T])
  210. dz = np.array([dx1.T, dx2.T, dx3.T])
  211. y = savgol_filter(z, 7, 3, axis=1, mode='interp', delta=delta)
  212. assert_allclose(y, z, atol=1e-10)
  213. dy = savgol_filter(z, 7, 3, axis=1, mode='interp', deriv=1, delta=delta)
  214. assert_allclose(dy, dz, atol=1e-10)
  215. # z has shape (21, 3, 2)
  216. z = z.swapaxes(0, 1).copy()
  217. dz = dz.swapaxes(0, 1).copy()
  218. y = savgol_filter(z, 7, 3, axis=0, mode='interp', delta=delta)
  219. assert_allclose(y, z, atol=1e-10)
  220. dy = savgol_filter(z, 7, 3, axis=0, mode='interp', deriv=1, delta=delta)
  221. assert_allclose(dy, dz, atol=1e-10)