matplotlib中带有光标的2D强度图

weylhg0b  于 2023-05-18  发布在  其他
关注(0)|答案(2)|浏览(101)

我目前正在尝试基于matplotlib(Python 2.7)创建一个简单的GUI。我的目标是绘制一个2d强度图,并使用用户控制光标查看x和y切片。
它已经按照我想要的方式工作了。(见下面的例子)。但似乎有一个主要的限制,在程序中给出的数组大小,我可以使用。如果数组中的条目超过几百万个,进程就会开始滞后。我想原因是我在诅咒者移动的过程中重新绘制了所有的图形。
是否有一个选项,我可以只重绘光标和切片,但不重绘强度贴图?我什么都没找到。或者,除了使用像Tkinter这样的真实的GUI编写游标之外,还有其他选择吗?
在下面的示例中,光标的初始位置为00。如果您在光标位置附近按下鼠标右键,并一直按下,直到您将光标移动到所需位置并释放按钮,则将跟随鼠标移动。

# -*- noplot -*-
    #from __future__ import print_function
    import matplotlib.pyplot as plt
    import numpy as np
    
    
    class Cursor(object):
        """
        creates a GUI object that plots given 2d data with imshow and creates a curser
        which can be moved by drag and drop. The horizontal and vertical line of the curser
        are giving the position of a sliced trough the 2d-data and are plotted separately.

        """
        def __init__(self,data,scale_x,scale_y):
            
            self.motion=False
            self.data=data
            self.scale_x=scale_x
            self.scale_y=scale_y
            self.create_fig()
            # text location in axes coords
            self.txt = self.ax1.text(0.7, 0.9, '', transform=self.ax1.transAxes)
            self.create_events()
            
    #        print self.range_x,self.range_y
    #        print 
    #        print 
            
        def create_events(self):
            """
            Handles user events
            """
            self.cid1=plt.connect('motion_notify_event', self.mouse_move)
            self.cid2=plt.connect('button_press_event', self.mouse_press)
            self.cid3=plt.connect('button_release_event', self.mouse_release)
            
        def create_fig(self):
            """
            Creates the GUI, initializes the cursers at minimum of the axes and plots the 2d-data
            """
            
            #Create figure and axes
            f=plt.figure(dpi=150)
            self.ax1=f.add_subplot(221)
            self.ax2=f.add_subplot(223,sharex=self.ax1)
            self.ax3=f.add_subplot(222,sharey=self.ax1)
            # plot in ax1
            self.ax1.imshow(self.data,interpolation='none',aspect='auto',extent=[np.min(self.scale_x),np.max(self.scale_x),np.min(self.scale_y),np.max(self.scale_y)])
            
            #Creates the limits
            self.ax1.axis([np.min(self.scale_x),np.max(self.scale_x),np.min(self.scale_y),np.max(self.scale_y)])        
            self.ax3.set_xlim(np.min(self.data),np.max(self.data)) 
            self.ax2.set_ylim(np.min(self.data),np.max(self.data))
            
    
                    
            #Create Curser @ minimum-minimum position of the axes
            self.lx = self.ax1.axhline(color='k')  # the horiz line
            self.ly = self.ax1.axvline(color='k')  # the vert line        
            self.lx.set_ydata(np.min(self.scale_y))
            self.ly.set_xdata(np.min(self.scale_x))
    
            #Creates sliced plots @ initial values of the curser
            # the change of scale needs to be considered therefore
            # the program checks for the minimum difference between curser pos and self.scale_... entries 
            # and uses the position of the entry to slice the data array
            self.slice_y,=self.ax3.plot(np.flipud(self.data[:,np.argmin(np.abs(self.scale_x-self.ly.get_xdata()))]),self.scale_y)
            self.slice_x,=self.ax2.plot(self.scale_x,self.data[np.shape(self.scale_y)-np.argmin(np.abs(self.scale_y-self.lx.get_ydata()))-1,:][0])
            # garanties fixed distances beetween the plots
            plt.tight_layout()
    
    
        def sliced_vertical(self,ax):
            #gets the sliced vertical sliced data
            self.slice_y.set_xdata(np.flipud(self.data[:,np.argmin(np.abs(self.scale_x-self.ly.get_xdata()))]))
        
        def sliced_horizontal(self,ax):
            #gets the horizontal sliced data
            self.slice_x.set_ydata(self.data[np.shape(self.scale_y)-np.argmin(np.abs(self.scale_y-self.lx.get_ydata()))-1,:])  
    
            
        def cursermovement(self,event):
            """
            tracks the curser movement and if a left click appeard near the curser
            the curser will folow the motion
            """
            if not event.inaxes:
                return
            if self.motion:   
                x, y = event.xdata, event.ydata
                # update the line positions
                self.lx.set_ydata(y)
                self.ly.set_xdata(x)
                #update the text
                self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
                #update the sliced data            
                self.sliced_vertical(self.ax2)
                self.sliced_horizontal(self.ax3)
                #replot everything
                plt.draw()        
            
            
        def mouse_move(self, event):
            self.cursermovement(event)
                
        def mouse_press(self,event):
            #check range for moving the cursers here in case of zoom in or out
            self.range_x=np.abs(self.ax1.get_xlim()[0]-self.ax1.get_xlim()[1])/20
            self.range_y=np.abs(self.ax1.get_ylim()[0]-self.ax1.get_ylim()[1])/20           
            # check if click occurred near cursor
            if (self.ly.get_xdata()+self.range_x>event.xdata>self.ly.get_xdata()-self.range_x) or (self.lx.get_ydata()+self.range_y>event.ydata>self.lx.get_ydata()-self.range_y):
                self.motion=True
            #curser jumps without motion to the mouse    
            self.cursermovement(event)
            
        def mouse_release(self,event):
            #checks if rigth mouse button was released
            self.motion=False
            
            
            
    """
    program starts here
    """        
    # define the plot range in x and y axes and change array size       
    t = np.arange(0.0, 40.0, 0.01)
    t2 = np.arange(0.0, 20.0, 0.01)
    #create a 2d grid to create the intensity map
    t_x,t_y=np.meshgrid(t,t2)
    #create the intensity map
    s = 10*np.sin(0.1*np.pi*(t_x))*np.sin(0.5*np.pi*t_y)+t_x+t_y
    #create the Gui class
    cursor = Cursor(s,t,t2)
    plt.show()
