在Matplotlib中,是否有一种异步弹出图形的方法?

17

在matplotlib中,是否有一种简单的方法可以绘制图形而不中断脚本的控制流程?

为了清晰起见,这里使用伪代码来说明我试图实现的内容:

fig1 = figure()
fig1.plot_a_figure(datasets)

for dataset in datasets:
   results = analyze(dataset)    # this takes several minutes
   update(fig1)
   pop_up_another_figure(results) # would like to have a look at this one
                                  # while the next dataset is being processed
当然,我可以使用savefig()保存这些中间图形,但我只需要快速地查看它们,最好让它们在屏幕上实时弹出。
编辑:一个可运行的示例:
#!/usr/bin/python
import pylab as plb
import matplotlib.pyplot as plt

fig1=plt.figure(1)
ax = fig1.add_subplot(1,1,1)

ax.plot([1,2,3],[4,5,6],'ro-')

#fig1.show()  # this does not show a figure if uncommented
plt.show()    # until the plot window is closed, the next line is not executed

print "doing something else now"

我是否遗漏了非常基础的东西?


我以为 show() 已经做到了这一点。你可以创建任意数量的图形,对每个图形使用 show() 方法,它们将在代码继续运行时保持显示状态。你能否发布一个可运行的示例来演示问题? - Paul
@ Paul:添加了一个示例。我是否漏掉了一些微不足道的东西? - ev-br
不,你绝对没有错过任何微不足道的东西。我只是看到了许多图表同时弹出,以为有一些线程正在运行,而show()不会获取锁。阅读这篇文章:https://dev59.com/U2445IYBdhLWcg3w3N6z#4662511 - Paul
我能想到三种方法:首先,如果您不介意图形是交互式的,您可以像您建议的那样保存图像,然后使用os.startfile()轻松启动该图像的查看器。如果您希望它是交互式的,并且不介意将数据写入磁盘,您可以使用subprocess.Popen()打开另一个Python脚本。第三种方法是尝试真正理解Matplotlib如何管理线程。 - Paul
2个回答

17

首先,别忘了一个简单的替代方法就是使用plt.figure(2)plt.figure(3)等来创建新的图形窗口。如果你真的想要更新现有的图形窗口,最好保持对线对象的句柄

h = ax.plot([1,2,3],[4,5,6],'ro-')

然后稍后您会执行类似以下的操作:

h[0].set_data(some_new_results)
ax.figure.canvas.draw()

至于问题的实质,如果您仍在与此进行斗争,请继续阅读。


如果您想使plt.show()是非阻塞的,您需要启用交互模式。为了修改您的可运行示例,以便立即打印"现在正在做其他事情",而不是等待图形窗口关闭,可以使用以下方法:

#!/usr/bin/python
import pylab as plb
import matplotlib.pyplot as plt

fig1=plt.figure(1)
ax = fig1.add_subplot(1,1,1)

ax.plot([1,2,3],[4,5,6],'ro-')

#fig1.show()  # this does not show a figure if uncommented
plt.ion()     # turns on interactive mode
plt.show()    # now this should be non-blocking

print "doing something else now"
raw_input('Press Enter to continue...')
然而,这只是事情的冰山一角,一旦您开始想要在与绘图交互时进行后台工作,就会出现许多复杂问题。这是使用本质上是状态机来绘制图形的自然结果,它不适用于线程和面向对象编程环境。
  • 昂贵的计算将必须进入工作线程(或替代地进入子进程)以避免冻结GUI。
  • 应该使用Queue以线程安全的方式传递输入数据并从工作函数中获取结果。
  • 根据我的经验,在工作线程中调用draw()不安全,因此您还需要设置一种重新绘制的调度方式。
  • 不同的后端可能会开始做奇怪的事情,TkAgg似乎是唯一可以正常工作的后端(请参见此处)。

最简单和最好的解决方案不是使用普通的Python解释器,而是使用ipython -pylab(正如ianalis所建议的那样),因为他们已经掌握了大部分使交互式内容平稳运行所需的技巧。这可以在没有ipython/pylab的情况下完成,但需要大量额外的工作。

注意:尽管我经常喜欢将工作线程拆分为使用ipython和pyplot GUI窗口,但要使线程平稳运行,我还需要使用另一个命令行参数ipython -pylab -wthread。我使用的是python 2.7.1+matplotlib v1.1.0,您的效果可能会有所不同。希望这可以帮到您!

Ubuntu用户注意:存储库现在已经一段时间停留在v0.99上,因此值得升级您的matplotlib,因为在v1.0发布之前有许多改进,包括Bugfix马拉松活动和对show()行为的重大更改。


我用plt.ion()尝试了你的例子,但对我无效,plt.show()仍然阻塞它。 我使用的是Ubuntu 10.04,python 2.6.5,matplotlib 0.99。 这个版本太旧了吗? - ev-br
也许它太旧了:在v1.0时,他们已经相当大地改变了show()的实现(请参见此处http://matplotlib.sourceforge.net/users/whats_new.html#multiple-calls-to-show-supported)。如果您将`plt.ion()`行移到脚本顶部,即在`import matplotlib.pyplot as plt之后,会得到什么结果?调用函数matplotlib.get_backend()`的结果是什么? - wim
get_backend() 返回 TkAgg,并且如果 plt.ion() 是脚本的第一行,那么就没有任何变化。 - ev-br
ax.figure.canvas.draw() 是一个阻塞/同步调用吗?我无法在任何地方找到这个信息。如果您知道或有一个解释这个问题的参考资料,将不胜感激。 - The Quantum Physicist

3

可能最简单的解决方案是使用IPython作为Python shell。使用-pylab选项运行它。

ipython -pylab

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