I am trying to plot a polygon of user clicks and render them over a matplotlib canvas:
def helperClick(self, clickEvent):
self.lastXClick = clickEvent.x
self.lastYClick = clickEvent.y
self.lastButtonClick = clickEvent.button
def measurePoly(self):
self.lastButtonClick = None
cid = self.ui.canvas2.mpl_connect('button_press_event', self.helperClick)
#Exit render loop on right click
while self.lastButtonClick != 3:
print('waiting')
if self.lastButtonClick == 1:
#Eventually render polygon on click of 1
print('clicked')
self.ui.canvas2.mpl_disconnect(cid)
#do more stuff with polygon data
I am just trying to "wait" for user clicks, do something on a user click, then continue down the function on a left-click. However, my infinite loop freezes up python and crashes. I know this is a bad way to do this (clearly as it doesn't work :P) but I am not sure how to properly do this.
Thanks,
tylerthemiler
It sounds like you're trying to manually run the "mainloop" in your own code?
The whole point of using callback functions is that you let the gui toolkit run its own mainloop (in this case, it's entered when you call show).
Here's a very simple example of something along the lines of what you're trying to do.
It adds verticies when you left-click on the (initially blank) plot, and then draws the corresponding polygon when you right click. Nothing is drawn until you right-click (It's not too hard to draw the polygon while you're adding points by left-clicking, but doing it efficiently in matplotlib is a bit verbose).
import matplotlib.pyplot as plt
class Plot(object):
def __init__(self):
self.poly = []
self.fig, self.ax = plt.subplots()
self.ax.axis([0, 10, 0, 10])
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
plt.show()
def on_click(self, event):
if event.button == 1:
self.poly.append((event.xdata, event.ydata))
elif event.button == 3:
self.draw_poly()
def draw_poly(self):
self.ax.fill(*zip(*self.poly))
self.poly = []
self.fig.canvas.draw()
Plot()
Related
I have tried to create an interactive matplotlib plot using some functions. I want to group the functions into one class (I am still new to this, took help from someone else's code)
import matplotlib.pyplot as plt
def draw_line(startx,starty):
ax = plt.gca()
xy = plt.ginput(1)
x = [startx,xy[0][0]]
y = [starty,xy[0][1]]
line = ax.plot(x,y, picker=True , pickradius = 5 , color = "blue")
ax.figure.canvas.draw()
def onclick(event):
"""
This implements click functionality. If it's a double click do something,
else ignore.
Once in the double click block, if its a left click, wait for a further
click and draw a line between the double click co-ordinates and that click
(using ginput(1) - the 1 means wait for one mouse input - a higher number
is used to get multiple clicks to define a polyline)
"""
ax = plt.gca()
if event.dblclick:
if event.button == 1:
# Draw line
draw_line(event.xdata,event.ydata) # here you click on the plot
else:
pass # Do nothing
if event.button == 1:
pass
def onpick(event):
ax = plt.gca()
"""
Handles the pick event - if an object has been picked, store a
reference to it. We do this by simply adding a reference to it
named 'stored_pick' to the axes object. Note that in python we
can dynamically add an attribute variable (stored_pick) to an
existing object - even one that is produced by a library as in this
case
"""
this_artist = event.artist # the picked object is available as event.artist
ax.picked_object = this_artist
def on_key(event):
"""
Function to be bound to the key press event
If the key pressed is delete and there is a picked object,
remove that object from the canvas
"""
if event.key == u'delete':
ax = plt.gca()
if ax.picked_object:
ax.picked_object.remove()
ax.picked_object = None
ax.figure.canvas.draw()
def applyplt():
fig = plt.gcf()
ax = plt.gca()
cidonclic = fig.canvas.mpl_connect('button_press_event', onclick)
cidonpic = fig.canvas.mpl_connect('pick_event', onpick)
cidonkey = fig.canvas.mpl_connect('key_press_event', on_key)
"""
Basic Plot to test the function.
"""
fig1 = plt.figure(figsize = (10,10))
gs = fig1.add_gridspec(10,10)
ax101 = fig1.add_subplot(gs[:,:])
ax101.set_ylim(0,10)
ax101.set_xlim(0,10)
applyplt()
plt.show()
I want to group these event functions in one class name(object) (e.g.: class Drawer(object))
If any other optimization can be done, please suggest that too. Thanks!
Here are the functions grouped into a class:
import matplotlib.pyplot as plt
class Interactivity:
def __init__(self, fig = None):
self.fig = plt.gcf() if fig is None else fig
self.ax = self.fig.gca()
self.connections = ()
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.disconnect()
def connect(self):
""" Install the event handlers for the plot. """
self.connections = (
self.fig.canvas.mpl_connect('button_press_event', self.onclick),
self.fig.canvas.mpl_connect('pick_event', self.onpick),
self.fig.canvas.mpl_connect('key_press_event', self.on_key),
)
def disconnect(self):
""" Uninstall the event handlers for the plot. """
for connection in self.connections:
self.fig.canvas.mpl_disconnect(connection)
def draw_line(self, startx, starty):
xy = plt.ginput(1)
x = [startx, xy[0][0]]
y = [starty, xy[0][1]]
self.ax.plot(x, y, picker=True, pickradius=5, color='blue')
self.ax.figure.canvas.draw()
def onclick(self, event):
"""
This implements click functionality. If it's a double click do
something, else ignore.
Once in the double click block, if its a left click, wait for a further
click and draw a line between the double click co-ordinates and that
click (using ginput(1) - the 1 means wait for one mouse input - a
higher number is used to get multiple clicks to define a polyline)
"""
print('onclick')
if event.dblclick:
if event.button == 1:
self.draw_line(event.xdata, event.ydata)
def onpick(self, event):
"""
Handles the pick event - if an object has been picked, store a
reference to it. We do this by simply adding a reference to it
named 'picked_object' to the axes object.
"""
print('onpick')
this_artist = event.artist
# the picked object is available as event.artist
self.ax.picked_object = this_artist
def on_key(self, event):
"""
Function to be bound to the key press event
If the key pressed is delete and there is a picked object,
remove that object from the canvas
"""
print('onkey: ', event.key)
if event.key == 'delete' and self.ax.picked_object:
self.ax.picked_object.remove()
self.ax.picked_object = None
self.ax.figure.canvas.draw()
Usage:
# Basic plot to test the functionality
fig = plt.figure(figsize = (10,10))
gs = fig.add_gridspec(10,10)
ax101 = fig.add_subplot(gs[:,:])
ax101.set_ylim(0,10)
ax101.set_xlim(0,10)
with Interactivity():
plt.show()
As you can see, it can be used as a context handler to install and uninstall the handlers automatically, or you can create an instance of it, and then call its connect method to install the handlers manually (and then later optionally call the disconnect method to uninstall the handlers).
I want to write unittest for interactive matplotlib plots. My problem is that I couldn't find a good way of simulating key press or mouse button press events. I know about pyautogui, but then I'd have to care about the position of the matplotlib window on the screen (also, for example on TravisCI I doubt it would work properly without configuring it). I've tried looking into Matplotlib's unittests, but I couldn't find anything useful yet. The best solution would be triggering an event inside the code without involving the GUI part, but so far I couldn't solve it.
The easiest example I've managed to come up with is below. You can mark points on the plot using the i key. The function here I want to test is on_press.
import numpy as np
import matplotlib.pyplot as plt
class PointRecorder:
def __init__(self, x, y):
plt.ion()
self.figure = plt.figure()
self.cid = self.figure.canvas.mpl_connect("key_press_event", self.on_press)
self.x = x
self.y = y
self.x_points, self.y_points = [2], [0.5]
plt.plot(self.x, self.y, "r")
self.pts, = plt.plot(self.x_points, self.y_points, "ko", markersize=6, zorder=99)
plt.show(block=True)
def on_press(self, event):
ix, iy = event.xdata, event.ydata
if event.inaxes is None:
return
if event.key == 'i':
self.x_points.append(ix)
self.y_points.append(iy)
self.pts.set_data(self.x_points, self.y_points)
if self.pts.stale:
self.figure.canvas.draw_idle()
def get_data(self):
return self.pts.get_data()
if __name__ == "__main__":
x = np.linspace(0, 6, 100)
y = np.sin(x)
graph = PointRecorder(x, y)
print(*graph.get_data())
Can you suggest a way how this kind of functionality should be tested properly?
I'm no expert on unit testing, but my guess is that you need to instantiate an Event object (in the case of key_press_event, it should be a KeyEvent) and call graph.on_press(event) from your testing code
As suggested, the solution is the following: First define a blank event.
from unittest import mock
def mock_event(xdata, ydata, button, key, fig, canvas, inaxes=True):
event = mock.Mock()
event.button = button
event.key = key
event.xdata, event.ydata = xdata, ydata
event.inaxes = inaxes
event.fig = fig
event.canvas = canvas
event.guiEvent = None
event.name = 'MockEvent'
return event
Then initialize the PointRecorder class above. After that, define a mock_event which suits the PointRecorder.on_press method properly. Also patch plt.show to avoid blocking execution.
#mock.patch("matplotlib.pyplot.show")
def test_insert(mock_show):
x, y = np.arange(100), np.arange(100)
obj = PointRecorder(x, y)
mck = mock_event(xdata=50, ydata=40, button="i", key="i", fig=obj.figure, canvas=obj.figure.canvas, inaxes=True)
obj.on_clicked(event=mck)
a, b = obj.get_data()
np.testing.assert_array_equal(a, np.array([2, 50])) # 2 was originally in the __init__
np.testing.assert_array_equal(b, np.array([0.5, 40])) # 0.5 was originally in the __init__
mock_show.assert_called()
I'd like to use a InfiniteLine in in pyqtgraph that moves with the mouse moving. Upon left-clicking the function shall return the xposition. I am able to retrieve the x-value on left-clicking and moving the InfiniteLine with the mouse movement. However I am stucked at simply returning the x position. I'd like to wait for the left click, as I want to repeat this procedure multiple times.
def addVerticalLineAndGetXOnClick(self):
def changePosVertLine(event):
if self.sceneBoundingRect().contains(event):
mousePoint = self.plotItem.vb.mapSceneToView(event)
line.setPos(mousePoint.x())
def onMouseClick(event):
if event.button() == 1:
self.removeItem(line)
self.scene().sigMouseClicked.disconnect()
self.setMouseTracking(False)
mousePoint = self.plotItem.vb.mapSceneToView(event.scenePos())
self.xPos.emit(mousePoint.x())
penLin = mkPen(color = '#000000', width = 1)
line = LRI(0.,pen = penLin, name= 'singleLineToGetXPos' )
self.addItem(line)
self.setMouseTracking(True)
self.scene().sigMouseMoved.connect(changePosVertLine)
self.scene().sigMouseClicked.connect(onMouseClick)
#wait to return after mouse click and return
"""???"""
Through the program interface button or mouse click to make the program pause, click the button or mouse again to allow the program to continue from the place where it was last stopped, the focus is to pause how to achieve.The time from the last pause again is uncertain.My codes are following:
import matplotlib.pyplot as plt
class ab():
def __init__(self,x,y):
self.x=x
self.y=y
a=ab(2,4)
b=ab(2,6)
c=ab(2,8)
d=ab(6,10)
f=ab(6,13)
e=ab(6,15)
task=[]
task.append(a)
task.append(b)
task.append(c)
task.append(d)
task.append(e)
task.append(f)
for i in task:
while i.x<=30:
print(i.x)
plt.plot(i.x,i.y,'o')
plt.pause(0.1)
i.x=i.x+2
i.y=i.y+2
plt.show()
You can combine matplotlib's animation module with matplotlib's event connections. The code below should do more or less what you want:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# I'm replacing your class with two lists for simplicity
x = [2,2,2,6,6,6]
y = [4,6,8,10,13,15]
# Set up the figure
fig, ax = plt.subplots()
point, = ax.plot(x[0],y[0],'o')
# Make sure all your points will be shown on the axes
ax.set_xlim(0,15)
ax.set_ylim(0,15)
# This function will be used to modify the position of your point
def update(i):
point.set_xdata(x[i])
point.set_ydata(y[i])
return point
# This function will toggle pause on mouse click events
def on_click(event):
if anim.running:
anim.event_source.stop()
else:
anim.event_source.start()
anim.running ^= True
npoints = len(x)
# This creates the animation
anim = FuncAnimation(fig, update, npoints, interval=100)
anim.running=True
# Here we tell matplotlib to call on_click if the mouse is pressed
cid = fig.canvas.mpl_connect('button_press_event', on_click)
# Finally, show the figure
plt.show()
I have a program which shows an image (fig 1). When the image is clicked it shows the colour in the image that was clicked in a separate Matplotlib window (fig 2). Fig 2 has some buttons that call different functions when they are clicked.
My problem is that the functions that are meant to be called in fig 2 are being called when fig 1 is clicked.
The code looks like this:
def show_fig1(img):
# Plot the image
plt.figure(1)
ax = plt.gca()
fig = plt.gcf()
implot = ax.imshow(img)
# Detect a click on the image
cid = fig.canvas.mpl_connect('button_press_event', on_pixel_click)
plt.show(block=True)
# Called when fig1 is clicked
def on_pixel_click(event):
if event.xdata != None and event.ydata != None:
# Do some computation here that gets the image for fig2
img = get_fig2_img()
show_fig2(img, event)
def show_fig2(img, event):
plt.figure(2)
plt.imshow(img)
# Specify coordinates of the button
ax = plt.axes([0.0, 0.0, 0.2, 0.1])
# Add the button
button = Button(ax, 'button')
# Detect a click on the button
button.on_clicked(test())
plt.show(block=True)
def test():
print "Button clicked"
So test() is called instantly when on_pixel_click() is called even though theoretically it should wait until the button is clicked because of the button.on_clicked() command.
Any help?
Thanks in advance :)
On this line:
button.on_clicked(test())
You are telling Python to execute your test function, rather than just passing a reference to it. Remove the brackets and it should sort it:
button.on_clicked(test)