J'essaie de filtrer/lisser le signal obtenu à partir d'un transducteur de pression d'une fréquence d'échantillonnage de 50 kHz. Un exemple de signal est présenté ci-dessous:
Je voudrais obtenir un signal lisse obtenu par loess dans MATLAB (je ne trace pas les mêmes données, les valeurs sont différentes).
J'ai calculé la densité spectrale de puissance à l'aide de la fonction psd () de matplotlib. La densité spectrale de puissance est également indiquée ci-dessous:
J'ai essayé d'utiliser le code suivant et obtenu un signal filtré:
import csv
import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
from scipy.signal import butter, lfilter, freqz
def butter_lowpass(cutoff, fs, order=5):
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = butter(order, normal_cutoff, btype='low', analog=False)
return b, a
def butter_lowpass_filter(data, cutoff, fs, order=5):
b, a = butter_lowpass(cutoff, fs, order=order)
y = lfilter(b, a, data)
return y
data = np.loadtxt('data.dat', skiprows=2, delimiter=',', unpack=True).transpose()
time = data[:,0]
pressure = data[:,1]
cutoff = 2000
fs = 50000
pressure_smooth = butter_lowpass_filter(pressure, cutoff, fs)
figure_pressure_trace = plt.figure(figsize=(5.15, 5.15))
figure_pressure_trace.clf()
plot_P_vs_t = plt.subplot(111)
plot_P_vs_t.plot(time, pressure, linewidth=1.0)
plot_P_vs_t.plot(time, pressure_smooth, linewidth=1.0)
plot_P_vs_t.set_ylabel('Pressure (bar)', labelpad=6)
plot_P_vs_t.set_xlabel('Time (ms)', labelpad=6)
plt.show()
plt.close()
Le résultat obtenu est:
J'ai besoin de plus de lissage, j'ai essayé de changer la fréquence de coupure mais des résultats satisfaisants ne peuvent pas être obtenus. MATLAB ne parvient pas à obtenir la même fluidité. Je suis sûr que cela peut être fait en Python, mais comment?
Vous pouvez trouver les données ici .
Mettre à jour
J'ai appliqué le lissage lowess de statsmodels, cela ne donne pas non plus de résultats satisfaisants.
Voici quelques suggestions.
Commencez par essayer la fonction lowess
de statsmodels
avec it=0
et modifiez un peu l'argument frac
:
In [328]: from statsmodels.nonparametric.smoothers_lowess import lowess
In [329]: filtered = lowess(pressure, time, is_sorted=True, frac=0.025, it=0)
In [330]: plot(time, pressure, 'r')
Out[330]: [<matplotlib.lines.Line2D at 0x1178d0668>]
In [331]: plot(filtered[:,0], filtered[:,1], 'b')
Out[331]: [<matplotlib.lines.Line2D at 0x1173d4550>]
Une deuxième suggestion consiste à utiliser scipy.signal.filtfilt
au lieu de lfilter
pour appliquer le filtre Butterworth. filtfilt
est le filtre forward-backward. Il applique le filtre deux fois, une fois en avant et une fois en arrière, entraînant un retard de phase nul.
Voici une version modifiée de votre script. Les changements significatifs sont l'utilisation de filtfilt
au lieu de lfilter
et le changement de cutoff
de 3000 à 1500. Vous pouvez expérimenter ce paramètre - des valeurs plus élevées permettent de mieux suivre l'apparition de l'augmentation de la pression, mais une valeur est trop élevé ne filtre pas les oscillations de 3 kHz (environ) après l’augmentation de la pression.
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, filtfilt
def butter_lowpass(cutoff, fs, order=5):
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = butter(order, normal_cutoff, btype='low', analog=False)
return b, a
def butter_lowpass_filtfilt(data, cutoff, fs, order=5):
b, a = butter_lowpass(cutoff, fs, order=order)
y = filtfilt(b, a, data)
return y
data = np.loadtxt('data.dat', skiprows=2, delimiter=',', unpack=True).transpose()
time = data[:,0]
pressure = data[:,1]
cutoff = 1500
fs = 50000
pressure_smooth = butter_lowpass_filtfilt(pressure, cutoff, fs)
figure_pressure_trace = plt.figure()
figure_pressure_trace.clf()
plot_P_vs_t = plt.subplot(111)
plot_P_vs_t.plot(time, pressure, 'r', linewidth=1.0)
plot_P_vs_t.plot(time, pressure_smooth, 'b', linewidth=1.0)
plt.show()
plt.close()
Voici l'intrigue du résultat. Notez la déviation dans le signal filtré sur le bord droit. Pour gérer cela, vous pouvez utiliser les paramètres padtype
et padlen
de filtfilt
ou, si vous savez que vous avez suffisamment de données, vous pouvez ignorer les bords du signal filtré.
Voici un exemple d'utilisation d'un ajustement loewess
. Notez que j'ai enlevé l'en-tête de data.dat
.
D'après le graphique, il semble que cette méthode fonctionne bien sur des sous-ensembles de données. L'ajustement simultané de toutes les données ne donne pas un résultat raisonnable. Donc, ce n'est probablement pas la meilleure méthode ici.
import pandas as pd
import matplotlib.pylab as plt
from statsmodels.nonparametric.smoothers_lowess import lowess
data = pd.read_table("data.dat", sep=",", names=["time", "pressure"])
sub_data = data[data.time > 21.5]
result = lowess(sub_data.pressure, sub_data.time.values)
x_smooth = result[:,0]
y_smooth = result[:,1]
tot_result = lowess(data.pressure, data.time.values, frac=0.1)
x_tot_smooth = tot_result[:,0]
y_tot_smooth = tot_result[:,1]
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(data.time.values, data.pressure, label="raw")
ax.plot(x_tot_smooth, y_tot_smooth, label="lowess 1%", linewidth=3, color="g")
ax.plot(x_smooth, y_smooth, label="lowess", linewidth=3, color="r")
plt.legend()
C'est le résultat que j'obtiens: