使用 Matplotlib 以非阻塞方式绘图
- 2025-01-08 08:49:00
- admin 原创
- 117
问题描述:
我在尝试使 matplotlib 绘制函数而不阻止执行时遇到问题。
我尝试过使用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. 我忘了说了,我想每次绘制某些内容时都更新现有窗口,而不是创建一个新窗口。
解决方案 1:
我花了很长时间寻找解决方案,最终找到了这个答案。
看起来,为了得到你(和我)想要的东西,你需要结合plt.ion()
,plt.show()
(而不是block=False
)和最重要的 ,plt.pause(.001)
(或你想要的任何时间)。需要暂停是因为 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()
解决方案 2:
对我而言有用的一个简单技巧如下:
在 show 中使用block = False参数: plt.show(block = False)
在.py 脚本末尾使用另一个 plt.show() 。
例子:
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()
是我的脚本的最后一行。
解决方案 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:将图保存到 numpy 数组
解决方案 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很好,但除了默认需要手动刷新的 Web 浏览器外,找不到合适的查看器
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 的答案来解决这个问题,
希望这可以节省人们在单独的线程上启动每个人物的时间,或者不必阅读这些小说才能解决这个问题
解决方案 6:
我发现plt.pause(0.001)
只需要命令,不需要其他任何东西。
plt.show() 和 plt.draw() 是不必要的和/或以某种方式阻塞。因此,这里有一个绘制和更新图形并继续运行的代码。本质上 plt.pause(0.001) 似乎是与 matlab 的drawnow 最接近的等价物。
不幸的是,这些图表不具有交互性(它们会冻结),除非你插入一个 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
解决方案 7:
Iggy 的回答对我来说是最容易理解的,但是当我执行后续命令时出现了以下错误,subplot
而当我刚才执行时却没有出现该命令show
:
MatplotlibDeprecationWarning:使用与先前轴相同的参数添加轴当前会重用先前的实例。在未来版本中,将始终创建并返回新实例。同时,可以通过向每个轴实例传递唯一标签来抑制此警告并确保未来的行为。
为了避免这个错误,在用户按下回车键后关闭(或清除)情节会有所帮助。
这是对我有用的代码:
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:
Python 包drawnow允许以非阻塞方式实时更新绘图。
它还可以与网络摄像头和OpenCV配合使用,例如绘制每帧的测量值。
请参阅原始帖子。
解决方案 9:
替换 matplotlib 的后端可以解决我的问题。
在之前写入下面的命令import matplotlib.pyplot as plt
。
替换后端命令应先运行。
导入 matplotlib
matplotlib.use('TkAgg')
我的答案来自Pycharm does not show plot
解决方案 10:
对于subplots
,我修改了@krs013 的示例,如下所示。每个循环中都必须有clear
面板,然后draw
。
from matplotlib import pyplot as plt
plt.ion()
def plot():
x = plt.np.arange(-50, 51)
fig, [ax1, ax2] = plt.subplots(ncols=2)
fig.canvas.toolbar.zoom()
for pow in range(1, 5): # plot x^1, x^2, ..., x^4
ax1.clear()
ax1.plot(x, x**pow)
plt.draw()
input("Press [enter] to continue.")
plot()
不需要pause
。