Python matplotlib中带光标的二维强度贴图
我目前正在尝试基于matplotlib(Python 2.7)创建一个简单的Gui。 我的目标是绘制一张2d强度图,并使用用户控制的光标查看x和y切片 它已经按照我想要的方式运行了(见下面的示例)。 但是,我可以使用的数组大小似乎在程序中有一个主要限制。 如果数组中的条目数超过几百万,则进程开始滞后。我想原因是我在移动光标的过程中重新绘制了所有的图形 有没有一个选项可以让我只重绘光标和切片,而不重绘强度贴图?我在那上面什么也没找到。 或者除了用Tkinter这样的真实Gui编写游标之外,还有其他选择吗 在下面的示例中,光标的初始位置为00。如果您在光标位置附近按下鼠标右键,并一直按住鼠标右键,直到将光标移动到所需位置并松开按钮,则将跟随鼠标移动Python matplotlib中带光标的二维强度贴图,python,python-2.7,events,matplotlib,Python,Python 2.7,Events,Matplotlib,我目前正在尝试基于matplotlib(Python 2.7)创建一个简单的Gui。 我的目标是绘制一张2d强度图,并使用用户控制的光标查看x和y切片 它已经按照我想要的方式运行了(见下面的示例)。 但是,我可以使用的数组大小似乎在程序中有一个主要限制。 如果数组中的条目数超过几百万,则进程开始滞后。我想原因是我在移动光标的过程中重新绘制了所有的图形 有没有一个选项可以让我只重绘光标和切片,而不重绘强度贴图?我在那上面什么也没找到。 或者除了用Tkinter这样的真实Gui编写游标之外,还有其他
# -*- noplot -*-
#from __future__ import print_function
import matplotlib.pyplot as plt
import numpy as np
class 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.
"""
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 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.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 occured near curser
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
"""
programm 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()
您好
Sinthoras提供了一个名为。您可以将其用于热图图中的线。这可以使用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()
如果只想在单击时移动光标,而不是移动鼠标,则可以断开其事件并连接新的按钮\u press\u 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
我希望光标可以在2d强度贴图上移动,然后在某些点离开他,这样我就可以在平滑的过程中观察切片和它们的变化 这是我目前解决问题的方法。光标就像在左下角初始化之前一样,您需要在该位置单击以将其拖动到指定位置,在该位置释放鼠标左键。我现在也实现了我自己的光标闪烁。它以类似于matplotlib游标的wy方式完成,具有水平线和垂直线。 我还补充说,现在您可以更改游标的数量。 在其5下方的示例中(它们都在底部的左角产卵) 如果您有更好的想法或实施方案,我很乐意在这里介绍。 但我还是欠你的,因为它现在运行得非常顺利,最多有1000万条条目:) 我的下一步是在诅咒者身上添加施恩天使 代码如下:
# -*- 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()
#-*-编码:utf-8-*-
"""
创建于2018年1月7日星期日20:08:00
@作者:辛托拉斯
"""
导入matplotlib
将matplotlib.pyplot作为plt导入
将numpy作为np导入
类GUI_游标(对象):
"""
创建一个Gui对象,该对象使用imshow打印给定的2d数据,并创建一个游标
可以通过拖放来移动光标。光标的水平线和垂直线
通过二维数据给出切片槽的位置,并单独绘制。
Scale_x和Scale_y表示轴的比例,因此也需要给出
"""
定义初始值(self,data,scale_x,scale_y,numCur=1):
self.choice=0
self.numCur=numCur
自我运动=错误
self.needclear=False
self.bg1=无
self.bg2=无
self.bg_main=无
self.data=data
self.scale\ux=scale\ux
self.scale\u y=scale\u y
self.create_fig()
self.create_事件()
def创建_事件(自):
"""
处理用户事件
"""
self.cid1=plt.connect('motion\u notify\u event',self.mouse\u move)
self.cid2=plt.connect('button\u press\u event',self.mouse\u press)
self.cid3=plt.connect('button\u release\u event',self.mouse\u release)
self.cid4=plt.connect(“绘制事件”,self.clear)
def创建图(自身):
"""
创建GUI,在最少的轴上初始化光标,并绘制2d数据
"""
#创建图形和子图
自身图=plt图(dpi=150)
self.ax1=self.fig.add_子批次(221)
self.ax2=self.fig.add_子批(223,sharex=self.ax1)
self.ax3=self.fig.add_子批(222,sharey=self.ax1)
#self.cursor=matplotlib.widgets.cursor(self.ax1,useblit=True)
#在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)])
#为三个绘图创建限制
self.ax1.轴([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))
#在地块之间有固定的距离
plt.紧_布局()
#将游标创建为对象
#这也实现了计划和其他所有需要的东西
self.Cursers=np.array([])
对于范围内的i(self.numCur):
self.Cursers=np.append(self.Cursers,curser(self,i))
打印“Weeee are在这里”
def游标移动(自身、事件):
"""
跟踪光标移动,如果光标附近出现左键单击
光标将跟随运动
"""
如果event.inaxes:
如果自行:
self.needclear=True
x、 y=event.xdata,event.ydata
#恢复单个子批次中的区域:
self.fig.canvas.restore\u区域(self.bg\u主)