问题:使用Matplotlib以非阻塞方式绘制
最近几天,我一直在玩Numpy和matplotlib。我在尝试使matplotlib绘制函数而不阻止执行时遇到问题。我知道这里已经有很多线程在问类似的问题,并且我已经在Google上搜索了很多,但是还没有成功完成这项工作。
我曾尝试按照某些人的建议使用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。我忘了说,我想在每次绘制图形时都更新现有窗口,而不是创建一个新窗口。
回答 0
我花了很长时间寻找解决方案,并找到了答案。
看起来,为了获得您(和我)想要的东西,您需要将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()
回答 1
一个对我有用的简单技巧如下:
- 在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()
是我脚本的最后一行。
回答 2
您可以通过将绘图写入数组,然后在另一个线程中显示该数组来避免阻塞执行。这是一个使用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()
结果:
免责声明:我是pypyulas的维护者。
回答 3
这些答案中有很多是超级夸张的,从我的发现中,答案并不是那么难理解。
您可以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的答案来解决这个问题,
希望这可以使某人不必在一个单独的线程上启动每个单独的角色,或者不必阅读这些小说就可以解决这个问题。
回答 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)
示例代码和结果
用于将绘图输出到文件的示例代码
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的屏幕截图,该过程也足够快以达到半实时更新率(我的设置可以time.sleep()
在间隔之间使用时接近10Hz )
回答 5
Iggy的答案对我来说是最容易遵循的,但是subplot
当我执行刚在执行的后续命令时却遇到了以下错误show
:
MatplotlibDeprecationWarning:当前使用与先前轴相同的参数添加轴将重用较早的实例。在将来的版本中,将始终创建并返回一个新实例。同时,通过向每个轴实例传递唯一的标签,可以抑制此警告,并确保将来的行为。
为了避免此错误,它有助于在用户点击Enter后关闭(或清除)绘图。
这是对我有用的代码:
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()
回答 6
Python包drawow允许以非阻塞方式实时更新绘图。
它还可以与网络摄像头和OpenCV配合使用,例如绘制每个帧的度量。
请参阅原始帖子。