Scipy.signal low pass filter deliver wrong results

Hello everyone,

When implementing low-pass filters for the development of an application for my department, an error occurs that I cannot solve.

The goal is the low-pass filtering of acceleration measurements (Nx4 array: time + 3-axis). The filter should be a 4th order Butterworth filter with the cut-off frequency fc.
I have implemented it like this:

fs_n = fs/2 #normalized signal frequency
fc_n = fc/fs_n #normalized cut-off frequency
b, a = signal.butter(order, fc_n, btype=“low”, analog=False)
data_flt = signal.filtfilt(b, a, data)

When i create a signal for testing, with a frequency f equals the cut-off frequency fc, the damping of the signal is 0.5 and not 0.707.

Further more the Damping of a Signal with a frequency of 10*fc is ~1/100 and not ~1/10.

Furthermore the signal.butter and the signal.bessel functions deliver the same parameters b and a for the same parameters order, and fc_n. which should not be correct in my understanding - since the frequencyresponses are different.

Following my complete code for testing. There I have generated 3 signals for comparison. One with f = 1/10fc, one with f=fc and one with 10fc. Then i filter the datasets with butter and also with bessel. In the end I compre the results in 3 plots (raw, butter and bessel):

#raw data
dur = 1 #duration
fs = 10000 #frequency of the data signal
f1 = 1
f2 = 10
f3 = 100
nMea = dur*fs #number of measuring points

#base data for filters
fc = 10
order = 1
fig1 = plt.figure(1)

#creating raw data
dataraw = np.zeros((nMea,4)) #Initializing array
dataraw[:,0] = np.linspace(0,dur,int(fsdur),endpoint=False) #time data
dataraw[:,1] = np.sin(2
np.pif1dataraw[:,0]) #1 Hz data
dataraw[:,2] = np.sin(2np.pif2dataraw[:,0]) #10 Hz data
dataraw[:,3] = np.sin(2
np.pif3dataraw[:,0]) #100 Hz data

#rohdaten anzeigen
ax1 = fig1.add_subplot(311)
ax1.plot(dataraw[:,0], copy.deepcopy(dataraw[:,1]), label='SIN ’ + str(f1) + " Hz",color=“b”)
ax1.plot(dataraw[:,0], copy.deepcopy(dataraw[:,2]), label='SIN ’ + str(f2) + " Hz",color=“r”)
ax1.plot(dataraw[:,0], copy.deepcopy(dataraw[:,3]), label='SIN ’ + str(f3) + " Hz",color=“g”)
ax1.set_title(“Rohdaten”)
ax1.set_xlabel(“Zeit [s]”)
ax1.legend()

#Butter
fs_n = fs/2
fc_n = fc/fs_n
b, a = signal.butter(order, fc_n, btype=“low”, analog=False)

#filter data
data1 = np.zeros((nMea,3))
data1[:,0] = signal.filtfilt(b, a, copy.deepcopy(dataraw[:,1]))
data1[:,1] = signal.filtfilt(b, a, copy.deepcopy(dataraw[:,2]))
data1[:,2] = signal.filtfilt(b, a, copy.deepcopy(dataraw[:,3]))

#showing butter data
ax2 = fig1.add_subplot(312)
ax2.plot(dataraw[:,0], data1[:,0], label='SIN ’ + str(f1) + " Hz",color=“b”)
ax2.plot(dataraw[:,0], data1[:,1], label='SIN ’ + str(f2) + " Hz",color=“r”)
ax2.plot(dataraw[:,0], data1[:,2], label='SIN ’ + str(f3) + " Hz",color=“g”)
ax2.set_title(“Butterworth Filter”)
ax2.set_xlabel(“Time[s]”)
ax2.legend()

#bessel
w2 = fc/(fs/2)
c, d = signal.bessel(order, w2, ‘low’)

#filter data
data2 = np.zeros((nMea,3))
data2[:,0] = signal.filtfilt(c, d, copy.deepcopy(dataraw[:,1]))
data2[:,1] = signal.filtfilt(c, d, copy.deepcopy(dataraw[:,2]))
data2[:,2] = signal.filtfilt(c, d, copy.deepcopy(dataraw[:,3]))

#showing besselfilter data
ax3 = fig1.add_subplot(313)
ax3.plot(dataraw[:,0], data2[:,0], label='SIN ’ + str(f1) + " Hz",color=“b”)
ax3.plot(dataraw[:,0], data2[:,1], label='SIN ’ + str(f2) + " Hz",color=“r”)
ax3.plot(dataraw[:,0], data2[:,2], label='SIN ’ + str(f3) + " Hz",color=“g”)
ax3.set_title(“Bessel Filter”)
ax3.set_xlabel(“Time[s]”)
ax3.legend()

#showing plots
plt.show(block=True)

Here are the plots i get:

Any ideas where the mistake is?

Further Information:

  • the comments in my code are in an other language and were translated for this post
  • i use the copy.deepcopy method because i wanted to avoid problems with references. I know it is not nessecary

You are applying the filter with scipy.signal.filtfilt, which applies the filter twice: once forward and once backwards. That means the magnitude of the gain is the square of the gain that one would get from applying the filter once, with, say, scipy.signal.lfilter. So your observation of the gain at fc is the expected behavior.

2 Likes