I am trying to update a plot with serial data with matplotlib FuncAnimation. I am using the following example to embed the plot in a wx app.
Embedding a matplotlib figure inside a WxPython panel
However, the plot is not updated and only the initial plot is displayed.
In fact, the update function is never executed which is checked with print statements in try and except blocks. You may see the script here.
import wx
from matplotlib.figure import Figure as Fig
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg as NavigationToolbar
from collections import deque
import serial
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib as mlp
import numpy as np
# Class that inherits wx.Panel. The purpose is to embed it into
# a wxPython App. That part can be seen in main()
class Serial_Plot(wx.Panel):
def __init__(self, parent, strPort, id=-1, dpi=None, **kwargs):
super().__init__(parent, id=id, **kwargs)
self.figure = Fig(figsize=(20,20))
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure)
self.plot_data, = self.ax.plot([1,2,3,4],[1,2,3,4])
self.toolbar = NavigationToolbar(self.canvas)
self.toolbar.Realize()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, 1, wx.EXPAND)
sizer.Add(self.toolbar, 0, wx.RIGHT | wx.EXPAND)
self.SetSizer(sizer)
self.Fit()
# Serial communication
self.ser = serial.Serial(strPort, 115200)
# Serial data initialized as deque. The serial readings from arduino
# are set to be one value per line.
self.vals = deque()
# matplotlib function animation
anim = animation.FuncAnimation(self.figure, self.update,
interval=20)
plt.show()
self.close
def update(self, i):
try:
print('trying')
# read serial line
data = float(self.ser.readline().decode('utf-8'))
print(data)
self.vals.append(data)
# update plot data
self.plot_data.set_data(range(len(self.vals)), self.vals)
except:
print('oops')
pass
return self.plot_data
def close(self):
# close serial
self.ser.flush()
self.ser.close()
def main():
app = wx.App(False)
frame = wx.Frame(None, -1, "WX APP!")
demo_plot = Serial_Plot(frame,'COM3')
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
As I said in my previous answer(Embedding matplotlib FuncAnimation in wxPython: Unwanted figure pop-up), trying to use animation.FuncAnimation() and plt.show() within wxpython is not going to work, because you have 2 main.loops.
If we cull all of the wx.python from your original question and pare it down to the basics, we get this:
from collections import deque
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
vals = deque()
figure = plt.figure(figsize=(20,20))
ax = plt.axes(xlim=(0, 1000), ylim=(0, 5000))
plot_data, = ax.plot([], [])
def update(i):
data = float(random.randint(1000, 5000))
vals.append(data)
plot_data.set_data(range(len(vals)), vals)
return plot_data
anim = animation.FuncAnimation(figure, update, interval=20)
plt.show()
It works because the animation function is controlled by plt.show() i.e. matplotlib but once you introduce wxpython, it controls the main.loop not matplotlib.
I believe that you will have to use a wxpython timer to control the serial device read, feeding data into the plot, as demonstrated in my previous answer.
Embedding matplotlib FuncAnimation in wxPython: Unwanted figure pop-up
Related
fig, ax = plt.subplots(figsize=(16,8),dpi=100,subplot_kwn {'projection':nccrs.PlateCarree()})
ax.set_global()
plt.subplots_adjust(left=0.04, bottom=0.02, right=0.96, top=0.96)
# set a figure window's title
fig2 = plt.gcf()
fig2.canvas.set_window_title('Metheoros 1.0')
mng = plt.get_current_fig_manager()
mng.Maximize(True)
I Tried this, but didn't work on mac
The first line should have an equals sign in place of the n in subplot_kwn:
fig, ax = plt.subplots(figsize=(16,8),dpi=100,subplot_kw= {'projection':ccrs.PlateCarree()})
You might want to check what you have imported cartopy.crs as, because that may cause problems as well.
EDIT:
So I did quite a bit of digging, and found that in mng has a method called 'full_screen_toggle', so in theory, you could call mng.full_screen_toggle() followed by mng.show(). I tried that, but that seemed to have no effect. I dug through the source code and found that the Mac OS X backend does not have a fullscreen function implemented (as far as I can tell).
That means that you'll have to use a different backend. You can change backends by calling plt.switch_backend('backend') where backend is your desired backend. This function accepts the following arguments:
'pdf', 'pgf', 'Qt4Agg', 'GTK', 'GTKAgg', 'ps', 'agg', 'cairo', 'MacOSX', 'GTKCairo', 'WXAgg', 'template', 'TkAgg', 'GTK3Cairo', 'GTK3Agg', 'svg', 'WebAgg', 'CocoaAgg', 'emf', 'gdk', 'WX'
# -*- coding: UTF-8 -*-
'''
Created on 4 de set de 2016
#author: VladimirCostadeAlencar
'''
from numpy import arange, sin, pi
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
import wx
class CanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, 0, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
def draw(self):
from ler_csv import ler_csv
from plotar_csv04 import plotar_pontos
nomearq = 'gps01.csv'
print 'Reading points...'
coords = ler_csv(nomearq)
figure, ax = plotar_pontos(self, coords)
print 'Plotting on Wx...'
self.canvas = FigureCanvas(self, 0, figure)
if __name__ == "__main__":
app = wx.PySimpleApp()
fr = wx.Frame(None, title='Metheoros v1.0 - 2016')
panel = CanvasPanel(fr)
panel.draw()
fr.Maximize(True)
fr.Show()
app.MainLoop()
I am using PyQt 4 for a basic GUI and matplotlib for a plot from which I want to read the coordinates of the plotted data points. Based on these examples (simple picking example), I have the simple problem that I cannot display the coordinates of a data point in a text field such as QtGui.QLabel(). I do not understand why I cannot call the instance Window.msg in the method onpick(). Probably it is because the instance it not given to the method. I only have a basic understanding of object oriented programming (but I am working on it), so the problem is my lack of knowledge.
My question: How to display the coordinates of chosen data (by clicking on it) from a matplotlib plot in my GUI based on PyQT (in that case in my label lbl)?
Also, it would be nice to highlight the chosen data point in the plot.
Here is my code (working):
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# This does not work:
#Window.lbl.setText(self.msg)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
The self is being overlapped by the picker (not sure why). In any case this should work:
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def changelabel(arg):
main.lbl.setText(str(arg[0])+' '+str(arg[1]))
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# Window.changelabel(self.msg)
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
, the change is in the setText function, since I call it directly from the variable (no self or Window).
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))
Starting with the working Matplotlib animation code shown below, my goal is to embed this animation (which is just a circle moving across the screen) within a PyQT4 GUI.
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import animation
fig,ax = plt.subplots()
ax.set_aspect('equal','box')
circle = Circle((0,0), 1.0)
ax.add_artist(circle)
ax.set_xlim([0,10])
ax.set_ylim([-2,2])
def animate(i):
circle.center=(i,0)
return circle,
anim = animation.FuncAnimation(fig,animate,frames=10,interval=100,repeat=False,blit=True)
plt.show()
I am able to accomplish this using the following code, but there is one hitch: I cannot get blitting to work.
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation
class Window(QtGui.QDialog): #or QtGui.QWidget ???
def __init__(self):
super(Window, self).__init__()
self.fig = Figure(figsize=(5,4),dpi=100)
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111) # create an axis
self.ax.hold(False) # discards the old graph
self.ax.set_aspect('equal','box')
self.circle = Circle((0,0), 1.0)
self.ax.add_artist(self.circle)
self.ax.set_xlim([0,10])
self.ax.set_ylim([-2,2])
self.button = QtGui.QPushButton('Animate')
self.button.clicked.connect(self.animate)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def animate(self):
self.anim = animation.FuncAnimation(self.fig,self.animate_loop,frames=10,interval=100,repeat=False,blit=False)
self.canvas.draw()
def animate_loop(self,i):
self.circle.center=(i,0)
return self.circle,
def main():
app = QtGui.QApplication(sys.argv)
ex = Window()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
When I set blit=True, after pressing the Animate button I get the following error:
a.figure.canvas.restore_region(bg_cache[a])
KeyError: matplotlib.axes._subplots.AxesSubplot object at 0x00000000095F1D30
In searching this error, I find many posts about how blitting does not work on Macs, but I am using Windows 7. I have tried replacing self.canvas.draw() with self.canvas.update(), but this does not work.
After looking at the source code of the animation module, I realized that there is an error in the Animation class (the dictionary bg_cache is empty, when it is accessed for the first time with blitting switched on).
This is fixed in the git version of matplotlib; however, in the most recent stable version 1.5.1, the bug is still present. You can either fix the bug in the matplotlib code itself or you can make a subclass to FuncAnimation. I chose that way, because it should still work after updating matplotlib.
from matplotlib import animation
class MyFuncAnimation(animation.FuncAnimation):
"""
Unfortunately, it seems that the _blit_clear method of the Animation
class contains an error in several matplotlib verions
That's why, I fork it here and insert the latest git version of
the function.
"""
def _blit_clear(self, artists, bg_cache):
# Get a list of the axes that need clearing from the artists that
# have been drawn. Grab the appropriate saved background from the
# cache and restore.
axes = set(a.axes for a in artists)
for a in axes:
if a in bg_cache: # this is the previously missing line
a.figure.canvas.restore_region(bg_cache[a])
Then, simpy use MyFuncAnimation instead of animation.FuncAnimation.
Took me a while to figure it out, but I hope it helps anybody.
After some time I managed to recreate the animation by using the underlying functions directly and not using the animation wrapper:
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation
from time import sleep
class Window(QtGui.QDialog): #or QtGui.QWidget ???
def __init__(self):
super(Window, self).__init__()
self.fig = Figure(figsize=(5, 4), dpi=100)
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111) # create an axis
self.ax.hold(False) # discards the old graph
self.ax.set_aspect('equal', 'box')
self.circle = Circle((0,0), 1.0, animated=True)
self.ax.add_artist(self.circle)
self.ax.set_xlim([0, 10])
self.ax.set_ylim([-2, 2])
self.button = QtGui.QPushButton('Animate')
self.button.clicked.connect(self.animate)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
self.canvas.draw()
self.ax_background = self.canvas.copy_from_bbox(self.ax.bbox)
def animate(self):
self.animate_loop(0)
def animate_loop(self,begin):
for i in range(begin,10):
self.canvas.restore_region(self.ax_background)
self.circle.center=(i,0)
self.ax.draw_artist(self.circle)
self.canvas.blit(self.ax.bbox)
self.canvas.flush_events()
sleep(0.1)
def main():
app = QtGui.QApplication(sys.argv)
ex = Window()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Maybe this will be of use to you.
I'm struggling on an application combining wxPython and matplotlib.
I want to embed an animated matplotlib object in an wxPanel. The Data should be added on runtime.
My Module Code:
(i cant get the correct formatting, see http://pastebin.com/PU5QFEzG)
'''
a panel to display a given set of data in a wxframe as a heatmap, using pcolor
from the matplotlib
#author: me
'''
import wx
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas #todo: OW 26.10.15 needed?
class plotPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = plt.Figure()
self.subplot = self.figure.add_subplot(111)
plt.title('test')
self.canvas = FigureCanvas(self, -1, self.figure) #ToDo: OW 26.10.15 Verstehen
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
self.dataSet = []
self.animator = animation.FuncAnimation(self.figure,self.anim, interval=1000)
def anim(self, a):
if(len(self.dataSet) == 0):
return 0
i = a % len(self.dataSet)
obj = self.subplot.pcolor(self.dataSet[i], cmap='RdBu')
return obj
def add_data(self, data):
self.dataSet.append(data)
#
# Code for a standalone test run
#
class TestFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self,parent,title=title,size=(1000,1000))
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Status Bar')
if __name__ == '__main__':
from numpy.random import rand #todo: OW 26.10.15 remove
app = wx.App(redirect=False)
frame = TestFrame(None, 'Debug Frame')
panel = plotPanel(frame)
frame.Show()
C = rand(10,10)
panel.add_data(C)
C = rand(10,10)
panel.add_data(C)
C = rand(10,10)
panel.add_data(C)
app.MainLoop()
Im now struggeling on adding more Details to the Graph, eg a colorbar or a title.
If I add self.subplot.title = 'test' in the anim_Fkt, i get "'str' object has no attribute 'get_animated'". If i try plt.title('test'), it has no effect.
Whats the correct way to add a title or a colorbar or a legend?
To add the features to an embedded matplotlib graph, you have to use object-oriented matplotlib API. A matplotlib graph consists of a figure (a matplotlib.figure.Figure instance), which has one or several axes (matplotlib.axes.Axes instances) in it. Each of them, in their turn, contains Drawables (lines, images, scatter plots etc).
A title can be added (or modified) to the Figure or to each Axes with setter methods, such as Figure.suptitle or Axes.set_title (or, in your case, self.figure.suptitle() or self.subplot.set_title).
The colorbar is a little bit trickier, as it needs a data object (a mappable) to be created: we can create it only in the anim function. Also, wo do not want to create many colorbar instances; once created, we only need to update it. Achieving that is easy: instantiate self.colorbar with None in constructor, and then check it against None in animation function: if it is None, then create colorbar, if it is not, then update it:
class plotPanel(wx.Panel):
def __init__(self, parent):
...
self.colorbar = None
def anim(self, a):
...
self.subplot.set_title("Frame %i" % a)
if self.colorbar is None:
self.colorbar = self.figure.colorbar(obj)
else:
self.colorbar.update_normal(obj)
self.colorbar.update_normal(obj)
return obj
I am trying to plot real-time data using matplotlib's scatter function (~200 fps) with a pyqt wrapper. Each frame consists of around 1000 points. However, I get a maximum of around 7 or 8 frames per second. My code is as follows:
import sys
import random
import types
import re
import sys
import os
import matplotlib.pyplot as plt
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib import pyplot as plt
from time import sleep
class Window(QtGui.QDialog):
def __init__(self, increment=10, nSteps=500, timestep=0.0004, parent=None):
super(Window, self).__init__(parent)
# frame increment
self.increment = increment
self.nSteps = nSteps
self.timestep = timestep # in seconds
# a figure instance to plot on
self.figure = plt.figure()
self.ax1 = self.figure.add_subplot(1, 1, 1)
# this is the Canvas Widget that displays the `figure`
# it takes the `figure` instance as a parameter to __init__
self.canvas = FigureCanvas(self.figure)
# this is the Navigation widget
# it takes the Canvas widget and a parent
self.toolbar = NavigationToolbar(self.canvas, self)
# Just some button connected to `plot` method
self.button = QtGui.QPushButton('Plot')
self.button.clicked.connect(self.start)
# the current frame
self.index = 0
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def start(self):
# connects timer to dynamic plot
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.updatePlot)
self.timer.start(100)
def updatePlot(self):
if self.index > self.increment*self.nSteps:
self.timer.stop()
return
data = [["ghost_points.dat", "green"], ["wall_points.dat", "red"], [("mps.%s.out") % self.index, "blue"]]
self.index += self.increment
self.ax1.cla() # clear axes
for i in data:
<gets x and y data and color>
self.ax1.scatter(x, y, c=color)
self.canvas.draw()
def mpsPlot():
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
mpsPlot()
I've looked at several other sources online, but none of them have provided substantial answers that have helped me in my goal. Is there any way to speed up my code to reach ~250 fps? If not, are there any alternatives to matplotlib that will allow me to reach this speed?