Python: pyQt, Qthread and matplotlib: subplots crash at the second call - python

I am trying to draw graphs with the result of a thread using matplotlib. First I launch the thread using a pyqt button, all is ok. But the second time I press the button, subplots crash because I suppose tuples can't be modified. This is my simplfied code that you can try and see by yourself:
from PyQt4 import QtGui, QtCore
import matplotlib.pyplot as plt
import numpy as np
from PyQt4.Qt import *
import sys
class thread(QThread):
def __init__(self, parent=None):
QThread.__init__(self, parent)
def __del__(self):
self.wait()
def render(self):
self.start()
def run(self):
data = [(1, 10), (3, 100), (4, 1000), (5, 100000)]
data_in_array = np.array(data)
transposed = data_in_array.T
x, y = transposed
fig, ax = plt.subplots(1,1)
ax.plot(x, y, 'ro')
ax.plot(x, y, 'b-')
plt.show()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
self.thread=thread()
def handleButton(self):
self.thread.render()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Please, how can I solve it to avoid crash and uses several times my button?
Thanks

This is not the right way to mix your Qt application and Matplotlib (which uses Qt in its backend).
See https://matplotlib.org/gallery/user_interfaces/embedding_in_qt_sgskip.html for a better explanation than I can provide.

I got the same problem, was searching solution.
By adding these 3 lines solved the issue. Please add these 3 lines before importing pyplot
import matplotlib as mpl
mpl.rcParams['backend'] = "qt4agg"
mpl.rcParams['backend.qt4'] = "PySide"

Related

Creating and updating a plot using a QPushButton in PyQt5

I am pretty new to PyQt5. I would like to have a button that, when clicked, generates and shows a plot. I cannot seem to find a way to use the QPushButton to call the PlotCanvas function successfully.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import QtWidgets, Qt
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import random
import numpy as np
import math
class GUI_Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.plot_button = QtWidgets.QPushButton(self)
self.plot_button.setText('Plot')
self.plot_button.setToolTip('Plots the function')
self.plot_button.move(50,240)
self.plot_button.clicked.connect(self.updater)
def updater(self):
PlotCanvas(self, inputparam=5)
print('Function has been called.')
The code above does not work (PlotCanvas doesn't plot) even though I receive the 'function has been called' printout.
However if I simply call PlotCanvas in initUI, it plots it as expected.
def initUI(self):
self.plot_button = QtWidgets.QPushButton(self)
self.plot_button.setText('Plot')
self.plot_button.setToolTip('Plots the function')
self.plot_button.move(50,240)
self.plot_button.clicked.connect(self.updater)
PlotCanvas(self, inputparam=5)
The above code plots the function as expected.
Furthermore, if I simply call the updater function without the pushbutton (shown below), the plot is shown.
def initUI(self):
self.plot_button = QtWidgets.QPushButton(self)
self.plot_button.setText('Plot')
self.plot_button.setToolTip('Plots the function')
self.plot_button.move(50,240)
#self.plot_button.clicked.connect(self.updater)
self.updater()
def updater(self):
PlotCanvas(self, inputparam=5)
print('Function has been called.')
I sense I do not know how 'self' works and by passing a clicked signal I am somehow messing things up. Any help/insight would be appreciated.
class PlotCanvas(FigureCanvas):
def __init__(self, parent=None, width=1.5, height=2, dpi=100, inputparam=0):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.LLA_Plotter(inputparam)
# This function performs calculations and outputs x,y data points to plot
ax = self.figure.add_subplot(111)
for i in range(0,90):
ax.scatter(x[i], y[i], color='b', s=0.6)
ax.set_title('testing plot')
plt.show()

Plotting with a for loop in matplotlib, with lists

form_class = uic.loadUiType("GUI.ui")[0] # Load the UI
class MainWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, target, Afb, np, conversion, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
if self.RadioButton.isChecked():
Ids, Ugs = unipolar_steuerkennlinie(self.target, self.Afb, self.np, self.conversion)
def plot_matplotlibwidget(self, *args):
for x in range(0, 40):
self.matplotlibwidget.axes.plot([args[0][x]], [[args[1][x]]])
self.matplotlibwidget.axes.figure.canvas.draw_idle()
Hi, I have this function and I don't really know how to plot correctly. Now it shows nothing but the does change the scale.
Since it is hard to completely replicate your code and error, I've created you a simple minimal GUI that has a MPL widget and draws a few random lines.
from PyQt4 import QtGui, QtCore
import sys
import functools
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
from matplotlib.figure import Figure
class test(QtGui.QWidget):
def __init__(self,parent=None):
self.widget=QtGui.QWidget.__init__(self, parent)
# Button to plot
self.btnPlot = QtGui.QPushButton('Plot')
self.btnPlot.connect(self.btnPlot, QtCore.SIGNAL('clicked()'), self.btnPlotPressed)
# Line edit for number of lines
self.qleN = QtGui.QLineEdit(str(0))
# Create canvas
self.setupMPLWidget()
# Layout
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.btnPlot)
self.hbox.addWidget(self.qleN)
self.hbox.addWidget(self.canvas)
self.setLayout(self.hbox)
self.show()
def btnPlotPressed(self):
"""Plots a few lines."""
# Get number of buttons to add
n=int(self.qleN.text())
# Generate some data
xs,ys=self.randData(n)
# Plot
self.plot_matplotlibwidget(xs,ys)
def randData(self,n):
"""Creates n random data sets."""
ys=[]
xs=[]
for i in range(n):
xs.append(np.arange(n-i))
ys.append(np.random.random(size=xs[-1].shape))
return xs,ys
def plot_matplotlibwidget(self,*args):
"""Plots list of datasets."""
for x in range(0, len(args[0])):
self.ax.plot(args[0][x], args[1][x])
self.ax.figure.canvas.draw_idle()
def setupMPLWidget(self):
"""Sets up a MPL figure to draw on."""
# Create parenting widget
self.plotFrame = QtGui.QWidget()
self.plotFrame.setMaximumWidth(1)
# Create Figure
self.fig = Figure( dpi=100)
#self.fig.set_size_inches(100,100,forward=True)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.plotFrame)
self.ax = self.fig.add_subplot(111)
def main():
#Creating application
app = QtGui.QApplication(sys.argv)
main_win = test()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
It has a little QLE where you can specify how many lines you want to draw. I hope this helps.

