使用 Matplotlib 以非阻塞方式绘图

Posted

技术标签:

【中文标题】使用 Matplotlib 以非阻塞方式绘图【英文标题】:Plotting in a non-blocking way with Matplotlib 【发布时间】:2015-03-31 21:25:09 【问题描述】:

最近几天我一直在玩 Numpy 和 matplotlib。我在尝试使 matplotlib 绘制一个函数而不阻塞执行时遇到问题。我知道这里已经有很多关于 SO 的帖子在问类似的问题,我已经用谷歌搜索了很多,但还没有成功。

我已尝试按照某些人的建议使用 show(block=False),但我得到的只是一个冻结的窗口。如果我只是调用 show(),结果会被正确绘制,但执行会被阻止,直到窗口关闭。从我读过的其他线程中,我怀疑 show(block=False) 是否有效取决于后端。它是否正确?我的后端是 Qt4Agg。你能看看我的代码,如果你发现有问题告诉我吗?这是我的代码。感谢您的帮助。

from math import *
from matplotlib import pyplot as plt
print(plt.get_backend())



def main():
    x = range(-50, 51, 1)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4

        y = [Xi**pow for Xi in x]
        print(y)

        plt.plot(x, y)
        plt.draw()
        #plt.show()             #this plots correctly, but blocks execution.
        plt.show(block=False)   #this creates an empty frozen window.
        _ = raw_input("Press [enter] to continue.")


if __name__ == '__main__':
    main()

PS。我忘了说我想在每次绘制某些东西时更新现有窗口,而不是创建一个新窗口。

【问题讨论】:

您在plt.show() 之前尝试过plt.ion() 的matplotlib 交互模式吗?然后它应该是非阻塞的,因为每个图都生成到一个子线程中。 @Anzel 我刚试过,但似乎没什么区别。 你是如何运行你的脚本的?如果我从终端/命令提示符运行您的示例代码,它似乎工作正常,但我认为我过去在尝试从 IPython QtConsole 或 IDE 执行此类操作时遇到了麻烦。 @Marius 啊哈!!你说的对。事实上,我是从我的 IDE (PyCharm) 的控制台运行它的。从 cmd 提示符运行时,plt.show(block=False) 工作正常!如果我问你是否找到任何想法/解决方案,我会问太多吗?非常感谢! 我真的不知道对不起。我不太了解 matplotlib 如何与控制台交互的细节,所以如果我需要使用matplotlib 做这些事情,我通常只是切换到从命令提示符运行。 【参考方案1】:

找了好久,找到了this answer。

看起来,为了得到你(和我)想要的东西,你需要plt.ion()plt.show()(而不是block=False)和最重要的是plt.pause(.001)(或任何时间)的组合你要)。 pause 是必需的,因为 GUI 事件在主代码休眠时发生,包括绘图。这可能是通过从睡眠线程中获取时间来实现的,所以也许 IDE 会搞砸——我不知道。

这是一个适用于我在 python 3.5 上的实现:

import numpy as np
from matplotlib import pyplot as plt

def main():
    plt.axis([-50,50,0,10000])
    plt.ion()
    plt.show()

    x = np.arange(-50, 51)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4
        y = [Xi**pow for Xi in x]
        plt.plot(x, y)
        plt.draw()
        plt.pause(0.001)
        input("Press [enter] to continue.")

if __name__ == '__main__':
    main()

【讨论】:

