PyQt - not showing instance of FigureCanvasQTAgg on QtWidget of TabPane - matplotlib

I'm continuing project described more in that question: PyQt - can't read Excel file
Basically my code looks like this right now:
# This is a sample Python script.
# Press Shift+F10 to execute it or replace it with your code.
# Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings.
import csv
import sys
import numpy as np
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import QDialog, QApplication, QFileDialog, QTableWidget, QTableWidgetItem, QTabWidget, QWidget
from PySide6.QtCore import Slot, SIGNAL
from PyQt6.uic import loadUi
import pandas as pd
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=12, height=5, dpi=100):
fig = Figure(figsize=(width, height), dpi=100)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
self.initUI()
def initUI(self):
loadUi('gui.ui', self)
self.btnShow.setEnabled(False)
self.btnLoad.setEnabled(False)
self.btnBrowse.clicked.connect(self.browseFiles)
self.btnLoad.clicked.connect(self.loadExcelData)
self.btnClean.clicked.connect(self.cleanData)
self.btnShow.clicked.connect(self.showGraphs)
#Slot()
def browseFiles(self):
fname = QFileDialog.getOpenFileName(self, 'Open a file', 'C:\\', "Excel (*.xls *.xlsx)")
self.filename.setText(fname[0])
self.btnLoad.setEnabled(True)
#Slot()
def loadExcelData(self):
column_names = ["Action", "TimeOfFailure", "ReverseRankR", "S(i)", "Cdf", "Ppf", "LogTime"]
df = pd.read_excel(self.filename.text(), "Sheet1", names=column_names)
if df.size == 0:
return
self.tableExcelData.setRowCount(df.shape[0])
self.tableExcelData.setColumnCount(df.shape[1])
self.tableExcelData.setHorizontalHeaderLabels(df.columns)
for row in df.iterrows():
values = row[1]
for col_index, value in enumerate(values):
tableItem = QTableWidgetItem(str(value))
self.tableExcelData.setItem(row[0], col_index, tableItem)
self.btnLoad.setEnabled(False)
self.btnShow.setEnabled(True)
#Slot()
def cleanData(self):
self.btnLoad.setEnabled(True)
self.btnShow.setEnabled(False)
self.tableExcelData.setRowCount(0)
self.tableExcelData.setColumnCount(0)
#Slot()
def showGraphs(self):
timeOfDays = []
cdf = []
ppf = []
logTime = []
for row in range(self.tableExcelData.rowCount()):
isFailure = False
for column in range(self.tableExcelData.columnCount()):
value = self.tableExcelData.item(row, column)
if(column == 0 and str(value.text()) == 'F'):
isFailure = True
if isFailure == True:
if(column == 1): #TimeOfDays
value = int(value.text())
timeOfDays.append(value)
elif(column == 4): #CDF
value = float(value.text())
cdf.append(value)
elif(column == 5):
value = float(value.text())
ppf.append(value)
elif(column == 6):
value = float(value.text())
logTime.append(value)
print(timeOfDays)
print(cdf)
print(ppf)
print(logTime)
#fig = Figure(figsize=(12,5), dpi=100)
#firstSubplot = fig.add_subplot(111)
#firstSubplot.scatter(timeOfDays, ppf, '*')
#firstSubplot.plot(timeOfDays, ppf)
#fig.show()
#plt.plot(timeOfDays, ppf)
#plt.show()
try:
canvasFig = MplCanvas()
canvasFig.axes.scatter(timeOfDays, ppf, s=5, color='red')
canvasFig.axes.plot(timeOfDays, ppf)
canvasFig.draw()
self.tabFirstGraph.setCentralWidget(canvasFig)
except Exception as e:
print('Error: ' + str(e))
#canvas = FigureCanvasTkAgg(fig, master=self)
#canvas.get_tk_widget().pack()
#canvas.draw()
# Press the green button in the gutter to run the script.
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWidget = QtWidgets.QStackedWidget()
mainWidget.addWidget(mainWindow)
mainWidget.show()
sys.exit(app.exec())
# See PyCharm help at https://www.jetbrains.com/help/pycharm/
I'm trying to generate two graphs (now it's code for only creation of one):
try:
canvasFig = MplCanvas()
canvasFig.axes.scatter(timeOfDays, ppf, s=5, color='red')
canvasFig.axes.plot(timeOfDays, ppf)
canvasFig.draw()
self.tabFirstGraph.setCentralWidget(canvasFig) #
except Exception as e:
print('Error: ' + str(e))
I tried to create another TabPane ("tabFirstGraph" as name of this object) and set canvas figure object to fill this QWidget instance. But I'm getting constantly this error:
Error: 'QWidget' object has no attribute 'setCentralWidget'
I assumed already that problem is with line above (QWidget, QTableWidget don't have this method). But how can I show my canvas figure graph on "First Graph" Tab Pane?
Thanks in advance for your all answers. :)

