2

I am trying to use interactive plotting with matplotlib to manually correct automatically-generated scatterplot data. (The program attempts to detect peaks in timeseries data and place a point on each one, but there are some artifacts in the timeseries data where peaks are inappropriately detected.)

I have successfully written code that plots the timeseries and overlays the scatterplot data, and then the user can add or remove points by clicking. However, I would like the script to save the edited point series as a new array and pass it to other functions etc. I can't really call the function without mouseclick events, so I don't know how to return the data.

Code is adapted from here: Interactively add and remove scatter points in matplotlib

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(figsize=(16,4))
a_input = np.sin(range(100))*np.random.normal(20,10,100)
b_input = [ 5, 15, 25, 30, 40, 50, 75, 85]

a = plt.plot(range(len(a_input)),a_input,color='red')[0]
b = plt.scatter(b_input,a_input[b_input],color='grey',s=50,picker=5)
newpeaks = np.array([])

def add_or_remove_point(event):
    global a
    xydata_a = np.stack(a.get_data(),axis=1)
    xdata_a = a.get_xdata()
    ydata_a = a.get_ydata()
    global b
    xydata_b = b.get_offsets()
    xdata_b = b.get_offsets()[:,0]
    ydata_b = b.get_offsets()[:,1]
    global newpeaks

    #click x-value
    xdata_click = event.xdata
    #index of nearest x-value in a
    xdata_nearest_index_a = (np.abs(xdata_a-xdata_click)).argmin()
    #new scatter point x-value
    new_xdata_point_b = xdata_a[xdata_nearest_index_a]
    #new scatter point [x-value, y-value]
    new_xydata_point_b = xydata_a[new_xdata_point_b,:]

    if event.button == 1:
        if new_xdata_point_b not in xdata_b:

            #insert new scatter point into b
            new_xydata_b = np.insert(xydata_b,0,new_xydata_point_b,axis=0)
            #sort b based on x-axis values
            new_xydata_b = new_xydata_b[np.argsort(new_xydata_b[:,0])]
            #update b
            b.set_offsets(new_xydata_b)
            newpeaks = b.get_offsets()[:,0]
            plt.draw()
    elif event.button == 3:
        if new_xdata_point_b in xdata_b:
            #remove xdata point b
            new_xydata_b = np.delete(xydata_b,np.where(xdata_b==new_xdata_point_b),axis=0)
            #print(new_xdata_point_b)
            #update b
            b.set_offsets(new_xydata_b)
            newpeaks = b.get_offsets()[:,0]
        plt.draw()

    print(len(newpeaks))
    return newpeaks

fig.canvas.mpl_connect('button_press_event',add_or_remove_point)
plt.show()

Interestingly, b.get_offsets() does remain updated outside of the function, so if I reassign newpeaks manaully in the commandline after the script completes, I get the right updated data. But if I place the assignment

newpeaks = b.get_offsets()[:,0]

after the function in the script, it gets run first and does not update when the plot is closed. I would like it to either update automatically as soon as the plot is closed or update the global variable somehow instead of having to do it manually. Any advice is welcome!

cxw014
  • 21
  • 2
  • I'm unsure if I understand this correclty. If you `print(newpeaks)` *after* `plt.show()` does it not contain the desired points? – ImportanceOfBeingErnest Sep 28 '18 at 20:33
  • No. If I put print(newpeaks) after plt.show(), it just returns []. – cxw014 Sep 28 '18 at 21:53
  • To clarify: what I meant is that when my script is done running, b.get_offsets() is updated with the correct peaks, so if I reassign newpeaks *manually* in the command line, it works. But if I try to do this assignment in the script, it runs before the function is called and does not update. I hope that helps. – cxw014 Sep 28 '18 at 21:56
  • If I copy your code, append the line `print(newpeaks)` to it at the very end, and run it, it prints the output correclty as `[ 5. 15. 25. ..., 50. 75. 85.]`. – ImportanceOfBeingErnest Sep 28 '18 at 22:31
  • You're right. My issue is if I run this from the Canopy commandline. When I run it using a terminal, it works. Well, I feel silly now, but thanks for your help. – cxw014 Sep 28 '18 at 22:44
  • Random guess: does it work consistently if you change it to `plt.show(block = True)`? – legoscia Sep 29 '18 at 02:03
  • Thanks for the suggestion! It still runs out of order even with this change. Canopy's code editor is just idiosyncratic, I guess. – cxw014 Sep 29 '18 at 21:05

0 Answers0