您的回答帮助我解决了我遇到的类似问题。以前,我有plt.draw,然后是plt.show(block = False),但后来它停止工作:图没有响应,关闭它使iPython崩溃。我的解决方案是删除plt.draw() 的每个实例并用plt.pause(0.001) 替换它。而不是像之前的plt.draw 那样在plt.show(block = False) 后面跟着plt.ion()plt.show()。我现在有一个MatplotlibDeprecationWarning,但它让我可以绘制我的数字,所以我对这个解决方案很满意。 请注意,在python 2.7中,您需要使用raw_input而不是input。见here 当反应式“动画”方法不可行时非常有用的解决方法!有人知道如何摆脱弃用警告吗? 我必须将暂停时间增加到 0.1 秒才能正常工作 @krs013:我记得过去这对我有用。但是,现在这可以在不使用 plt.ion()plt.draw() 的情况下工作。不知道matplotlib版本有没有变化(我用的是最新版3.1.2【参考方案2】:

一个对我有用的简单技巧如下:

    在 show 中使用 block = False 参数:plt.show(block = False) 使用 另一个 plt.show() 在 .py 脚本的末尾

示例

import matplotlib.pyplot as plt

plt.imshow(add_something)
plt.xlabel("x")
plt.ylabel("y")

plt.show(block=False)

#more code here (e.g. do calculations and use print to see them on the screen

plt.show()

注意plt.show()是我脚本的最后一行。

【讨论】:

这会产生(对我来说,在 Linux、Anaconda、Python 2.7、默认后端)一个 blank 窗口,该窗口在执行结束之前一直保持空白,直到它最终被填充in. 对于在执行过程中更新绘图没有用。 :-( @sh37211 不确定你的目标是什么。在某些情况下,您尝试绘制某些东西,但在 plot 命令之后您还有其他命令,那么这很有用,因为它允许您绘制并执行其他命令。有关更多信息,请参阅此帖子:***.com/questions/458209/…。如果你想更新一个情节,那么它应该是另一种方式。 @sh37211 我只使用 plt.show(block=False) 得到了相同的症状(windows10/python 3.8.7 和 Linux mint/python 3.8.5)。在这两种情况下,我都使用 plt.show(block=False);plt.pause(0.01) 获得所需的绘图更新。 唯一对我有用的方法 与 Windows(10) 上的 @sh37211 相同。我不需要最后一个 plt.show() 仍然是一个有趣的解决方案。谢谢。【参考方案3】:

您可以通过将绘图写入数组,然后在不同的线程中显示该数组来避免阻塞执行。下面是使用来自pyformulas 0.2.8 的 pf.screen 同时生成和显示绘图的示例:

import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time

fig = plt.figure()

canvas = np.zeros((480,640))
screen = pf.screen(canvas, 'Sinusoid')

start = time.time()
while True:
    now = time.time() - start

    x = np.linspace(now-2, now, 100)
    y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
    plt.xlim(now-2,now+1)
    plt.ylim(-3,3)
    plt.plot(x, y, c='black')

    # If we haven't already shown or saved the plot, then we need to draw the figure first...
    fig.canvas.draw()

    image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
    image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))

    screen.update(image)

#screen.close()

结果:

免责声明:我是 pyformulas 的维护者。

参考:Matplotlib: save plot to numpy array

【讨论】:

【参考方案4】:

实时绘图

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
# plt.axis([x[0], x[-1], -1, 1])      # disable autoscaling
for point in x:
    plt.plot(point, np.sin(2 * point), '.', color='b')
    plt.draw()
    plt.pause(0.01)
# plt.clf()                           # clear the current figure

如果数据量过多,您可以使用简单的计数器降低更新率

cnt += 1
if (cnt == 10):       # update plot each 10 points
    plt.draw()
    plt.pause(0.01)
    cnt = 0

程序退出后保持绘图

这是我无法找到满意答案的实际问题,我想要在脚本完成后没有关闭的绘图(如 MATLAB),

如果你想,脚本完成后,程序就终止了,没有逻辑的方式来保持剧情,所以有两种选择

    阻止脚本退出(这是 plt.show() 而不是我想要的) 在单独的线程上运行绘图(太复杂)

这对我来说并不令人满意,所以我找到了另一种开箱即用的解决方案

SaveToFile 并在外部查看器中查看

为此,保存和查看都应该很快,查看者不应锁定文件,应自动更新内容

选择保存格式

基于矢量的格式既小又快

SVG 很好,但除了默认情况下需要手动刷新的网络浏览器之外找不到合适的查看器 PDF 可以支持矢量格式,并且有支持实时更新的轻量级查看器

具有实时更新功能的快速轻量级查看器

PDF有几个不错的选择

在 Windows 上,我使用SumatraPDF,它免费、快速且轻巧(我的情况仅使用 1.8MB RAM)