fcg9iug3

fcg9iug31#

Matplotlib提供了一个名为Cursor的小部件。您已经可以将其用于热图图中的线。这可以使用blitting来避免总是重画画布,

matplotlib.widgets.Cursor(ax1,useblit=True)

要更新旁边的图,您可以使用相同的blitting技术,但需要手动实现。这样,只有在移动鼠标时改变的线条将被更新,因此整个交互体验更加流畅。

import matplotlib.pyplot as plt
import matplotlib.widgets
import numpy as np

# define the plot range in x and y axes and change array size       
t = np.arange(0.0, 40.0, 0.01)
t2 = np.arange(0.0, 20.0, 0.01)
#create a 2d grid to create the intensity map
t_x,t_y=np.meshgrid(t,t2)
#create the intensity map
s = 10*np.sin(0.1*np.pi*(t_x))*np.sin(0.5*np.pi*t_y)+t_x+t_y
#create the Gui class

fig=plt.figure(dpi=150)
ax1=fig.add_subplot(221)
ax2=fig.add_subplot(223,sharex=ax1)
ax3=fig.add_subplot(222,sharey=ax1)
ax1.margins(0)
ax2.margins(0)
ax3.margins(0)
ax2.set_ylim(s.min(), s.max())
ax3.set_xlim(s.min(), s.max())

ax1.imshow(s,aspect='auto')
l2, = ax2.plot(np.arange(0,s.shape[1]),np.ones(s.shape[1])*np.nan)
l3, = ax3.plot(np.ones(s.shape[0])*np.nan, np.arange(0,s.shape[0]))

class Cursor():
    def __init__(self, **kwargs):
        self.cursor = matplotlib.widgets.Cursor(ax1,useblit=True,**kwargs)
        self.cid = fig.canvas.mpl_connect("motion_notify_event", self.cursor_move)
        self.cid2 = fig.canvas.mpl_connect("draw_event", self.clear)
        self.bg1 = None
        self.bg2 = None
        self.needclear = False

    def cursor_move(self,event):
        if event.inaxes == ax1:
            self.needclear = True
            x,y = int(event.xdata),int(event.ydata)
            slice_y = s[:,x]
            slice_x = s[y,:]
            l2.set_ydata(slice_x)
            l3.set_xdata(slice_y)
            fig.canvas.restore_region(self.bg1)
            fig.canvas.restore_region(self.bg2)
            l2.set_visible(True); l3.set_visible(True)
            ax2.draw_artist(l2)
            ax3.draw_artist(l3)
            fig.canvas.blit(ax2.bbox)
            fig.canvas.blit(ax3.bbox)
        else:
            if self.needclear:
                self.clear()
                self.needclear = False

    def clear(self, event=None):
        l2.set_visible(False); l3.set_visible(False)
        self.bg1 = fig.canvas.copy_from_bbox(ax2.bbox)
        self.bg2 = fig.canvas.copy_from_bbox(ax3.bbox)

c = Cursor(color="crimson")

plt.show()

如果你只想在点击时移动光标,而不是移动鼠标,你可以断开它的事件并连接一个新的button_press_event。代码的相关部分则为

# code as above

