在 PyQT4 GUI 中嵌入 funcAnimation 并使 blitting 正常工作

4

从下面展示的工作中的Matplotlib动画代码开始,我的目标是将此动画(仅为屏幕上移动的圆)嵌入PyQT4 GUI中。

import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import animation

fig,ax = plt.subplots()
ax.set_aspect('equal','box')
circle = Circle((0,0), 1.0)
ax.add_artist(circle)
ax.set_xlim([0,10])
ax.set_ylim([-2,2])

def animate(i):
    circle.center=(i,0)
    return circle, 

anim = animation.FuncAnimation(fig,animate,frames=10,interval=100,repeat=False,blit=True)

plt.show()

我能使用以下代码完成此操作,但是有一个问题:我无法使blitting工作。
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation

class Window(QtGui.QDialog): #or QtGui.QWidget ???

    def __init__(self):
        super(Window, self).__init__()
        self.fig = Figure(figsize=(5,4),dpi=100)
        self.canvas = FigureCanvas(self.fig)
        self.ax = self.fig.add_subplot(111)  # create an axis
        self.ax.hold(False)  # discards the old graph
        self.ax.set_aspect('equal','box')
        self.circle = Circle((0,0), 1.0)
        self.ax.add_artist(self.circle)
        self.ax.set_xlim([0,10])
        self.ax.set_ylim([-2,2])
        self.button = QtGui.QPushButton('Animate')
        self.button.clicked.connect(self.animate)

        # set the layout
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)
        self.setLayout(layout)

    def animate(self):
        self.anim = animation.FuncAnimation(self.fig,self.animate_loop,frames=10,interval=100,repeat=False,blit=False)
        self.canvas.draw()

    def animate_loop(self,i):
        self.circle.center=(i,0)
        return self.circle, 

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Window()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()  

当我设置blit=True后,按下“Animate”按钮后,我遇到了以下错误:

a.figure.canvas.restore_region(bg_cache[a]) KeyError: matplotlib.axes._subplots.AxesSubplot object at 0x00000000095F1D30

搜索此错误时,我发现许多帖子提到在Mac上无法使用blitting,但我正在使用Windows 7。我尝试将self.canvas.draw()替换为self.canvas.update(),但这没有起作用。


看起来我正在追随你的脚步:你能找到解决方法了吗? - Underdetermined
很遗憾,还没有。 - dyson
2个回答

4

在查看动画模块的源代码之后,我发现Animation类存在一个错误(当blitting开启时,字典bg_cache为空,第一次访问时)。

这个问题已经在matplotlib的git版本中得到修复;然而,在最新的稳定版本1.5.1中,该bug仍然存在。您可以在matplotlib代码本身中修复该bug,或者您可以创建FuncAnimation的子类。我选择了后者,因为在更新matplotlib后仍应该能够正常工作。

from matplotlib import animation

class MyFuncAnimation(animation.FuncAnimation):
    """
    Unfortunately, it seems that the _blit_clear method of the Animation
    class contains an error in several matplotlib verions
    That's why, I fork it here and insert the latest git version of
    the function.
    """
    def _blit_clear(self, artists, bg_cache):
        # Get a list of the axes that need clearing from the artists that
        # have been drawn. Grab the appropriate saved background from the
        # cache and restore.
        axes = set(a.axes for a in artists)
        for a in axes:
            if a in bg_cache: # this is the previously missing line
                a.figure.canvas.restore_region(bg_cache[a])

然后,只需使用 MyFuncAnimation 而不是 animation.FuncAnimation。

我花了一些时间才弄明白,但我希望这能对任何人有所帮助。


2

一段时间后,我成功地通过直接使用底层函数而不是使用动画包装器来重新创建了该动画:

import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation
from time import sleep

class Window(QtGui.QDialog): #or QtGui.QWidget ???

    def __init__(self):
        super(Window, self).__init__()
        self.fig = Figure(figsize=(5, 4), dpi=100)
        self.canvas = FigureCanvas(self.fig)
        self.ax = self.fig.add_subplot(111)  # create an axis
        self.ax.hold(False)  # discards the old graph
        self.ax.set_aspect('equal', 'box')
        self.circle = Circle((0,0), 1.0, animated=True)
        self.ax.add_artist(self.circle)
        self.ax.set_xlim([0, 10])
        self.ax.set_ylim([-2, 2])
        self.button = QtGui.QPushButton('Animate')
        self.button.clicked.connect(self.animate)

        # set the layout
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.canvas)
        layout.addWidget(self.button)
        self.setLayout(layout)
        self.canvas.draw()
        self.ax_background = self.canvas.copy_from_bbox(self.ax.bbox)

    def animate(self):
        self.animate_loop(0)

    def animate_loop(self,begin):
        for i in range(begin,10):
            self.canvas.restore_region(self.ax_background)
            self.circle.center=(i,0)
            self.ax.draw_artist(self.circle)
            self.canvas.blit(self.ax.bbox)
            self.canvas.flush_events()
            sleep(0.1)

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Window()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main() 

也许这对你有用。

太完美了!非常感谢! - dyson
我遇到了一个新问题,我的坐标轴在尝试修改它们之前不会适当地改变(在此处发布:http://stackoverflow.com/questions/36811952/deleting-old-axes-when-blitting-in-matplotlib-animation-embedded-in-pyqt4)。你以前遇到过这个问题吗? - dyson
是的,我遇到过这个问题。不,我还没有找到解决方法。抱歉。最终我转向使用C#来处理我现在正在做的事情。以后可能会再次研究这个问题。 - Underdetermined
为什么你给 animate_loop 一个叫做 i 的参数,然后立即通过运行 for i in range(10) 来覆盖它? - Guimoute
@Guimoute 看起来像是一个 bug,已经将函数的输入更改为执行一些有用的操作... - Underdetermined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接