在 Linux 上有几个选项,例如 Evince (GNOME) 和 Ocular (KDE)

示例代码和结果

将绘图输出到文件的示例代码

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(2 * x)
plt.plot(x, y)
plt.savefig("fig.pdf")

第一次运行后,在上面提到的一个查看器中打开输出文件并享受。

这是 VSCode 和 SumatraPDF 的屏幕截图,该过程也足够快以获得半实时更新率(我的设置可以接近 10Hz,只需在间隔之间使用time.sleep()

【讨论】:

【参考方案5】:

其中很多答案都非常夸张,据我所知,答案并不难理解。

您可以根据需要使用plt.ion(),但我发现使用plt.draw() 一样有效

对于我的特定项目,我正在绘制图像,但您可以使用plot()scatter() 或其他任何东西来代替figimage(),没关系。

plt.figimage(image_to_show)
plt.draw()
plt.pause(0.001)

或者

fig = plt.figure()
...
fig.figimage(image_to_show)
fig.canvas.draw()
plt.pause(0.001)

如果您使用的是实际数字。 我使用@krs013 和@Default Picture 的答案来解决这个问题 希望这可以避免有人在单独的线程上发布每个人物,或者不必为了弄清楚这一点而阅读这些小说

【讨论】:

在循环中使用它(不断更新图像)时,我注意到执行时间很差。添加 plt.clf() ,在每次迭代中清除所有绘制的图像似乎可以解决问题。 那是因为不清除图像是内存泄漏,会导致 matplotlib 尝试绘制所有现有迭代,这会减慢执行速度【参考方案6】:

Python 包 drawow 允许以非阻塞方式实时更新绘图。 它还可以与网络摄像头和 OpenCV 一起使用,例如为每一帧绘制测量值。 请参阅original post。

【讨论】:

【参考方案7】:

Iggy's answer 对我来说是最容易遵循的,但是在执行后续的 subplot 命令时出现以下错误,而我刚刚执行 show 时不存在该命令:

MatplotlibDeprecationWarning:使用相同的参数添加轴 作为先前的轴当前重用较早的实例。在未来 版本,总是会创建并返回一个新实例。 同时,这个警告可以被压制,未来的行为 通过将唯一标签传递给每个轴实例来确保。

为了避免这个错误,在用户点击回车后关闭(或clear)绘图会有所帮助。

这是对我有用的代码:

def plt_show():
    '''Text-blocking version of plt.show()
    Use this instead of plt.show()'''
    plt.draw()
    plt.pause(0.001)
    input("Press enter to continue...")
    plt.close()

【讨论】:

【参考方案8】:

我发现plt.pause(0.001) 命令是唯一需要的东西。

plt.show() 和 plt.draw() 是不必要的和/或以一种或另一种方式阻塞。所以这是一个绘制和更新图形并继续运行的代码。本质上 plt.pause(0.001) 似乎是最接近 matlab 的 drawow 的等价物。

不幸的是,这些图不会是交互式的(它们会冻结),除非您插入一个 input() 命令,但随后代码将停止。

plt.pause(interval) 命令的文档说明:

如果有活动图,会在暂停前更新显示...... 这可以用于粗略的动画。

这正是我们想要的。试试这个代码:

import numpy as np
from matplotlib import pyplot as plt

x = np.arange(0, 51)               # x coordinates  
         
for z in range(10, 50):

    y = np.power(x, z/10)          # y coordinates of plot for animation

    plt.cla()                      # delete previous plot
    plt.axis([-50, 50, 0, 10000])  # set axis limits, to avoid rescaling
    plt.plot(x, y)                 # generate new plot
    plt.pause(0.1)                 # pause 0.1 sec, to force a plot redraw

【讨论】:

以上是关于使用 Matplotlib 以非阻塞方式绘图的主要内容,如果未能解决你的问题,请参考以下文章

以非阻塞方式等待 CIContext 渲染任务

如何以非阻塞方式压缩文件

如何以非阻塞方式处理 websocket 数据?

Spring如何以非阻塞方式匹配bcrypt密码

以非阻塞方式打开 QDialog

使用 seaborn 和 matplotlib 对热图进行注释的绘图