Related

unable to update matplotlib figure in pyqt5

I am studying MDIwindow in pyqt5
I want to update the ForcemapWidget figure when I click on the HeatmapWidget figure. However, the diagram did not update.
It is confirmed that the canvas in the ForcemapWidget is updated by the print function. We know that the coordinates are also obtained. But for some reason, the chart doesn't update.
import numpy as np
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QVBoxLayout,QApplication,QWidget,QMainWindow,QMdiArea,QAction,QMdiSubWindow,QTextEdit, \
QComboBox,QLineEdit,QPushButton,QCheckBox,QFormLayout
import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class MDIWindow(QMainWindow):
count=0
def __init__(self):
super ().__init__()
self.mdi=QMdiArea()
self.setCentralWidget(self.mdi)
bar=self.menuBar()
file=bar.addMenu("File")
file.addAction("New")
file.addAction("Cascade")
file.addAction("Tiled")
file.triggered[QAction].connect(self.tritri)
self.setWindowTitle("MDI Application")
# 最初のサブウィンドウを作成
sub=QMdiSubWindow()
form_widget=FormWidget()
sub.setWidget(form_widget)
sub.setWindowTitle("Sub Window 1")
self.mdi.addSubWindow(sub)
sub.show()
sub2=QMdiSubWindow()
# ForcemapWidget.force_update()
form_widget=ForcemapWidget()
# form_widget.update()
# print(form_widget)
sub2.setWidget (form_widget)
sub2.setWindowTitle("Sub Window 2")
self.mdi.addSubWindow(sub2)
sub2.show()
def tritri(self,p):
if p.text()=="New":
MDIWindow.count=MDIWindow.count+1
sub=QMdiSubWindow()
heatmap_widget=HeatmapWidget()
sub.setWidget(heatmap_widget)
sub.setWindowTitle("Sub Window"+str(MDIWindow.count))
self.mdi.addSubWindow(sub)
sub.show()
if p.text()=="Cascade":
self.mdi.cascadeSubWindows()
if p.text()=="Tiled":
self.mdi.tileSubWindows ()
class HeatmapWidget(QWidget):
def __init__(self):
super().__init__()
self.forcemapWidget=ForcemapWidget
fig,ax=plt.subplots (figsize = (1,1))
heatmap=np.random.rand (256,256)
im=ax.imshow(heatmap,cmap = "hot")
fig.colorbar(im)
ax.set_title("Heatmap")
fig.canvas.mpl_connect("button_press_event",self.onclick)
canvas=FigureCanvas(fig)
layout=QVBoxLayout()
layout.addWidget(canvas)
self.setLayout (layout)
def onclick(self,event):
x,y=event.xdata,event.ydata
forcemap=ForcemapWidget(x)
class ForcemapWidget (QWidget):
def __init__(self,x=None):
super().__init__()
self.figure=plt.figure()
self.initUI(x)
def initUI(self,x):
self.figure.clear()
ax=self.figure.add_subplot(111)
if x is not None:
heatmap=np.random.rand(int (x),256)
else:
heatmap=np.random.rand(256,256)
im=ax.imshow(heatmap,cmap = "hot")
# self.figure.colorbar (im)
canvas=FigureCanvas(self.figure)
layout=QVBoxLayout()
layout.addWidget(canvas)
self.setLayout(layout)
self.update()
class FormWidget(QWidget):
def __init__(self):
super().__init__()
form_layout = QFormLayout()
combo = QComboBox()
combo.addItems(["Option 1", "Option 2", "Option 3"])
form_layout.addRow("Dropdown:", combo)
line_edit = QLineEdit()
form_layout.addRow("Input:", line_edit)
button = QPushButton("Submit")
form_layout.addRow(button)
check_box = QCheckBox()
form_layout.addRow("Checkbox:", check_box)
self.setLayout(form_layout)
app = QApplication(sys.argv)
mdi = MDIWindow()
mdi.show()
app.exec_()