class Cursor():
    def __init__(self, **kwargs):
        self.cursor = matplotlib.widgets.Cursor(ax1,useblit=True,**kwargs)
        self.cursor.disconnect_events()
        self.cursor.connect_event('draw_event', self.cursor.clear)
        self.cursor.connect_event('button_press_event', self.cursor.onmove)

        self.cid = fig.canvas.mpl_connect("button_press_event", self.cursor_move)
        self.cid2 = fig.canvas.mpl_connect("draw_event", self.clear)
        self.bg1 = None
        self.bg2 = None
        self.needclear = False

    # rest of code as above
yhxst69z

yhxst69z2#

我希望光标是移位超过2D强度图,然后离开他在某些点,所以我可以观看切片,也他们的变化在一个顺利的过程中。
这是我目前的问题解决方案。光标就像之前在左下角初始化,你需要在那个位置单击鼠标拖动到指定的位置,在那里你释放鼠标左键。我现在也实现了我自己的curser blitting。它是以类似于matplotlib游标的水平线和垂直线来完成的。我现在还补充说,你可以改变cursers的数量。在下面的示例中,它的5个(所有这些都在底部左角产生)如果你有一个更好的想法或实现,我会很高兴在这里它。不过,我欠你,因为它现在运行smoothley与arrray多达10万条目:)我的下一步是添加义务天使诅咒
Find a picture here
代码如下:

# -*- coding: utf-8 -*-
"""
Created on Sun Jan 07 20:08:00 2018

@author: Sinthoras
"""
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

class GUI_Cursor(object):
        """
        creates a Gui objekt that plots given 2d data with imshow and creates a curser
        wich can be moved by drag and drop. The horizontal and vertical line of the curser
        are giving the position of a sliced trough the 2d-data and are plottet separetly.
        Scale _x and scale_y represent the scale of the axes and therefore needs to be given too
        """
        def __init__(self,data,scale_x,scale_y,numCur=1):

            self.choice=0
            self.numCur=numCur
            self.motion = False
            self.needclear = False
            self.bg1 = None
            self.bg2 = None
            self.bg_main = None
            self.data=data
            self.scale_x=scale_x 
            self.scale_y=scale_y 
            self.create_fig()
            self.create_events()

        def create_events(self):
            """
            Handles user events
            """
            self.cid1=plt.connect('motion_notify_event', self.mouse_move)
            self.cid2=plt.connect('button_press_event', self.mouse_press)
            self.cid3=plt.connect('button_release_event', self.mouse_release)
            self.cid4=plt.connect("draw_event", self.clear)

        def create_fig(self):
            """
            Creates the GUI, initializes the cursers at minimum of the axes and plots the 2d-data
            """

            #Create figure and subplots
            self.fig=plt.figure(dpi=150)
            self.ax1=self.fig.add_subplot(221)
            self.ax2=self.fig.add_subplot(223,sharex=self.ax1)
            self.ax3=self.fig.add_subplot(222,sharey=self.ax1)
            #self.cursor = matplotlib.widgets.Cursor(self.ax1,useblit=True)

            # plot in ax1
            self.main=self.ax1.imshow(self.data,interpolation='none',aspect='auto',extent=[np.min(self.scale_x),np.max(self.scale_x),np.min(self.scale_y),np.max(self.scale_y)])

            #Creates the limits for the three plots
            self.ax1.axis([np.min(self.scale_x),np.max(self.scale_x),np.min(self.scale_y),np.max(self.scale_y)]) 
            self.ax2.set_ylim(np.min(self.data),np.max(self.data))
            self.ax3.set_xlim(np.min(self.data),np.max(self.data)) 

            #garanties fixed distances beetween the plots
            plt.tight_layout()      
            #creates the curser as an object
            #also goes true the plotteing and everthing else needed
            self.Cursers=np.array([])
            for i in range (self.numCur):            
                        self.Cursers=np.append(self.Cursers,curser(self,i))

            print 'here weeeee are'

        def cursermovement(self,event):
            """
            tracks the curser movement and if a left click appeard near the curser
            the curser will folow the motion
            """

            if event.inaxes:            
                if self.motion: 
                    self.needclear = True
                    x, y = event.xdata, event.ydata

                    #restore the regions in the individual subplots:                                  
                    self.fig.canvas.restore_region(self.bg_main)
                    self.fig.canvas.restore_region(self.bg1)
                    self.fig.canvas.restore_region(self.bg2) 

                    for cur in self.Cursers:
                      if(cur==self.choice):  
                          cur.update(x,y)
                      else:
                          cur.update(None,None)
                                #blit command for the subplots
                    self.fig.canvas.blit(self.ax1.bbox)
                    self.fig.canvas.blit(self.ax2.bbox)
                    self.fig.canvas.blit(self.ax3.bbox)      
                    #update the text
                    #self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))


        def clear(self, event=None):
            print 'here'

            for cur in self.Cursers:                
                cur.clear()

            self.bg_main=self.fig.canvas.copy_from_bbox(self.ax1.bbox)
            self.bg1 = self.fig.canvas.copy_from_bbox(self.ax2.bbox)
            self.bg2 = self.fig.canvas.copy_from_bbox(self.ax3.bbox)

        def mouse_move(self, event):
            self.cursermovement(event)

        def mouse_press(self,event):
            #check range for moving the cursers here in case of zoom in or out
            self.range_x=np.abs(self.ax1.get_xlim()[0]-self.ax1.get_xlim()[1])/20
            self.range_y=np.abs(self.ax1.get_ylim()[0]-self.ax1.get_ylim()[1])/20  

            # check if click occured near curser

            min_x=np.abs(self.Cursers[0].lx.get_ydata()-event.ydata)
            min_y=np.abs(self.Cursers[0].ly.get_xdata()-event.xdata)  

            self.choice=self.Cursers[0]
            for cur in self.Cursers:

                if ((np.abs(cur.lx.get_ydata()-event.ydata)<min_x) or (np.abs(cur.ly.get_xdata()-event.xdata)<min_y)):

                    min_x=np.abs(cur.lx.get_ydata()-event.ydata)
                    min_y=np.abs(cur.ly.get_xdata()-event.xdata)
                    self.choice=cur

            if (min_x<+self.range_x) or (min_y<self.range_y):
                self.motion=True

            #curser jumps without motion to the mouse    
            self.cursermovement(event)

        def mouse_release(self,event):
            #checks if rigth mouse button was released
            self.motion=False

