You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The response of mouse zoom & pan is slow with Qt4Agg backend. #2559
@zhangruoyu@jason-s@t What OS/qt version are you using? pyqt or pysides? I use qt4agg + pysides for my daily work and have never noticed any behaviour I would call slow and the example program is snappy on my system.
The Qt docs state that using update rather than repaint is the proper usage. I am hesitant to go against the recommended usage without a very good reason.
Windows 7. PySide. Anaconda.
I often plot 50,000 - 200,000 points on four or five linked graphs (x-axes linked together), so maybe you never see slow behavior the way I do.
I am using windows 7, PyQt4.9.6, WinPython @tacaswell , have you tried the code I posted above, comment & uncomment the line patch_qt4agg(), and try mouse zoom & pan by dragging with left or right button.
I think because update() doesn't repaint immediately, it makes the response of mouse action slow.
@zhangruoyu What do you mean by 'slow'? I can not reproduce this at all.
If I plot 500k points (itplt.plot(np.linspace(0, 1, 500000), np.random.rand(500000), 'o')) is slow to respond while panning with either code. In both cases the zoom rectangle updates quickly. If I zoom in to a reasonable number of points on the screen than pan is responsive again in both cases (which makes me thing the bottle neck is in FigureCanvasAgg.draw(self).
I suspect that the problem is windows (I am using linux)
The difference between update and repaint is that update schedules a repaint for the next time the system is idle. Usually this is what you want to do, since you may move the mouse many times between each "frame", and you don't want to have to draw all of the intermediate frames -- in principle repainting with each mouse move should be slower. However, perhaps the scheduling of events is slower/behaves differently on Windows.
However, I can't detect a difference on Linux -- so I would argue if changing to repaint has no detectable change on Linux and Mac, but improves things considerably on Windows, than we make the change.
Just a voice possibly against this idea... what if you have a very large
image to display? The computer is already taking up a lot of cycles to
handle the redraws on zoom and pan. To then force it to actually paint all
those frames would only add to the burden. I would be curious how the two
options degrade performance-wise at the upper end of displaying data. I
regularly call imshow() on 7000x3000 images (1km resolution CONUS data),
and then zoom in and pan around to the areas I want to view.
The difference between update and repaint is that update schedules a
repaint for the next time the system is idle. Usually this is what you
want to do, since you may move the mouse many times between each "frame",
and you don't want to have to draw all of the intermediate frames -- in
principle repainting with each mouse move should be slower. However,
perhaps the scheduling of events is slower/behaves differently on Windows.
However, I can't detect a difference on Linux -- so I would argue if
changing to repaint has no detectable change on Linux and Mac, but improves
things considerably on Windows, than we make the change.
Reply to this email directly or view it on GitHubhttps://github.com//issues/2559#issuecomment-35903750
.
My plan for dealing with this was to add an instantiation time switch:
if on_windows():
self._priv_redraw = self.repaint
else:
self._priv_redraw = self.update
and then have draw call the private function.
If the canvas.draw ever gets called in a repaint of something else (which given that we can be embedded is something we can't control) using repaint can lead to an infinite loop and I don't think we should expose the linux/mac users to that risk.
I would like to reinforce @WeatherGod's point; I am strongly opposed to putting in code that is contrary to the basic design of the gui toolkit to handle this Windows problem. A Windows conditional as suggested by @tacaswell would be reasonable to try. It should include a comment explaining the rationale.
Actually, I had a slightly different situation. I have a matplotlib contourf figure in a pyQt window which I continuously want to update (playing a movie). One update works, however, when I looped over time the canvas was not updated but it seemed that all changes were hold until the last time step, and then only the last frame would show up.
I used matplotlib 1.4.3 with python 2.7.9 and pyqt 4.10.4
For the canvas I created a manual widget in qt-designer which I promoted to a mplCanvas class. something like
frommatplotlib.backends.backend_qt4aggimportFigureCanvasQTAggasFigureCanvasimportmatplotlib.pyplotaspltclassMplCanvas(FigureCanvas):
def__init__(self):
.... somelineshereself.figure, self.axes=plt.subplots(nrows=1, ncols=2, subplot_kw=dict(projection='polar'), figsize=(13, 6))
self.contour_plots= []
forindex, axinenumerate(self.axes):
self.contour_plots.append(
ax.contourf(np.zeros([1,1]),np.zeros([1,1]),np.zeros([1,1])))
classmplWidget(QtGui.QWidget):
def__init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.mplcanvas=MplCanvas()
self.vbl=QtGui.QVBoxLayout()
self.vbl.addWidget(self.mplcanvas)
self.setLayout(self.vbl)
defupdatePlots(self,radar):
""" Here the radar plot is defined. :param radar: :return: """# this line force the canvas to be redrawn after leaving the routinepatch_qt4agg()
forindex, axinenumerate(self.mplcanvas.axes):
ax.clear()
forindex, axinenumerate(self.mplcanvas.axes):
ax.hold(True)
tmpcont.append(ax.contourf( data_of_plot[index]))
ax.hold(False)
# loop over the axes of the plot and create plotforindex,contourplotinenumerate(self.mplcanvas.contour_plots):
contourplot.collections=[]
forcollintmpcont[index].collections:
contourplot.collections.append(coll)
... somemorestuffself.mplcanvas.figure.canvas.draw()
self.mplcanvas.repaint()
Those last two line did not cause an update of the screen when playing a movie. With your patch patch_qt4agg() it works. sorry, bit more complicated example because of the qt implementation. In the update routine I need to clear the canvas from the old plot and then push the new plot in the the collections (by making a temporary plot). Or is there something else I did wrong which could prevent from using your patch ? Anyway, it works now, so thanks for that. But any hints for improvement appreciated!
And a related issue you are probably having is that you are not giving the gui event loop a chance to re-paint it's self. Have a look at how the animation module code works.
Hi Tacaswell,
I like you comments and example and I tried to apply it to my case as it looks cleaner this way. It only does not work. First of all, ax_contour['contour'] does not have a .remove() attribute. The items from the collection list do, so I was assuming you meant that.
Also you have combinded the class MplCanvas(FigureCanvas) and class mplWidget(QtGui.QWidget). However, I have created my canvas with qt-desinger by adding a manual widget and promoting it. The interface of the mplWiget is given by qt-desinger (in the generate .py user interface) and does not allow me to pass a argument. By splitting it I can add my own argument to the mplCanvas class, that is why I keep it separate.
Anyway, I think that is not relevant. The problem I have now is that althoug you example (slightly modified) works, however, the old contour plot is not deleted. I can see that because it is a part of a pie which rotates and every new update of the rotation angle to old contour is not deleted, although I have looped over the collection and called the remove button.
Here my example. Do you have any idea why the old contours do not get removed?
importModules.CoordinateTransformsasacf# -*- coding: utf-8 -*-"""Created on Sat Feb 7 21:25:39 2015@author: eelco"""fromPyQt4importQtGuiimportnumpyasnpfrommatplotlib.figureimportFigurefrommatplotlibimportcmfrommatplotlib.backends.backend_qt4aggimportFigureCanvasQTAggasFigureCanvasimportmatplotlib.gridspecasgridspecimportlogginglogging.root.handlers= []
logging.basicConfig(format='[%(levelname)-7s] %(message)s', level=logging.DEBUG)
defpatch_qt4agg():
importmatplotlib.backends.backend_qt4aggasbackendcode="""def draw( self ): FigureCanvasAgg.draw(self) self.repaint()FigureCanvasQTAgg.draw = draw"""exec(code, backend.__dict__)
classMplCanvas(FigureCanvas):
""" Create the canvas containing 2 sub plots with the amplitude and phase """def__init__(self):
ncols=2plotlabels= ["Wave field", "Radar scan"]
self.plot_type="Wave Field"self.figure=Figure(figsize=(13, 10),dpi=100)
gs=gridspec.GridSpec(1,ncols)
# create some extra spacegs.update(left=0.15,right=.85,bottom=0.3, wspace=0.25)
self._axes_contours= []
self._axes_lines= []
self._axes_point= []
# add the empty sub figuresforninrange(ncols):
ax=self.figure.add_subplot(gs[:,n],projection='polar')
ax.set_title(plotlabels[n], y=1.08)
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_ylim(0, 1000)
ax.set_yticks(np.linspace(0, 1000, 4, endpoint=True))
self._axes_contours.append({'ax': ax,'contour': None,'points': None,'lines': None})
# add two color barsself._axes_cbars=[]
self._axes_cbars.append(self.figure.add_axes([.02, 0.35, 0.02, 0.5]))
self._axes_cbars.append(self.figure.add_axes([.92, 0.35, 0.02, 0.5]))
FigureCanvas.__init__(self, self.figure)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
classmplWidget(QtGui.QWidget):
""" Widget for the matplotlib canvas and some routines to update the data and the x and y ranges. There as 2 subplots """def__init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.mplcanvas=MplCanvas()
self.vbl=QtGui.QVBoxLayout()
self.vbl.addWidget(self.mplcanvas)
self.setLayout(self.vbl)
# @profiledefupdatePlots(self,radar):
""" Here the radar plot is defined. :param radar: :return: """# this line force the canvas to be redrawn after leaving the routine#patch_qt4agg()logging.debug("Updating the plots")
radiigrid=[radar.polar_meshgrid[1]]
radiigrid.append(radar.scanned_area_rt_grid[1])
anglegrid=[radar.polar_meshgrid[0]]
anglegrid.append(radar.scanned_area_rt_grid[0])
valuegrid=[radar.wave_field.eta1,radar.wave_field.eta2]
vrange=[[-2,2],[-2,2]]
cmbar=[cm.coolwarm,cm.coolwarm]
# set the contour levels for both plotsnumber_of_contour_levels=7contour_levels=[]
foriinrange(2):
contour_levels.append(np.linspace(vrange[i][0], vrange[i][1],
number_of_contour_levels, endpoint=True))
# loop over the axes of the plot and create plotforindex,ax_contourinenumerate(self.mplcanvas._axes_contours):
ifax_contour['contour']:
fori,collinenumerate(ax_contour['contour'].collections):
logging.debug("remove coll {} ".format(i))
ax_contour['contour'].collections.remove(coll)
ax_contour['contour'].collections=Noneax_contour['contour']=Noneax_contour['contour']=ax_contour['ax'].contourf(
radiigrid[index], anglegrid[index],
valuegrid[index],
contour_levels[index],
cmap=cmbar[index], linewidth=0, antialiased=False)
# finally, call the redrawing of the canvasself.mplcanvas.figure.canvas.draw()
To answer my own question, I found a way how to remove the contour plots
First of all, I noticed tht the for loop over the collection still leaves items in the collection which are called silent_list. This silent list is shorted than the initial collection. So repeating the loop eventually also deletes all the silent_list items. If I introduced a while statement I can repeat the loop over the collection as long as there are no items in the list left. That is the first trick
The second is that I also have to delete the contours from the axis itself, which I stored in the ax field of the dictionary.
The part of the code which does the redraw of the contour plot now is:
Although the redrawing of the frames is now correct, if I play a movie again I run in the problem that frames got buffered. Now also the trick with the patch does not work. I read you comment about the time needed to redraw the canvas. I can not find exactly in the animation module what I need to do to force a redraw. If I simple add a time.sleep(1) this does not work either. Any hint?
Activity
Tillsten commentedon Oct 30, 2013
Can Confirm that matplotlib is much more responsive with the change. I always taught
matplotlib is really slow without blitting...
jason-s commentedon Jan 23, 2014
Until this is released, is there a workaround in the meantime, e.g. subclassing FigureCanvasQTAgg?
I'm not that familiar with the nuances of trying to do so. I'm using an explicit FigureCanvasQTAgg with other UI elements:
so if subclassing would work I can definitely do it.
zhangruoyu commentedon Jan 24, 2014
Use the patch_qt4agg() function:
tacaswell commentedon Feb 21, 2014
@zhangruoyu @jason-s @t What OS/qt version are you using? pyqt or pysides? I use qt4agg + pysides for my daily work and have never noticed any behaviour I would call slow and the example program is snappy on my system.
The Qt docs state that using
updaterather thanrepaintis the proper usage. I am hesitant to go against the recommended usage without a very good reason.http://qt-project.org/doc/qt-4.8/qwidget.html#update
http://qt-project.org/doc/qt-4.8/qwidget.html#repaint
jason-s commentedon Feb 21, 2014
Windows 7. PySide. Anaconda.
I often plot 50,000 - 200,000 points on four or five linked graphs (x-axes linked together), so maybe you never see slow behavior the way I do.
If I get time, I will put together an example.
zhangruoyu commentedon Feb 21, 2014
I am using windows 7, PyQt4.9.6, WinPython
@tacaswell , have you tried the code I posted above, comment & uncomment the line
patch_qt4agg(), and try mouse zoom & pan by dragging with left or right button.I think because
update()doesn't repaint immediately, it makes the response of mouse action slow.tacaswell commentedon Feb 21, 2014
@zhangruoyu What do you mean by 'slow'? I can not reproduce this at all.
If I plot 500k points (
itplt.plot(np.linspace(0, 1, 500000), np.random.rand(500000), 'o')) is slow to respond while panning with either code. In both cases the zoom rectangle updates quickly. If I zoom in to a reasonable number of points on the screen than pan is responsive again in both cases (which makes me thing the bottle neck is inFigureCanvasAgg.draw(self).I suspect that the problem is windows (I am using linux)
(edit: removed double sentence)
Tillsten commentedon Feb 22, 2014
Windows user here, the response is sluggish while panning even for a simple [1, 2, 3] plot.
mdboom commentedon Feb 24, 2014
The difference between
updateandrepaintis thatupdateschedules arepaintfor the next time the system is idle. Usually this is what you want to do, since you may move the mouse many times between each "frame", and you don't want to have to draw all of the intermediate frames -- in principlerepaintingwith each mouse move should be slower. However, perhaps the scheduling of events is slower/behaves differently on Windows.However, I can't detect a difference on Linux -- so I would argue if changing to repaint has no detectable change on Linux and Mac, but improves things considerably on Windows, than we make the change.
WeatherGod commentedon Feb 24, 2014
Just a voice possibly against this idea... what if you have a very large
image to display? The computer is already taking up a lot of cycles to
handle the redraws on zoom and pan. To then force it to actually paint all
those frames would only add to the burden. I would be curious how the two
options degrade performance-wise at the upper end of displaying data. I
regularly call imshow() on 7000x3000 images (1km resolution CONUS data),
and then zoom in and pan around to the areas I want to view.
On Mon, Feb 24, 2014 at 11:23 AM, Michael Droettboom <
notifications@github.com> wrote:
tacaswell commentedon Feb 24, 2014
My plan for dealing with this was to add an instantiation time switch:
and then have
drawcall the private function.If the
canvas.drawever gets called in arepaintof something else (which given that we can be embedded is something we can't control) usingrepaintcan lead to an infinite loop and I don't think we should expose the linux/mac users to that risk.mdboom commentedon Feb 24, 2014
@WeatherGod: The actually drawing of the frame happens always, regardless, in order to support the animation model
It is only the copying of the buffer to the window/screen that would make any difference here.
mdboom commentedon Feb 24, 2014
@tacaswell: That's a good point about the infinite loop. I suppose we have to guard against that somehow...
efiring commentedon Feb 24, 2014
I would like to reinforce @WeatherGod's point; I am strongly opposed to putting in code that is contrary to the basic design of the gui toolkit to handle this Windows problem. A Windows conditional as suggested by @tacaswell would be reasonable to try. It should include a comment explaining the rationale.
tacaswell commentedon Feb 26, 2014
@zhangruoyu @Tillsten @jason-s Please see #2844. I think this should address the issue.
tacaswell commentedon Apr 21, 2014
Closing as #2844 should have fixed this.
eelcovv commentedon Mar 5, 2015
The patch_qt4agg solution of zhangruoyu works for me. Thanks!
tacaswell commentedon Mar 5, 2015
@eelcovv Does the issue persist in 1.4.3 with out the patch?
eelcovv commentedon Mar 6, 2015
Actually, I had a slightly different situation. I have a matplotlib contourf figure in a pyQt window which I continuously want to update (playing a movie). One update works, however, when I looped over time the canvas was not updated but it seemed that all changes were hold until the last time step, and then only the last frame would show up.
I used matplotlib 1.4.3 with python 2.7.9 and pyqt 4.10.4
For the canvas I created a manual widget in qt-designer which I promoted to a mplCanvas class. something like
Those last two line did not cause an update of the screen when playing a movie. With your patch patch_qt4agg() it works. sorry, bit more complicated example because of the qt implementation. In the update routine I need to clear the canvas from the old plot and then push the new plot in the the collections (by making a temporary plot). Or is there something else I did wrong which could prevent from using your patch ? Anyway, it works now, so thanks for that. But any hints for improvement appreciated!
Eelco
tacaswell commentedon Mar 6, 2015
It is rather hard to follow your example. I don't really understand what you are doing with temporary plots and moving artists between axes.
I would not use
pyplotif you are doing embedding.You also probably do not want to monkey patch the class every time you call update.
I suspect you want to be doing something like
tacaswell commentedon Mar 6, 2015
And a related issue you are probably having is that you are not giving the gui event loop a chance to re-paint it's self. Have a look at how the animation module code works.
eelcovv commentedon Mar 9, 2015
Hi Tacaswell,
I like you comments and example and I tried to apply it to my case as it looks cleaner this way. It only does not work. First of all, ax_contour['contour'] does not have a .remove() attribute. The items from the collection list do, so I was assuming you meant that.
Also you have combinded the class MplCanvas(FigureCanvas) and class mplWidget(QtGui.QWidget). However, I have created my canvas with qt-desinger by adding a manual widget and promoting it. The interface of the mplWiget is given by qt-desinger (in the generate .py user interface) and does not allow me to pass a argument. By splitting it I can add my own argument to the mplCanvas class, that is why I keep it separate.
Anyway, I think that is not relevant. The problem I have now is that althoug you example (slightly modified) works, however, the old contour plot is not deleted. I can see that because it is a part of a pie which rotates and every new update of the rotation angle to old contour is not deleted, although I have looped over the collection and called the remove button.
Here my example. Do you have any idea why the old contours do not get removed?
eelcovv commentedon Mar 9, 2015
To answer my own question, I found a way how to remove the contour plots
First of all, I noticed tht the for loop over the collection still leaves items in the collection which are called silent_list. This silent list is shorted than the initial collection. So repeating the loop eventually also deletes all the silent_list items. If I introduced a while statement I can repeat the loop over the collection as long as there are no items in the list left. That is the first trick
The second is that I also have to delete the contours from the axis itself, which I stored in the ax field of the dictionary.
The part of the code which does the redraw of the contour plot now is:
eelcovv commentedon Mar 9, 2015
Although the redrawing of the frames is now correct, if I play a movie again I run in the problem that frames got buffered. Now also the trick with the patch does not work. I read you comment about the time needed to redraw the canvas. I can not find exactly in the animation module what I need to do to force a redraw. If I simple add a time.sleep(1) this does not work either. Any hint?
eelcovv commentedon Mar 13, 2015
Found the solution here:
http://stackoverflow.com/questions/9465047/make-an-animated-wave-with-drawpolyline-in-pyside-pyqt
Just adding
In the time loop after updating the plot prevents blocking the screen