#pandas #matplotlib #pyqt4 #seaborn #qthread
#pandas #matplotlib #pyqt4 #seaborn #qthread
Вопрос:
Я понимаю, что был опубликован один или два других вопроса, которые связаны, но не совсем то, что мне нужно. Я создаю этот графический интерфейс, который активирует модуль нажатием кнопки. Этот модуль python, который активируется нажатием кнопки, генерирует тепловые карты из нескольких фреймов данных pandas и сохраняет эти изображения, которые, в свою очередь, затем сохраняются в xlsx с помощью pandas ExcelWriter.
Я пытался реализовать QThread, поскольку другие примеры stackoverflow пытались объяснить подобные проблемы, но я продолжаю получать эту ошибку : "It is not safe to use pixmaps outside the GUI thread"
. Я понимаю, что технически я не создаю тепловую карту внутри ОСНОВНОГО потока gui, но я подумал, что с QThread я все еще нахожусь внутри «a» потока gui. Эти фреймы данных, на которых основаны тепловые карты, иногда могут иметь большой размер, и я в некоторой степени понимаю концепцию отправки сигнала в основной поток gui при создании тепловой карты и функции тепловой карты внутри основного класса gui … но я боюсь, что это будет проблематично позже впередача такого большого количества данных .. это больше похоже на конвейерную обработку, чем на потоковую обработку. Я просто хочу, чтобы этот рабочий поток создавал эти изображения и сохранял их, а затем брал эти сохраненные файлы и сохранял их в формате xlsx, не прерывая основной графический интерфейс..
(ПРИМЕЧАНИЕ: это упрощенная версия, в реальной программе будет создано несколько таких потоков почти одновременно, и внутри каждого потока будет создано несколько тепловых карт)
—main.py—
import sys
from MAIN_GUI import *
from PyQt4 import QtGui, QtCore
from excel_dummy import *
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):
Excelify = excelify()
Excelify.start()
self.connect(Excelify,QtCore.SIGNAL('donethread(QString)'),(self.done))
def done(self):
print('done')
main_gui = MAIN_GUI()
main_gui.show()
main_gui.raise_()
sys.exit(app.exec_())
—excel_dummy.py—
import os, pandas as pd
from pandas import ExcelWriter
import numpy as np
import seaborn.matrix as sm
from PyQt4 import QtCore
from PyQt4.QtCore import QThread
from matplotlib.backends.backend_agg import FigureCanvas
from matplotlib.figure import Figure
import time
class excelify(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
path = 'home/desktop/produced_files'
with ExcelWriter(path '/final.xlsx', engine='xlsxwriter') as writer:
workbook = writer.book
worksheet = workbook.add_worksheet()
heatit = self.heatmap()
worksheet.insert_image('C3',path '/' 'heat.jpg')
worksheet.write(2, 2, 'just write something')
writer.save()
print('file size: %s "%s"' % (os.stat(path).st_size, path))
time.slee(0.3)
self.emit(QtCore.SIGNAL('donethread(QString)'),'')
def heatmap(self):
df = pd.DataFrame(np.array([[1,22222,33333],[2,44444,55555],[3,44444,22222],[4,55555,33333]]),columns=['hour','in','out'])
dfu = pd.DataFrame(df.groupby([df.in,df.hour]).size())
dfu.reset_index(inplace=True)
dfu.rename(columns={'0':'Count'})
dfu.columns=['in','hour','Count']
dfu_2 = dfu.copy()
mask=0
fig = Figure()
ax = fig.add_subplot(1,1,1)
canvas = FigureCanvas(fig)
df_heatmap = dfu_2.pivot('in','hour','Count').fillna(0)
sm.heatmap(df_heatmap,ax=ax,square=True,annot=False,mask=mask)
fig.savefig(path '/' heat.jpg')
—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:
Несмотря на то, что вы явно используете Agg
серверную часть для создания своей фигуры, похоже Seaborn
, что в вашей системе по-прежнему используется серверная часть по умолчанию, которая, скорее всего Qt4Agg
, является интерактивной серверной частью. Мы хотим, чтобы вместо этого Seaborn использовал a non-interactive backend
, чтобы избежать каких-либо ошибок (см. Документацию matplotlib для получения более подробной информации о бэкэндах). Для этого укажите Matplotlib
в своем импорте использовать Agg
серверную часть и импортировать Seaborn после Matplotlib.
Вам также нужно будет сохранить свой рисунок в формате png, поскольку jpg не поддерживается Agg
серверной частью. Если у вас нет каких-то особых причин для использования jpg, png обычно является лучшим форматом для графиков.
Наконец, вы могли бы использовать буфер памяти вместо сохранения ваших изображений во временный файл перед сохранением их в книге Excel. Я не тестировал это, но, вероятно, это будет быстрее, если вы работаете с большими файлами.
Ниже приведен MWE, который я написал, который включает в себя вышеупомянутые пункты и который не выдает никаких ошибок в моей системе в Python3.4:
import pandas as pd
import time
from pandas import ExcelWriter
import numpy as np
from PyQt4 import QtCore, QtGui
import matplotlib as mpl
mpl.use('Agg')
from matplotlib.backends.backend_agg import FigureCanvas
import seaborn.matrix as sm
try: # Python 2 (not tested)
from cStringIO import StringIO as BytesIO
except ImportError: # Python 3
from io import BytesIO
class MAIN_GUI(QtGui.QWidget):
def __init__(self):
super(MAIN_GUI, self).__init__()
self.worker = Excelify()
btn = QtGui.QPushButton('Run')
disp = QtGui.QLabel()
self.setLayout(QtGui.QGridLayout())
self.layout().addWidget(btn, 0, 0)
self.layout().addWidget(disp, 2, 0)
self.layout().setRowStretch(1, 100)
btn.clicked.connect(self.worker.start)
self.worker.figSaved.connect(disp.setText)
class Excelify(QtCore.QThread):
figSaved = QtCore.pyqtSignal(str)
def run(self):
self.figSaved.emit('Saving figure to Workbook.')
t1 = time.clock()
image_data = self.heatmap()
with ExcelWriter('final.xlsx', engine='xlsxwriter') as writer:
wb = writer.book
ws = wb.add_worksheet()
ws.insert_image('C3', 'heat.png', {'image_data': image_data})
writer.save()
t2 = time.clock()
self.figSaved.emit('Done in %f sec.' % (t2-t1))
def heatmap(self):
df = pd.DataFrame(np.array([[1, 22222, 33333], [2, 44444, 55555],
[3, 44444, 22222], [4, 55555, 33333]]),
columns=['hour', 'in', 'out'])
dfu = pd.DataFrame(df.groupby([df.out, df.hour]).size())
dfu.reset_index(inplace=True)
dfu.rename(columns={'0': 'Count'})
dfu.columns = ['in', 'hour', 'Count']
fig = mpl.figure.Figure()
fig.set_canvas(FigureCanvas(fig))
ax = fig.add_subplot(111)
df_heatmap = dfu.pivot('in', 'hour', 'Count').fillna(0)
sm.heatmap(df_heatmap, ax=ax, square=True, annot=False, mask=0)
buf= BytesIO()
fig.savefig(buf, format='png')
return(buf)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MAIN_GUI()
w.show()
w.setFixedSize(200, 100)
sys.exit(app.exec_())