Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/scipy/signal/_savitzky_golay.py : 12%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import numpy as np
2from scipy.linalg import lstsq
3from math import factorial
4from scipy.ndimage import convolve1d
5from ._arraytools import axis_slice
8def savgol_coeffs(window_length, polyorder, deriv=0, delta=1.0, pos=None,
9 use="conv"):
10 """Compute the coefficients for a 1-D Savitzky-Golay FIR filter.
12 Parameters
13 ----------
14 window_length : int
15 The length of the filter window (i.e., the number of coefficients).
16 `window_length` must be an odd positive integer.
17 polyorder : int
18 The order of the polynomial used to fit the samples.
19 `polyorder` must be less than `window_length`.
20 deriv : int, optional
21 The order of the derivative to compute. This must be a
22 nonnegative integer. The default is 0, which means to filter
23 the data without differentiating.
24 delta : float, optional
25 The spacing of the samples to which the filter will be applied.
26 This is only used if deriv > 0.
27 pos : int or None, optional
28 If pos is not None, it specifies evaluation position within the
29 window. The default is the middle of the window.
30 use : str, optional
31 Either 'conv' or 'dot'. This argument chooses the order of the
32 coefficients. The default is 'conv', which means that the
33 coefficients are ordered to be used in a convolution. With
34 use='dot', the order is reversed, so the filter is applied by
35 dotting the coefficients with the data set.
37 Returns
38 -------
39 coeffs : 1-D ndarray
40 The filter coefficients.
42 References
43 ----------
44 A. Savitzky, M. J. E. Golay, Smoothing and Differentiation of Data by
45 Simplified Least Squares Procedures. Analytical Chemistry, 1964, 36 (8),
46 pp 1627-1639.
48 See Also
49 --------
50 savgol_filter
52 Notes
53 -----
55 .. versionadded:: 0.14.0
57 Examples
58 --------
59 >>> from scipy.signal import savgol_coeffs
60 >>> savgol_coeffs(5, 2)
61 array([-0.08571429, 0.34285714, 0.48571429, 0.34285714, -0.08571429])
62 >>> savgol_coeffs(5, 2, deriv=1)
63 array([ 2.00000000e-01, 1.00000000e-01, 2.07548111e-16, -1.00000000e-01,
64 -2.00000000e-01])
66 Note that use='dot' simply reverses the coefficients.
68 >>> savgol_coeffs(5, 2, pos=3)
69 array([ 0.25714286, 0.37142857, 0.34285714, 0.17142857, -0.14285714])
70 >>> savgol_coeffs(5, 2, pos=3, use='dot')
71 array([-0.14285714, 0.17142857, 0.34285714, 0.37142857, 0.25714286])
73 `x` contains data from the parabola x = t**2, sampled at
74 t = -1, 0, 1, 2, 3. `c` holds the coefficients that will compute the
75 derivative at the last position. When dotted with `x` the result should
76 be 6.
78 >>> x = np.array([1, 0, 1, 4, 9])
79 >>> c = savgol_coeffs(5, 2, pos=4, deriv=1, use='dot')
80 >>> c.dot(x)
81 6.0
82 """
84 # An alternative method for finding the coefficients when deriv=0 is
85 # t = np.arange(window_length)
86 # unit = (t == pos).astype(int)
87 # coeffs = np.polyval(np.polyfit(t, unit, polyorder), t)
88 # The method implemented here is faster.
90 # To recreate the table of sample coefficients shown in the chapter on
91 # the Savitzy-Golay filter in the Numerical Recipes book, use
92 # window_length = nL + nR + 1
93 # pos = nL + 1
94 # c = savgol_coeffs(window_length, M, pos=pos, use='dot')
96 if polyorder >= window_length:
97 raise ValueError("polyorder must be less than window_length.")
99 halflen, rem = divmod(window_length, 2)
101 if rem == 0:
102 raise ValueError("window_length must be odd.")
104 if pos is None:
105 pos = halflen
107 if not (0 <= pos < window_length):
108 raise ValueError("pos must be nonnegative and less than "
109 "window_length.")
111 if use not in ['conv', 'dot']:
112 raise ValueError("`use` must be 'conv' or 'dot'")
114 if deriv > polyorder:
115 coeffs = np.zeros(window_length)
116 return coeffs
118 # Form the design matrix A. The columns of A are powers of the integers
119 # from -pos to window_length - pos - 1. The powers (i.e., rows) range
120 # from 0 to polyorder. (That is, A is a vandermonde matrix, but not
121 # necessarily square.)
122 x = np.arange(-pos, window_length - pos, dtype=float)
123 if use == "conv":
124 # Reverse so that result can be used in a convolution.
125 x = x[::-1]
127 order = np.arange(polyorder + 1).reshape(-1, 1)
128 A = x ** order
130 # y determines which order derivative is returned.
131 y = np.zeros(polyorder + 1)
132 # The coefficient assigned to y[deriv] scales the result to take into
133 # account the order of the derivative and the sample spacing.
134 y[deriv] = factorial(deriv) / (delta ** deriv)
136 # Find the least-squares solution of A*c = y
137 coeffs, _, _, _ = lstsq(A, y)
139 return coeffs
142def _polyder(p, m):
143 """Differentiate polynomials represented with coefficients.
145 p must be a 1-D or 2-D array. In the 2-D case, each column gives
146 the coefficients of a polynomial; the first row holds the coefficients
147 associated with the highest power. m must be a nonnegative integer.
148 (numpy.polyder doesn't handle the 2-D case.)
149 """
151 if m == 0:
152 result = p
153 else:
154 n = len(p)
155 if n <= m:
156 result = np.zeros_like(p[:1, ...])
157 else:
158 dp = p[:-m].copy()
159 for k in range(m):
160 rng = np.arange(n - k - 1, m - k - 1, -1)
161 dp *= rng.reshape((n - m,) + (1,) * (p.ndim - 1))
162 result = dp
163 return result
166def _fit_edge(x, window_start, window_stop, interp_start, interp_stop,
167 axis, polyorder, deriv, delta, y):
168 """
169 Given an N-d array `x` and the specification of a slice of `x` from
170 `window_start` to `window_stop` along `axis`, create an interpolating
171 polynomial of each 1-D slice, and evaluate that polynomial in the slice
172 from `interp_start` to `interp_stop`. Put the result into the
173 corresponding slice of `y`.
174 """
176 # Get the edge into a (window_length, -1) array.
177 x_edge = axis_slice(x, start=window_start, stop=window_stop, axis=axis)
178 if axis == 0 or axis == -x.ndim:
179 xx_edge = x_edge
180 swapped = False
181 else:
182 xx_edge = x_edge.swapaxes(axis, 0)
183 swapped = True
184 xx_edge = xx_edge.reshape(xx_edge.shape[0], -1)
186 # Fit the edges. poly_coeffs has shape (polyorder + 1, -1),
187 # where '-1' is the same as in xx_edge.
188 poly_coeffs = np.polyfit(np.arange(0, window_stop - window_start),
189 xx_edge, polyorder)
191 if deriv > 0:
192 poly_coeffs = _polyder(poly_coeffs, deriv)
194 # Compute the interpolated values for the edge.
195 i = np.arange(interp_start - window_start, interp_stop - window_start)
196 values = np.polyval(poly_coeffs, i.reshape(-1, 1)) / (delta ** deriv)
198 # Now put the values into the appropriate slice of y.
199 # First reshape values to match y.
200 shp = list(y.shape)
201 shp[0], shp[axis] = shp[axis], shp[0]
202 values = values.reshape(interp_stop - interp_start, *shp[1:])
203 if swapped:
204 values = values.swapaxes(0, axis)
205 # Get a view of the data to be replaced by values.
206 y_edge = axis_slice(y, start=interp_start, stop=interp_stop, axis=axis)
207 y_edge[...] = values
210def _fit_edges_polyfit(x, window_length, polyorder, deriv, delta, axis, y):
211 """
212 Use polynomial interpolation of x at the low and high ends of the axis
213 to fill in the halflen values in y.
215 This function just calls _fit_edge twice, once for each end of the axis.
216 """
217 halflen = window_length // 2
218 _fit_edge(x, 0, window_length, 0, halflen, axis,
219 polyorder, deriv, delta, y)
220 n = x.shape[axis]
221 _fit_edge(x, n - window_length, n, n - halflen, n, axis,
222 polyorder, deriv, delta, y)
225def savgol_filter(x, window_length, polyorder, deriv=0, delta=1.0,
226 axis=-1, mode='interp', cval=0.0):
227 """ Apply a Savitzky-Golay filter to an array.
229 This is a 1-D filter. If `x` has dimension greater than 1, `axis`
230 determines the axis along which the filter is applied.
232 Parameters
233 ----------
234 x : array_like
235 The data to be filtered. If `x` is not a single or double precision
236 floating point array, it will be converted to type ``numpy.float64``
237 before filtering.
238 window_length : int
239 The length of the filter window (i.e., the number of coefficients).
240 `window_length` must be a positive odd integer. If `mode` is 'interp',
241 `window_length` must be less than or equal to the size of `x`.
242 polyorder : int
243 The order of the polynomial used to fit the samples.
244 `polyorder` must be less than `window_length`.
245 deriv : int, optional
246 The order of the derivative to compute. This must be a
247 nonnegative integer. The default is 0, which means to filter
248 the data without differentiating.
249 delta : float, optional
250 The spacing of the samples to which the filter will be applied.
251 This is only used if deriv > 0. Default is 1.0.
252 axis : int, optional
253 The axis of the array `x` along which the filter is to be applied.
254 Default is -1.
255 mode : str, optional
256 Must be 'mirror', 'constant', 'nearest', 'wrap' or 'interp'. This
257 determines the type of extension to use for the padded signal to
258 which the filter is applied. When `mode` is 'constant', the padding
259 value is given by `cval`. See the Notes for more details on 'mirror',
260 'constant', 'wrap', and 'nearest'.
261 When the 'interp' mode is selected (the default), no extension
262 is used. Instead, a degree `polyorder` polynomial is fit to the
263 last `window_length` values of the edges, and this polynomial is
264 used to evaluate the last `window_length // 2` output values.
265 cval : scalar, optional
266 Value to fill past the edges of the input if `mode` is 'constant'.
267 Default is 0.0.
269 Returns
270 -------
271 y : ndarray, same shape as `x`
272 The filtered data.
274 See Also
275 --------
276 savgol_coeffs
278 Notes
279 -----
280 Details on the `mode` options:
282 'mirror':
283 Repeats the values at the edges in reverse order. The value
284 closest to the edge is not included.
285 'nearest':
286 The extension contains the nearest input value.
287 'constant':
288 The extension contains the value given by the `cval` argument.
289 'wrap':
290 The extension contains the values from the other end of the array.
292 For example, if the input is [1, 2, 3, 4, 5, 6, 7, 8], and
293 `window_length` is 7, the following shows the extended data for
294 the various `mode` options (assuming `cval` is 0)::
296 mode | Ext | Input | Ext
297 -----------+---------+------------------------+---------
298 'mirror' | 4 3 2 | 1 2 3 4 5 6 7 8 | 7 6 5
299 'nearest' | 1 1 1 | 1 2 3 4 5 6 7 8 | 8 8 8
300 'constant' | 0 0 0 | 1 2 3 4 5 6 7 8 | 0 0 0
301 'wrap' | 6 7 8 | 1 2 3 4 5 6 7 8 | 1 2 3
303 .. versionadded:: 0.14.0
305 Examples
306 --------
307 >>> from scipy.signal import savgol_filter
308 >>> np.set_printoptions(precision=2) # For compact display.
309 >>> x = np.array([2, 2, 5, 2, 1, 0, 1, 4, 9])
311 Filter with a window length of 5 and a degree 2 polynomial. Use
312 the defaults for all other parameters.
314 >>> savgol_filter(x, 5, 2)
315 array([1.66, 3.17, 3.54, 2.86, 0.66, 0.17, 1. , 4. , 9. ])
317 Note that the last five values in x are samples of a parabola, so
318 when mode='interp' (the default) is used with polyorder=2, the last
319 three values are unchanged. Compare that to, for example,
320 `mode='nearest'`:
322 >>> savgol_filter(x, 5, 2, mode='nearest')
323 array([1.74, 3.03, 3.54, 2.86, 0.66, 0.17, 1. , 4.6 , 7.97])
325 """
326 if mode not in ["mirror", "constant", "nearest", "interp", "wrap"]:
327 raise ValueError("mode must be 'mirror', 'constant', 'nearest' "
328 "'wrap' or 'interp'.")
330 x = np.asarray(x)
331 # Ensure that x is either single or double precision floating point.
332 if x.dtype != np.float64 and x.dtype != np.float32:
333 x = x.astype(np.float64)
335 coeffs = savgol_coeffs(window_length, polyorder, deriv=deriv, delta=delta)
337 if mode == "interp":
338 if window_length > x.size:
339 raise ValueError("If mode is 'interp', window_length must be less "
340 "than or equal to the size of x.")
342 # Do not pad. Instead, for the elements within `window_length // 2`
343 # of the ends of the sequence, use the polynomial that is fitted to
344 # the last `window_length` elements.
345 y = convolve1d(x, coeffs, axis=axis, mode="constant")
346 _fit_edges_polyfit(x, window_length, polyorder, deriv, delta, axis, y)
347 else:
348 # Any mode other than 'interp' is passed on to ndimage.convolve1d.
349 y = convolve1d(x, coeffs, axis=axis, mode=mode, cval=cval)
351 return y