I have to plot a grid of subplots (16x16 ones, for example). I use matplotlib but It is a bit too slow, especially when I have to redraw the plots (for example, when their size is changed). How can I make it faster?
P.S. Here is an example of code:
import sys
import matplotlib
matplotlib.use('Qt4Agg')
import pylab
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import PyQt4
from PyQt4 import QtCore, QtGui, Qt
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# generate the plot
fig = Figure(figsize=(800, 800), facecolor=(1, 1, 1), edgecolor=(0, 0, 0))
for i in range(256):
ax = fig.add_subplot(16, 16, i)
ax.plot([0, 1])
ax.set_xticks([])
ax.set_yticks([])
# ax.set_title("Mega %i" % (i,))
# generate the canvas to display the plot
canvas = FigureCanvas(fig)
canvas.setMinimumWidth(640)
canvas.setMinimumHeight(640)
# generate layout
layout = QtGui.QVBoxLayout();
layout.addWidget(canvas)
layout.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
# generate widget
widget = QtGui.QWidget()
widget.setLayout(layout)
# generate scroll area
win = QtGui.QScrollArea()
win.setWidget(widget)
win.setMinimumWidth(100)
win.setWidgetResizable(True)
win.show()
canvas.draw()
sys.exit(app.exec_())
I don't have an environment to test your code but I these steps work for me:
Using cProfile to profile the whole thing (f.e. How can you profile a python script?).
Usually its one or two functions which slow down all.
Search stackoverflow/the internet for these function names. Usually 1-2 people solved the performance issue already.
Greetings Kuishi
Related
When I change the scale of the axis of my image, my ScaleBar shows the incorrect scale. How do I update the scale bar when I change the axes?
from PyQt5 import QtWidgets
import pyqtgraph as pg
import numpy as np
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
imvOCTTopLeft = pg.ImageView(view=pg.PlotItem())
imvOCTTopLeft.setImage(np.random.normal(size=(100,100)))
imvOCTTopLeft.view.getAxis('left').setScale(0.6)
imvOCTTopLeft.view.getAxis('bottom').setScale(0.4)
scale = pg.ScaleBar(size=10,suffix = "px")
viewbox = imvOCTTopLeft.view
if not isinstance(viewbox, pg.ViewBox): viewbox = viewbox.getViewBox()
scale.setParentItem(viewbox)
scale.anchor((1, 1), (1, 1), offset=(-20, -20))
imvOCTTopLeft.show()
sys.exit(app.exec_())
This image shows that the scale bar is showing approximately 4 pixels but states that it is showing 10 pixels.
I think this is because I changed the axis scale.
This seems to be a bug: link. The viewbox rescales after sigRangeChanged is emitted.
"Hacky" solution is to delay the ScaleBar update:
(You might need to play around with the time, 100 and 10 worked for me. If it doesnt work, increase it.)
from PyQt5 import QtWidgets, QtCore
import pyqtgraph as pg
import numpy as np
def updateDelay(scale, time):
QtCore.QTimer.singleShot(time, scale.updateBar)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
plotItem = pg.PlotItem()
imvOCTTopLeft = pg.ImageView(view=plotItem)
imvOCTTopLeft.setImage(np.random.normal(size=(100, 100)))
imvOCTTopLeft.view.getAxis('left').setScale(0.6)
scale = 0.4 #edit
imvOCTTopLeft.view.getAxis('bottom').setScale(scale) #edit
scale = pg.ScaleBar(size=10*(1/scale), suffix="px") #edit
scale.text.setText('10 px') #edit
plotItem.sigRangeChanged.connect(lambda: updateDelay(scale, 10)) # here: time=10ms
viewbox = imvOCTTopLeft.view
if not isinstance(viewbox, pg.ViewBox): viewbox = viewbox.getViewBox()
scale.setParentItem(viewbox)
scale.anchor((1, 1), (1, 1), offset=(-20, -20))
imvOCTTopLeft.show()
updateDelay(scale, 100) # here time=100ms
sys.exit(app.exec_())
Result:
The following code
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import matplotlib.pyplot as plt
import numpy as np
class View(QGraphicsView):
def __init__(self):
super(View, self).__init__()
self.initScene(5)
def initScene(self,h):
self.scene = QGraphicsScene()
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.figure.subplots_adjust(left=0.03,right=1,bottom=.1,top=1,wspace=0, hspace=0)
ax = self.figure.add_subplot(111)
ax.set_xlim([0,1000])
data = np.random.rand(1000)
ax.plot(data, '-')
arr_img = plt.imread('sampleimage.jpg',format='jpg')
im = OffsetImage(arr_img,zoom=.9)
ab = AnnotationBbox(im, (.5, .5), xycoords='axes fraction')
ax.add_artist(ab)
self.canvas.draw()
self.setScene(self.scene)
self.scene.addWidget(self.canvas)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
#self.setGeometry(150, 150, 700, 550)
self.view = View()
self.setCentralWidget(self.view)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
produces the output seen below on the left. On the right, is the original image ('sampleimage.jpg') which I imported in the code.
The difference in resolution is apparent. Is there a way to add images to plots, whilst retaining their quality?
In the code from the question the OffsetImage is given an argument zoom=0.9. This means that each pixel of the original image takes 0.9/0.72=1.25 pixels on screen. Hence 5 pixels of the original image needs to squeezed into 4 pixels on screen. This inevitably leads to some artifacts as observed in the output of the code.
If the requirement is to show the image in the exact resolution of the original image, you need to make sure to use exactly one pixel per pixel for the OffsetImage. This would be accomplished by setting the zoom to the ppi of 72. divided by the figure dpi (100 by default).
OffsetImage(arr_img, zoom=72./self.figure.dpi)
As a result, the image shown would indeed have the same dimensions in the matplotlib plot as the original image.
I would like a TextItem that maintains a constant position on the graph while scaling the y-axis, essentially the same functionality as legend only as a TextItem where I can change the text as needed. I cannot figure out how to do this. Any suggestions welcome.
This example shows the problem. On the lefthand graph, scaling the y-axis causes the text to move whereas on the righthand graph the legend stays in a constant position as you scale. I would like the position of the textItem to be defined like the legend position, in a constant position relative to the graph window. Alternatively if someone knows how to change the format of the legend and update the text that would also work, but from my reading of the documentation this is not possible.
import pyqtgraph as pg
from PyQt4 import QtGui
import numpy as np
import sys
def main():
app = QtGui.QApplication(sys.argv)
widg = QtGui.QWidget()
widg.move(100, 100)
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
pgWidg = pg.GraphicsLayoutWidget()
pgWidg.resize(750, 250)
graph1 = pgWidg.addPlot(row=1, col=1)
graph2 = pgWidg.addPlot(row=1, col=2)
curve1 = graph1.plot(y=np.sin(np.linspace(1, 21, 1000)), pen='k')
curve2 = graph2.plot(y=np.sin(np.linspace(1, 21, 1000)), pen='k')
graph1.addItem(curve1)
graph2.addItem(curve2)
graph1.setMouseEnabled(x=False, y=True)
graph2.setMouseEnabled(x=False, y=True)
graph1Text = pg.TextItem(text = 'A1', color=(0, 0, 0))
graph1.addItem(graph1Text)
graph1Text.setPos(150, 1)
legend = graph2.addLegend()
style = pg.PlotDataItem(pen='w')
legend.addItem(style, 'A2')
grid = QtGui.QGridLayout()
grid.addWidget(pgWidg, 0,0)
widg.setLayout(grid)
widg.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
7 bilion years later of googling...
label = pg.LabelItem("Error", size="36pt", color="FF0000")
label.setParentItem(self.plotInstance)
label.anchor(itemPos=(1,0), parentPos=(1,0), offset=(-10,10))
where self.plotInstance = pg.PlotWidget.getPlotItem()
works on PyQt5 and pyqtgraph 0.12
This is a somewhat old question now- Hopefully this will help someone. Answering this helped me answer my own question.
Please note that I used PySide2 rather than PyQt4- I don't think this is significantly different to PyQt4. I am also using pyqtgraph 0.11.1.
There is a getLabel() method of the LegendItem that returns the LabelItem inside the legend for a given plotItem. This should allow you to do what you want.
You created your legend with this code:
legend = graph2.addLegend()
style = pg.PlotDataItem(pen='w')
legend.addItem(style, 'A2')
You can then get the labelitem with:
legend_labelitem = legend.getLabel(style)
With that you should be able to change the properties - such as using .setText() to set a new legend text:
legend_labelitem.setText('Something else')
The full code would end up as this:
import pyqtgraph as pg
# from PySide2 import QtGui # <---- tested with this
from PyQt4 import QtGui
import numpy as np
import sys
def main():
app = QtGui.QApplication(sys.argv)
widg = QtGui.QWidget()
widg.move(100, 100)
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
pgWidg = pg.GraphicsLayoutWidget()
pgWidg.resize(750, 250)
graph1 = pgWidg.addPlot(row=1, col=1)
graph2 = pgWidg.addPlot(row=1, col=2)
curve1 = graph1.plot(y=np.sin(np.linspace(1, 21, 1000)), pen='k')
curve2 = graph2.plot(y=np.sin(np.linspace(1, 21, 1000)), pen='k')
graph1.addItem(curve1)
graph2.addItem(curve2)
graph1.setMouseEnabled(x=False, y=True)
graph2.setMouseEnabled(x=False, y=True)
graph1Text = pg.TextItem(text = 'A1', color=(0, 0, 0))
graph1.addItem(graph1Text)
graph1Text.setPos(150, 1)
legend = graph2.addLegend()
style = pg.PlotDataItem(pen='w')
legend.addItem(style, 'A2')
legend_labelitem = legend.getLabel(style) # <---------
legend_labelitem.setText('Something else') # <---------
grid = QtGui.QGridLayout()
grid.addWidget(pgWidg, 0,0)
widg.setLayout(grid)
widg.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
It produces this:
I have a matplotlib figure with many axes, and the scrolling/zooming becomes unusably slow. Is there anyway to speed it up?
As an example, try scrolling one of the axes produced with this code:
import matplotlib.pyplot as plt
fig,plts = plt.subplots(10,10)
plt.show()
(I am on a Mac, using the macosx backend. The QT4Agg backend seemed similarly sluggish.)
I think the slowdown comes from matplotlib redrawing the entire figure, rather than just the subplot you want to zoom. I have found that you can speed things up by creating multiple figures and embedding them in a PyQt widget.
Here's a quick proof of concept using 'figure_enter_event' and a bit of ugly hackery to allow the use of a single navigation toolbar across all figures. Note that I have only attempted to make the pan and zoom features work properly. By peeking at the source of NavigationToolbar2 in backend_bases.py some more I'm sure you could adapt it to your needs.
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import pyqtSlot
import matplotlib
matplotlib.use('Qt5Agg')
matplotlib.rcParams['backend.qt5'] = 'PyQt5'
matplotlib.rcParams.update({'figure.autolayout': True})
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import numpy as np
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
# Construct the plots
playout = QtWidgets.QGridLayout()
playout.setContentsMargins(0, 0, 0, 0)
for row in range(0, 10):
for col in range(0, 10):
fig = Figure()
ax = fig.add_subplot(111)
canvas = FigureCanvas(fig)
canvas.mpl_connect('figure_enter_event', self.enterFigure)
playout.addWidget(canvas, row, col, 1, 1)
t = np.arange(-2*np.pi, 2*np.pi, step=0.01)
ax.plot(t, np.sin(row*t) + np.cos(col*t))
# Assign toolbar to first plot
self.navbar = NavigationToolbar(playout.itemAtPosition(0, 0).widget(), self)
cwidget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(cwidget)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(self.navbar)
layout.addLayout(playout)
self.setCentralWidget(cwidget)
def enterFigure(self, event):
self.navbar.canvas = event.canvas
event.canvas.toolbar = self.navbar
self.navbar._idDrag = event.canvas.mpl_connect('motion_notify_event', self.navbar.mouse_move)
# Toggle control off and then on again for the current canvas
if self.navbar._active:
if self.navbar._active == 'PAN':
self.navbar.pan()
self.navbar.pan()
elif self.navbar._active == 'ZOOM':
self.navbar.zoom()
self.navbar.zoom()
app = QtWidgets.QApplication(sys.argv)
win = MainWindow()
win.show()
app.exec_()
I'm trying to include a matplotlib figure in a Python Gtk3 application I'm writing. I'd like to set the background colour of the figure to be transparent, so that the figure just shows up against the natural grey background of the application, but nothing I've tried so far seems to be working.
Here's an MWE:
from gi.repository import Gtk
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import numpy as np
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
fig, ax = plt.subplots()
#fig.patch.set_alpha(0.0)
x,y = np.array([[0, 1], [0, 0]])
line = mlines.Line2D(x, y, c='#729fcf')
ax.add_line(line)
plt.axis('equal')
plt.axis('off')
fig.tight_layout()
sw = Gtk.ScrolledWindow()
sw.set_border_width(50)
canvas = FigureCanvas(fig)
sw.add_with_viewport(canvas)
layout = Gtk.Grid()
layout.add(sw)
self.add(layout)
win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
If the fig.patch.set_alpha(0.0) line is uncommented, the colour of the background just changes to white, rather than grey. All suggestions greatly appreciated!
It seems to me that it's the axes background that needs to be hidden. You might try using ax.patch.set_facecolor('None') or ax.patch.set_visible(False).
Alternatively, have you tried setting both the figure and axes patches off? This may be accomplished by:
for item in [fig, ax]:
item.patch.set_visible(False)
I solved it this way. It's not ideal, but it works.
from gi.repository import Gtk
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import numpy as np
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
from matplotlib.colors import ColorConverter
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
fig, ax = plt.subplots()
#fig.patch.set_alpha(0.0)
x,y = np.array([[0, 1], [0, 0]])
line = mlines.Line2D(x, y, c='#729fcf')
ax.add_line(line)
plt.axis('equal')
plt.axis('off')
fig.tight_layout()
win = Gtk.Window()
style = win.get_style_context()
bg_colour = style.get_background_color(
Gtk.StateType.NORMAL).to_color().to_floats()
cc = ColorConverter()
cc.to_rgba(bg_colour)
fig.patch.set_facecolor(bg_colour)
sw = Gtk.ScrolledWindow()
sw.set_border_width(50)
canvas = FigureCanvas(fig)
sw.add_with_viewport(canvas)
layout = Gtk.Grid()
layout.add(sw)
self.add(layout)
win = MyWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
A combination of
fig.patch.set_visible(False)
self.setStyleSheet("background-color:transparent;")
works for me (self is a subclass of Canvas).
Any of these two alone do not work.