Getting blitting to work in funcAnimation embedded in PyQT4 GUI

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.

Manual time loop control of FuncAnimation in matplotlib

I'm looking for something similar to FuncAnimation with blit, but instead of having a the library call a function at a fixed timestep, I want to call the function myself whenever I'm ready. I don't understand what matplotlib does with the axes returned by the function to update them. I'm working with live data coming from outside sources and I want the refresh rate to be synced with that data.
I have done it something like this
import sys
import os
import random
from PySide import QtGui,QtCore
os.environ['QT_API'] = 'pyside'
from matplotlib import use
use('Qt4Agg')
import pylab as plt
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.setWindowTitle('Widgets')
self.setGeometry(300, 300, 250, 150)
self.wid = QtGui.QWidget()
self.grid = QtGui.QGridLayout()
self.wid.setLayout(self.grid)
self.setCentralWidget(self.wid)
self.dat = []
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.toc)
self.timer.start(100)
self.show()
self.fig = plt.figure(13)
plt.show()
def toc(self):
val = random.uniform(-1.7, 0.78)
self.dat.append(val)
plt.ion()
fig = plt.figure(13)
plt.clf()
plt.plot(self.dat)
plt.ioff()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

plot mutiple line on the same figure using mplwidget in python(x,y)

I was trying to plot multiple line/dots using the mplwidget in Qtdesigner plugin. Usually when i use matplotlib in python by default it will keep the first line graph and plot another graph on top of that for comparison. But in Qtdesigner,after i am using matplotlib as a widget object, i select figure object then addsubplot and then plot the graph, it seems like it will delete the old line graph and plot the new one. I'm pretty sure there's something wrong with the coding, but i'm new to this GUI stuff, i'm not sure which part of it went wrong
import sys
from PyQt4 import QtGui, QtCore
from window import Ui_MainWindow
import sqlite3
import os
from datetime import datetime
import calendar
import numpy
os.chdir("C:\Data")
conn = sqlite3.connect('FBG.db')
c=conn.cursor()
class Main(QtGui.QMainWindow):
def searching_database(self):
self.ui.listWidget.clear()
data = self.ui.Inputname.text()
for df in c.execute("select name from sqlite_master where type='table'; "):
strdf=str(df)
if len(data)==0:
break
if strdf[3:(len(data)+3)] == data: # the name for df start from position 3 due to "[u "
self.ui.listWidget.addItem(strdf[3:-3])
else:
pass
def delete_selection(self):
self.ui.listWidget_3.takeItem(self.ui.listWidget_3.currentRow())
def clear_graph(self):
self.ui.listWidget_3.clear()
self.ax.clear()
self.ui.mplwidget.draw()
def plot_graph(self):
b=self.ui.listWidget.currentItem().text()
b=str(b)
self.ui.listWidget_3.addItem(b)
time1= QtCore.QDateTime(self.ui.dateTimeEdit.dateTime())
date1 = time1.toPyDateTime()
timestamp1 = calendar.timegm(date1.utctimetuple()) #return a integer value
time2= QtCore.QDateTime(self.ui.dateTimeEdit_2.dateTime())
date2 = time2.toPyDateTime()
timestamp2 = calendar.timegm(date2.utctimetuple())
time=[]
data=[]
for df in c.execute('''select * from '''+ b ):
time= numpy.append(time, df[0])
data= numpy.append(data, df[1])
self.ax.plot([2,4,5,6],[1,5,6,7],label=b) % set up for matplot widget
self.ax.plot([1,3,4,5],[2,4,5,6],label=b+"afasdasdasd") % set up for matplot widget
self.ax.legend() % set up for matplot widget
self.ui.mplwidget.draw() % set up for matplot widget
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.Inputname.textChanged.connect(self.searching_database)
self.ui.listWidget.itemDoubleClicked.connect(self.plot_graph)
self.ui.pushButton.clicked.connect(self.plot_graph)
self.ui.Delete.clicked.connect(self.delete_selection)
self.ui.Clear.clicked.connect(self.clear_graph)
self.ui.mplwidget.axes.set_title("Strain/Temperature vs Time") % set up for matplot widget
self.fig = self.ui.mplwidget.figure % set up for matplot widget
self.ax = self.fig.add_subplot(1,1,1) % set up for matplot widget
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window= Main()
window.show()
sys.exit(app.exec_())
The code is running fine. But i think there must be some code prevent me getting what i am trying to achieve. I think it might those codes with "% set up for matplot widget" following them. Any suggestions would be good.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class myWidget(QWidget):
def __init__(self, parent=None):
super(myWidget, self).__init__(parent)
self.fig = plt.figure()
self.canvas = FigureCanvas(self.fig)
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.canvas)
self.setLayout(self.vbox)
self.plotCurve(self.canvas)
def plotCurve(self, FigureCanvas):
left, width = 0.1, 0.8
rect1 = [left, 0.2, width, 0.6]
FigureCanvas.figure.set_facecolor('white')
axescolor = '#f6f6f6' # the axies background color
FigureCanvas.figure.clear()
ax1 = FigureCanvas.figure.add_axes(rect1, axisbg=axescolor) #left, bottom, width, height
ax1.set_title('Some Curve')
x = range(10, 20)
y = range(10, 20)
z = range(20, 30)
p1, = ax1.plot(x, y, 'ro')
p2, = ax1.plot(x, z, '-')
ax1.set_ylabel('Some Label')
FigureCanvas.draw()
def main():
app = QApplication(sys.argv)
form = myWidget()
form.show()
app.exec_()
main()
Try this:
self.ui.mplwidget.axes.hold(True)
This should let you plot two data sets on the same axis, just like you can in interactive mode. Worked for me!

Categories