I would like to display a 3D-Animation in my Qt5-Gui. Everything works as expected, but unfortunately the scene is not getting updated when I don't interact with the vtkWidget. In other words: When I want to see the animation, I need to click continously with the mouse on the widget. I'd be greatful for any help.
import sys
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
import numpy as np
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
class mainWindow(Qt.QMainWindow):
def __init__(self, parent = None):
Qt.QMainWindow.__init__(self, parent)
self.frame = Qt.QFrame()
self.vl = Qt.QVBoxLayout()
self.button = QtWidgets.QPushButton("TestButton")
self.label = QtWidgets.QLabel("This is a label")
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
#Create Source
self.source = vtk.vtkCylinderSource()
self.source.SetCenter(0, 0, 0)
self.source.SetRadius(5.0)
#Create Mapper
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.source.GetOutputPort())
#Create Actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
#Create poke matrix for cylinder
self.pMatrix = vtk.vtkMatrix4x4()
self.vl.addWidget(self.vtkWidget)
self.vl.addWidget(self.button)
self.vl.addWidget(self.label)
self.ren = vtk.vtkRenderer()
self.ren.AddActor(self.actor)
self.renWin = self.vtkWidget.GetRenderWindow()
self.renWin.AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
#Settings
self.ren.SetBackground(0.2, 0.2, 0.2)
self.timeStep = 20 #ms
self.total_t = 0
#Inititalize Window, Interactor, Renderer, Layout
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
self.ren.ResetCamera()
self.show()
self.iren.Initialize()
# Create Timer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.timerCallback)
self.timer.start(self.timeStep)
def timerCallback(self, *args):
self.total_t += self.timeStep / 1000
#Rotate Cylinder
angle = 2 * np.pi * self.total_t
rotMatrix = np.array([[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]])
for i in range(3):
for j in range(3):
self.pMatrix.SetElement(i, j, rotMatrix[i, j])
self.actor.PokeMatrix(self.pMatrix)
self.ren.Render()
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = mainWindow()
sys.exit(app.exec_())
After reading the paintEvent()-Method of this file, I managed to find out, that one needs to call the Render()-Method of the Interactor-object. So instead of self.ren.Render() one needs to call self.iren.Render(). Then everything works.
Complete example code:
import sys
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
import numpy as np
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
class mainWindow(Qt.QMainWindow):
def __init__(self, parent = None):
Qt.QMainWindow.__init__(self, parent)
self.frame = Qt.QFrame()
self.vl = Qt.QVBoxLayout()
self.button = QtWidgets.QPushButton("TestButton")
self.label = QtWidgets.QLabel("This is a label")
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
#Create Source
self.source = vtk.vtkCylinderSource()
self.source.SetCenter(0, 0, 0)
self.source.SetRadius(5.0)
#Create Mapper
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.source.GetOutputPort())
#Create Actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
#Create poke matrix for cylinder
self.pMatrix = vtk.vtkMatrix4x4()
self.vl.addWidget(self.vtkWidget)
self.vl.addWidget(self.button)
self.vl.addWidget(self.label)
self.ren = vtk.vtkRenderer()
self.ren.AddActor(self.actor)
self.renWin = self.vtkWidget.GetRenderWindow()
self.renWin.AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
#Settings
self.ren.SetBackground(0.2, 0.2, 0.2)
self.timeStep = 20 #ms
self.total_t = 0
#Inititalize Window, Interactor, Renderer, Layout
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
self.ren.ResetCamera()
self.show()
self.iren.Initialize()
# Create Timer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.timerCallback)
self.timer.start(self.timeStep)
def timerCallback(self, *args):
self.total_t += self.timeStep / 1000
#Rotate Cylinder
angle = 2 * np.pi * self.total_t
rotMatrix = np.array([[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]])
for i in range(3):
for j in range(3):
self.pMatrix.SetElement(i, j, rotMatrix[i, j])
self.actor.PokeMatrix(self.pMatrix)
self.iren.Render() #NOT: self.ren.Render()
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = mainWindow()
sys.exit(app.exec_())
Thanks #cakelover, I was facing same issue but in C++, your solution helped me resolve it:
//PCLVisualizer pointer
pcl::visualization::PCLVisualizer::Ptr viewer_3D;
//Renderer method to update the visualizer
viewer_3D->getRenderWindow()->GetInteractor()->Render();
Related
I now want to use the Custom Button function of matplotlib, refer to this document: https://matplotlib.org/stable/gallery/widgets/buttons.html, it works quite well.
But when I want to combine it with PyQt5, I first create a main window with a button in it. When the button is clicked, a plot will pop up, but the button in the plot loses its response.
code show as below:
import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class Index:
ind = 0
def __init__(self, l, freqs):
self.l = l
self.freqs = freqs
def next(self, event):
self.ind += 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * t)
self.l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * t)
self.l.set_ydata(ydata)
plt.draw()
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 button - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 200
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
button = QPushButton('PyQt5 button', self)
button.setToolTip('This is an example button')
button.move(100, 70)
button.clicked.connect(self.on_click)
self.show()
#pyqtSlot()
def on_click(self):
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2 * np.pi * freqs[0] * t)
l, = ax.plot(t, s, lw=2)
callback = Index(l, freqs)
axprev = fig.add_axes([0.7, 0.05, 0.1, 0.075])
axnext = fig.add_axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I want to know why?
In this question:
matplotlib event doesn't work when I use button clicked connect in pyqt5, I see that it seems to define a window myself , and then embed matplotlib, but I don't understand why
Is there a document that says we must do this?
I tried Macos, linux, windows, it works under macos, but the button doesn't respond under linux and windows.
I suspect it has something to do with QCoreApplication::exec: The event loop is already running, but I don't understand why the qt problem affects matplotlib. Is the signal of matplotlib registered to pyqt5?
Yes, Yes, You need not to a PlotEx, ref to Why aren't the matplotlib checkboxes working in pyQt5?
I understood that is because the button is local var, I need a more big scope.
the right code is :
import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class Index:
ind = 0
def __init__(self, l, freqs, t):
self.l = l
self.freqs = freqs
self.t = t
def next(self, event):
self.ind += 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * self.t)
self.l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * self.t)
self.l.set_ydata(ydata)
plt.draw()
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 button - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 200
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
button = QPushButton('PyQt5 button', self)
button.setToolTip('This is an example button')
button.move(100, 70)
button.clicked.connect(self.on_click)
self.show()
#pyqtSlot()
def on_click(self):
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2 * np.pi * freqs[0] * t)
l, = ax.plot(t, s, lw=2)
callback = Index(l, freqs, t)
axprev = fig.add_axes([0.7, 0.05, 0.1, 0.075])
axnext = fig.add_axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.bnext = bnext
plt.bprev = bprev
plt.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
The difference is add (and fix some bugs):
plt.bnext = bnext
plt.bprev = bprev
I want to add a cross hair that snaps to data points and be updated on mouse move. I found this example that works well:
import numpy as np
import matplotlib.pyplot as plt
class SnappingCursor:
"""
A cross hair cursor that snaps to the data point of a line, which is
closest to the *x* position of the cursor.
For simplicity, this assumes that *x* values of the data are sorted.
"""
def __init__(self, ax, line):
self.ax = ax
self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--')
self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--')
self.x, self.y = line.get_data()
self._last_index = None
# text location in axes coords
self.text = ax.text(0.72, 0.9, '', transform=ax.transAxes)
def set_cross_hair_visible(self, visible):
need_redraw = self.vertical_line.get_visible() != visible
self.vertical_line.set_visible(visible)
self.horizontal_line.set_visible(visible)
self.text.set_visible(visible)
return need_redraw
def on_mouse_move(self, event):
if not event.inaxes:
self._last_index = None
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.draw()
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
index = min(np.searchsorted(self.y, y), len(self.y) - 1)
if index == self._last_index:
return # still on the same data point. Nothing to do.
self._last_index = index
x = self.x[index]
y = self.y[index]
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw()
y = np.arange(0, 1, 0.01)
x = np.sin(2 * 2 * np.pi * y)
fig, ax = plt.subplots()
ax.set_title('Snapping cursor')
line, = ax.plot(x, y, 'o')
snap_cursor = SnappingCursor(ax, line)
fig.canvas.mpl_connect('motion_notify_event', snap_cursor.on_mouse_move)
plt.show()
But I get into trouble when I want to adapt the code with the PyQt5 and show the plot in a GUI. My code is:
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
class SnappingCursor:
"""
A cross hair cursor that snaps to the data point of a line, which is
closest to the *x* position of the cursor.
For simplicity, this assumes that *x* values of the data are sorted.
"""
def __init__(self, ax, line):
self.ax = ax
self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--')
self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--')
self.x, self.y = line.get_data()
self._last_index = None
# text location in axes coords
self.text = ax.text(0.72, 0.9, '', transform=ax.transAxes)
def set_cross_hair_visible(self, visible):
need_redraw = self.vertical_line.get_visible() != visible
self.vertical_line.set_visible(visible)
self.horizontal_line.set_visible(visible)
self.text.set_visible(visible)
return need_redraw
def on_mouse_move(self, event):
if not event.inaxes:
self._last_index = None
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.draw()
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
index = min(np.searchsorted(self.y, y), len(self.y) - 1)
if index == self._last_index:
return # still on the same data point. Nothing to do.
self._last_index = index
x = self.x[index]
y = self.y[index]
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw()
class Window(QMainWindow):
def __init__(self):
super().__init__()
widget=QWidget()
vbox=QVBoxLayout()
plot1 = FigureCanvas(Figure(tight_layout=True, linewidth=3))
ax = plot1.figure.subplots()
x = np.arange(0, 1, 0.01)
y = np.sin(2 * 2 * np.pi * x)
line, = ax.plot(x, y, 'o')
snap_cursor = SnappingCursor(ax, line)
plot1.mpl_connect('motion_notify_event', snap_cursor.on_mouse_move)
vbox.addWidget(plot1)
widget.setLayout(vbox)
self.setCentralWidget(widget)
self.setWindowTitle('Example')
self.show()
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
By running the above code, the data is plotted properly, but the cross hair is only shown in its initial position and does not move by mouse movement. Data values are also not displayed.
I have found a similar question here too, but the question is not answered clearly.
There are 2 problems:
snap_cursor is a local variable that will be removed when __init__ finishes executing. You must make him a member of the class.
The initial code of the tutorial is designed so that the point that information is displayed is the horizontal line that passes through the cursor and intersects the curve. In your initial code it differs from the example and also does not work for your new curve so I restored the logic of the tutorial.
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class SnappingCursor:
"""
A cross hair cursor that snaps to the data point of a line, which is
closest to the *x* position of the cursor.
For simplicity, this assumes that *x* values of the data are sorted.
"""
def __init__(self, ax, line):
self.ax = ax
self.horizontal_line = ax.axhline(color="k", lw=0.8, ls="--")
self.vertical_line = ax.axvline(color="k", lw=0.8, ls="--")
self.x, self.y = line.get_data()
self._last_index = None
# text location in axes coords
self.text = ax.text(0.72, 0.9, "", transform=ax.transAxes)
def set_cross_hair_visible(self, visible):
need_redraw = self.vertical_line.get_visible() != visible
self.vertical_line.set_visible(visible)
self.horizontal_line.set_visible(visible)
self.text.set_visible(visible)
return need_redraw
def on_mouse_move(self, event):
if not event.inaxes:
self._last_index = None
need_redraw = self.set_cross_hair_visible(False)
if need_redraw:
self.ax.figure.canvas.draw()
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
index = min(np.searchsorted(self.x, x), len(self.x) - 1)
if index == self._last_index:
return # still on the same data point. Nothing to do.
self._last_index = index
x = self.x[index]
y = self.y[index]
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text("x=%1.2f, y=%1.2f" % (x, y))
self.ax.figure.canvas.draw()
class Window(QMainWindow):
def __init__(self):
super().__init__()
widget = QWidget()
vbox = QVBoxLayout(widget)
x = np.arange(0, 1, 0.01)
y = np.sin(2 * 2 * np.pi * x)
canvas = FigureCanvas(Figure(tight_layout=True, linewidth=3))
ax = canvas.figure.subplots()
ax.set_title("Snapping cursor")
(line,) = ax.plot(x, y, "o")
self.snap_cursor = SnappingCursor(ax, line)
canvas.mpl_connect("motion_notify_event", self.snap_cursor.on_mouse_move)
vbox.addWidget(canvas)
self.setCentralWidget(widget)
self.setWindowTitle("Example")
app = QApplication(sys.argv)
w = Window()
w.show()
app.exec()
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_())
I am using matplotlib figure embedded in pyqt5 to draw a frame taking the height and the width from line edit entries.It works well but when I change the values inside the line edits and click on the push button it will draw another frame over the previous one. I have tried plt.gcf().clear() and ax.clear and they did not work.
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.ax.plot(x, y)
self.canvas.draw()
Picture of the app
Instead of clearing the axes you may update the line that is drawn with the new coordinates.
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
self.line, = self.ax.plot([])
self.ax.axis([0,100,0,100])
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.line.set_data(x,y)
self.canvas.draw()
if __name__=='__main__':
app = QApplication(sys.argv)
w = Window()
app.exec_()
As mentioned in the comment above, you need to call self.ax.clear() every time you click the button. Otherwise you will be redrawing on the same plot. Not sure why your graph doesnt reset on clear(), but here is the code I ran which worked fine. Let me know if it works for you:
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
self.ax.clear()
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.ax.plot(x, y)
self.canvas.draw()
if __name__=='__main__':
app = QApplication(sys.argv)
w = Window()
app.exec_()
I want to make imshow() function update (256*873)array quickly. set_array()'s speed is about 15 times a second. I hope it can be about 30 times a second. there is a demo which using imshow() and set_array().Anybody help?Thank you so much!
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
TIMER_ID = wx.NewId()
class CanvasFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'SinWave', size=(600,600))
self.figure = Figure()
self.canvas = FigureCanvas(self, -1, self.figure)
self.axes = self.figure.add_subplot(111)
mapdata = np.random.rand(256, 873)
self.img_artist = self.axes.imshow(mapdata, origin='lower', interpolation='nearest', aspect='auto',extent=[0,873,0,256])
self.img_artist.set_clim(vmin=0, vmax=1)
self.timer = wx.Timer(self) # 创建定时器
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(100) # 设定时间间隔
self.cnt=0
def OnTimer(self, evt):
mapdata = np.random.rand(256, 873)
self.img_artist.set_data(mapdata)
self.canvas.draw()
self.cnt +=1
print self.cnt
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = CanvasFrame()
frame.Show()
app.MainLoop()