class curser(object):
    """
    Creates one vertical and one horizontal curser in the ax1 plot of the figure
    Input is simply the Gui class itself

    """

    def __init__(self,GUI,index):

            self.GUI=GUI
            self.index=index
            print GUI

            self.lx = GUI.ax1.axhline(color='k')  # the horiz line
            self.ly = GUI.ax1.axvline(color='k')  # the vert line 

            #sets the inital position of the curser needs to change maybe
            self.lx.set_ydata(np.min(self.GUI.scale_y))
            self.ly.set_xdata(np.min(self.GUI.scale_x))

            #Creates sliced plots @ initial values of the curser
            # the change of scale needs to be considered therefore
            # the programm checks for the minimum difference beetween curser pos and self.scale_... entries 
            # and uses the position of the entry to slice the data array
            self.slice_y,=self.GUI.ax3.plot(np.flipud(GUI.data[:,np.argmin(np.abs(self.GUI.scale_x-self.ly.get_xdata()))]),GUI.scale_y)
            self.slice_x,=self.GUI.ax2.plot(self.GUI.scale_x,GUI.data[np.shape(GUI.scale_y)-np.argmin(np.abs(self.GUI.scale_y-self.lx.get_ydata()))-1,:][0])

    def update(self,x,y):
            if(x!=None and y!=None):
                # update the line positions        
                self.lx.set_ydata(y)
                self.ly.set_xdata(x)
                #self.ly2.set_xdata(np.abs((np.min(self.GUI.scale_x)-np.max(self.GUI.scale_x)))/2)
                # updates the side plots for this curser
                self.slice_y.set_xdata(np.flipud(self.GUI.data[:,np.argmin(np.abs(self.GUI.scale_x-self.ly.get_xdata()))]))            
                self.slice_x.set_ydata(self.GUI.data[np.shape(self.GUI.scale_y)-np.argmin(np.abs(self.GUI.scale_y-self.lx.get_ydata()))-1,:])           
                #replot everything  

            #make sure the plots are visible
            self.lx.set_visible(True)
            self.ly.set_visible(True)
            self.slice_x.set_visible(True)
            self.slice_y.set_visible(True)

            #draw command for changed  element
            self.GUI.ax1.draw_artist(self.lx)
            self.GUI.ax1.draw_artist(self.ly)                    
            self.GUI.ax2.draw_artist(self.slice_x)
            self.GUI.ax3.draw_artist(self.slice_y)


    def clear(self):
                self.slice_x.set_visible(False)
                self.slice_y.set_visible(False)
                self.lx.set_visible(False)
                self.ly.set_visible(False)  

"""
programm starts here
"""        

    # define the plot range in x and y axes and change array size       
t = np.arange(0.0, 40.0, 0.015)
t2 = np.arange(0.0, 20.0, 0.01)
    #create a 2d grid to create the intensity map
t_x,t_y=np.meshgrid(t,t2)

    #create the intensity map
s = 10*np.sin(0.1*np.pi*(t_x))*np.sin(0.5*np.pi*t_y)+t_x+t_y

    #create the Gui class
cursor = GUI_Cursor(s,t,t2,5)
plt.show()

问候辛索拉斯

相关问题