The objective is to find the leading and trailing valleys from a list of local maxima in a 1-D signal, as illustrated in the figure below
To do this,
I proposed to first find the peaks via the find_peaks of scipy.signal. This return a list of index where the local maxima occur.
Secondly, the signal.argrelextrema were employed to extract all the valleys. Finally, the third step is to select the closest boundary value pair (pair values from the valleys index) that each of the index from the find_peaks can reside.
While the code below works, but I am wondering whether there is more efficient way (can be Numpy of scipy) of doing this. I notice, the argrelextrema and searchsorted take some time for very long 1D signal
The full code to reproduce the above figure and
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import signal
from scipy.misc import electrocardiogram
from scipy.signal import find_peaks
x = electrocardiogram()[2000:2500]
peaks, _ = find_peaks(x, height=0.7)
valley_indexes = signal.argrelextrema(x, np.less)[0]
i = np.searchsorted(valley_indexes, peaks)
m = ~np.isin(i, [0, len(valley_indexes)])
df = pd.DataFrame({'peaks_point': peaks})
df.loc[m, 'leftp'], df.loc[m, 'rightp'] = valley_indexes[i[m] - 1], valley_indexes[i[m]]
leftp=df['leftp'].to_numpy().astype(int)
rightp=df['rightp'].to_numpy().astype(int)
plt.plot(x)
plt.plot(peaks, x[peaks], "x",label='Local Maxima')
plt.scatter(leftp, x[leftp],marker='x',s=100,alpha=0.7,label='leading_valleys')
plt.scatter(rightp,x[rightp],marker='x',s=100,alpha=0.7,label='Trailing_valleys')
plt.plot(np.zeros_like(x), "--", color="gray")
plt.legend()
plt.show()