How improve this Matplotlib animation?

I have written a program that plots a logarithm gradually. However, there are two issues:
When the animation is completed, and next I minimize the animation window and then maximize it again, the graph is gone.
When I close the animation window while it is still plotting, then the thread myDataLoop continues, and still prints 'done' after some time. The next time I run the program, it will take longer to start the animation, and the animation becomes glitchy. (This continues until I restart the kernel)
How can I solve this?
import numpy as np
import time
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import matplotlib
matplotlib.use("Qt5Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import threading
#Time in seconds needed to construct the figure
Time=10
Modified_length=10**9
#We plot a log function
cumulative=[]
for i in range(1,int(Modified_length/8000)+1):
cumulative.append(np.log(i*8000))
class CustomMainWindow(QMainWindow):
def __init__(self):
super(CustomMainWindow, self).__init__()
# Define the geometry of the main window
self.setGeometry(0, 30, 1600, 830)
self.setWindowTitle(" ")
# Create FRAME_A
self.FRAME_A = QFrame(self)
self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QColor(210,210,235,255).name())
self.LAYOUT_A = QGridLayout()
self.FRAME_A.setLayout(self.LAYOUT_A)
self.setCentralWidget(self.FRAME_A)
# Place the matplotlib figure
self.myFig = CustomFigCanvas()
self.LAYOUT_A.addWidget(self.myFig, *(0,1))
# Add the callbackfunc to ..
myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
myDataLoop.start()
self.show()
return
def addData_callbackFunc(self, value):
# print("Add data: " + str(value))
self.myFig.addData(value)
return
''' End Class '''
class CustomFigCanvas(FigureCanvas, TimedAnimation):
def __init__(self):
self.addedData = []
# print(matplotlib.__version__)
# The data
self.xlim = int(Modified_length)
self.n = np.linspace(0, self.xlim - 1, int(self.xlim/8000))
self.y=[0]
# The window
self.fig = Figure(figsize=(5,5), dpi=120)
self.ax1 = self.fig.add_subplot(111)
# self.ax1 settings
self.line1 = Line2D([], [], color='blue')
self.ax1.add_line(self.line1)
self.ax1.set_xlim(0, self.xlim - 1)
self.ax1.set_ylim(0,100)
self.ax2 = self.ax1.twinx()
FigureCanvas.__init__(self, self.fig)
TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)
return
def new_frame_seq(self):
return iter(range(self.n.size))
def _init_draw(self):
lines = [self.line1]
for l in lines:
l.set_data([], [])
return
def addData(self, value):
self.addedData.append(value)
return
def _step(self, *args):
# Extends the _step() method for the TimedAnimation class.
try:
TimedAnimation._step(self, *args)
except Exception:
TimedAnimation._stop(self)
pass
return
def _draw_frame(self, framedata):
global Q
while(len(self.addedData) > 0):
self.y=np.append(self.y,self.addedData[0])
del(self.addedData[0])
l=len(self.y)
self.line1.set_data(self.n[ 0 : l], self.y[ 0 : l ])
self._drawn_artists = [self.line1]
return
''' End Class '''
# You need to setup a signal slot mechanism, to
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QObject):
data_signal = pyqtSignal(float)
''' End Class '''
def dataSendLoop(addData_callbackFunc):
# Setup the signal-slot mechanism.
mySrc = Communicate()
mySrc.data_signal.connect(addData_callbackFunc)
# Use the log data
# n = np.linspace(0, int(Modified_length)-1, int(Modified_length))
totaly=cumulative
i = 0
while(True):
if(i > int(Modified_length/8000)-1):
print('done')
break
i = 0
time.sleep(100*Time/Modified_length)
for j in range(100):
mySrc.data_signal.emit(totaly[i]) # <- Here you emit a signal!
i += 1
###
###
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Plastique'))
myGUI = CustomMainWindow()
sys.exit(app.exec_())

Releasing pan function in NavigationToolbar2QT

