Как фильтровать / сглаживать с помощью SciPy / Numpy?

Я пытаюсь фильтровать / сглаживать сигнал, полученный от датчика давления с частотой дискретизации 50 кГц. Ниже показан пример сигнала:

enter image description here

Я хотел бы получить гладкий сигнал, полученный лессом в MATLAB (я не строю одни и те же данные, значения разные).

enter image description here

Я рассчитал спектральную плотность мощности с помощью функции psd () matplotlib, а также спектральная плотность мощности ниже:

enter image description here

Я попытался использовать следующий код и получил отфильтрованный сигнал:

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

вывод, который я получаю:

enter image description here

Мне нужно больше сглаживания, я попытался изменить частоту среза, но по-прежнему удовлетворительные результаты не могут быть получены. Я не могу получить такую же гладкость MATLAB. Я уверен, что это можно сделать в Python, но как?

вы можете найти данные здесь.

обновление

я применил сглаживание lowess из statsmodels, это также не дает удовлетворительных результатов.

enter image description here

2 ответов


вот несколько предложений.

сначала попробуйте . Он применяет фильтр дважды, один раз вперед и один раз назад, что приводит к нулевой фазе задержка.

вот модифицированная версия вашего скрипта. Существенными изменениями являются использование filtfilt вместо lfilter, и смены cutoff от 3000 до 1500. Возможно, вы захотите поэкспериментировать с этим параметром-более высокие значения приводят к лучшему отслеживанию начала увеличения давления, но слишком высокое значение не отфильтровывает колебания 3 кГц (примерно) после увеличения давления.

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

вот график результата. Примечание отклонение отфильтрованного сигнала на правом краю. Чтобы справиться с этим, вы можете поэкспериментировать с padtype и padlen параметры filtfilt, или, если вы знаете, что у вас достаточно данных, вы можете отбросить края отфильтрованного сигнала.

plot of filtfilt result


пример использования loewess fit. Обратите внимание, что я удалил заголовок из data.dat.

из графика кажется, что этот метод хорошо работает на подмножествах данных. Подгонка всех данных сразу не дает разумного результата. Так что, наверное, это не лучший метод.

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

Это результат, который я получаю:

plot