0

I am developing a simple algorithm for the detection of peaks in a signal. To troubleshoot my algorithm (and to showcase it), I would like to observe the signal and the detected peaks all along the signal duration (i.e. 20 minutes at 100Hz = 20000 time-points).

I thought that the best way to do it would be to create an animated plot with matplotlib.animation.FuncAnimation that would continuously show the signal sliding by 1 time-points and its superimposed peaks within a time windows of 5 seconds (i.e. 500 time-points). The signal is stored in a 1D numpy.ndarray while the peaks information are stored in a 2D numpy.ndarray containing the x and y coordinates of the peaks.

This is a "still frame" of how the plot would look like.

enter image description here

Now the problem is that I cannot wrap my head around the way of doing this with FuncAnimation.

If my understanding is correct I need three main pieces: the init_func parameter, a function that create the empty frame upon which the plot is drawn, the func parameter, that is the function that actually create the plot for each frame, and the parameter frames which is defined in the help as Source of data to pass func and each frame of the animation.

Looking at examples of plots with FuncAnimation, I can only find use-cases in which the data to plot are create on the go, like here, or here, where the data to plot are created on the basis of the frame.

What I do not understand is how to implement this with data that are already there, but that are sliced on the basis of the frame. I would thus need the frame to work as a sort of sliding window, in which the first window goes from 0 to 499, the second from 1to 500 and so on until the end of the time-points in the ndarray, and an associated func that will select the points to plot on the basis of those frames. I do not know how to implement this.

I add the code to create a realistic signal, to simply detect the peaks and to plot the 'static' version of the plot I would like to animate:

import neurokit2 as nk
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
from scipy.signal import find_peaks
#create realistic data
data = nk.ecg_simulate(duration = 50, sampling_rate = 100, noise = 0.05,\
                       random_state = 1)
#scale data
scaler = MinMaxScaler()
scaled_arr = scaler.fit_transform(data.reshape(-1,1))
#find peaks
peak = find_peaks(scaled_arr.squeeze(), height = .66,\
        distance = 60, prominence = .5)
#plot
plt.plot(scaled_arr[0:500])
plt.scatter(peak[0][peak[0] < 500],\
         peak[1]['peak_heights'][peak[0] < 500],\
             color = 'red')
2
  • 1
    If you want answers, you are expected to provide reproducible data. Respondents have the hassle of creating their own sample data and the psychological burden of new revisions that may occur because the data they create is different from your data. It is even better if you have the code in the process of creation. Commented Oct 28, 2021 at 9:59
  • You are completely right. I have added the code to create data, detect peaks and create the static version of the plot that I would like to animate. Thanks for pointing this out. Commented Oct 28, 2021 at 11:35

2 Answers 2

2

I've created an animation using the data you presented; I've extracted the data in 500 increments for 5000 data and updated the graph. To make it easy to extract the data, I have created an index of 500 rows, where id[0] is the start row, id1 is the end row, and the number of frames is 10. This code works, but the initial settings and dataset did not work in the scatter plot, so I have drawn the scatter plot directly in the loop process.

import neurokit2 as nk
from sklearn.preprocessing import MinMaxScaler
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy.signal import find_peaks
import numpy as np

#create realistic data
data = nk.ecg_simulate(duration = 50, sampling_rate = 100, noise = 0.05, random_state = 1)

#scale data
scaler = MinMaxScaler()
scaled_arr = scaler.fit_transform(data.reshape(-1,1))
#find peaks
peak = find_peaks(scaled_arr.squeeze(), height = .66, distance = 60, prominence = .5)

ymin, ymax = min(scaled_arr), max(scaled_arr)
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot([],[], lw=2)
scat = ax.scatter([], [], s=20, facecolor='red')

idx = [(s,e) for s,e in zip(np.arange(0,len(scaled_arr), 1), np.arange(499,len(scaled_arr)+1, 1))]

def init():
    line.set_data([], []) 
    return line, 

def animate(i):
  id = idx[i]
  #print(id[0], id[1])
  line.set_data(np.arange(id[0], id[1]), scaled_arr[id[0]:id[1]])
  x = peak[0][(peak[0] > id[0]) & (peak[0] < id[1])]
  y = peak[1]['peak_heights'][(peak[0] > id[0]) & (peak[0] < id[1])]
  #scat.set_offsets(x, y)
  ax.scatter(x, y, s=20, c='red')
  ax.set_xlim(id[0], id[1])
  ax.set_ylim(ymin, ymax)
  return line,scat

anim = FuncAnimation(fig, animate, init_func=init, frames=50, interval=50, blit=True) 

plt.show()

enter image description here

Sign up to request clarification or add additional context in comments.

10 Comments

I don't have time to deal with the assignment anymore, but maybe tomorrow I'll have time.
Thanks, it definitely goes in the right direction and gives me some good idea. I will try to mange to make it work to show the signal at 1 time-point increase within a 5 seconds windows.
What aspects of my response do not meet your requirements?
Your solution plots non-overlapping windows of 5 seconds. It is possible that it was not clear from my question, but I would like the signal to be plotted a time-point at a time, within a 5 seconds window. I hope it is clearer now.
Are you talking about adding to the 5 second unit from the beginning? I didn't understand the question that way. Do you want me to modify the code? Or can you handle it yourself?
|
1

Probably not exactly what you want, but hope it can help,

import neurokit2 as nk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from sklearn.preprocessing import MinMaxScaler
import numpy as np
from matplotlib.animation import FuncAnimation


fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

# This function is called periodically from FuncAnimation
def animate(i, xs, ys):

    xs = xs[i]
    ys = ys[i]

    # Draw x and y lists
    ax.clear()
    ax.plot(xs, ys)

if __name__=="__main__":
   
  data = nk.ecg_simulate(duration = 50, sampling_rate = 100, noise = 0.05, random_state = 1)
  scaler = MinMaxScaler()
  scaled_arr = scaler.fit_transform(data.reshape(-1,1))
  
  ys = scaled_arr.flatten()
  ys = [ys[0:50*i] for i in range(1, int(len(ys)/50)+1)]
  xs = [np.arange(0, len(ii)) for ii in ys ]
  
 
  ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=500)
  ani.save('test.gif')

enter image description here

1 Comment

This is very good and it definitely points me in the right direction, I just need to find a way to avoid the "cumulative effect" and make it stick the plot to a 5 seconds window.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.