@@ -15,48 +15,85 @@ def pyx_exp_smoothed_value(np.ndarray[dtype_t, ndim=1] kernel, dtype_t alpha, np
1515 cdef Py_ssize_t Nk = kernel.shape[0 ]
1616 cdef Py_ssize_t i
1717 cdef dtype_t value, conv
18+ cdef double a = (1. - alpha)** N
1819
1920 # as we disable the cython bounds checking, do it by ourselves
2021 # it is absolutely needed as the kernel is not of infinite size !!
2122 if N > Nk:
2223 N = Nk
24+ a = 0.
2325
2426 if N == 0 :
2527 value = previous
26- else :
27- conv = 0.
28-
29- for i in range (0 , N):
30- conv = conv + kernel[Nk - N + i]* data[i]
31-
32- value = alpha * conv + previous* ( 1. - alpha) ** N
28+
29+ conv = 0.
30+
31+ for i in range (0 , N):
32+ conv = conv + kernel[Nk - N + i]* data[i]
33+
34+ value = alpha * conv + previous* a
3335
3436 return value
3537
38+
3639def pyx_exp_smoothed_value_numpy (np.ndarray[dtype_t , ndim = 1 ] kernel, dtype_t alpha , np.ndarray[dtype_t , ndim = 2 ] data, np.ndarray[dtype_t , ndim = 1 ] previous):
37- cdef Py_ssize_t N = data.shape[1 ]
40+ """
41+ Compute exponential filtering of data with given kernel and alpha.
42+ The filter is applied on the 2nd axis (axis=1), independently for each row (axis=0).
43+
44+ This is equivalent to applying the following IIR filter on each row of data:
45+ y_i = alpha*x_i + (1-alpha)*y_{i-1}
46+ where x_i is the input data, y_i the filtered data, and y_{i-1} the previous filtered value.
47+
48+ We can unroll the recurrence a bit:
49+ y_{i+1} = a*x_{i+1} + (1-a)*y_i
50+ = a*x_{i+1} + (1-a)*a*x_i + (1-a)^2*y_{i-1}
51+ y_{i+2} = a*x_{i+2} + (1-a)*y_{i+1}
52+ = a*x_{i+2} + (1-a)*a*x_{i+1} + (1-a)^2*a*x_i + (1-a)^3*y_{i-1}
53+ ...
54+
55+ This unrolling can be implemented with a convolution of a precomputed kernel.
56+
57+ By using a precomputed kernel, this function is optimized to process a large number of samples at once.
58+
59+ Parameters:
60+ -----------
61+ kernel : 1D array
62+ the pre-computed convolution kernel for the exponential smoothing
63+ alpha : float
64+ the exponential smoothing factor, between 0 and 1
65+ data : 2D array
66+ the input data to filter
67+ previous : 1D array
68+ the previous filtered value
69+ Returns:
70+ --------
71+ 1D array
72+ the filtered value"""
3873 cdef Py_ssize_t Nf = data.shape[0 ]
74+ cdef Py_ssize_t Nt = data.shape[1 ]
3975 cdef Py_ssize_t Nk = kernel.shape[0 ]
4076 cdef Py_ssize_t i, j
4177 cdef np.ndarray[dtype_t, ndim= 1 ] value, conv
42- cdef double a = (1. - alpha)** N
78+ cdef double a = (1. - alpha)** Nt
4379
4480 # as we disable the cython bounds checking, do it by ourselves
4581 # it is absolutely needed as the kernel is not of infinite size !!
46- if N > Nk:
47- N = Nk
82+ if Nt > Nk:
83+ Nt = Nk
84+ a = 0.
4885
49- if N == 0 :
86+ if Nt == 0 :
5087 return previous
51- else :
52- conv = np.zeros(Nf)
53- value = np.zeros(Nf)
54-
55- for i in range (0 , N):
56- for j in range (Nf):
57- conv[j] = conv[j] + kernel[Nk - N + i]* data[j, i]
58-
88+
89+ conv = np.zeros(Nf, dtype = dtype)
90+ value = np.zeros(Nf, dtype = dtype)
91+
92+ for i in range (0 , Nt):
5993 for j in range (Nf):
60- value [j] = alpha * conv[j] + previous[j] * a
94+ conv [j] = conv[j] + kernel[Nk - Nt + i] * data[j, i]
6195
62- return value
96+ for j in range (Nf):
97+ value[j] = alpha * conv[j] + previous[j]* a
98+
99+ return value
0 commit comments