Why is pyplot having issues with PyQt4's QThread?

时间:2016-10-20 19:06:21

标签: python-2.7 matplotlib pyqt4 qthread

I am designing a gui that creates multiple QThreads to be activated. Each thread creates an excel workbook with Pandas Excelwriter and creates a heatmap using seaborn and saves that heatmap (for later use by the user for whatever) and then places it into the excel workbook.

I believe the error is that pyplot is not made into its own instance for the thread that is created..rather a resource that all threads are pointing to..if I run just one thread, there is no issue...two or more threads..in this example 4, there are internal pyplot errors pointing to dictionary size change occurring.

The issue I'm having is when pyplot is put into play. Do I have to do something specific to pyplot like I had to for getting the right backend for matplotlib? I thought the changes I made for matplotlib is inherent to pyplot?

---main.py---

import sys
from MAIN_GUI import *
from PyQt4 import QtGui, QtCore
from excel_dummy import *


df1 = pd.DataFrame(np.array([[1,22222,33333],[2,44444,55555],[3,44444,22222],[4,55555,33333]]),columns=['hour','input','out'])
df2 = pd.DataFrame(np.array([[1,22233,33344],[2,44455,55566],[3,44455,22233],[4,55566,33344]]),columns=['hour','input','out'])
df3 = pd.DataFrame(np.array([[1,23456,34567],[2,98765,45674],[3,44444,22222],[4,44455,34443]]),columns=['hour','input','out'])
df4 = pd.DataFrame(np.array([[1,24442,33443],[2,44444,54455],[3,45544,24442],[4,54455,33443]]),columns=['hour','input','out'])

df_list = [df1,df2,df3,df4]

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)


class MAIN_GUI(QtGui.QMainWindow):
    def __init__(self):
        super(MAIN_GUI, self).__init__()
        self.uiM = Ui_MainWindow()
        self.uiM.setupUi(self)
        self.connect(self.uiM.updateALL_Button,QtCore.SIGNAL('clicked()'),self.newThread)

    def newThread(self):

        count = 0
        for df in df_list:
            count += 1
            Excelify = excelify(df,count)
            self.connect(Excelify,QtCore.SIGNAL('donethread(QString)'),(self.done))
            Excelify.start()


    def done(self):
        print('done')


main_gui = MAIN_GUI()
main_gui.show()
main_gui.raise_()
sys.exit(app.exec_())

---excel_dummy.py---

import pandas as pd

import numpy as np
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import QThread
import time
import matplotlib as mpl
mpl.use('Agg')
from matplotlib.backends.backend_agg import FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import seaborn.matrix as sm

class excelify(QThread):
    def __init__(self,df,count):
        QThread.__init__(self)
        self.df = df
        self.count = count

    def run(self):

        heatit = self.heatmap()

        self.emit(QtCore.SIGNAL('donethread(QString)'),'')

    def heatmap(self):

        dfu = pd.DataFrame(self.df.groupby([self.df.input,self.df.hour]).size())
        dfu.reset_index(inplace=True)
        dfu.rename(columns={'0':'Count'})
        dfu.columns=['input','hour','Count']
        dfu_2 = dfu.copy()

        mask=0
        fig = Figure()
        ax = fig.add_subplot(1,1,1)
        fig.set_canvas(FigureCanvas(fig))
        df_heatmap = dfu_2.pivot('input','hour','Count').fillna(0)

        sm.heatmap(df_heatmap,ax=ax,square=True,annot=False,mask=mask)

        plt.ylabel('ID')
        plt.xlabel('Hour')
        plt.title('heatmap for df' + str(self.count))
        plt.savefig(path + '/' + 'heat' + str(self.count) + '.png')
        plt.close()

---MAIN_GUI.py---

from PyQt4 import QtCore,QtGui
try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.unicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(320,201)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.updateALL_Button = QtGui.QPushButton(self.centralwidget)
        self.updateALL_Button.setGeometry(QtCore.QRect(40,110,161,27))
        self.updateALL_Button.setFocusPolicy(QtCore.Qt.NoFocus)
        self.updateALL_Button.setObjectName(_fromUtf8("Options_updateALL_Button"))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 320, 24))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self,MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.updateALL_Button.setText(_translate("MainWindow", "updateALL", None))

1 个答案:

答案 0 :(得分:1)

虽然问题中的代码仍然不是minimal example(一些未定义的变量),但问题所在的更清楚。

首先,一个问题可能是MAIN_GUI类失去了对线程的引用,因此在完成之前它将被垃圾收集。人们可以通过将所有线程放在列表中来防止这种情况。 [见下面的MAIN_GUI代码]

其次,您不能直接使用pyplot来同时操作不同的数字。或者换句话说,如果同时存在几个,pyplot应该如何知道在plt.ylabel('ID')设置ylabel的哪个数字? 解决这个问题的方法是创建不同的数字,并且仅使用object oriented方法在这些数字中工作。 [参见下面的excelify代码]

以下是代码的相关部分,其中我还更改了信号以返回绘图编号以便于调试。

MAIN_GUI:

class MAIN_GUI(QtGui.QMainWindow):
    def __init__(self):
        super(MAIN_GUI, self).__init__()
        self.uiM = Ui_MainWindow()
        self.uiM.setupUi(self)
        self.connect(self.uiM.updateALL_Button,QtCore.SIGNAL('clicked()'),self.newThread)
        self.threats=[]

    def newThread(self):
        count = 0
        for df in df_list:
            count += 1
            Excelify = excelify(df,count)
            self.connect(Excelify,QtCore.SIGNAL('donethread(int)'),(self.done))
            # appending all threats to a class attribute, 
            # such that they will persist and not garbage collected
            self.threats.append(Excelify)
            Excelify.start()

    def done(self, val=None):
        print('done with {nr}'.format(nr=val))

excelify:

class excelify(QThread):
    def __init__(self,df,count):
        QThread.__init__(self)
        self.df = df
        self.count = count

    def run(self):
        heatit = self.heatmap()
        self.emit(QtCore.SIGNAL('donethread(int)'),self.count)

    def heatmap(self):
        print ("{nr} started".format(nr=self.count) )
        dfu = pd.DataFrame(self.df.groupby([self.df.input,self.df.hour]).size())
        dfu.reset_index(inplace=True)
        dfu.rename(columns={'0':'Count'})
        dfu.columns=['input','hour','Count']
        dfu_2 = dfu.copy()
        mask=0

        # create a figure and only work within this figure
        # no plt.something inside the threat
        fig = Figure()
        ax = fig.add_subplot(1,1,1)
        fig.set_canvas(FigureCanvas(fig))
        df_heatmap = dfu_2.pivot('input','hour','Count').fillna(0)

        sm.heatmap(df_heatmap,ax=ax,square=True,annot=False,mask=mask)

        ax.set_ylabel('ID')
        ax.set_xlabel('Hour')
        ax.set_title('heatmap for df' + str(self.count))
        fig.savefig( 'heat' + str(self.count) + '.png')
        fig.clear()
        del fig