web-dev-qa-db-fra.com

Comment filtrer/lisser avec SciPy/Numpy?

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:

enter image description here

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).

enter image description here

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:

enter image description here

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:

enter image description here

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.

enter image description here

15
Nimal Naser

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>]

plot

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é.

plot of filtfilt result

23
Warren Weckesser

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:

plot

0
cel