Search code examples
python-2.7matplotlibdata-analysis

How to interactively find and annotate local max (peaks) in matplotlib?


I'm trying to analyze some spectra for finding spectroscopic peaks, I've writen this simple code to find the max Y value (the peak) between two X data by clicking before and after the peak that I want to find. This works and I can get the coordinate of the peak but I would like to automatically annotate the peak found.

import matplotlib.pyplot as plt 
X=[1,2,3,4,5,6,7,8,9,10]
Y=[1,1,1,2,10,2,1,1,1,1]
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(X,Y,label="prova") #plot the function
#plt.legend(loc=1, ncol=1, shadow=True)
plt.xlim(min(X) * 0.9, max(X) * 1.1)
plt.ylim(min(Y) * 0.9, max(Y) * 1.1)
plt.ylabel(r'Y axis')
plt.xlabel(r'X axis')
Diz=dict(zip(X,Y)) #create a dictionary that associate X with Y
Interval=[] #interval where the user search for peaks
PeaksList=[] #list of peaks found
def onclick(event):
    print 'First limit at =%f'%(event.xdata)
    Interval.append(event.xdata)
    if len(Interval)%2==0:
        a=Interval[-2]       
        b=Interval[-1]    
        if b<a: #if the user select first the highest value these statements filp it!
            A=b
            B=a
        else:
            A=a
            B=b
      #find the max Y value: the peak!
        peakY=0 #max Y value
        piccoX=0 #value of the X associate to the peak
        for i in [ j for j in X if A<j<B] :
            if Diz[i]>peakY:
                peakY=Diz[i]
                piccoX=i
        print "Interval: %f - %f  Peak at: %f " %(a,b,piccoX)
        PeaksList.append([piccoX,peakY])
        ax.annotate("picco", xy=(piccoX,peakY),  xycoords='data',
                xytext=(-50, 30), textcoords='offset points',
                arrowprops=dict(arrowstyle="->")
                )      


plt.show()         
cid = fig.canvas.mpl_connect('button_press_event', onclick)

This is what I would like to have after the second click: enter image description here


Solution

  • You have to redraw the figure - simply by adding a plt.draw to the onclick method.
    Moreover you have to connect the event before you show the figure. Of course, here your annotation text is 'picco' not '5'.

    Try:

    import matplotlib.pyplot as plt 
    X=[1,2,3,4,5,6,7,8,9,10]
    Y=[1,1,1,2,10,2,1,1,1,1]
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(X,Y,label="prova") #plot the function
    #plt.legend(loc=1, ncol=1, shadow=True)
    plt.xlim(min(X) * 0.9, max(X) * 1.1)
    plt.ylim(min(Y) * 0.9, max(Y) * 1.1)
    plt.ylabel(r'Y axis')
    plt.xlabel(r'X axis')
    Diz=dict(zip(X,Y)) #create a dictionary that associate X with Y
    Interval=[] #interval where the user search for peaks
    PeaksList=[] #list of peaks found
    def onclick(event):
        print 'First limit at =%f'%(event.xdata)
        Interval.append(event.xdata)
        if len(Interval)%2==0:
            a=Interval[-2]       
            b=Interval[-1]    
            if b<a: #if the user select first the highest value these statements filp it!
                A=b
                B=a
            else:
                A=a
                B=b
          #find the max Y value: the peak!
            peakY=0 #max Y value
            piccoX=0 #value of the X associate to the peak
            for i in [ j for j in X if A<j<B] :
                if Diz[i]>peakY:
                    peakY=Diz[i]
                    piccoX=i
            print "Interval: %f - %f  Peak at: %f " %(a,b,piccoX)
            PeaksList.append([piccoX,peakY])
            ax.annotate("picco", xy=(piccoX,peakY),  xycoords='data',
                    xytext=(-50, 30), textcoords='offset points',
                    arrowprops=dict(arrowstyle="->")
                    )
            plt.draw()
    
    cid = fig.canvas.mpl_connect('button_press_event', onclick)
    plt.show()     
    

    Maybe visually more appealing (using the spanselector):

    import matplotlib.pyplot as plt 
    from matplotlib.widgets import SpanSelector
    X=[1,2,3,4,5,6,7,8,9,10]
    Y=[1,1,1,2,10,2,1,1,1,1]
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.plot(X,Y,label="prova") #plot the function
    #plt.legend(loc=1, ncol=1, shadow=True)
    plt.xlim(min(X) * 0.9, max(X) * 1.1)
    plt.ylim(min(Y) * 0.9, max(Y) * 1.1)
    plt.ylabel(r'Y axis')
    plt.xlabel(r'X axis')
    Diz=dict(zip(X,Y)) #create a dictionary that associate X with Y
    Interval=[] #interval where the user search for peaks
    PeaksList=[] #list of peaks found
    def onclick(xmin, xmax):
        print 'Interval: {0} - {1}'.format(xmin,xmax)
        peakY=0 #max Y value
        piccoX=0 #value of the X associate to the peak
        for i in [ j for j in X if xmin<j<xmax] :
            if Diz[i]>peakY:
                peakY=Diz[i]
                piccoX=i
        print "Peak at: %f " % piccoX
        PeaksList.append([piccoX,peakY])
        ax.annotate("picco", xy=(piccoX,peakY),  xycoords='data',
                xytext=(-50, 30), textcoords='offset points',
                arrowprops=dict(arrowstyle="->"))
        plt.draw()
    
    span = SpanSelector(ax, onclick, 'horizontal', useblit=True,
                        rectprops=dict(alpha=0.5, facecolor='blue') )
    plt.show()