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

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

введите здесь описание изображения

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

введите здесь описание изображения

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

введите здесь описание изображения

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

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

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

введите здесь описание изображения

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

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

Обновить

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

введите здесь описание изображения


person Nimal Naser    schedule 16.02.2015    source источник


Ответы (2)


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

Во-первых, попробуйте функцию lowess из statsmodels с it=0 и немного подправьте аргумент 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>]

сюжет

Второе предложение — использовать scipy.signal.filtfilt вместо lfilter для применения алгоритма Баттерворта. фильтр. filtfilt — фильтр вперед-назад. Он применяет фильтр дважды, один раз вперед и один раз назад, что приводит к нулевой фазовой задержке.

Вот модифицированная версия вашего скрипта. Существенными изменениями являются использование 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 или, если вы знаете, что у вас достаточно данных, вы можете отбросить края отфильтрованного сигнала.

график результата фильтрации

person Warren Weckesser    schedule 16.02.2015
comment
Именно то, что я хотел. Но как вы выбрали аргумент frac? Это получается методом проб и ошибок или это может быть связано с флуктуациями сигнала, ведь мне приходится это делать для нескольких файлов (>1000). - person Nimal Naser; 17.02.2015
comment
Я начал с проб и ошибок, но вы можете понять ценность постфактум. Большие колебания в хвосте ступенчатой ​​характеристики составляют примерно 3,1 кГц. При частоте дискретизации 50 кГц это соответствует примерно 16 выборкам за цикл. Чтобы метод Лоусса устранял эти колебания, вам нужно окно, достаточно большое, чтобы включить несколько колебаний. В данных 2000 отсчетов, а 2000*0,025 — это 50, что включает примерно 3 колебания. Если спектр данных во всех ваших файлах одинаков, но количество выборок в каждом файле разное, вы можете попробовать использовать frac=50.0/num_samples. - person Warren Weckesser; 17.02.2015

Вот пример использования подгонки loewess. Обратите внимание, что я удалил заголовок из 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()

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

сюжет

person cel    schedule 16.02.2015
comment
Это выглядит хорошо, поэтому вы разделили данные и применили сглаживание. Но я должен сделать это для нескольких файлов более тысячи файлов, и повышение давления не всегда происходит в 21 мс. - person Nimal Naser; 16.02.2015
comment
@NimalNaser, я обновил ответ. Без разделения этот метод не работает, и хотя результаты после разделения выглядят довольно хорошо, я сомневаюсь, что это лучший метод для вашей проблемы. Тем не менее я хотел показать результаты для справки :) - person cel; 16.02.2015
comment
@NimalNaser и еще одно обновление. На этот раз я резко уменьшил область сглаживания. Тем не менее вы видите, что это далеко не оптимально. - person cel; 16.02.2015
comment
Градиент изменен. Я должен сохранить градиент. - person Nimal Naser; 16.02.2015