I want to cancel pan function from other button. So far, my understanding is that when I want to pan&zoom image, I will click 'Pan' button. If I would like to do other function, e.g. 'Mark' function (in my case), I have to click 'Pan' Button again, then click whatever button I want to do.
I have searched for solving this and found something like 'release_pan', 'button_release_event', but I don't understand how to implement them correctly.
To be clear, I want to cancel pan function from 'Mark' button, and here is my code.
import sys
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Cursor
from matplotlib.backends.qt_compat import QtCore, QtWidgets
if QtCore.qVersion() >= "5.":
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.coor = [0,0] #temporary user selection
self.cid = None
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
butt = QtWidgets.QHBoxLayout()
self.static_canvas = FigureCanvas(Figure(figsize=(5, 5), dpi=100))
self.addToolBar = NavigationToolbar(self.static_canvas, self)
self.addToolBar.hide()
self.home = QtWidgets.QPushButton('Home')
self.pan = QtWidgets.QPushButton('Pan')
self.mark = QtWidgets.QPushButton('Mark')
butt.addWidget(self.home)
butt.addWidget(self.pan)
butt.addWidget(self.mark)
layout.addLayout(butt)
layout.addWidget(self.static_canvas)
self._static_ax = self.static_canvas.figure.subplots()
self.tar = plt.imread(r'my_image.tif').copy()
self._static_ax.imshow(self.tar)
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
#trigger zone
self.home.clicked.connect(self.Home)
self.pan.clicked.connect(self.Pan)
self.mark.clicked.connect(self.Mark)
def coor_onclick(self, event):
"""
This function will get coordination from click and plot it on canvas
"""
#check out-figure click
if event.xdata == None or event.ydata == None:
pass
else:
self.coor[0] = int(event.xdata)
self.coor[1] = int(event.ydata)
# print(self.coor)
#show line marking on canvas
tar = self.tar.copy()
#NOTE:: self.coor = [x,y] = [col, row]
# x = self.coor[0]
# y = self.coor[1]
#marking line
for r in range(tar.shape[1]):
for c in range(tar.shape[0]):
tar[self.coor[1], c] = [255, 0, 0]
tar[r, self.coor[0]] = [255, 0, 0]
#set final mark on canvas
self._static_ax.clear()
self._static_ax.imshow(tar)
self._static_ax.axis('off')
# Set cursor
self.cursor = Cursor(self._static_ax, horizOn=True, vertOn=True, useblit=True,
color = 'r', linewidth = 1)
self.static_canvas.draw()
def Home(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
self.addToolBar.home()
def Pan(self):
if self.cid is None:
pass
else:
#disconnect to self.coor_onclick
self.static_canvas.mpl_disconnect(self.cid)
self.addToolBar.pan()
def Mark(self):
self.cid = self.static_canvas.mpl_connect('button_press_event', self.coor_onclick)
if __name__ == "__main__":
# Check whether there is already a running QApplication (e.g., if running
# from an IDE).
qapp = QtWidgets.QApplication.instance()
if not qapp:
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
app.activateWindow()
app.raise_()
qapp.exec_()
I have modified from matplotlib documentation.
Check the current mode of NavigationToolbar and if the mode is "PAN", set the mode off by calling pan() again (which will uncheck the action (check out the source code for more details.)).
FYI:
You can check the current mode of the NavigationToolbar by using NavigationToolbar.mode.name, currently there are two modes: "ZOOM" and "PAN".
In your code, change function Mark like this:
def Mark(self):
# if the current mode is Pan, set the mode off by unchecking it.
if self.nav_toolbar.mode.name == "PAN":
self.nav_toolbar.pan()
self.cid = self.static_canvas.mpl_connect(
'button_press_event', self.coor_onclick)

Resize one subplot after removing another

Problem description:
I'm building an interface for my lab, I intergrarted matplotlib with pyqt5 widget, there is a real time video display widget working on multi-thread and queue. I managed to show single shot with cross-section plot by adding divider. However, when I remove the cross-section plots, and redraw the figure_idle, the video frame can never moved back to its initial position with its initial size. I adjust the figure with navigator_tool_bar (top, bottom...), However it seems that there are blank areas left after removing the cross-section plots. Do you have any idea?
The initial figure:
Display cross-sections:
Clear cross-section and redraw:
Video widget code:
class VideoViewer(FigureCanvas):
def __init__(self, parent=None, width=4, height=4, dpi=70):
self.figwidth = width
self.figheight = height
self.fig = Figure(figsize=(self.figwidth, self.figheight), dpi=dpi)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
self.axes = self.fig.add_subplot()
self.fig.subplots_adjust(top=0.975,bottom=0.048,left=0.029,right=0.983,hspace=0.2,wspace=0.2)
Cross section code:
#create cross-sections
self.divider = make_axes_locatable(self.axes)
self.top_ax = self.divider.append_axes("top", 1.05, pad=0.2,sharex=self.axes)
self.right_ax = self.divider.append_axes("right", 1.05,pad=0.2,sharey=self.axes)
#create lines
self.v_line = self.axes.axvline(xvlinepos, color='r')
self.h_line = self.axes.axhline(yhlinepos, color='g')
self.v_cross, = self.right_ax.plot(Norm_xvlinedata,np.arange(self.ImageData.shape[0]), 'r-')
self.h_cross, = self.top_ax.plot(np.arange(self.ImageData.shape[1]),Norm_yhlinedata, 'g-')
Clear cross-section code:
def ClearCrossSection(self):
self.fig.canvas.mpl_disconnect(self.pick_event_v)
self.fig.canvas.mpl_disconnect(self.pick_event_h)
self.h_line.remove()
self.v_line.remove()
self.v_cross.remove()
self.h_cross.remove()
self.right_ax.remove()
self.top_ax.remove()
self.fig.canvas.draw_idle()
What I did:
Light_layout + subplot_adjust -----> does not work.
Constrained_layout -----> does not work.
Constrained_layout + GridSpec by declaring at beginning self.axes takes all cols and rows.-----> Does not work.
An exemple of the problem:
import sys
# GUI
from PyQt5.QtWidgets import*
from PyQt5.QtCore import *
from PyQt5.QtGui import *
# Matplotlib
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import matplotlib.gridspec as gridspec
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
from mpl_toolkits.axes_grid1 import make_axes_locatable
from PIL import Image
import matplotlib.lines as lines
# Generate data
import numpy as np
'''
need to update video frame, I'm using blitting, so better not clear the whole figure.
the whole code could be too long to show here.
'''
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.resize(800,600)
self.setGeometry(350,250,950,600)
# Creat MainWidget
self.MainWidget = QWidget()
self.LayoutMainWidget = QGridLayout()
self.MainWidget.setLayout(self.LayoutMainWidget)
# Matplotlib widget
self.MatplotViewer = VideoViewer()
self.FigureTool = NavigationToolbar(self.MatplotViewer, self)
# Button plot image
self.ButtPltImg = QPushButton("Plot Image")
# BUtton plot cross
self.ButtPltCross = QPushButton("Cross View")
self.ButtPltCross.setCheckable(True)
self.ButtPltCross.setStyleSheet("background-color: Green")
# add widgets
self.LayoutMainWidget.addWidget(self.MatplotViewer,0,0,7,7)
self.LayoutMainWidget.addWidget(self.FigureTool, 7,0,1,7)
self.LayoutMainWidget.addWidget(self.ButtPltImg, 2,7,1,1)
self.LayoutMainWidget.addWidget(self.ButtPltCross, 3,7,1,1)
# Set central widget
self.setCentralWidget(self.MainWidget)
self.connection()
def GenerateImage(self, state):
if self.ButtPltCross.isChecked():
self.ButtPltCross.setChecked(False)
self.MatplotViewer.ClearCrossSection()
self.MatplotViewer.UpdateFrame()
else:
self.MatplotViewer.UpdateFrame()
def PlotCrossSection(self, state):
if self.ButtPltCross.isChecked():
self.MatplotViewer.PlotCrossSection()
def ClearCrossSection(self, state):
if not(self.ButtPltCross.isChecked()):
self.MatplotViewer.ClearCrossSection()
def connection(self):
self.ButtPltImg.clicked.connect(lambda state=True: self.GenerateImage(state))
self.ButtPltCross.clicked.connect(lambda state=True: self.PlotCrossSection(state))
self.ButtPltCross.clicked.connect(lambda state=True: self.ClearCrossSection(state))
class VideoViewer(FigureCanvas):
def __init__(self, parent=None, width=4, height=4, dpi=70):
# Figure
self.fig = Figure(figsize=(width, height), dpi=dpi)
# Init Parent
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
# Ax
self.axes = self.fig.add_subplot(111)
self.fig.subplots_adjust(top=0.975,bottom=0.048,left=0.029,right=0.983,hspace=0.2,wspace=0.2)
# Plot init image
self.PlotInitFrame()
def PlotInitFrame(self):
self.ImageData = self.ImageGenerate()
self.image = self.axes.imshow(self.ImageData, cmap='Greys', interpolation='none')
self.fig.canvas.draw_idle()
def UpdateFrame(self):
self.ImageData = self.ImageGenerate()
self.image.set_data(self.ImageData)
self.fig.canvas.draw_idle()
def PlotCrossSection(self):
# create axes
self.divider = make_axes_locatable(self.axes)
self.top_ax = self.divider.append_axes("top", 1.05, pad=0.2,sharex=self.axes)
self.right_ax = self.divider.append_axes("right", 1.05, pad=0.2,sharey=self.axes)
self.top_ax.xaxis.set_tick_params(labelbottom=False)
self.right_ax.yaxis.set_tick_params(labelleft=False)
# set cross section limit
self.right_ax.set_xlim(right=1.05)
self.top_ax.set_ylim(top=1.05)
# some pars
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
v_mid = int((xmin + xmax)/2)
h_mid = int((ymin + ymax)/2)
# set line
self.v_line = lines.Line2D([v_mid, v_mid], [ymin, ymax], color='r', pickradius=5)
self.axes.add_line(self.v_line)
self.h_line = lines.Line2D([xmin, xmax], [h_mid, h_mid], color='g', pickradius=5)
self.axes.add_line(self.h_line)
# set cross section data
Norm_xvlinedata = self.NormalizeData(self.ImageData[:,v_mid])
self.v_cross, = self.right_ax.plot(Norm_xvlinedata, np.arange(self.ImageData.shape[0]), 'r-')
Norm_yhlinedata = self.NormalizeData(self.ImageData[h_mid,:])
self.h_cross, = self.top_ax.plot(np.arange(self.ImageData.shape[1]), Norm_yhlinedata, 'g-')
self.fig.canvas.draw_idle()
def NormalizeData(self, data_temp):
min_temp = np.min(data_temp)
max_temp = np.max(data_temp)
if min_temp != max_temp:
return (data_temp-min_temp)/(max_temp-min_temp)
else:
return data_temp/data_temp
def ClearCrossSection(self):
self.v_line.remove()
self.h_line.remove()
self.top_ax.remove()
self.right_ax.remove()
self.fig.canvas.draw_idle()
def ImageGenerate(self):
xx,yy = np.meshgrid(np.linspace(-502,502,1024),np.linspace(-502,502,1024))
r = np.sqrt(xx**2+yy**2)
AMP = np.random.randint(150,250)
SIG = np.random.randint(200,250)
T = np.random.randint(115,135)
return AMP*np.exp(-(r)**2/(2*SIG**2))*np.cos(2*np.pi/T*r)
if __name__ == '__main__':
app = QApplication(sys.argv)
MainWindow = Window()
MainWindow.showMaximized()
sys.exit(app.exec_())

How save a Matplotlib figure with Pickle in a Pyqt5 environment?

I'm facing an issue and I cannot get rid of it.
I'm trying to use the Pickle package in order to save a matplotlib figure to replot it if i want to.
So far I have the below code which open a Qt window and plot some curves in it if the 'if' condition in lfpViewer.__Init__() is 1 (I put 0 only to check the pickle load function).
So I added, to the toolbar, two buttons where I can save a .pickle of the current figure or load a .pickle from a previous figure.
import pickle
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import os
import matplotlib
import numpy as np
matplotlib.use('Qt5Agg')
import matplotlib.patches as patches
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.centralWidget = QWidget()
self.color = self.centralWidget.palette().color(QPalette.Background)
self.setCentralWidget(self.centralWidget)
self.plotview = QGroupBox(" ")
self.layout_plotview = QVBoxLayout()
self.mascenelfp = lfpViewer(self)
self.layout_plotview.addWidget(self.mascenelfp)
self.centralWidget.setLayout(self.layout_plotview)
class lfpViewer(QGraphicsView):
def __init__(self, parent=None):
super(lfpViewer, self).__init__(parent)
self.parent=parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.setBackgroundBrush(QBrush(self.parent.color))# self.setBackgroundBrush(QBrush(QColor(200, 200, 200)))
self.figure = plt.figure(facecolor=[self.parent.color.red()/255,self.parent.color.green()/255,self.parent.color.blue()/255]) #Figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
self.save_button = QPushButton()
self.save_button.setIcon(QIcon(os.path.join('icons','SaveData.png')))
self.save_button.setToolTip("Save Figure Data")
self.toolbar.addWidget(self.save_button)
self.save_button.clicked.connect(self.saveFigData)
self.load_button = QPushButton()
self.load_button.setIcon(QIcon(os.path.join('icons','LoadData.png')))
self.load_button.setToolTip("Load Figure Data")
self.toolbar.addWidget(self.load_button)
self.load_button.clicked.connect(self.loaddatapickle)
if 0:
t=np.arange(1000)
self.axes_l=self.figure.add_subplot(311)
self.axes_l.plot(t, np.sin(2*3.14*100*t))
self.axes_Y=self.figure.add_subplot(312)
self.axes_Y.plot(t, np.cos(2*3.14*100*t))
self.axes_Yi=self.figure.add_subplot(313)
self.axes_Yi.plot(t, np.tan(2*3.14*100*t))
self.canvas.setGeometry(0, 0, 1600, 500 )
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
def loaddatapickle(self):
fileName = QFileDialog.getOpenFileName(self,'Load Data', '', 'pickle (*.pickle)')
if (fileName[0] == '') :
return
fileName = str(fileName[0])
filehandler = open(fileName , 'rb')
self.figure = pickle.load(filehandler)
filehandler.close()
self.canvas.draw()
self.parent.parent.processEvents()
return
def saveFigData(self):
fileName = QFileDialog.getSaveFileName(self,'Save Figure Data', '', 'pickle (*.pickle)')
if (fileName[0] == '') :
return
fileName = str(fileName[0])
file_pi = open(fileName, 'wb')
pickle.dump(self.figure, file_pi, -1)
file_pi.close()
return
def main():
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.showMaximized()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The save seems works (well, at least, I have a file), but the load button do absolutly nothing !
Even If I have a .pickle file, I don't know if pickle save the correct binary of the figure because when I load the pickle file in debug mode, I get lot of red stuff.
Look for the below image :
If I do the code without PyQt5, it works fine, for instance, with the below code:
import pickle
import matplotlib
import numpy as np
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
def loaddatapickle():
filehandler = open('test.pickle' , 'rb')
figure = pickle.load(filehandler )
filehandler.close()
return figure
def saveFigData(figure):
file_pi = open('test.pickle', 'wb')
pickle.dump(figure , file_pi, 1)
file_pi.close()
return
figure = plt.figure( ) #Figure()
Save= 0
if Save==1:
t=np.arange(1000)
axes_l=figure.add_subplot(311)
axes_l.plot(t, np.sin(2*3.14*100*t))
axes_Y=figure.add_subplot(312)
axes_Y.plot(t, np.cos(2*3.14*100*t))
axes_Yi=figure.add_subplot(313)
axes_Yi.plot(t, np.tan(2*3.14*100*t))
saveFigData(figure)
else:
figure=loaddatapickle()
plt.show()
If somebody have an idea of what is going on here, please tell me !
Have a nice day.
I can't be sure if this solves the problem you are facing, but let's give it a try. I need to mention, I don't have QT5 available and I'm working with python 2.7 and matplotlib 2.0.0. But the solution here might be valid for general cases.
When adapting the program to pyqt4 and running it, I found out that the pickling works fine. Also the unpickling did not throw any error, so I suspected that there might be a problem of displaying the unpickled figure.
What turns out to allow loading the figure is to not only load the figure into self.figure but to recreate the canvas with this unpickled figure and newly add it to the layout:
def loaddatapickle(self):
#needed to change some stuff here, since in Qt4 the dialog directly returns a string
fileName = QFileDialog.getOpenFileName(self,'Load Data', '' )
if (fileName == '') :
return
fileName = str(fileName)
filehandler = open(fileName , 'rb')
self.figure = pickle.load(filehandler)
filehandler.close()
# remove the old canvas
self.layout().removeWidget(self.canvas)
# create a new canvas
self.canvas = FigureCanvas(self.figure)
# add the new canvas at the position of the old one
self.layout().addWidget(self.canvas, 1)
self.canvas.draw()
self.parent.parent.processEvents()
return
Of course it would be better to directly update the canvas with the new figure, but I haven't found any way to do that.