ひかりの備忘録

Python で極値の検出

概要

信号の極値を検出する。

信号の生成

例として疑似的な信号を生成する。

  • 3 [Hz] の信号 + 0.01 [Hz] の信号 + ノイズ
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy import signal

t = np.arange(30 * 100) / 100
x = np.sin(2 * np.pi * t / 3) + np.random.randn(len(t)) * 0.2 + np.sin(2 * np.pi * t / 100)
df = pd.DataFrame({'Data': x}, index=pd.to_datetime(t, unit='s').time)

fig, ax = plt.subplots()
df.plot(ax=ax, xlim=['00:00:00', '00:00:10'])
plt.show()

tmp

信号の性質を調べる

def acorr(df: pd.DataFrame, ra: int = 3, fs=100):
    x = np.correlate(df.Data.values, df.Data.values, mode='full')
    t = (np.arange(len(x)) - len(x) / 2 + 0.5) / fs
    x /= x.max()
    fig, ax = plt.subplots()
    ax.set_xlim(-ra, ra)
    ax.set_ylim(0, 1)
    ax.plot(t, x)
    ax.set_title('Autocorrelation')
    ax.grid(axis='x')
    ax.set_xticks(np.linspace(-ra, ra, 2 * ra + 1))
    plt.show()

acorr(df)

主な信号は 1/3 [Hz] であることがわかる。そのため、1/3 [Hz] でローパスフィルターをかける。

tmp

ローパスフィルターをかけ、極値の検出

ローパスフィルターをかけ、極値を検出できるようにする。

1/3 [Hz] でローパスフィルターをかけても正しく検出されない場合は、 周波数を下げていく。

tmp

fs = 10
lpf = 0.1
b, a = signal.butter(5, lpf / fs * 2, 'low')
df['Filter'] = signal.filtfilt(b, a, df.Data.values)
max_idx = signal.argrelmax(df['Filter'].values)
min_idx = signal.argrelmin(df['Filter'].values)

df['max_index'] = False
df.iloc[max_idx[0], 2] = True
df['min_index'] = False
df.iloc[min_idx[0], 3] = True

fig, ax = plt.subplots()
df.plot(ax=ax)
ax.set_xlim(['00:00:00', '00:00:30'])
ax.scatter(
    df.loc[df['max_index'], ['Filter']].index, 
    df.loc[df['max_index'], ['Filter']], 
    color='tab:orange', 
    zorder=3)
ax.scatter(
    df.loc[df['min_index'], ['Filter']].index, 
    df.loc[df['min_index'], ['Filter']], 
    color='tab:green', 
    zorder=3)
plt.show()